@lattices/cli 0.4.14 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (181) hide show
  1. package/README.md +5 -7
  2. package/apps/mac/Info.plist +4 -4
  3. package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
  4. package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/bin/lattices-app.ts +110 -17
  6. package/bin/lattices-build +125 -0
  7. package/bin/lattices-dev +89 -16
  8. package/bin/lattices.ts +977 -16
  9. package/docs/agents.md +81 -4
  10. package/docs/ai-chat-ux-review.md +416 -0
  11. package/docs/api.md +135 -3
  12. package/docs/app.md +30 -8
  13. package/docs/config.md +4 -0
  14. package/docs/mouse-gestures.md +60 -1
  15. package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
  16. package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
  17. package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
  18. package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
  19. package/docs/reference/dewey.config.ts +2 -2
  20. package/docs/release.md +171 -0
  21. package/docs/repo-structure.md +5 -5
  22. package/docs/voice.md +11 -27
  23. package/package.json +11 -10
  24. package/apps/mac/Package.swift +0 -27
  25. package/apps/mac/Sources/AppShell/App.swift +0 -26
  26. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
  27. package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
  28. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
  29. package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
  30. package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
  31. package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
  32. package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
  33. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
  34. package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
  35. package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
  36. package/apps/mac/Sources/AppShell/MainView.swift +0 -847
  37. package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
  38. package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
  39. package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
  40. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
  41. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
  42. package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
  43. package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
  44. package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
  45. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
  46. package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
  47. package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
  48. package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
  49. package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
  50. package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
  51. package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
  52. package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
  53. package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
  54. package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
  55. package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
  56. package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
  57. package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
  58. package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
  59. package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
  60. package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
  61. package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
  62. package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
  63. package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
  64. package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
  65. package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
  66. package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
  67. package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
  68. package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
  69. package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
  70. package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
  71. package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
  72. package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
  73. package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
  74. package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
  75. package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
  76. package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
  77. package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
  78. package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
  79. package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
  80. package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
  81. package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
  82. package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
  83. package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
  84. package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
  85. package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
  86. package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
  87. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
  88. package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
  89. package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
  90. package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
  91. package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
  92. package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
  93. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
  94. package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
  95. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
  96. package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
  97. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
  98. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
  99. package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
  100. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
  101. package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
  102. package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
  103. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
  104. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
  105. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
  106. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
  107. package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
  108. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
  109. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
  110. package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
  111. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
  112. package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
  113. package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
  114. package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
  115. package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
  116. package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
  117. package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
  118. package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
  119. package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
  120. package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
  121. package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
  122. package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
  123. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
  124. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
  125. package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
  126. package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
  127. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
  128. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
  129. package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
  130. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
  131. package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
  132. package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
  133. package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
  134. package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
  135. package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
  136. package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
  137. package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
  138. package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
  139. package/apps/mac/Sources/Core/System/Capability.swift +0 -79
  140. package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
  141. package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
  142. package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
  143. package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
  144. package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
  145. package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
  146. package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
  147. package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
  148. package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
  149. package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
  150. package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
  151. package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
  152. package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
  153. package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
  154. package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
  155. package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
  156. package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
  157. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
  158. package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
  159. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
  160. package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
  161. package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
  162. package/apps/mac/Sources/UI/ActionRow.swift +0 -78
  163. package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
  164. package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
  165. package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
  166. package/apps/mac/Sources/UI/Theme.swift +0 -164
  167. package/apps/mac/Tests/StageDragTests.swift +0 -333
  168. package/apps/mac/Tests/StageJoinTests.swift +0 -313
  169. package/apps/mac/Tests/StageManagerTests.swift +0 -280
  170. package/apps/mac/Tests/StageTileTests.swift +0 -353
  171. package/swift/Package.swift +0 -20
  172. package/swift/Sources/DeckKit/DeckAction.swift +0 -51
  173. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
  174. package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
  175. package/swift/Sources/DeckKit/DeckHost.swift +0 -7
  176. package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
  177. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
  178. package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
  179. package/swift/Sources/DeckKit/DeckValue.swift +0 -93
  180. package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
  181. package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
@@ -1,313 +0,0 @@
1
- import XCTest
2
- import CoreGraphics
3
- import AppKit
4
-
5
- /// Experiments: can we programmatically create a new stage by joining windows?
6
- ///
7
- /// Stage Manager groups windows into stages. When a user drags a window from
8
- /// the strip onto the center stage, it joins that stage. Can we replicate this
9
- /// via AX, CGS, or simulated events?
10
- final class StageJoinTests: XCTestCase {
11
-
12
- // MARK: - Helpers
13
-
14
- struct LiveWindow {
15
- let wid: UInt32
16
- let app: String
17
- let pid: Int32
18
- let title: String
19
- let bounds: CGRect
20
- let isOnScreen: Bool
21
- }
22
-
23
- /// Get all real app windows (layer 0, >= 50x50)
24
- func getRealWindows() -> [LiveWindow] {
25
- guard let list = CGWindowListCopyWindowInfo(
26
- [.optionAll, .excludeDesktopElements],
27
- kCGNullWindowID
28
- ) as? [[String: Any]] else { return [] }
29
-
30
- let skip: Set<String> = [
31
- "Window Server", "Dock", "Control Center", "SystemUIServer",
32
- "Notification Center", "Spotlight", "WindowManager",
33
- "Lattices",
34
- ]
35
-
36
- return list.compactMap { info in
37
- guard let wid = info[kCGWindowNumber as String] as? UInt32,
38
- let owner = info[kCGWindowOwnerName as String] as? String,
39
- let pid = info[kCGWindowOwnerPID as String] as? Int32,
40
- let boundsDict = info[kCGWindowBounds as String] as? NSDictionary
41
- else { return nil }
42
-
43
- var rect = CGRect.zero
44
- guard CGRectMakeWithDictionaryRepresentation(boundsDict, &rect) else { return nil }
45
- let title = info[kCGWindowName as String] as? String ?? ""
46
- let layer = info[kCGWindowLayer as String] as? Int ?? 0
47
- let isOnScreen = info[kCGWindowIsOnscreen as String] as? Bool ?? false
48
-
49
- guard layer == 0, rect.width >= 50, rect.height >= 50 else { return nil }
50
- guard !skip.contains(owner) else { return nil }
51
-
52
- return LiveWindow(wid: wid, app: owner, pid: pid, title: title,
53
- bounds: rect, isOnScreen: isOnScreen)
54
- }
55
- }
56
-
57
- /// Get AX window elements for a PID
58
- func getAXWindows(pid: Int32) -> [AXUIElement] {
59
- let app = AXUIElementCreateApplication(pid)
60
- var ref: CFTypeRef?
61
- guard AXUIElementCopyAttributeValue(app, kAXWindowsAttribute as CFString, &ref) == .success,
62
- let windows = ref as? [AXUIElement] else { return [] }
63
- return windows
64
- }
65
-
66
- /// Move an AX window to a position
67
- func moveAXWindow(_ axWin: AXUIElement, to point: CGPoint) -> Bool {
68
- var p = point
69
- let posValue = AXValueCreate(.cgPoint, &p)!
70
- return AXUIElementSetAttributeValue(axWin, kAXPositionAttribute as CFString, posValue) == .success
71
- }
72
-
73
- /// Resize an AX window
74
- func resizeAXWindow(_ axWin: AXUIElement, to size: CGSize) -> Bool {
75
- var s = size
76
- let sizeValue = AXValueCreate(.cgSize, &s)!
77
- return AXUIElementSetAttributeValue(axWin, kAXSizeAttribute as CFString, sizeValue) == .success
78
- }
79
-
80
- /// Raise an AX window
81
- func raiseAXWindow(_ axWin: AXUIElement) -> Bool {
82
- AXUIElementPerformAction(axWin, kAXRaiseAction as CFString) == .success
83
- }
84
-
85
- /// Snapshot which windows are onscreen (active stage)
86
- func activeStageWids() -> Set<UInt32> {
87
- Set(getRealWindows().filter { $0.isOnScreen && $0.bounds.width > 250 }.map(\.wid))
88
- }
89
-
90
- /// Print stage state
91
- func printStageState(label: String) {
92
- let windows = getRealWindows()
93
- let active = windows.filter { $0.isOnScreen && $0.bounds.width > 250 }
94
- let strip = windows.filter { $0.isOnScreen && $0.bounds.width < 250 && $0.bounds.origin.x < 220 }
95
- print("\n[\(label)]")
96
- print(" Active stage: \(active.map { "\($0.app)(\($0.wid))" }.joined(separator: ", "))")
97
- print(" Strip: \(Set(strip.map(\.app)).sorted().joined(separator: ", "))")
98
- }
99
-
100
- // MARK: - Approach 1: Activate two apps rapidly
101
-
102
- func testJoinByActivation() throws {
103
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
104
- try XCTSkipUnless(smEnabled, "Stage Manager is OFF")
105
-
106
- let windows = getRealWindows()
107
-
108
- // Find two different apps that are NOT in the current stage
109
- let offscreenApps = Dictionary(grouping: windows.filter { !$0.isOnScreen && $0.bounds.width > 250 },
110
- by: \.app)
111
- let candidates = offscreenApps.keys.sorted()
112
- guard candidates.count >= 2 else {
113
- print("Need at least 2 offscreen apps, found: \(candidates)")
114
- return
115
- }
116
-
117
- let appA = candidates[0]
118
- let appB = candidates[1]
119
- let winA = offscreenApps[appA]!.first!
120
- let winB = offscreenApps[appB]!.first!
121
-
122
- print("Attempting to join \(appA) and \(appB) by rapid activation")
123
- printStageState(label: "BEFORE")
124
-
125
- // Activate app A
126
- let nsAppA = NSRunningApplication(processIdentifier: winA.pid)
127
- nsAppA?.activate()
128
- Thread.sleep(forTimeInterval: 0.3)
129
-
130
- printStageState(label: "After activating \(appA)")
131
-
132
- // Now immediately activate app B — does SM merge them?
133
- let nsAppB = NSRunningApplication(processIdentifier: winB.pid)
134
- nsAppB?.activate()
135
- Thread.sleep(forTimeInterval: 0.5)
136
-
137
- printStageState(label: "After activating \(appB)")
138
-
139
- // Check: are both apps in the active stage?
140
- let finalActive = getRealWindows().filter { $0.isOnScreen && $0.bounds.width > 250 }
141
- let activeApps = Set(finalActive.map(\.app))
142
- let joined = activeApps.contains(appA) && activeApps.contains(appB)
143
- print("\nResult: both apps in active stage? \(joined)")
144
- print("Active apps: \(activeApps.sorted())")
145
- }
146
-
147
- // MARK: - Approach 2: Move offscreen window into active stage area via AX
148
-
149
- func testJoinByAXMove() throws {
150
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
151
- try XCTSkipUnless(smEnabled, "Stage Manager is OFF")
152
-
153
- let windows = getRealWindows()
154
-
155
- // Find the active stage center
156
- let activeWindows = windows.filter { $0.isOnScreen && $0.bounds.width > 250 }
157
- guard let anchor = activeWindows.first else {
158
- print("No active stage window found")
159
- return
160
- }
161
-
162
- // Find an offscreen app to pull in
163
- let offscreen = windows.filter { !$0.isOnScreen && $0.bounds.width > 250 && $0.app != anchor.app }
164
- guard let target = offscreen.first else {
165
- print("No offscreen window to test with")
166
- return
167
- }
168
-
169
- print("Attempting to join \(target.app)[\(target.wid)] into \(anchor.app)'s stage via AX move")
170
- printStageState(label: "BEFORE")
171
-
172
- // Get AX window for target
173
- let axWindows = getAXWindows(pid: target.pid)
174
- guard let axWin = axWindows.first else {
175
- print("Could not get AX window for \(target.app)")
176
- return
177
- }
178
-
179
- // Move it right next to the anchor window
180
- let destPoint = CGPoint(x: anchor.bounds.origin.x + 50, y: anchor.bounds.origin.y + 50)
181
- let moved = moveAXWindow(axWin, to: destPoint)
182
- print("AX move result: \(moved)")
183
-
184
- // Raise it
185
- let raised = raiseAXWindow(axWin)
186
- print("AX raise result: \(raised)")
187
-
188
- Thread.sleep(forTimeInterval: 0.5)
189
- printStageState(label: "After AX move + raise")
190
-
191
- // Did it join the stage?
192
- let finalActive = getRealWindows().filter { $0.isOnScreen && $0.bounds.width > 250 }
193
- let activeApps = Set(finalActive.map(\.app))
194
- let joined = activeApps.contains(target.app) && activeApps.contains(anchor.app)
195
- print("\nResult: \(target.app) joined \(anchor.app)'s stage? \(joined)")
196
- print("Active apps: \(activeApps.sorted())")
197
- }
198
-
199
- // MARK: - Approach 3: AX move + activate target app (without switching stage)
200
-
201
- func testJoinByMoveAndActivate() throws {
202
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
203
- try XCTSkipUnless(smEnabled, "Stage Manager is OFF")
204
-
205
- let windows = getRealWindows()
206
- let activeWindows = windows.filter { $0.isOnScreen && $0.bounds.width > 250 }
207
- guard let anchor = activeWindows.first else { return }
208
-
209
- let offscreen = windows.filter { !$0.isOnScreen && $0.bounds.width > 250 && $0.app != anchor.app }
210
- guard let target = offscreen.first else { return }
211
-
212
- print("Attempting: move \(target.app) via AX, then activate it")
213
- printStageState(label: "BEFORE")
214
-
215
- // Step 1: Move target window into center area via AX
216
- let axWindows = getAXWindows(pid: target.pid)
217
- guard let axWin = axWindows.first else { return }
218
-
219
- let dest = CGPoint(x: anchor.bounds.midX, y: anchor.bounds.midY)
220
- _ = moveAXWindow(axWin, to: dest)
221
- _ = resizeAXWindow(axWin, to: CGSize(width: 800, height: 600))
222
-
223
- Thread.sleep(forTimeInterval: 0.2)
224
-
225
- // Step 2: Activate anchor app first (keep it in stage)
226
- NSRunningApplication(processIdentifier: anchor.pid)?.activate()
227
- Thread.sleep(forTimeInterval: 0.1)
228
-
229
- // Step 3: Raise the target window (without activate, to avoid stage switch)
230
- _ = raiseAXWindow(axWin)
231
-
232
- Thread.sleep(forTimeInterval: 0.5)
233
- printStageState(label: "After move + raise (no activate)")
234
-
235
- // Step 4: Now try activating both
236
- NSRunningApplication(processIdentifier: anchor.pid)?.activate()
237
- Thread.sleep(forTimeInterval: 0.1)
238
- // Use AX to set focused on target window
239
- AXUIElementSetAttributeValue(axWin, kAXFocusedAttribute as CFString, kCFBooleanTrue)
240
- AXUIElementSetAttributeValue(axWin, kAXMainAttribute as CFString, kCFBooleanTrue)
241
-
242
- Thread.sleep(forTimeInterval: 0.5)
243
- printStageState(label: "After setting focus+main on target")
244
-
245
- let finalActive = getRealWindows().filter { $0.isOnScreen && $0.bounds.width > 250 }
246
- let activeApps = Set(finalActive.map(\.app))
247
- let joined = activeApps.contains(target.app) && activeApps.contains(anchor.app)
248
- print("\nResult: joined? \(joined) — active apps: \(activeApps.sorted())")
249
- }
250
-
251
- // MARK: - Approach 4: CGS space manipulation — put both windows on same space
252
-
253
- func testJoinBySameSpace() throws {
254
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
255
- try XCTSkipUnless(smEnabled, "Stage Manager is OFF")
256
-
257
- // Load CGS functions
258
- typealias CGSConnectionID = UInt32
259
- typealias CGSMainConnectionIDFunc = @convention(c) () -> CGSConnectionID
260
- typealias CGSAddWindowsToSpacesFunc = @convention(c) (CGSConnectionID, CFArray, CFArray) -> Void
261
- typealias CGSGetActiveSpaceFunc = @convention(c) (CGSConnectionID) -> Int
262
-
263
- guard let handle = dlopen("/System/Library/PrivateFrameworks/SkyLight.framework/SkyLight", RTLD_LAZY),
264
- let mainConnSym = dlsym(handle, "CGSMainConnectionID"),
265
- let addToSpacesSym = dlsym(handle, "CGSAddWindowsToSpaces"),
266
- let activeSpaceSym = dlsym(handle, "CGSGetActiveSpace")
267
- else {
268
- print("Could not load SkyLight functions")
269
- return
270
- }
271
-
272
- let CGSMainConnectionID = unsafeBitCast(mainConnSym, to: CGSMainConnectionIDFunc.self)
273
- let CGSAddWindowsToSpaces = unsafeBitCast(addToSpacesSym, to: CGSAddWindowsToSpacesFunc.self)
274
- let CGSGetActiveSpace = unsafeBitCast(activeSpaceSym, to: CGSGetActiveSpaceFunc.self)
275
-
276
- let cid = CGSMainConnectionID()
277
- let activeSpace = CGSGetActiveSpace(cid)
278
- print("Connection: \(cid), Active space: \(activeSpace)")
279
-
280
- let windows = getRealWindows()
281
- let activeWindows = windows.filter { $0.isOnScreen && $0.bounds.width > 250 }
282
- guard let anchor = activeWindows.first else { return }
283
-
284
- let offscreen = windows.filter { !$0.isOnScreen && $0.bounds.width > 250 && $0.app != anchor.app }
285
- guard let target = offscreen.first else { return }
286
-
287
- print("Attempting: add \(target.app)[\(target.wid)] to space \(activeSpace) via CGSAddWindowsToSpaces")
288
- printStageState(label: "BEFORE")
289
-
290
- // Add target window to active space
291
- let windowIDs = [target.wid] as CFArray
292
- let spaceIDs = [activeSpace] as CFArray
293
- CGSAddWindowsToSpaces(cid, windowIDs, spaceIDs)
294
-
295
- Thread.sleep(forTimeInterval: 0.5)
296
- printStageState(label: "After CGSAddWindowsToSpaces")
297
-
298
- // Also try raising it via AX
299
- let axWindows = getAXWindows(pid: target.pid)
300
- if let axWin = axWindows.first {
301
- _ = raiseAXWindow(axWin)
302
- Thread.sleep(forTimeInterval: 0.3)
303
- printStageState(label: "After raise")
304
- }
305
-
306
- let finalActive = getRealWindows().filter { $0.isOnScreen && $0.bounds.width > 250 }
307
- let activeApps = Set(finalActive.map(\.app))
308
- let joined = activeApps.contains(target.app) && activeApps.contains(anchor.app)
309
- print("\nResult: joined? \(joined) — active apps: \(activeApps.sorted())")
310
-
311
- dlclose(handle)
312
- }
313
- }
@@ -1,280 +0,0 @@
1
- import XCTest
2
- import CoreGraphics
3
- import AppKit
4
-
5
- final class StageManagerTests: XCTestCase {
6
-
7
- // MARK: - Detection
8
-
9
- func testStageManagerEnabled() {
10
- let defaults = UserDefaults(suiteName: "com.apple.WindowManager")
11
- let enabled = defaults?.bool(forKey: "GloballyEnabled") ?? false
12
- print("Stage Manager enabled: \(enabled)")
13
-
14
- // Also read all known keys
15
- let keys = [
16
- "GloballyEnabled",
17
- "GloballyEnabledEver",
18
- "AutoHide",
19
- "AppWindowGroupingBehavior",
20
- "HideDesktop",
21
- "StageManagerHideWidgets",
22
- "EnableStandardClickToShowDesktop",
23
- "EnableTiledWindowMargins",
24
- "EnableTilingByEdgeDrag",
25
- "EnableTopTilingByEdgeDrag",
26
- "StandardHideDesktopIcons",
27
- "StandardHideWidgets",
28
- ]
29
-
30
- print("\n=== com.apple.WindowManager preferences ===")
31
- for key in keys {
32
- let val = defaults?.object(forKey: key)
33
- print(" \(key): \(val ?? "nil" as Any)")
34
- }
35
-
36
- // Not asserting true/false — just verifying we can read the domain
37
- XCTAssertNotNil(defaults, "Should be able to open com.apple.WindowManager domain")
38
- }
39
-
40
- // MARK: - Window Classification
41
-
42
- func testClassifyWindows() {
43
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
44
-
45
- guard smEnabled else {
46
- print("Stage Manager is OFF — skipping window classification")
47
- return
48
- }
49
-
50
- guard let windowList = CGWindowListCopyWindowInfo(
51
- [.optionAll, .excludeDesktopElements],
52
- kCGNullWindowID
53
- ) as? [[String: Any]] else {
54
- XCTFail("Could not get window list")
55
- return
56
- }
57
-
58
- var activeStage: [(wid: UInt32, app: String, title: String, bounds: CGRect)] = []
59
- var stripThumbnails: [(wid: UInt32, app: String, title: String, bounds: CGRect)] = []
60
- var hiddenStage: [(wid: UInt32, app: String, title: String, bounds: CGRect)] = []
61
- var gestureOverlays: [(wid: UInt32, bounds: CGRect)] = []
62
- var appIcons: [(wid: UInt32, bounds: CGRect)] = []
63
-
64
- let mainScreen = NSScreen.main!
65
- let stripMaxX: CGFloat = 220 // strip occupies roughly left 220px
66
- let thumbnailMaxSize: CGFloat = 250
67
-
68
- for info in windowList {
69
- guard let wid = info[kCGWindowNumber as String] as? UInt32,
70
- let owner = info[kCGWindowOwnerName as String] as? String,
71
- let boundsDict = info[kCGWindowBounds as String] as? NSDictionary
72
- else { continue }
73
-
74
- var rect = CGRect.zero
75
- guard CGRectMakeWithDictionaryRepresentation(boundsDict, &rect) else { continue }
76
-
77
- let title = info[kCGWindowName as String] as? String ?? ""
78
- let layer = info[kCGWindowLayer as String] as? Int ?? 0
79
- let isOnScreen = info[kCGWindowIsOnscreen as String] as? Bool ?? false
80
-
81
- guard layer == 0 else { continue }
82
- guard rect.width >= 10, rect.height >= 10 else { continue }
83
-
84
- // WindowManager process windows (GBOs and app icons)
85
- if owner == "WindowManager" {
86
- if title == "Gesture Blocking Overlay" || title.isEmpty {
87
- if rect.width <= 80 && rect.height <= 80 {
88
- appIcons.append((wid: wid, bounds: rect))
89
- } else {
90
- gestureOverlays.append((wid: wid, bounds: rect))
91
- }
92
- }
93
- continue
94
- }
95
-
96
- // Skip non-app processes
97
- let skipOwners: Set<String> = [
98
- "Window Server", "Dock", "Control Center", "SystemUIServer",
99
- "Notification Center", "Spotlight",
100
- ]
101
- if skipOwners.contains(owner) { continue }
102
-
103
- if !isOnScreen {
104
- // Hidden in another stage
105
- hiddenStage.append((wid: wid, app: owner, title: title, bounds: rect))
106
- } else if rect.width < thumbnailMaxSize && rect.height < thumbnailMaxSize
107
- && rect.origin.x < stripMaxX {
108
- // Strip thumbnail
109
- stripThumbnails.append((wid: wid, app: owner, title: title, bounds: rect))
110
- } else if rect.width >= 50 && rect.height >= 50 {
111
- // Active stage window
112
- activeStage.append((wid: wid, app: owner, title: title, bounds: rect))
113
- }
114
- }
115
-
116
- print("\n=== Stage Manager Window Classification ===")
117
- print("Screen: \(Int(mainScreen.frame.width))x\(Int(mainScreen.frame.height))")
118
-
119
- print("\n--- Active Stage (\(activeStage.count) windows) ---")
120
- for w in activeStage {
121
- print(" [\(w.wid)] \(w.app) — \"\(w.title)\"")
122
- print(" bounds: \(Int(w.bounds.origin.x)),\(Int(w.bounds.origin.y)) \(Int(w.bounds.width))x\(Int(w.bounds.height))")
123
- }
124
-
125
- print("\n--- Strip Thumbnails (\(stripThumbnails.count) windows) ---")
126
- for w in stripThumbnails {
127
- print(" [\(w.wid)] \(w.app) — \"\(w.title)\"")
128
- print(" bounds: \(Int(w.bounds.origin.x)),\(Int(w.bounds.origin.y)) \(Int(w.bounds.width))x\(Int(w.bounds.height))")
129
- }
130
-
131
- print("\n--- Hidden in Other Stages (\(hiddenStage.count) windows) ---")
132
- for w in hiddenStage {
133
- print(" [\(w.wid)] \(w.app) — \"\(w.title)\"")
134
- print(" bounds: \(Int(w.bounds.origin.x)),\(Int(w.bounds.origin.y)) \(Int(w.bounds.width))x\(Int(w.bounds.height))")
135
- }
136
-
137
- print("\n--- Gesture Blocking Overlays (\(gestureOverlays.count)) ---")
138
- for g in gestureOverlays {
139
- print(" [\(g.wid)] bounds: \(Int(g.bounds.origin.x)),\(Int(g.bounds.origin.y)) \(Int(g.bounds.width))x\(Int(g.bounds.height))")
140
- }
141
-
142
- print("\n--- App Icons in Strip (\(appIcons.count)) ---")
143
- for a in appIcons {
144
- print(" [\(a.wid)] bounds: \(Int(a.bounds.origin.x)),\(Int(a.bounds.origin.y)) \(Int(a.bounds.width))x\(Int(a.bounds.height))")
145
- }
146
-
147
- // Try to correlate GBOs to strip thumbnails by proximity
148
- print("\n--- GBO ↔ Thumbnail Correlation ---")
149
- for gbo in gestureOverlays {
150
- let gboCenter = CGPoint(x: gbo.bounds.midX, y: gbo.bounds.midY)
151
- var closest: (wid: UInt32, app: String, dist: CGFloat) = (0, "", .greatestFiniteMagnitude)
152
- for thumb in stripThumbnails {
153
- let thumbCenter = CGPoint(x: thumb.bounds.midX, y: thumb.bounds.midY)
154
- let dist = hypot(gboCenter.x - thumbCenter.x, gboCenter.y - thumbCenter.y)
155
- if dist < closest.dist {
156
- closest = (thumb.wid, thumb.app, dist)
157
- }
158
- }
159
- if closest.dist < 300 {
160
- print(" GBO [\(gbo.wid)] → Thumbnail [\(closest.wid)] \(closest.app) (dist: \(Int(closest.dist))px)")
161
- } else {
162
- print(" GBO [\(gbo.wid)] → no match (closest: \(Int(closest.dist))px)")
163
- }
164
- }
165
- }
166
-
167
- // MARK: - Stage Grouping Heuristic
168
-
169
- func testInferStageGroups() {
170
- let smEnabled = UserDefaults(suiteName: "com.apple.WindowManager")?.bool(forKey: "GloballyEnabled") ?? false
171
-
172
- guard smEnabled else {
173
- print("Stage Manager is OFF — skipping stage grouping")
174
- return
175
- }
176
-
177
- guard let windowList = CGWindowListCopyWindowInfo(
178
- [.optionAll, .excludeDesktopElements],
179
- kCGNullWindowID
180
- ) as? [[String: Any]] else {
181
- XCTFail("Could not get window list")
182
- return
183
- }
184
-
185
- // Stage Manager groups windows together. When "All at Once" is selected,
186
- // all windows from one app move together. We can infer groups by looking
187
- // at which offscreen windows share similar thumbnail strip positions.
188
-
189
- struct WinInfo {
190
- let wid: UInt32
191
- let app: String
192
- let title: String
193
- let bounds: CGRect
194
- let isOnScreen: Bool
195
- let pid: Int32
196
- }
197
-
198
- var appWindows: [String: [WinInfo]] = [:]
199
- let skipOwners: Set<String> = [
200
- "Window Server", "Dock", "Control Center", "SystemUIServer",
201
- "Notification Center", "Spotlight", "WindowManager",
202
- ]
203
-
204
- for info in windowList {
205
- guard let wid = info[kCGWindowNumber as String] as? UInt32,
206
- let owner = info[kCGWindowOwnerName as String] as? String,
207
- let pid = info[kCGWindowOwnerPID as String] as? Int32,
208
- let boundsDict = info[kCGWindowBounds as String] as? NSDictionary
209
- else { continue }
210
-
211
- var rect = CGRect.zero
212
- guard CGRectMakeWithDictionaryRepresentation(boundsDict, &rect) else { continue }
213
- let title = info[kCGWindowName as String] as? String ?? ""
214
- let layer = info[kCGWindowLayer as String] as? Int ?? 0
215
- let isOnScreen = info[kCGWindowIsOnscreen as String] as? Bool ?? false
216
-
217
- guard layer == 0, rect.width >= 50, rect.height >= 50 else { continue }
218
- guard !skipOwners.contains(owner) else { continue }
219
-
220
- let w = WinInfo(wid: wid, app: owner, title: title, bounds: rect,
221
- isOnScreen: isOnScreen, pid: pid)
222
- appWindows[owner, default: []].append(w)
223
- }
224
-
225
- // Classify into stages
226
- var currentStageApps: Set<String> = []
227
- var otherStageApps: Set<String> = []
228
-
229
- for (app, wins) in appWindows {
230
- let hasOnScreen = wins.contains { $0.isOnScreen && $0.bounds.width > 250 }
231
- if hasOnScreen {
232
- currentStageApps.insert(app)
233
- } else {
234
- otherStageApps.insert(app)
235
- }
236
- }
237
-
238
- print("\n=== Inferred Stage Groups ===")
239
- print("\n🟢 Current Stage:")
240
- for app in currentStageApps.sorted() {
241
- let wins = appWindows[app]!.filter { $0.isOnScreen }
242
- print(" \(app) (\(wins.count) window\(wins.count == 1 ? "" : "s"))")
243
- for w in wins {
244
- print(" [\(w.wid)] \"\(w.title)\" — \(Int(w.bounds.width))x\(Int(w.bounds.height))")
245
- }
246
- }
247
-
248
- print("\n🔵 Other Stages:")
249
- for app in otherStageApps.sorted() {
250
- let wins = appWindows[app]!
251
- let onScreen = wins.filter { $0.isOnScreen }
252
- let offScreen = wins.filter { !$0.isOnScreen }
253
- print(" \(app) (\(offScreen.count) hidden, \(onScreen.count) thumbnail)")
254
- for w in offScreen.prefix(3) {
255
- print(" [\(w.wid)] \"\(w.title)\" — \(Int(w.bounds.width))x\(Int(w.bounds.height))")
256
- }
257
- if offScreen.count > 3 { print(" ... and \(offScreen.count - 3) more") }
258
- }
259
- }
260
-
261
- // MARK: - Preferences Change Detection
262
-
263
- func testStageManagerPrefsObservation() {
264
- // Test that we can observe preference changes via polling
265
- let defaults = UserDefaults(suiteName: "com.apple.WindowManager")
266
- let initial = defaults?.bool(forKey: "GloballyEnabled") ?? false
267
- print("Initial Stage Manager state: \(initial)")
268
-
269
- // Quick re-read to verify consistency
270
- let reread = defaults?.bool(forKey: "GloballyEnabled") ?? false
271
- XCTAssertEqual(initial, reread, "Preference should be stable across reads")
272
-
273
- // Read AppWindowGroupingBehavior
274
- let grouping = defaults?.integer(forKey: "AppWindowGroupingBehavior") ?? -1
275
- print("AppWindowGroupingBehavior: \(grouping) (\(grouping == 0 ? "All at Once" : grouping == 1 ? "One at a Time" : "unknown"))")
276
-
277
- let autoHide = defaults?.bool(forKey: "AutoHide") ?? false
278
- print("AutoHide (strip): \(autoHide)")
279
- }
280
- }