Build and run
app/scripts/bundle.sh
open app/build/Spacetile.app
app/scripts/bundle.sh does five things:
- Builds the release binary.
- Wraps it in
app/build/Spacetile.app, stamped with a build number from the date and time. - Compiles the app icon,
app/Resources/AppIcon.icon(an Icon Composer file; open it in Icon Composer to edit), withactool. - Generates the App Intents metadata that the Focus filter needs. Xcode normally does this; the script runs Apple’s processor on SwiftPM’s output.
- Signs the app with the first “Apple Development” identity in your keychain.
That signature ties the Accessibility permission to the certificate, so it survives rebuilds. Check it with codesign -d -r- app/build/Spacetile.app: it should show a certificate requirement, not a cdhash. If signing fails with “unable to build chain”, add Apple’s WWDR G3 intermediate certificate to the login keychain.
Run the tests for the tiling logic with swift test --package-path app.
Permissions
- Accessibility (System Settings → Privacy & Security → Accessibility). Needed to see, move, resize and focus other apps’ windows, for the mouse event tap, and for instant Space switching. The Welcome window asks for it on first launch, then checks your macOS settings and offers another window manager’s shortcuts to start from (see Commands), and Spacetile starts within a second of it being granted. If it’s turned off while Spacetile runs, tiling pauses, the menu-bar item shows ⚠︎ with a Grant Accessibility… row, and Spacetile relaunches itself once it’s back on.
- Screen Recording (optional; System Settings → Privacy & Security → Screen & System Audio Recording). Only for Settings → General → Show window previews, which draws windows’ content in the mini-map and profile editor. Turning the setting on asks for it. Without it, or with the setting off, windows show their app’s icon and Spacetile never reads their content. Pictures stay in memory and are never saved.
- Open at login is a toggle in Settings → General.
macOS settings
Settings → Setup checks these and links to each one:
| Setting | Needed | Why |
|---|---|---|
| Automatically rearrange Spaces based on most recent use | Off | Otherwise Space numbers shuffle |
| Stage Manager | Off | It moves windows itself |
| Drag windows to screen edges to tile | Off | Its preview competes with Spacetile’s drop zones. The rest of macOS tiling works with Spacetile (see macOS tiling) |
| Trackpad swipe between full-screen apps | On | macOS 27 only accepts instant switching with this gesture enabled |
| More than one Space | Suggested | Spacetile can’t create Spaces; add them in Mission Control (⌃↑, then +) |
“When switching to an application, switch to a Space with open windows” can stay however you like it. With it on, arriving on an empty Desktop makes macOS activate another app and follow it to its Space. Spacetile switches back when its own switch bounces like this; a trackpad swipe or ⌃→ to an empty Desktop can still bounce.
Developer commands
app/scripts/spacetile-ctl sends any command from Commands to the running app, like the app binary does for other key binders. It also takes these:
| Command | What it does |
|---|---|
frames |
Prints the windows on the current Space with their frames |
app/scripts/fullscreen-probe prints what the window server says about full-screen Spaces and Split View, the windows on them, and the macOS tiling settings; --menu <App> also lists that app’s Window menu items. It’s for checking how a new macOS release reports these.
The rest are development aids. They only work in a build made with app/scripts/bundle.sh --dev:
| Command | What it does |
|---|---|
settings-shot <tab> <path> |
Saves a tab as a PNG; <tab>@<height> captures a taller window |
minimap-shot <path> |
Saves the mini-map as a PNG |
min-size <window id> |
Asks a window for 300 × 300 pt, logs the size it actually takes after 0.1 s and 0.5 s, then puts it back in its tile |
minimap |
Opens or closes the mini-map, as clicking the menu-bar item does |
welcome-shot <page> <path> |
Saves a Welcome page (permission, macOS, presets or keys) as a PNG. Scrolling lists don’t render |
menubar-shot <path> |
Saves the menu-bar item’s image |
overlay-preview <path> |
Saves every drop-overlay style side by side |
hover <x> <y> |
Runs focus follows mouse for that point |
show <window id> |
What clicking a window in the mini-map does: switches to its Space, on whichever display, and focuses it. Window ids come from frames |
wake |
Runs what Spacetile does after the Mac wakes: settle, check displays, lay out again |
fullscreen <app> |
Toggles native full screen on that app’s focused window |
spaces-override <n> |
Settings acts as if there were n Spaces (0 clears) |
login on |
Registers open at login |
Spacetile logs to the unified log under the com.kodehort.spacetile subsystem:
log show --last 5m --predicate 'subsystem == "com.kodehort.spacetile"' --style compact
It also marks layout passes, Space changes, Space switches and profile loads as signpost intervals in the performance category. Record Spacetile with Instruments’ os_signpost (or Logging) template to see how long each takes.
How it works without SIP
These private window-server calls work with SIP on, and were checked on macOS 27.0:
| Job | How |
|---|---|
| Moving a window to another Space | SLSBridgedMoveWindowsToManagedSpaceOperation |
| Instant Space switching | Synthetic dock-swipe events carrying an IOHID payload |
| Focusing a window, with or without raising | _SLPSSetFrontProcessWithOptions and key-window event records, as yabai does |
| Reading Spaces and window membership | SLSCopyManagedDisplaySpaces and SLSCopySpacesForWindows |
| Full screen and Split View | The public AXFullScreen attribute, the app’s own Window ▸ Full-Screen Tile menu items through Accessibility, and a synthetic click in the Split View picker. TileLayoutManager in SLSCopyManagedDisplaySpaces says which windows are a full-screen Space’s |
A macOS update could break any of these; each one is isolated, so Spacetile degrades rather than failing outright.
Limits
- Spacetile can’t create, delete or reorder Spaces.
- No window opacity, layers or sticky windows; those need SIP off.
- Clicking a tile, or focusing it with the keyboard, can bring it above a floating window from another app.
- Private APIs rule out the Mac App Store. Public distribution needs a paid developer account for notarization.