8 of 132 min

Window Manager in Hammerspoon

Window management in macOS often fails on details: focus, animations and apps’ internal states. In my Hammerspoon setup windows are controlled directly through the Hammerspoon API (working with the window object and its geometry, not through UI states), so the result is more stable and predictable.

My basic window control interface is built on a Ctrl + Option + key layer. These are quick commands held in muscle memory:

  • Ctrl+Option+A pins a window to the left edge (left half)

  • Ctrl+Option+D pins it to the right edge (right half)

  • Ctrl+Option+S centres the window (and so on)

There are many such combinations: each “logical” key has its own action for moving or resizing a window. For speed on a Mac this is critical: a window stops being an object you “have to arrange by hand” and becomes a state called up with one command.

1) Zero animation

hs.window.animationDuration = 0

2) Two ways of positioning

2.1) moveToUnit(…)

Reliable tiling in screen fractions (0..1): left and right halves, quarters and so on. Resilient to different screens and the Dock/Menu Bar.

2.2) setFrame(…)

For when a specific zone is needed: a 25% column, the bottom quarter, centring and so on. An example of a “right 25% column” from the config:

local win = hs.window.focusedWindow()
local screenFrame = win:screen():frame()
win:setFrame({
  x = screenFrame.x + screenFrame.w * 0.75,
  y = screenFrame.y,
  w = screenFrame.w * 0.25,
  h = screenFrame.h
})

Simply: the working area of the screen is taken and the window is given a rectangle where the width is 25% and x starts at 75% (that is, the right edge).

3) Toggle “arrange / restore”

Before tiling, the original frame is saved in windowStates, and then the window can be returned “as it was” with one action. This makes layouts reversible.

4) App window grid + return

The scenario gathers the current app’s windows, lays them out in a grid on the screen, and restores the original positions when run again. It quickly tidies up and just as quickly brings everything back.

5) Moving between monitors

Moving to a neighbouring screen by topology: west/east/north/south. This is more convenient than “monitor no. 2” because it works by the real arrangement of the screens.

6) Micro-adjustment

Step shifts (in the config the step is 100px) cover the case of “almost perfect, but needs a nudge”.

Why this is more reliable than system hotkeys and some ready-made solutions

System and some third-party commands are often tied to UI state (focus, animations, whether the app “caught up”). Here everything is deterministic: Hammerspoon gets the window, calculates the target geometry and applies it directly (moveToUnit / setFrame / moveToScreen), saving state if needed (windowStates). The plus of Hammerspoon is that I can assemble my own commands and set any parameters in them: exact sizes and coordinates, binding to a screen, shift steps, conditions, saving and restoring state, and any other characteristics my workflow needs.

In the next post: my approach to controlling media through Hammerspoon with delays and fallback.

Series contents
  1. Keyboard layers
  2. Karabiner = a set of JSON rules
  3. 1) Caps Lock = Hyper
  4. 2) A layer on Space: tap = space, hold = F18
  5. 3) backtick hold = F17
  6. 4) Russian layout: targeted rules for Russian typing only
  7. Hammerspoon
  8. Window Manager in Hammerspoon
  9. Media control as scenarios in Hammerspoon
  10. Hammerspoon as a local server and a "remote control"
  11. Monitors and a stable workplace (Hammerspoon)
  12. A MIDI keyboard and OBS as a hardware controller (Hammerspoon)
  13. The topic of keyboard layers is closed for now