A lightweight macOS menu bar app that remembers window positions and Spaces assignments per display configuration and restores them when you reconnect your screens.
Every time you disconnect and reconnect external monitors, macOS shuffles all your windows onto whatever screen it feels like. Fullscreen windows get collapsed. Chrome tabs you had on your left monitor end up stacked on your laptop. IntelliJ disappears into a random Space. You spend 5 minutes dragging everything back.
Latch snapshots your complete window arrangement — positions, sizes, and which Space on which display each window belongs to — and restores it automatically when the same monitors reconnect.
Most window managers (Rectangle, Magnet, etc.) handle position and size but ignore Spaces entirely. Latch uses private CGS/SkyLight APIs (the same ones powering yabai, Amethyst, and alt-tab-macos) to directly move windows between Spaces — including restoring native fullscreen windows to their correct display.
No SIP changes required. These are undocumented but functional APIs that don't need System Integrity Protection disabled.
AppDelegate → Menu bar UI + display change listener
DisplayManager → Fingerprints connected displays (vendor + model + serial + arrangement)
SpaceManager → Wraps private CGS/SkyLight APIs for Space enumeration and window-space ops
WindowManager → Captures/restores window positions + space assignments via AX + CGS APIs
LayoutStore → Persists layouts as JSON in ~/Library/Application Support/Latch/
PrivateAPIs.h → C declarations for undocumented CGS functions
| Function | Purpose |
|---|---|
CGSMainConnectionID |
Get WindowServer connection |
CGSCopyManagedDisplaySpaces |
Enumerate all Spaces per display |
CGSManagedDisplayGetCurrentSpace |
Get active Space for a display |
CGSCopySpacesForWindows |
Which Spaces a window is on |
CGSAddWindowsToSpaces |
Add a window to a Space |
CGSRemoveWindowsFromSpaces |
Remove a window from a Space |
_AXUIElementGetWindow |
Bridge AXUIElement → CGWindowID |
CGSCopyBestManagedDisplayForRect |
Map screen region → display UUID |
- Each window's Space is stored as an index per display (e.g., "2nd Space on display ABC"), not a raw Space ID (those change across reboots).
- Display matching uses display UUIDs (stable across reconnects for the same physical display).
- When restoring, windows are moved using an add-then-remove pattern (add to target Space first, then remove from old Space) to avoid the zero-Spaces state that macOS blocks.
- Fullscreen windows are handled by exiting fullscreen → moving to target screen → re-entering fullscreen.
- macOS 13+ (Ventura or later)
- Xcode Command Line Tools (
xcode-select --install) - Accessibility permission (prompted on first launch)
chmod +x build.sh
./build.sh
cp -r build/Latch.app /Applications/
open /Applications/Latch.appThe build script auto-detects your architecture (Apple Silicon or Intel).
| Action | What it does |
|---|---|
| Save Current Layout (⌘S) | Snapshots all window positions + Space assignments |
| Restore Layout (⌘R) | Restores everything for the current display config |
| Auto-Restore | Toggle: auto-restore when displays reconnect |
| Restore Delay | Wait time before restore (1–5s, default 2s) |
- Connect your monitors and arrange everything: Chrome fullscreen on left, IntelliJ fullscreen on right, Slack on laptop, etc.
- Click the menu bar icon → Save Current Layout
- Done. Next time you reconnect the same monitors, it all snaps back — including fullscreen windows on the right Spaces.
Save different layouts for different setups:
- Home office (laptop + 2 externals)
- Work office (laptop + ultrawide)
- Laptop only
System Settings → General → Login Items → + → select Latch
- Private APIs can break. Apple doesn't document or guarantee these functions. They've been stable for years across macOS 12–15 and are used by major tools (yabai has 25k stars), but a future macOS update could change them. If that happens, Latch falls back to position-only restoration (the AX API part is fully public).
- Fullscreen restoration has a visual flicker. The window exits fullscreen, moves, and re-enters fullscreen. Takes about 1 second per window. No way around this — macOS Spaces are opaque even to private APIs when it comes to fullscreen.
- Space count matters. If you saved a layout with 3 Spaces on your left monitor but now only have 2, windows from Space 3 get placed on Space 2 (last available). Create the same number of Spaces before restoring for best results.
- Amethyst/yabai conflict. If you're already running a tiling window manager, Latch's space moves may fight with it. Disable one or the other for space management.
Human-readable JSON in ~/Library/Application Support/Latch/<config-hash>.json.
GPL-3.0 — see LICENSE.