← home

Gutter

2 min readrepo ↗download ↗

Purpose

Stage Manager is great for switching apps, but macOS has no default window size. New windows open either small or full width, covering the strip of recent apps on the left, so I ended up resizing every window by hand.

Gutter watches for new windows and fits them to the screen with a margin you choose. The strip stays visible on every display, and you never touch a window edge again.

Gutter's menu bar panel

Highlights

How it works

Gutter uses the macOS Accessibility API to listen for AXWindowCreated events from running apps. When a standard window appears, it's moved and resized to the screen's visible area minus your margins. Dialogs, sheets, full-screen and non-resizable windows are left alone. Whether Stage Manager is on is read from the com.apple.WindowManager preferences.

More technical information
  • Coordinates across displays: the Accessibility API and AppKit measure screens from different corners, so the fitting math converts between them for every display. It's covered by unit tests that run in CI.
  • Safe limits: margins can't add up to more than half of a display, and the sliders and preview show the real limits for each one.
  • Unresponsive apps: calls to other apps time out, so one frozen app can't freeze Gutter.
  • Refitting: windows moved to another display, and every window when displays are connected or disconnected, are fitted again to that display's gap.

Shipping it

Gutter is free and isn't distributed through Apple's paid developer program, so macOS can't vouch for it. Instead, every release is built in public by GitHub Actions from the source in the repo: a universal (Apple Silicon + Intel) app, packaged as a .dmg and a .zip with checksums and build attestations, so anyone can check that a download matches the code.

It installs with Homebrew:

brew install --cask jleitaom/tap/gutter
Why a self-signed certificate?

macOS ties Accessibility access to an app's signature. Ad-hoc signed builds look like a new app every time, so each update would silently lose access. Every release is signed with the same self-signed certificate instead, so macOS recognizes the new version as the same app, and updates through Sparkle keep working without granting access again.