What It Does

macOS doesn’t remember how you arranged things — just where each window currently sits. The moment you plug into an external display, come home to a single-screen setup, or switch between spaces, you’re rearranging windows again.

SpaceMan lets you capture a layout as a named snapshot and apply it later, with a menu bar that only ever lists snapshots applicable to where you are right now.

How It Works

A “workspace” is identified by the combination of display count, sorted display UUIDs, and current Mission Control space. The same physical space-number on different display configurations is treated as distinct — snapshots don’t cross-contaminate.

Snapshots are named and manual. You choose when to capture and when to restore. No automatic captures, no auto-restore on display changes.

Taking a Snapshot

  1. Arrange your windows the way you want on the current space
  2. Click the rocket icon in the menu bar and choose Snapshot current workspace…
  3. Give it a short label

That’s it. The snapshot is tied to exactly this workspace fingerprint and will only surface again when you’re on the same space with the same display configuration.

Restoring a Snapshot

  1. Navigate to the target Mission Control space first
  2. Plug in or unplug any displays needed for the target workspace
  3. Open the menu — the snapshots listed are the ones captured on exactly this workspace fingerprint
  4. Click one

SpaceMan will find each app in the snapshot, launch it in the background if it’s not running, spawn extra windows if needed, and apply the captured position, size, and minimised/hidden state. A summary dialog reports what happened — positioned, launched, spawned, and skipped.

Full State Restoration

ScenarioBehaviour
App already runningExisting windows are moved into place
App not runningLaunched in the background, no focus steal
Fewer windows than the snapshotExtra windows spawned via menu-bar driving
Minimised or hidden statePreserved per-window; hidden falls back to minimised if AX can’t re-hide
App not installedSkipped and reported in the summary dialog

The management window lists every snapshot grouped by display configuration, with inline rename and delete.

Technical Details

Workspace fingerprint

WorkspaceFingerprint is (displayCount, sorted display UUIDs, managedSpaceID). Display UUIDs come from CGDisplayCreateUUIDFromDisplayID; the space ID is read from the private CGSCopyManagedDisplaySpaces API.

Space IDs are stable while a space exists but change if you delete and recreate one — an accepted limitation. Snapshots tied to a deleted space invalidate silently.

Window capture

CoreGraphics’s optionOnScreenOnly window list is scoped to the current Mission Control space. AX (kAXWindowsAttribute) enumerates windows across all spaces. SpaceMan cross-references the two — using the private _AXUIElementGetWindow — so a snapshot captures exactly the windows visible on the current space, and nothing more.

Each window record stores bundle ID, app name, title, frame, display UUID, state (normal / minimised / hidden), and orderInApp — the window’s zero-based index within its app’s AX list at capture time. This order is used to match records back to live windows at restore time.

Spawning windows

No universal AX action exists for “give this app another window”. WindowSpawner walks the app’s AX menu bar and presses the first enabled item whose title matches New Window, New Finder Window, New Browser Window, and so on — first against a preferred-match list, then against a broader “starts with New, contains Window” heuristic.

Architecture

FilePurpose
AppDelegate.swiftOwns the NSStatusItem, builds the menu, routes actions
MenuBuilder.swiftBuilds the menu, filtered by current workspace fingerprint
WorkspaceFingerprint.swiftComputes (displayCount, UUIDs, spaceID) identity
WindowCapture.swiftCG/AX cross-reference to enumerate current-space windows
SnapshotModel.swiftCodable snapshot representation and window records
SnapshotStore.swiftLoad/save JSON with .bak rotation and corrupt-file recovery
SnapshotRestore.swiftAsync restore pipeline on the main actor
WindowSpawner.swiftAX menu-bar driving to spawn extra app windows
SnapshotManagementView.swiftSwiftUI management window: rename, delete, group by config
CGSPrivate.swiftSwift bindings for private CoreGraphics space APIs

Storage

Snapshots live in ~/Library/Application Support/JorvikSpaceMan/snapshots.json. A rolling .bak is written before every save; corrupt files are preserved to a timestamped copy rather than silently overwritten. Five snapshots per workspace, maximum — exceeding the cap evicts the oldest.

Installation

Two formats on every release — both signed and notarised, pick whichever suits:

On first launch, grant Accessibility permission when prompted.

Settings

Right-click the rocket icon and choose Settings… to configure:

Auto-updates are handled by Sparkle. Use the “Check for Updates…” menu item to check on demand; Sparkle’s prompt offers an “Automatically download and install updates in the future” checkbox the first time an update is available. SpaceMan checks for new versions automatically once a day in the background.

Permissions

Accessibility is required. Without it, SpaceMan can’t read window state or apply position changes. macOS will prompt on first launch; permission status and a grant button are also available in the Settings panel.

Building from Source

  1. Clone the repo: git clone https://github.com/PerpetualBeta/SpaceMan.git
  2. Run gmake build
  3. Launch with open .build/SpaceMan.app

gmake build compiles with swiftc -O and produces a Developer ID–signed .app bundle.

Requirements

macOS 14 (Sonoma) or later. Universal binary (Apple Silicon and Intel).