Skip to content

feat: list Spaces and switch a display's Space - #66

Merged
iamnbutler merged 1 commit into
mainfrom
feat/spaces
Oct 17, 2026
Merged

iamnbutler merged 1 commit into
mainfrom
feat/spaces

Conversation

@iamnbutler

@iamnbutler iamnbutler commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Adds Spaces support (A and B of the Spaces plan). Moving a window between Spaces (C) stays unsupported and is documented.

desktop_spaces (read only, sends no input)

  • Lists each display's Spaces left to right: space_id, a one-based index, kind (desktop or fullscreen), is_current, and the windows assigned to each (pid, window_id and app name, no titles; capped at 50 per Space).
  • Each display carries its desktop_screen index, bounds and current_space_id. Also reports spans_displays.
  • focus gives the pointer's display and the frontmost app's frontmost window and its display.
  • switch_left and switch_right read Mission Control's shortcuts (symbolic hotkeys 79 and 81) from com.apple.symbolichotkeys.
  • desktop_windows rows gain space_ids.
  • The reads use CGSCopyManagedDisplaySpaces, CGSCopySpacesForWindows and _CGSDefaultConnection directly in the client. These are read only and need no permission. I didn't use Peekaboo's SpaceManagementService because its resolveDisplayID maps every display to the first NSScreen, and isActive uses the global active Space. Displays are matched by UUID through CGDisplayCreateUUIDFromDisplayID.

desktop_switch_space (foreground input)

  • Takes a space_id.
  • It presses the configured Move left a space or Move right a space chord through the host's existing hotkey path (the same one desktop_system_key uses), one step at a time. After each step it polls WindowServer for up to 2 s for the expected next Space and stops if that Space doesn't appear.
  • completed only when the final read shows the target as current; otherwise unknown, with space_after.
  • Display agreement: unless Spaces span displays or there's only one display, every step is refused before the key unless both the pointer's display and the frontmost window's display are the target Space's display. The refusal names the display that's off and gives a hint (desktop_hover / desktop_focus).
  • Refused when a shortcut is off or rebound to something other than an arrow with modifiers, when the session is locked, when the target is more than 16 steps away, or when the Space id is unknown. Already current: completed with nothing sent.
  • The fn bit in the stored mask is not pressed, because Peekaboo would post a real fn/Globe key event. So ctrl,left and ctrl,right are sent.

Docs: new Spaces section in docs/tools.md, which also covers Mission Control and App Exposé via desktop_system_key control up/down. New Not-supported bullet: moving windows between Spaces is Dock-only since macOS 14.5, with the manual route (Mission Control via desktop_system_key, then a foreground desktop_drag). The tool count goes from 39 to 41. No bridge op or patch change, so the op count stays at 127.

Checked live (dev app signed with the Developer ID identity, against the running host):

  • spaces read: 2 displays, 1 Space each, shortcuts enabled.
  • Switching to the already-current Space: completed, nothing sent.
  • Unknown space_id and a non-integer space_id: refused.
  • npm run check passes.
  • desktop_windows space_ids: Finder's windows report Space 1, and its hidden helper windows report none.

Pending: screen locked

  • A real multi-step switch, including whether ctrl,left/ctrl,right without the fn flag triggers the Space switch.
  • The display-agreement refusal with two displays.
  • Whether the manual Mission Control route can be inspected and dragged as documented.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant