Tags: aerospace, macos, tiling-window-manager, hyprland, dwindle, window-manager, applescript, jxa, dotfiles
Four small scripts that make AeroSpace (a tiling window manager for macOS) behave a bit more like Hyprland's dwindle layout:
- A solo window in a workspace can be toggled to a floating, centered window (at 70% of its screen) and back, instead of always filling the whole space.
- As more windows are added to a workspace, they build a 2x2 grid (window 3 stacks under window 2, window 4 stacks under window 1) instead of AeroSpace's default behavior of just adding new side-by-side columns forever.
- A 5th+ window overflows to the closest workspace on the same monitor that has room, instead of cramming a 5th column into an already-full workspace.
- Manually sending a window to another workspace (
move-node-to-workspace) re-applies the same 2x2 grid placement in the destination workspace.
- macOS
- AeroSpace installed and running
- Bash (macOS ships with one new enough for this)
- On first run, macOS will prompt for Accessibility permission (System
Settings > Privacy & Security > Accessibility) so
osascript/System Events can read and set window position/size. Grant it to your terminal/AeroSpace.
center-float.sh— toggles the sole window in the focused workspace between tiling and floating+centered at 70% of its current screen. No-op if the workspace has more than one window.restore-tiling.sh [workspace]— whenever a workspace gains a 2nd window, un-floats any window left floating bycenter-float.shso it rejoins the tiling layout instead of overlapping the new window. Defaults to the focused workspace if none is given.grid-layout.sh [workspace] [window-id]— runs on every new window (and frommove-and-retile.sh, see below). Callsrestore-tiling.shfirst, then applies the 2x2 grid rule: the 3rd window in a workspace joins under window 2 (right column becomes a vertical stack), the 4th joins under window 1 (left column stacks too). A 5th+ window is moved to the closest workspace on the same monitor with fewer than 4 windows (focus follows), or left as a plain new column if every workspace on that monitor is full. Defaults to the focused workspace/window when called with no arguments (the on-window-detected case); accepts an explicit workspace + window-id so it can also retile a workspace that isn't currently focused (seemove-and-retile.sh).move-and-retile.sh [--follow] <target-workspace>— AeroSpace has no callback for "window moved to another workspace" (onlyon-window-detected, for newly created windows), so a manualmove-node-to-workspacenever triggeredgrid-layout.shin the destination workspace. This script captures the focused window's id, moves it to<target-workspace>viamove-node-to-workspace --window-id, then callsgrid-layout.shagainst wherever that window actually landed — explicitly by window-id, since focus doesn't necessarily follow a plain move. Pass--followto also switch the current view to the target workspace after the move (for bindings like "send window to next workspace and follow it there").
# Copy the scripts next to your aerospace config
cp center-float.sh restore-tiling.sh grid-layout.sh move-and-retile.sh ~/.config/aerospace/
chmod +x ~/.config/aerospace/{center-float,restore-tiling,grid-layout,move-and-retile}.shThen add to your ~/.aerospace.toml:
# Runs the grid-placement logic (and, transitively, restore-tiling.sh) on
# every new window. `if = 'true'` is required by AeroSpace to explicitly
# opt in to matching every window unconditionally.
on-window-detected = [
{ if = 'true', check-further-callbacks = true, run = 'exec-and-forget $HOME/.config/aerospace/grid-layout.sh' },
]
[mode.main.binding]
# ...your other bindings...
alt-shift-c = 'exec-and-forget $HOME/.config/aerospace/center-float.sh'
# Replace plain move-node-to-workspace bindings with move-and-retile.sh
# so the destination workspace gets re-tiled into the grid too.
alt-shift-1 = 'exec-and-forget $HOME/.config/aerospace/move-and-retile.sh 1'
# ...repeat for 2-9...
alt-shift-left = 'exec-and-forget $HOME/.config/aerospace/move-and-retile.sh --follow prev'
alt-shift-right = 'exec-and-forget $HOME/.config/aerospace/move-and-retile.sh --follow next'Reload with aerospace reload-config (or esc in your service binding mode,
if you have esc = ['reload-config', 'mode main'] set up like the AeroSpace
default config does).
- AeroSpace has no "window closed" event, only "window detected" (created). So none of this reacts to closing windows — e.g. closing window 3 back down to 2 windows won't automatically re-flow the layout. That's a real constraint of AeroSpace's callback model, not a bug here.
- The overflow rule (5th+ window) assumes your workspaces are grouped by
monitor via
workspace-to-monitor-force-assignmentin.aerospace.toml, and searches only that monitor's group for room, in ascending workspace order. center-float.sh's 70% size is a plain constant (relSizenear the top of the script) — change it to whatever ratio you want.