{"id":50522720,"url":"https://github.com/michaeltorbert/spaces-manager","last_synced_at":"2026-06-03T06:00:31.984Z","repository":{"id":361308231,"uuid":"1253927097","full_name":"michaeltorbert/spaces-manager","owner":"michaeltorbert","description":"Menu-bar utility for naming Mission Control spaces on macOS. Click to switch, right-click to rename or delete.","archived":false,"fork":false,"pushed_at":"2026-05-30T03:43:37.000Z","size":874,"stargazers_count":0,"open_issues_count":12,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T04:14:10.370Z","etag":null,"topics":["apple-silicon","macos","menu-bar","mission-control","spaces","swift"],"latest_commit_sha":null,"homepage":null,"language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michaeltorbert.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-30T00:30:31.000Z","updated_at":"2026-05-30T03:43:40.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/michaeltorbert/spaces-manager","commit_stats":null,"previous_names":["michaeltorbert/spaces-manager"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/michaeltorbert/spaces-manager","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeltorbert%2Fspaces-manager","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeltorbert%2Fspaces-manager/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeltorbert%2Fspaces-manager/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeltorbert%2Fspaces-manager/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michaeltorbert","download_url":"https://codeload.github.com/michaeltorbert/spaces-manager/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michaeltorbert%2Fspaces-manager/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33850627,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-03T02:00:06.370Z","response_time":59,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["apple-silicon","macos","menu-bar","mission-control","spaces","swift"],"created_at":"2026-06-03T06:00:17.789Z","updated_at":"2026-06-03T06:00:31.976Z","avatar_url":"https://github.com/michaeltorbert.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"# SpacesManager\n\nA small Swift menu-bar utility for macOS that lets you give Mission Control spaces custom names, switch between them from the menu bar, and see the current space's name in a brief HUD when you switch.\n\n**Non-goal:** this does **not** rename the \"Desktop 1 / Desktop 2 / …\" labels inside Apple's Mission Control UI. Doing that requires injecting code into the Dock process, which requires disabling System Integrity Protection. SpacesManager deliberately avoids that route — it's a parallel UI, not a Mission Control patch.\n\nBuilt and tested on **macOS Tahoe 26.3.1, Apple Silicon (M4)**. Older macOS versions probably work too — see [Compatibility](#compatibility).\n\n---\n\n## What it does\n\n- **Menu bar item** shows the current space's custom name (falls back to \"Desktop N\").\n- **Click the menu bar item** to see all spaces grouped by display name, with the active one marked.\n- **Click a space row** to switch to it. The first switch prompts for Accessibility permission so SpacesManager can send the same Dock swipe event as a trackpad space switch. Full-screen app spaces appear in the menu and can be targeted too.\n- **Hover a normal desktop space row** for quick buttons: move the frontmost window there, rename, or delete the space. Delete asks for confirmation first.\n- **Brief HUD** fades in at the top of the screen on every space switch, showing the name.\n- **Rename Current Space…** quick action in the menu.\n- **Rename All Spaces…** opens a window for bulk editing.\n- **Names persist** in `UserDefaults` under bundle id `local.spacesmanager`, keyed by each space's UUID (or a stable per-display fallback key when macOS returns an empty UUID).\n- **Self-updates** via Sparkle: a daily background check + a \"Check for Updates…\" menu item in release builds. Updates are verified with an EdDSA signature embedded in the app; only releases signed with the matching private key will install.\n- **Hold Option while the menu is open** to swap maintenance actions: local dev builds change \"Check for Updates…\" to \"Download Release Anyway…\", and \"Quit SpacesManager\" changes to \"Relaunch SpacesManager.\"\n\n---\n\n## Requirements\n\n- macOS 13 or later (built with `-target arm64-apple-macos13`)\n- Xcode Command Line Tools (`xcode-select --install`) for `swiftc` and `codesign`\n\nNo Apple Developer account, no entitlements, no Screen Recording permission, no SIP changes. Click-to-switch requires Accessibility permission.\n\nBecause SpacesManager is a menu-bar agent (`LSUIElement=true`), its app icon appears in Finder, Get Info, Spotlight, and release artifacts, not in the Dock or app switcher.\n\n## Build\n\n```sh\n./build.sh\n```\n\nOn the first run this downloads Sparkle 2.9.2 (~15 MB) into `Frameworks/` and caches it for subsequent builds. The script then compiles `Sources/*.swift`, copies `Sparkle.framework` into the app bundle, strips xattrs, ad-hoc signs the nested XPC services + framework + outer app in that order, and verifies the chain passes `codesign --verify --deep --strict`.\n\n## Install\n\nDownload the latest `SpacesManager-\u003cversion\u003e.zip` from [Releases](https://github.com/michaeltorbert/spaces-manager/releases), unzip, and:\n\n```sh\nxattr -dr com.apple.quarantine ~/Downloads/SpacesManager.app\nmv ~/Downloads/SpacesManager.app /Applications/\nopen /Applications/SpacesManager.app\n```\n\nThe `xattr` line strips Gatekeeper's quarantine flag. Without it macOS refuses to launch the ad-hoc-signed app and offers no override path. (Right-click → Open → Open Anyway sometimes works too, but Tahoe is grumpier and `xattr` is the reliable path.) Future updates flow through Sparkle and don't trip this — only the first install does.\n\nTo start at login: **System Settings → General → Login Items \u0026 Extensions → +** under \"Open at Login\" → pick `/Applications/SpacesManager.app`.\n\n## Releasing a new version\n\n```sh\ngit tag v1.0.1\ngit push origin v1.0.1\n```\n\nCI builds, signs, publishes the appcast, attaches the zip to the GitHub Release. Installed copies auto-update on the next daily check. See [RELEASING.md](RELEASING.md) for the full runbook (what each workflow step does, version conventions, rollback, the one-time setup that's already been done).\n\n---\n\n## Project layout\n\n```\nSources/                       Swift source split by concern\nAssets/                        app icon source, generator script, and compiled .icns\nInfo.plist                      bundle metadata + Sparkle keys (SUFeedURL, SUPublicEDKey)\nbuild.sh                        vendors Sparkle, runs swiftc, codesigns the nested chain, verifies\nPackage.swift                   IDE indexing only; the real build is build.sh\n.github/workflows/release.yml   tag-triggered: build, sign, regenerate appcast, publish\nFrameworks/                     gitignored; populated by build.sh on first run\nLICENSE                         MIT\nREADME.md                       this file\nRELEASING.md                    release runbook (cut a release, rollback, key-management notes)\n```\n\nSmall raw-`swiftc` Swift app. No Xcode project; `Package.swift` is for IDE indexing only. Sparkle is vendored as a prebuilt framework copied into `Contents/Frameworks/`. Edit the source, run `./build.sh`, and (during development) drag the new `build/SpacesManager.app` over the installed copy. If the app icon changes, regenerate `Assets/AppIcon.svg` and `Assets/AppIcon.icns` with `Assets/build-icon.sh`. Local dev builds mark themselves so **Download Release Anyway…** can offer the newest signed release zip even though the dev build's internal version is pinned above release builds; hold Option while the menu is open to swap **Check for Updates…** to that dev-only action. For real releases, push a tag.\n\n---\n\n## Tahoe private API findings\n\nSpacesManager uses Apple's private CoreGraphics Services (CGS) / SkyLight APIs because there is no public macOS API for enumerating or manipulating Mission Control spaces. This makes it un-shippable on the Mac App Store but works fine for personal use.\n\nProbed against macOS 26.3.1 (Tahoe) on Apple Silicon, here's what I found:\n\n### Still works\n\n| Symbol | Use |\n|---|---|\n| `CGSMainConnectionID` | get the default WindowServer connection |\n| `CGSGetActiveSpace` | current space ID |\n| `CGSCopyManagedDisplaySpaces` | full topology of displays → spaces, with `uuid`, `id64`, `ManagedSpaceID`, `Current Space` |\n| `CGSSpaceDestroy` | delete a space |\n| `CGSCopyActiveMenuBarDisplayIdentifier` | which display has the menu bar |\n| `SLSManagedDisplayGetCurrentSpace` | per-display current space |\n| `SLSSpaceDestroy` | (alias of CGSSpaceDestroy) |\n| `SLSCopySpacesForWindows` | map regular app windows from `CGWindowListCopyWindowInfo` back to their Mission Control spaces for menu counts and dominant-app metadata |\n| `SLSCopyWindowsWithOptionsAndTags`, `SLSMoveWindowsToManagedSpace`, `SLSAddWindowsToSpaces`, `SLSRemoveWindowsFromSpaces` | window↔space membership helpers; SpacesManager uses the move path for \"move frontmost window here\" row actions |\n| `SLSHWCaptureWindowList`, `SLSCaptureWindowsContentsToRectWithOptions` | window-image capture (not yet used) |\n| `SLSSpaceSetType`, `SLSSpaceGetType` | space type |\n\n### Broken / not viable on Tahoe\n\n| Symbol | Was used for | Workaround |\n|---|---|---|\n| `CGSManagedDisplaySetCurrentSpace` / `SLSManagedDisplaySetCurrentSpace` | direct row-click switching | symbol still exists, but on Tahoe it only changes WindowServer bookkeeping and surfaces target-space windows over the current desktop. SpacesManager uses Accessibility-gated synthetic Dock swipe gestures instead. |\n\n### Removed / missing on Tahoe\n\n| Symbol | Was used for | Workaround |\n|---|---|---|\n| `SLSAddSpacesToManagedDisplay` / `CGSAddSpacesToManagedDisplay` | attaching a created space to a display | gone — `SLSSpaceCreate` still works but the created space can't be made visible, so the programmatic-add path is effectively dead. To create a new space, open Mission Control yourself (F3 / gesture / Spotlight) and click `+`. |\n| `CoreDockSendNotification` in `Dock.framework` | opening Mission Control / Expose without keypress | the `Dock.framework` private bundle no longer exists at the old path. Use `NSWorkspace.openApplication(at: \"/System/Applications/Mission Control.app\")` instead. |\n\nThese results are from a runtime `dlsym` probe; if you're on a different macOS version, results may differ.\n\n---\n\n## Why not the Mac App Store?\n\nApple's App Store Review:\n\n1. Statically scans the binary for private API symbol references — all CGS/SLS symbols are flagged and rejected.\n2. Requires sandboxing — and the sandbox blocks the WindowServer connection that CGS calls depend on at runtime anyway.\n\nThe App Store apps that *do* offer space-naming work around this by avoiding CGS entirely: they listen to the public `NSWorkspace.activeSpaceDidChangeNotification`, assign sequential counter IDs (not UUIDs), and use Accessibility to synthesize space-switch keypresses. The tradeoff: names break when spaces are reordered or deleted, and most cap at ~10 spaces.\n\nSpacesManager takes the opposite tradeoff: stable UUID-keyed naming and an unlimited number of spaces, at the cost of being unshippable.\n\n---\n\n## Compatibility\n\n| macOS | Status |\n|---|---|\n| 26 (Tahoe) | ✓ developed and tested here |\n| 13–15 (Ventura → Sequoia) | should work; identical APIs except programmatic space creation may use the now-missing attach symbol if you patch it back in for older systems |\n| 12 (Monterey) and earlier | `LSMinimumSystemVersion` is set to 13; build target can be lowered |\n\nIf you try it on another version, PRs with findings are welcome.\n\n---\n\n## License\n\nMIT — see [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeltorbert%2Fspaces-manager","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichaeltorbert%2Fspaces-manager","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichaeltorbert%2Fspaces-manager/lists"}