Unsung Moments

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 f13f20 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.