@lattices/cli 0.4.9 → 0.4.11

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 (201) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +13 -13
  3. package/{app → apps/mac}/Lattices.app/Contents/Info.plist +10 -2
  4. package/{app → apps/mac}/Lattices.app/Contents/MacOS/Lattices +0 -0
  5. package/{app → apps/mac}/Package.swift +2 -1
  6. package/apps/mac/Resources/Pets/assistant-spark/pet.json +62 -0
  7. package/apps/mac/Resources/Pets/assistant-spark/spritesheet.webp +0 -0
  8. package/apps/mac/Resources/Pets/scout-ranger/pet.json +6 -0
  9. package/apps/mac/Resources/Pets/scout-ranger/spritesheet.webp +0 -0
  10. package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +27 -0
  11. package/apps/mac/Sources/AppShell/AppDelegate.swift +189 -0
  12. package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +25 -0
  13. package/{app → apps/mac}/Sources/AppShell/AppShellView.swift +18 -3
  14. package/{app → apps/mac}/Sources/AppShell/AppUpdater.swift +4 -3
  15. package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +87 -0
  16. package/{app → apps/mac}/Sources/AppShell/LatticesRuntime.swift +43 -0
  17. package/{app → apps/mac}/Sources/AppShell/MainView.swift +116 -63
  18. package/apps/mac/Sources/AppShell/MenuBarController.swift +177 -0
  19. package/{app → apps/mac}/Sources/AppShell/OnboardingView.swift +72 -60
  20. package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +366 -0
  21. package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +70 -0
  22. package/{app → apps/mac}/Sources/AppShell/Preferences.swift +37 -2
  23. package/{app → apps/mac}/Sources/AppShell/SettingsView.swift +815 -156
  24. package/{app → apps/mac}/Sources/AppShell/SettingsWindow.swift +10 -0
  25. package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +13 -0
  26. package/{app → apps/mac}/Sources/Core/Actions/HotkeyStore.swift +6 -1
  27. package/{app → apps/mac}/Sources/Core/Actions/IntentEngine.swift +2 -0
  28. package/{app → apps/mac}/Sources/Core/Daemon/DaemonServer.swift +5 -0
  29. package/{app → apps/mac}/Sources/Core/Daemon/LatticesApi.swift +365 -0
  30. package/{app → apps/mac}/Sources/Core/Desktop/DesktopModel.swift +1 -0
  31. package/{app → apps/mac}/Sources/Core/Desktop/OcrModel.swift +17 -13
  32. package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +33 -0
  33. package/{app → apps/mac}/Sources/Core/Desktop/WindowDragSnapController.swift +18 -217
  34. package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewStore.swift +4 -5
  35. package/{app → apps/mac}/Sources/Core/Desktop/WindowTiler.swift +19 -13
  36. package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +124 -0
  37. package/apps/mac/Sources/Core/Input/EventTapThread.swift +54 -0
  38. package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +20 -0
  39. package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +335 -0
  40. package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +141 -0
  41. package/{app → apps/mac}/Sources/Core/Input/MouseGestureConfig.swift +155 -20
  42. package/apps/mac/Sources/Core/Input/MouseGestureController.swift +2259 -0
  43. package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +170 -0
  44. package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +39 -0
  45. package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +624 -0
  46. package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +56 -0
  47. package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +46 -27
  48. package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +580 -162
  49. package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +1240 -0
  50. package/{app → apps/mac}/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +11 -23
  51. package/{app → apps/mac}/Sources/Core/Pi/PiChatDock.swift +90 -43
  52. package/{app → apps/mac}/Sources/Core/Pi/PiChatSession.swift +676 -43
  53. package/{app → apps/mac}/Sources/Core/Pi/PiProviderSetupCallout.swift +5 -5
  54. package/{app → apps/mac}/Sources/Core/Pi/PiWorkspaceView.swift +93 -44
  55. package/apps/mac/Sources/Core/System/Capability.swift +79 -0
  56. package/{app → apps/mac}/Sources/Core/System/PermissionChecker.swift +43 -8
  57. package/{app → apps/mac}/Sources/Core/Voice/AudioProvider.swift +225 -56
  58. package/bin/handsoff-infer.ts +14 -5
  59. package/bin/handsoff-worker.ts +11 -7
  60. package/bin/infer.ts +406 -0
  61. package/bin/lattices-app.ts +57 -7
  62. package/bin/lattices-dev +40 -1
  63. package/bin/lattices.ts +1 -1
  64. package/docs/agent-execution-plan.md +9 -9
  65. package/docs/api.md +119 -0
  66. package/docs/app.md +1 -0
  67. package/docs/companion-deck.md +1 -1
  68. package/docs/gesture-customization-proposal.md +520 -0
  69. package/docs/mouse-gestures.md +79 -0
  70. package/docs/overview.md +2 -2
  71. package/docs/presentation-execution-review.md +9 -9
  72. package/docs/proposals/LAT-001-gesture-visual-customization.md +522 -0
  73. package/docs/proposals/LAT-002-shared-overlay-canvas.md +353 -0
  74. package/docs/proposals/LAT-003-menu-bar-controller-architecture.md +291 -0
  75. package/docs/proposals/LAT-004-interactive-overlay-actors.md +534 -0
  76. package/docs/reference/dewey.config.ts +74 -0
  77. package/docs/reference/install-agent.md +79 -0
  78. package/docs/repo-structure.md +100 -0
  79. package/docs/voice-error-model.md +7 -7
  80. package/docs/voice.md +18 -0
  81. package/package.json +23 -13
  82. package/swift/Package.swift +20 -0
  83. package/swift/Sources/DeckKit/DeckAction.swift +51 -0
  84. package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +152 -0
  85. package/swift/Sources/DeckKit/DeckCockpit.swift +82 -0
  86. package/swift/Sources/DeckKit/DeckHost.swift +7 -0
  87. package/swift/Sources/DeckKit/DeckManifest.swift +145 -0
  88. package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +533 -0
  89. package/swift/Sources/DeckKit/DeckTrackpad.swift +63 -0
  90. package/swift/Sources/DeckKit/DeckValue.swift +93 -0
  91. package/swift/Sources/DeckKit/DeckVoiceError.swift +88 -0
  92. package/swift/Tests/DeckKitTests/DeckKitTests.swift +286 -0
  93. package/app/Sources/AppShell/AppDelegate.swift +0 -408
  94. package/app/Sources/Core/Input/KeyboardRemapController.swift +0 -184
  95. package/app/Sources/Core/Input/KeyboardRemapStore.swift +0 -84
  96. package/app/Sources/Core/Input/MouseGestureController.swift +0 -1203
  97. package/app/Sources/Core/Input/MouseShortcutStore.swift +0 -107
  98. /package/{app → apps/mac}/Info.plist +0 -0
  99. /package/{app → apps/mac}/Lattices.app/Contents/Resources/AppIcon.icns +0 -0
  100. /package/{app → apps/mac}/Lattices.app/Contents/Resources/tap.wav +0 -0
  101. /package/{app → apps/mac}/Lattices.app/Contents/_CodeSignature/CodeResources +0 -0
  102. /package/{app → apps/mac}/Lattices.entitlements +0 -0
  103. /package/{app → apps/mac}/Resources/tap.wav +0 -0
  104. /package/{app → apps/mac}/Sources/AppShell/App.swift +0 -0
  105. /package/{app → apps/mac}/Sources/AppShell/CliActionLauncher.swift +0 -0
  106. /package/{app → apps/mac}/Sources/AppShell/HomeDashboardView.swift +0 -0
  107. /package/{app → apps/mac}/Sources/AppShell/KeyRecorderView.swift +0 -0
  108. /package/{app → apps/mac}/Sources/AppShell/MainWindow.swift +0 -0
  109. /package/{app → apps/mac}/Sources/Core/Actions/HotkeyManager.swift +0 -0
  110. /package/{app → apps/mac}/Sources/Core/Actions/IntentSchema.swift +0 -0
  111. /package/{app → apps/mac}/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -0
  112. /package/{app → apps/mac}/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -0
  113. /package/{app → apps/mac}/Sources/Core/Actions/Intents/FocusIntent.swift +0 -0
  114. /package/{app → apps/mac}/Sources/Core/Actions/Intents/HelpIntent.swift +0 -0
  115. /package/{app → apps/mac}/Sources/Core/Actions/Intents/KillIntent.swift +0 -0
  116. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -0
  117. /package/{app → apps/mac}/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -0
  118. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -0
  119. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -0
  120. /package/{app → apps/mac}/Sources/Core/Actions/Intents/ScanIntent.swift +0 -0
  121. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SearchIntent.swift +0 -0
  122. /package/{app → apps/mac}/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -0
  123. /package/{app → apps/mac}/Sources/Core/Actions/Intents/TileIntent.swift +0 -0
  124. /package/{app → apps/mac}/Sources/Core/Actions/PaletteCommand.swift +0 -0
  125. /package/{app → apps/mac}/Sources/Core/Actions/VoiceIntentResolver.swift +0 -0
  126. /package/{app → apps/mac}/Sources/Core/Companion/CompanionActivityLog.swift +0 -0
  127. /package/{app → apps/mac}/Sources/Core/Companion/CompanionKeyboardController.swift +0 -0
  128. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -0
  129. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -0
  130. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -0
  131. /package/{app → apps/mac}/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -0
  132. /package/{app → apps/mac}/Sources/Core/Companion/LatticesDeckHost.swift +0 -0
  133. /package/{app → apps/mac}/Sources/Core/Daemon/DaemonProtocol.swift +0 -0
  134. /package/{app → apps/mac}/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -0
  135. /package/{app → apps/mac}/Sources/Core/Desktop/AppTypeClassifier.swift +0 -0
  136. /package/{app → apps/mac}/Sources/Core/Desktop/DesktopModelTypes.swift +0 -0
  137. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryManager.swift +0 -0
  138. /package/{app → apps/mac}/Sources/Core/Desktop/InventoryPath.swift +0 -0
  139. /package/{app → apps/mac}/Sources/Core/Desktop/MouseFinder.swift +0 -0
  140. /package/{app → apps/mac}/Sources/Core/Desktop/OcrStore.swift +0 -0
  141. /package/{app → apps/mac}/Sources/Core/Desktop/PlacementSpec.swift +0 -0
  142. /package/{app → apps/mac}/Sources/Core/Desktop/SessionWindowLocator.swift +0 -0
  143. /package/{app → apps/mac}/Sources/Core/Desktop/TilePickerView.swift +0 -0
  144. /package/{app → apps/mac}/Sources/Core/Desktop/WindowPreviewCard.swift +0 -0
  145. /package/{app → apps/mac}/Sources/Core/Desktop/WindowSelectionStore.swift +0 -0
  146. /package/{app → apps/mac}/Sources/Core/Input/KeyboardRemapConfig.swift +0 -0
  147. /package/{app → apps/mac}/Sources/Core/Input/MouseInputDeviceStore.swift +0 -0
  148. /package/{app → apps/mac}/Sources/Core/Input/MouseInputEventViewer.swift +0 -0
  149. /package/{app → apps/mac}/Sources/Core/Overlays/AppWindowShell.swift +0 -0
  150. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -0
  151. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -0
  152. /package/{app → apps/mac}/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -0
  153. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -0
  154. /package/{app → apps/mac}/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -0
  155. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -0
  156. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -0
  157. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDController.swift +0 -0
  158. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -0
  159. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -0
  160. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -0
  161. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDState.swift +0 -0
  162. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -0
  163. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -0
  164. /package/{app → apps/mac}/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -0
  165. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -0
  166. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -0
  167. /package/{app → apps/mac}/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -0
  168. /package/{app → apps/mac}/Sources/Core/Overlays/OverlayPanelShell.swift +0 -0
  169. /package/{app → apps/mac}/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -0
  170. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -0
  171. /package/{app → apps/mac}/Sources/Core/Pi/PiAuthPromptCard.swift +0 -0
  172. /package/{app → apps/mac}/Sources/Core/Pi/PiInstallCallout.swift +0 -0
  173. /package/{app → apps/mac}/Sources/Core/System/DiagnosticLog.swift +0 -0
  174. /package/{app → apps/mac}/Sources/Core/System/EventBus.swift +0 -0
  175. /package/{app → apps/mac}/Sources/Core/System/ProcessModel.swift +0 -0
  176. /package/{app → apps/mac}/Sources/Core/System/ProcessQuery.swift +0 -0
  177. /package/{app → apps/mac}/Sources/Core/System/SystemTelemetryMonitor.swift +0 -0
  178. /package/{app → apps/mac}/Sources/Core/Voice/AdvisorLearningStore.swift +0 -0
  179. /package/{app → apps/mac}/Sources/Core/Voice/AgentSession.swift +0 -0
  180. /package/{app → apps/mac}/Sources/Core/Voice/HandsOffSession.swift +0 -0
  181. /package/{app → apps/mac}/Sources/Core/Voice/VoiceChatView.swift +0 -0
  182. /package/{app → apps/mac}/Sources/Core/Voice/VoxClient.swift +0 -0
  183. /package/{app → apps/mac}/Sources/Core/Workspace/Project.swift +0 -0
  184. /package/{app → apps/mac}/Sources/Core/Workspace/ProjectScanner.swift +0 -0
  185. /package/{app → apps/mac}/Sources/Core/Workspace/SessionLayerStore.swift +0 -0
  186. /package/{app → apps/mac}/Sources/Core/Workspace/SessionManager.swift +0 -0
  187. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/Terminal.swift +0 -0
  188. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -0
  189. /package/{app → apps/mac}/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -0
  190. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -0
  191. /package/{app → apps/mac}/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -0
  192. /package/{app → apps/mac}/Sources/Core/Workspace/WorkspaceManager.swift +0 -0
  193. /package/{app → apps/mac}/Sources/UI/ActionRow.swift +0 -0
  194. /package/{app → apps/mac}/Sources/UI/OrphanRow.swift +0 -0
  195. /package/{app → apps/mac}/Sources/UI/ProjectRow.swift +0 -0
  196. /package/{app → apps/mac}/Sources/UI/TabGroupRow.swift +0 -0
  197. /package/{app → apps/mac}/Sources/UI/Theme.swift +0 -0
  198. /package/{app → apps/mac}/Tests/StageDragTests.swift +0 -0
  199. /package/{app → apps/mac}/Tests/StageJoinTests.swift +0 -0
  200. /package/{app → apps/mac}/Tests/StageManagerTests.swift +0 -0
  201. /package/{app → apps/mac}/Tests/StageTileTests.swift +0 -0
@@ -36,7 +36,6 @@ final class WindowDragSnapController {
36
36
 
37
37
  private var dragCandidate: DragWindowCandidate?
38
38
  private var activeSession: DragSession?
39
- private var overlayPanels: [String: WindowSnapOverlayPanel] = [:]
40
39
  private var modifierModeEnabled = false
41
40
  private var windowHasMoved = false
42
41
 
@@ -203,9 +202,7 @@ final class WindowDragSnapController {
203
202
  }
204
203
 
205
204
  private func hideOverlays() {
206
- for panel in overlayPanels.values {
207
- panel.orderOut(nil)
208
- }
205
+ ScreenOverlayCanvasController.shared.removeLayers(owner: .dragSnap)
209
206
  }
210
207
 
211
208
  private func captureFocusedWindow(at mouseLocation: NSPoint) -> DragWindowCandidate? {
@@ -271,7 +268,7 @@ final class WindowDragSnapController {
271
268
 
272
269
  var resolved: [ResolvedSnapZone] = []
273
270
  for screen in NSScreen.screens {
274
- let screenID = Self.screenID(for: screen)
271
+ let screenID = ScreenOverlayCanvasController.screenID(for: screen)
275
272
  for (zone, placement, triggerFractions, priority) in baseZones {
276
273
  let triggerRect = Self.screenRect(for: triggerFractions, on: screen)
277
274
  let previewRect = Self.screenRect(fromAX: WindowTiler.tileFrame(for: placement, on: screen))
@@ -312,17 +309,14 @@ final class WindowDragSnapController {
312
309
  private func render(zones: [ResolvedSnapZone], hoveredZone: ResolvedSnapZone?) {
313
310
  let config = WorkspaceManager.shared.snapZonesConfig
314
311
  let grouped = Dictionary(grouping: zones, by: \.screenID)
315
- let activeScreenIDs = Set(grouped.keys)
312
+ var layers: [ScreenOverlayLayerSnapshot] = []
316
313
 
317
314
  for screen in NSScreen.screens {
318
- let screenID = Self.screenID(for: screen)
315
+ let screenID = ScreenOverlayCanvasController.screenID(for: screen)
319
316
  guard let screenZones = grouped[screenID], !screenZones.isEmpty else { continue }
320
317
 
321
- let panel = overlayPanels[screenID] ?? makeOverlayPanel(for: screen)
322
- panel.setFrame(screen.frame, display: false)
323
-
324
318
  let localZones = screenZones.map {
325
- WindowSnapOverlayView.Zone(
319
+ ScreenOverlaySnapZone(
326
320
  id: $0.id,
327
321
  label: $0.label,
328
322
  rect: $0.visibleRect.offsetBy(dx: -screen.frame.minX, dy: -screen.frame.minY),
@@ -334,7 +328,7 @@ final class WindowDragSnapController {
334
328
  ? hoveredZone?.previewRect.offsetBy(dx: -screen.frame.minX, dy: -screen.frame.minY)
335
329
  : nil
336
330
 
337
- panel.overlayView.model = WindowSnapOverlayView.Model(
331
+ let payload = ScreenOverlaySnapZonesPayload(
338
332
  zones: localZones,
339
333
  previewRect: previewRect,
340
334
  previewLabel: nil,
@@ -344,26 +338,20 @@ final class WindowDragSnapController {
344
338
  cornerRadius: config.cornerRadius ?? SnapZonesConfig.defaults.cornerRadius ?? 18
345
339
  )
346
340
 
347
- panel.orderFrontRegardless()
348
- }
349
-
350
- for (screenID, panel) in overlayPanels where !activeScreenIDs.contains(screenID) {
351
- panel.orderOut(nil)
341
+ layers.append(
342
+ ScreenOverlayLayerSnapshot(
343
+ id: ScreenOverlayLayerID("dragSnap.\(screenID)"),
344
+ owner: .dragSnap,
345
+ screen: .screen(id: screenID),
346
+ zIndex: 100,
347
+ opacity: 1,
348
+ payload: .snapZones(payload),
349
+ expiresAt: nil
350
+ )
351
+ )
352
352
  }
353
- }
354
353
 
355
- private func makeOverlayPanel(for screen: NSScreen) -> WindowSnapOverlayPanel {
356
- let panel = WindowSnapOverlayPanel(frame: screen.frame)
357
- overlayPanels[Self.screenID(for: screen)] = panel
358
- return panel
359
- }
360
-
361
- private static func screenID(for screen: NSScreen) -> String {
362
- let key = NSDeviceDescriptionKey("NSScreenNumber")
363
- if let number = screen.deviceDescription[key] as? NSNumber {
364
- return number.stringValue
365
- }
366
- return screen.localizedName
354
+ ScreenOverlayCanvasController.shared.replaceLayers(owner: .dragSnap, with: layers)
367
355
  }
368
356
 
369
357
  private static func screenRect(for fractions: (CGFloat, CGFloat, CGFloat, CGFloat), on screen: NSScreen) -> CGRect {
@@ -439,190 +427,3 @@ final class WindowDragSnapController {
439
427
  Swift.max(lower, Swift.min(upper, value))
440
428
  }
441
429
  }
442
-
443
- private final class WindowSnapOverlayPanel: NSPanel {
444
- let overlayView = WindowSnapOverlayView(frame: .zero)
445
-
446
- init(frame: CGRect) {
447
- super.init(
448
- contentRect: frame,
449
- styleMask: [.borderless, .nonactivatingPanel],
450
- backing: .buffered,
451
- defer: false
452
- )
453
-
454
- isOpaque = false
455
- backgroundColor = .clear
456
- hasShadow = false
457
- ignoresMouseEvents = true
458
- level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
459
- collectionBehavior = [.canJoinAllSpaces, .stationary, .fullScreenAuxiliary]
460
- isMovable = false
461
- hidesOnDeactivate = false
462
- animationBehavior = .none
463
- overlayView.frame = NSRect(origin: .zero, size: frame.size)
464
- overlayView.autoresizingMask = [.width, .height]
465
- contentView = overlayView
466
- }
467
-
468
- override var canBecomeKey: Bool { false }
469
- override var canBecomeMain: Bool { false }
470
- }
471
-
472
- private final class WindowSnapOverlayView: NSView {
473
- struct Zone {
474
- let id: String
475
- let label: String
476
- let rect: CGRect
477
- let isHovered: Bool
478
- }
479
-
480
- struct Model {
481
- let zones: [Zone]
482
- let previewRect: CGRect?
483
- let previewLabel: String?
484
- let zoneOpacity: CGFloat
485
- let highlightOpacity: CGFloat
486
- let previewOpacity: CGFloat
487
- let cornerRadius: CGFloat
488
-
489
- static let empty = Model(
490
- zones: [],
491
- previewRect: nil,
492
- previewLabel: nil,
493
- zoneOpacity: 0.10,
494
- highlightOpacity: 0.22,
495
- previewOpacity: 0.18,
496
- cornerRadius: 18
497
- )
498
- }
499
-
500
- var model: Model = .empty {
501
- didSet { needsDisplay = true }
502
- }
503
-
504
- override init(frame frameRect: NSRect) {
505
- super.init(frame: frameRect)
506
- wantsLayer = true
507
- layer?.backgroundColor = NSColor.clear.cgColor
508
- }
509
-
510
- required init?(coder: NSCoder) {
511
- fatalError("init(coder:) has not been implemented")
512
- }
513
-
514
- override func draw(_ dirtyRect: NSRect) {
515
- NSColor.clear.setFill()
516
- bounds.fill()
517
-
518
- for zone in model.zones {
519
- drawZone(zone)
520
- }
521
-
522
- if let previewRect = model.previewRect {
523
- drawPreview(previewRect, label: model.previewLabel)
524
- }
525
- }
526
-
527
- private func drawZone(_ zone: Zone) {
528
- let rect = zone.rect.insetBy(dx: 1.5, dy: 1.5)
529
- let radius = min(model.cornerRadius, min(rect.width, rect.height) * 0.34)
530
- let path = NSBezierPath(roundedRect: rect, xRadius: radius, yRadius: radius)
531
- let idleStrength = max(0.35, min(model.zoneOpacity / 0.10, 1.4))
532
- let hoverStrength = max(0.35, min(model.highlightOpacity / 0.22, 1.4))
533
-
534
- let shadow = NSShadow()
535
- shadow.shadowBlurRadius = zone.isHovered ? 18 : 10
536
- shadow.shadowOffset = NSSize(width: 0, height: -2)
537
- shadow.shadowColor = NSColor.black.withAlphaComponent(zone.isHovered ? 0.20 : 0.10)
538
-
539
- NSGraphicsContext.saveGraphicsState()
540
- shadow.set()
541
- let baseTop = NSColor(
542
- calibratedWhite: 0.13,
543
- alpha: zone.isHovered ? 0.42 * hoverStrength : 0.22 * idleStrength
544
- )
545
- let baseBottom = NSColor(
546
- calibratedWhite: 0.07,
547
- alpha: zone.isHovered ? 0.34 * hoverStrength : 0.15 * idleStrength
548
- )
549
- NSGradient(starting: baseTop, ending: baseBottom)?.draw(in: path, angle: -90)
550
- NSGraphicsContext.restoreGraphicsState()
551
-
552
- if zone.isHovered {
553
- let glowPath = path.copy() as! NSBezierPath
554
- glowPath.lineWidth = 6
555
- NSColor(calibratedRed: 0.25, green: 0.84, blue: 0.58, alpha: model.highlightOpacity * 0.28).setStroke()
556
- glowPath.stroke()
557
- }
558
-
559
- path.lineWidth = zone.isHovered ? 1.6 : 1.0
560
- NSColor(
561
- calibratedRed: 0.52,
562
- green: 0.94,
563
- blue: 0.72,
564
- alpha: zone.isHovered ? 0.54 * hoverStrength : 0.10 * idleStrength
565
- ).setStroke()
566
- path.stroke()
567
-
568
- let lipRect = CGRect(x: rect.minX + 1.5, y: rect.maxY - 2.5, width: rect.width - 3, height: 2)
569
- if lipRect.width > 0 {
570
- let lipPath = NSBezierPath(roundedRect: lipRect, xRadius: 1, yRadius: 1)
571
- NSColor.white.withAlphaComponent(zone.isHovered ? 0.18 : 0.08).setFill()
572
- lipPath.fill()
573
- }
574
-
575
- drawLabel(zone.label, in: rect, emphasized: zone.isHovered)
576
- }
577
-
578
- private func drawPreview(_ rect: CGRect, label: String?) {
579
- let previewRect = rect.insetBy(dx: 10, dy: 10)
580
- let radius = min(model.cornerRadius, min(previewRect.width, previewRect.height) * 0.14)
581
- let path = NSBezierPath(roundedRect: previewRect, xRadius: radius, yRadius: radius)
582
-
583
- NSColor(calibratedWhite: 1.0, alpha: model.previewOpacity * 0.22).setFill()
584
- path.fill()
585
-
586
- path.lineWidth = 1.6
587
- path.setLineDash([10, 8], count: 2, phase: 0)
588
- NSColor(
589
- calibratedRed: 0.44,
590
- green: 0.90,
591
- blue: 0.68,
592
- alpha: max(0.34, model.previewOpacity * 3.2)
593
- ).setStroke()
594
- path.stroke()
595
- path.setLineDash([], count: 0, phase: 0)
596
-
597
- let innerPath = NSBezierPath(roundedRect: previewRect.insetBy(dx: 7, dy: 7), xRadius: max(radius - 4, 8), yRadius: max(radius - 4, 8))
598
- innerPath.lineWidth = 1
599
- NSColor.white.withAlphaComponent(max(0.08, model.previewOpacity * 1.2)).setStroke()
600
- innerPath.stroke()
601
-
602
- if let label {
603
- let tagRect = CGRect(x: previewRect.minX + 14, y: previewRect.maxY - 34, width: 110, height: 24)
604
- let tagPath = NSBezierPath(roundedRect: tagRect, xRadius: 12, yRadius: 12)
605
- NSColor(calibratedWhite: 0.08, alpha: 0.62).setFill()
606
- tagPath.fill()
607
- NSColor.white.withAlphaComponent(0.10).setStroke()
608
- tagPath.lineWidth = 1
609
- tagPath.stroke()
610
- drawLabel(label, in: tagRect, emphasized: true)
611
- }
612
- }
613
-
614
- private func drawLabel(_ label: String, in rect: CGRect, emphasized: Bool) {
615
- let font = NSFont.monospacedSystemFont(ofSize: emphasized ? 11 : 10, weight: emphasized ? .semibold : .medium)
616
- let attributes: [NSAttributedString.Key: Any] = [
617
- .font: font,
618
- .foregroundColor: NSColor.white.withAlphaComponent(emphasized ? 0.92 : 0.72),
619
- ]
620
- let attr = NSAttributedString(string: label.uppercased(), attributes: attributes)
621
- let size = attr.size()
622
- let drawPoint = CGPoint(
623
- x: rect.midX - size.width / 2,
624
- y: rect.midY - size.height / 2
625
- )
626
- attr.draw(at: drawPoint)
627
- }
628
- }
@@ -53,11 +53,10 @@ final class WindowPreviewStore: ObservableObject {
53
53
  queue.async { [weak self] in
54
54
  guard let self else { return }
55
55
 
56
- let cgImage = CGWindowListCreateImage(
57
- .null,
58
- .optionIncludingWindow,
59
- CGWindowID(wid),
60
- [.boundsIgnoreFraming, .nominalResolution]
56
+ let cgImage = WindowCapture.image(
57
+ listOption: .optionIncludingWindow,
58
+ windowID: CGWindowID(wid),
59
+ imageOption: [.boundsIgnoreFraming, .nominalResolution]
61
60
  )
62
61
 
63
62
  let image = cgImage.map {
@@ -641,6 +641,14 @@ enum WindowTiler {
641
641
  "switchToAdjacentSpace: offset=\(offset) point=\(formatCGPoint(context.point)) displayId=\(context.display.displayId) active=\(context.activeSpaceId) displayCurrent=\(context.display.currentSpaceId) resolved=\(context.currentSpaceId) target=\(targetText) spaces=\(spaces)"
642
642
  )
643
643
 
644
+ if let target = context.target {
645
+ let switched = switchToSpace(spaceId: target.id)
646
+ DiagnosticLog.shared.info("switchToAdjacentSpace: SkyLight \(switched ? "reached" : "missed") target \(target.id)")
647
+ if switched {
648
+ return true
649
+ }
650
+ }
651
+
644
652
  if getDisplaySpaces().count == 1 {
645
653
  if let finalSpaceId = switchToAdjacentSpaceViaSystemShortcut(
646
654
  offset: offset,
@@ -663,10 +671,7 @@ enum WindowTiler {
663
671
  DiagnosticLog.shared.warn("switchToAdjacentSpace: system shortcut stayed on \(context.currentSpaceId), falling back to SkyLight target \(target.id)")
664
672
  }
665
673
 
666
- guard let target = context.target else { return false }
667
- let switched = switchToSpace(spaceId: target.id)
668
- DiagnosticLog.shared.info("switchToAdjacentSpace: SkyLight \(switched ? "reached" : "missed") target \(target.id)")
669
- return switched
674
+ return false
670
675
  }
671
676
 
672
677
  /// Find a window by its title tag and return its CGWindowID and owner PID
@@ -2094,16 +2099,17 @@ enum WindowTiler {
2094
2099
 
2095
2100
  private static func switchToAdjacentSpaceViaSystemShortcut(offset: Int, displayId: String, initialSpaceId: Int) -> Int? {
2096
2101
  let keyCode: CGKeyCode = offset < 0 ? 123 : 124
2097
- let script = """
2098
- tell application "System Events"
2099
- key code \(keyCode) using control down
2100
- end tell
2101
- return "ok"
2102
- """
2103
- let result = ProcessQuery.shell(["/usr/bin/osascript", "-e", script])
2104
- if result != "ok" {
2105
- DiagnosticLog.shared.warn("switchToAdjacentSpace: system shortcut script did not complete for offset \(offset)")
2102
+ guard let source = CGEventSource(stateID: .combinedSessionState),
2103
+ let down = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true),
2104
+ let up = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) else {
2105
+ DiagnosticLog.shared.warn("switchToAdjacentSpace: system shortcut event source unavailable for offset \(offset)")
2106
+ return nil
2106
2107
  }
2108
+ down.flags = .maskControl
2109
+ up.flags = .maskControl
2110
+ down.post(tap: .cghidEventTap)
2111
+ usleep(12_000)
2112
+ up.post(tap: .cghidEventTap)
2107
2113
  return waitForSpaceChange(displayId: displayId, initialSpaceId: initialSpaceId, timeout: 1.2)
2108
2114
  }
2109
2115
 
@@ -0,0 +1,124 @@
1
+ import Foundation
2
+
3
+ /// Self-healing circuit breaker for session-wide `CGEventTap`s.
4
+ ///
5
+ /// macOS disables a tap (`tapDisabledByTimeout`) when its callback exceeds
6
+ /// the OS budget. The naive recovery — re-enable and continue — fights the
7
+ /// OS in a loop when the underlying cause is still present, and the system
8
+ /// input pipeline keeps stuttering.
9
+ ///
10
+ /// This breaker counts trips inside a rolling window and backs off in
11
+ /// escalating cooldowns: 30s → 2 min → permanent (until app restart or
12
+ /// manual re-arm). During cooldown the tap stays disabled — input flows
13
+ /// through the OS without our interference. On cooldown expiry, `rearm`
14
+ /// fires on the main queue to re-enable the tap.
15
+ ///
16
+ /// Thread-safe; `recordTrip()` is safe to call from the event-tap thread.
17
+ final class EventTapBreaker {
18
+ enum State: Equatable {
19
+ case armed
20
+ case paused(cooldownSec: Int)
21
+ case disabled
22
+ }
23
+
24
+ private let label: String
25
+ private let trippedWindow: TimeInterval = 600 // 10 min rolling window
26
+ private let cooldowns: [TimeInterval] = [30, 120] // trip 1 → 30s, trip 2 → 2 min, trip 3+ → permanent
27
+
28
+ private let lock = NSLock()
29
+ private var tripsInWindow: [Date] = []
30
+ private var permanentlyDisabled = false
31
+ private var pendingRearm: DispatchWorkItem?
32
+ private var _state: State = .armed
33
+
34
+ /// Called on the main queue when a cooldown elapses. Caller wires this
35
+ /// to `CGEvent.tapEnable(tap:, enable: true)`.
36
+ var rearm: (() -> Void)?
37
+
38
+ /// Called on the main queue whenever `state` transitions. UI uses this
39
+ /// to surface "paused" / "disabled" messages and re-enable affordances.
40
+ var onStateChanged: ((State) -> Void)?
41
+
42
+ init(label: String) {
43
+ self.label = label
44
+ }
45
+
46
+ var state: State {
47
+ lock.lock(); defer { lock.unlock() }
48
+ return _state
49
+ }
50
+
51
+ /// Record that the OS just delivered `.tapDisabledByTimeout`. Schedules
52
+ /// a re-enable after the appropriate cooldown, or marks the breaker
53
+ /// permanently open after too many trips.
54
+ @discardableResult
55
+ func recordTrip() -> Bool {
56
+ lock.lock()
57
+ if permanentlyDisabled { lock.unlock(); return false }
58
+
59
+ let now = Date()
60
+ tripsInWindow.removeAll { now.timeIntervalSince($0) > trippedWindow }
61
+ tripsInWindow.append(now)
62
+
63
+ let count = tripsInWindow.count
64
+ if count > cooldowns.count {
65
+ permanentlyDisabled = true
66
+ pendingRearm?.cancel()
67
+ pendingRearm = nil
68
+ _state = .disabled
69
+ lock.unlock()
70
+ DiagnosticLog.shared.error("\(label): tap tripped \(count)× in \(Int(trippedWindow))s — disabled until app restart or manual re-arm")
71
+ notifyStateChanged(.disabled)
72
+ return false
73
+ }
74
+
75
+ let cooldown = cooldowns[count - 1]
76
+ _state = .paused(cooldownSec: Int(cooldown))
77
+ let nextState: State = .paused(cooldownSec: Int(cooldown))
78
+
79
+ pendingRearm?.cancel()
80
+ let work = DispatchWorkItem { [weak self] in
81
+ guard let self else { return }
82
+ DiagnosticLog.shared.info("\(self.label): tap auto-recovering")
83
+ self.lock.lock()
84
+ self._state = .armed
85
+ self.lock.unlock()
86
+ self.notifyStateChanged(.armed)
87
+ self.rearm?()
88
+ }
89
+ pendingRearm = work
90
+ lock.unlock()
91
+
92
+ DiagnosticLog.shared.warn("\(label): tap disabled by OS (trip #\(count)) — paused for \(Int(cooldown))s")
93
+ notifyStateChanged(nextState)
94
+ DispatchQueue.main.asyncAfter(deadline: .now() + cooldown, execute: work)
95
+ return false
96
+ }
97
+
98
+ /// Clears all trip history and any pending cooldown. Caller should
99
+ /// re-enable the tap after this to actually recover.
100
+ /// Use cases: tap (re)install, manual re-arm from Settings.
101
+ func reset() {
102
+ lock.lock()
103
+ let wasNotArmed = _state != .armed
104
+ pendingRearm?.cancel()
105
+ pendingRearm = nil
106
+ tripsInWindow.removeAll()
107
+ permanentlyDisabled = false
108
+ _state = .armed
109
+ lock.unlock()
110
+ if wasNotArmed {
111
+ DiagnosticLog.shared.info("\(label): tap state reset (armed)")
112
+ notifyStateChanged(.armed)
113
+ }
114
+ }
115
+
116
+ private func notifyStateChanged(_ newState: State) {
117
+ guard let callback = onStateChanged else { return }
118
+ if Thread.isMainThread {
119
+ callback(newState)
120
+ } else {
121
+ DispatchQueue.main.async { callback(newState) }
122
+ }
123
+ }
124
+ }
@@ -0,0 +1,54 @@
1
+ import Foundation
2
+ import CoreFoundation
3
+
4
+ /// Hosts a long-lived thread + CFRunLoop dedicated to CGEventTap callbacks,
5
+ /// so taps installed at `.headInsertEventTap` don't add main-thread latency
6
+ /// to every keyboard/mouse event in the user's session.
7
+ ///
8
+ /// Callbacks fire on this thread — callers must hop AppKit/UI work back to
9
+ /// main themselves (DispatchQueue.main.async).
10
+ final class EventTapThread {
11
+ static let shared = EventTapThread()
12
+
13
+ private let lock = NSLock()
14
+ private var runLoop: CFRunLoop?
15
+
16
+ private init() {
17
+ let ready = DispatchSemaphore(value: 0)
18
+ let thread = Thread { [unowned self] in
19
+ let loop = CFRunLoopGetCurrent()
20
+ // Keep the run loop alive across add/remove cycles by anchoring a
21
+ // no-op port; otherwise CFRunLoopRun() returns when the last
22
+ // source is removed.
23
+ let keepalive = NSMachPort()
24
+ RunLoop.current.add(keepalive, forMode: .common)
25
+ self.lock.lock()
26
+ self.runLoop = loop
27
+ self.lock.unlock()
28
+ ready.signal()
29
+ CFRunLoopRun()
30
+ }
31
+ thread.qualityOfService = .userInteractive
32
+ thread.name = "com.arach.lattices.EventTapThread"
33
+ thread.start()
34
+ ready.wait()
35
+ }
36
+
37
+ func add(source: CFRunLoopSource) {
38
+ lock.lock()
39
+ let loop = runLoop
40
+ lock.unlock()
41
+ guard let loop else { return }
42
+ CFRunLoopAddSource(loop, source, .commonModes)
43
+ CFRunLoopWakeUp(loop)
44
+ }
45
+
46
+ func remove(source: CFRunLoopSource) {
47
+ lock.lock()
48
+ let loop = runLoop
49
+ lock.unlock()
50
+ guard let loop else { return }
51
+ CFRunLoopRemoveSource(loop, source, .commonModes)
52
+ CFRunLoopWakeUp(loop)
53
+ }
54
+ }
@@ -0,0 +1,20 @@
1
+ import Foundation
2
+
3
+ enum InputCaptureResetCenter {
4
+ static func reset(reason: String) {
5
+ if Thread.isMainThread {
6
+ performReset(reason: reason)
7
+ } else {
8
+ DispatchQueue.main.async {
9
+ performReset(reason: reason)
10
+ }
11
+ }
12
+ }
13
+
14
+ private static func performReset(reason: String) {
15
+ DiagnosticLog.shared.warn("InputCapture: reset for \(reason)")
16
+ ScreenOverlayCanvasController.shared.resetInputCapture(reason: reason)
17
+ MouseGestureController.shared.resetForSystemInputBoundary(reason: reason)
18
+ KeyboardRemapController.shared.resetForSystemInputBoundary(reason: reason)
19
+ }
20
+ }