Notes on a Better Aerospace Config
I’m a long-time tiling window manager user on Linux and especially like the control scheme of COSMIC, which I find really intuitive. To get the same ergonomics on macOS, I turned to Aerospace. It took some tuning to get right, but Aerospace’s flexible scripting makes almost any behavior possible. Below are the tricks I picked up along the way.
Factor Out Common Routines into f13–f20 Bindings
We can factor out common routines into service mode and call them via trigger-binding --mode service. The keys f13 through f20 are great placeholders since they’re rarely used.
[mode.service.binding]
f20 = """exec-and-forget \
[ "$(aerospace list-windows --focused --count)" != "1" ] && \
aerospace workspace-back-and-forth
""" # prevent focus into empty workspace
Move Focus with Vim Keys, even across Monitors
By default, the focus command only moves focus within a workspace; the --boundaries all-monitors-outer-frame flag lets us move across monitors. The trigger-binding --mode service f20 routine prevents focusing into an empty workspace, which can be confusing at times.
alt-h = [
'focus left --boundaries all-monitors-outer-frame',
'trigger-binding --mode service f20',
]
alt-j = 'focus down'
alt-k = 'focus up'
alt-l = [
'focus right --boundaries all-monitors-outer-frame',
'trigger-binding --mode service f20',
]
Move Window, across Workspaces, across Monitors
Similar to the move-focus setup above, the --boundaries all-monitors-outer-frame flag lets us to move the window onto other monitors. Achieving similar behavior for workspaces is more involved: we need --boundaries-action fail to detect when we’re moving beyond the current workspace, and if so, move the window to the next or prev workspace in the list.
alt-shift-h = 'move left --boundaries all-monitors-outer-frame'
alt-shift-j = """exec-and-forget \
aerospace move down --boundaries-action fail || \
aerospace list-workspaces --monitor focused \
| aerospace move-node-to-workspace --focus-follows-window --stdin next
"""
alt-shift-k = """exec-and-forget \
aerospace move up --boundaries-action fail || \
aerospace list-workspaces --monitor focused \
| aerospace move-node-to-workspace --focus-follows-window --stdin prev
"""
alt-shift-l = 'move right --boundaries all-monitors-outer-frame'
Open a New App Window
For apps that support multiple windows, open -a doesn’t open a new one — it just brings focus to the existing instance. To actually create a new window, we can use AppleScript. Below are examples for Safari and Ghostty.
alt-b = """exec-and-forget osascript -e '
set wasRunning to application "Safari" is running
tell application "Safari"
if wasRunning then
make new document
end if
activate
end tell
'"""
alt-t = """exec-and-forget osascript -e '
set wasRunning to application "Ghostty" is running
tell application "Ghostty"
if wasRunning then
set newWin to new window
activate window newWin
else
activate
end if
end tell
'"""
If the app doesn’t support AppleScript, it can often be done through the command line, like with VS Code.
alt-c = 'exec-and-forget open -na "Visual Studio Code" --args -n'
Use Border Color to Indicate Binding Modes
Aerospace is often combined with JankyBorders. The border style is scriptable, so we can use different colors to represent binding modes.
[mode.main.binding]
alt-enter = [
'mode service',
# entering service mode, set color to red
'exec-and-forget borders active_color=0xffd65d0e',
]
[mode.service.binding]
enter = [
'mode main',
# back to main mode, set color to grey
'exec-and-forget borders active_color=0xffd5c4a1',
]
These tweaks got my macOS setup feeling close enough to COSMIC that the muscle memory carries over. The full config is available on GitHub, feel free to take whatever’s useful.