@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.
- package/README.md +5 -7
- package/apps/mac/Info.plist +4 -4
- package/apps/mac/Lattices.app/Contents/Info.plist +4 -12
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/bin/lattices-app.ts +110 -17
- package/bin/lattices-build +125 -0
- package/bin/lattices-dev +89 -16
- package/bin/lattices.ts +977 -16
- package/docs/agents.md +81 -4
- package/docs/ai-chat-ux-review.md +416 -0
- package/docs/api.md +135 -3
- package/docs/app.md +30 -8
- package/docs/config.md +4 -0
- package/docs/mouse-gestures.md +60 -1
- package/docs/proposals/LAT-004-interactive-overlay-actors.md +1 -1
- package/docs/proposals/LAT-005-action-runtime-product-spine.md +914 -0
- package/docs/proposals/LAT-006-mira-in-lattices.md +553 -0
- package/docs/proposals/LAT-007-unified-app-shell.md +128 -0
- package/docs/reference/dewey.config.ts +2 -2
- package/docs/release.md +171 -0
- package/docs/repo-structure.md +5 -5
- package/docs/voice.md +11 -27
- package/package.json +11 -10
- package/apps/mac/Package.swift +0 -27
- package/apps/mac/Sources/AppShell/App.swift +0 -26
- package/apps/mac/Sources/AppShell/AppActivationCoordinator.swift +0 -27
- package/apps/mac/Sources/AppShell/AppDelegate.swift +0 -189
- package/apps/mac/Sources/AppShell/AppServicesBootstrap.swift +0 -25
- package/apps/mac/Sources/AppShell/AppShellView.swift +0 -171
- package/apps/mac/Sources/AppShell/AppUpdater.swift +0 -305
- package/apps/mac/Sources/AppShell/CliActionLauncher.swift +0 -50
- package/apps/mac/Sources/AppShell/HomeDashboardView.swift +0 -133
- package/apps/mac/Sources/AppShell/HotkeyBootstrap.swift +0 -87
- package/apps/mac/Sources/AppShell/KeyRecorderView.swift +0 -210
- package/apps/mac/Sources/AppShell/LatticesRuntime.swift +0 -104
- package/apps/mac/Sources/AppShell/MainView.swift +0 -847
- package/apps/mac/Sources/AppShell/MainWindow.swift +0 -83
- package/apps/mac/Sources/AppShell/MenuBarController.swift +0 -177
- package/apps/mac/Sources/AppShell/OnboardingView.swift +0 -483
- package/apps/mac/Sources/AppShell/PermissionsAssistantView.swift +0 -366
- package/apps/mac/Sources/AppShell/PermissionsAssistantWindow.swift +0 -70
- package/apps/mac/Sources/AppShell/Preferences.swift +0 -297
- package/apps/mac/Sources/AppShell/SettingsView.swift +0 -3163
- package/apps/mac/Sources/AppShell/SettingsWindow.swift +0 -34
- package/apps/mac/Sources/AppShell/WorkspaceInspectorPresenter.swift +0 -13
- package/apps/mac/Sources/Core/Actions/HotkeyManager.swift +0 -256
- package/apps/mac/Sources/Core/Actions/HotkeyStore.swift +0 -399
- package/apps/mac/Sources/Core/Actions/IntentEngine.swift +0 -988
- package/apps/mac/Sources/Core/Actions/IntentSchema.swift +0 -94
- package/apps/mac/Sources/Core/Actions/Intents/CreateLayerIntent.swift +0 -54
- package/apps/mac/Sources/Core/Actions/Intents/DistributeIntent.swift +0 -56
- package/apps/mac/Sources/Core/Actions/Intents/FocusIntent.swift +0 -69
- package/apps/mac/Sources/Core/Actions/Intents/HelpIntent.swift +0 -41
- package/apps/mac/Sources/Core/Actions/Intents/KillIntent.swift +0 -47
- package/apps/mac/Sources/Core/Actions/Intents/LatticeIntent.swift +0 -53
- package/apps/mac/Sources/Core/Actions/Intents/LaunchIntent.swift +0 -67
- package/apps/mac/Sources/Core/Actions/Intents/ListSessionsIntent.swift +0 -32
- package/apps/mac/Sources/Core/Actions/Intents/ListWindowsIntent.swift +0 -30
- package/apps/mac/Sources/Core/Actions/Intents/ScanIntent.swift +0 -52
- package/apps/mac/Sources/Core/Actions/Intents/SearchIntent.swift +0 -190
- package/apps/mac/Sources/Core/Actions/Intents/SwitchLayerIntent.swift +0 -50
- package/apps/mac/Sources/Core/Actions/Intents/TileIntent.swift +0 -61
- package/apps/mac/Sources/Core/Actions/PaletteCommand.swift +0 -439
- package/apps/mac/Sources/Core/Actions/VoiceIntentResolver.swift +0 -713
- package/apps/mac/Sources/Core/Companion/CompanionActivityLog.swift +0 -70
- package/apps/mac/Sources/Core/Companion/CompanionKeyboardController.swift +0 -141
- package/apps/mac/Sources/Core/Companion/LatticesCompanionBridgeServer.swift +0 -454
- package/apps/mac/Sources/Core/Companion/LatticesCompanionCockpit.swift +0 -555
- package/apps/mac/Sources/Core/Companion/LatticesCompanionSecurityCoordinator.swift +0 -629
- package/apps/mac/Sources/Core/Companion/LatticesCompanionTrackpadController.swift +0 -204
- package/apps/mac/Sources/Core/Companion/LatticesDeckHost.swift +0 -1463
- package/apps/mac/Sources/Core/Daemon/DaemonProtocol.swift +0 -114
- package/apps/mac/Sources/Core/Daemon/DaemonServer.swift +0 -427
- package/apps/mac/Sources/Core/Daemon/LatticesApi.swift +0 -2965
- package/apps/mac/Sources/Core/Desktop/AccessibilityTextExtractor.swift +0 -111
- package/apps/mac/Sources/Core/Desktop/AppTypeClassifier.swift +0 -106
- package/apps/mac/Sources/Core/Desktop/DesktopModel.swift +0 -331
- package/apps/mac/Sources/Core/Desktop/DesktopModelTypes.swift +0 -73
- package/apps/mac/Sources/Core/Desktop/InventoryManager.swift +0 -35
- package/apps/mac/Sources/Core/Desktop/InventoryPath.swift +0 -43
- package/apps/mac/Sources/Core/Desktop/MouseFinder.swift +0 -527
- package/apps/mac/Sources/Core/Desktop/OcrModel.swift +0 -467
- package/apps/mac/Sources/Core/Desktop/OcrStore.swift +0 -329
- package/apps/mac/Sources/Core/Desktop/PlacementSpec.swift +0 -195
- package/apps/mac/Sources/Core/Desktop/SessionWindowLocator.swift +0 -139
- package/apps/mac/Sources/Core/Desktop/TilePickerView.swift +0 -209
- package/apps/mac/Sources/Core/Desktop/WindowCapture.swift +0 -33
- package/apps/mac/Sources/Core/Desktop/WindowDragSnapController.swift +0 -429
- package/apps/mac/Sources/Core/Desktop/WindowPreviewCard.swift +0 -100
- package/apps/mac/Sources/Core/Desktop/WindowPreviewStore.swift +0 -112
- package/apps/mac/Sources/Core/Desktop/WindowSelectionStore.swift +0 -76
- package/apps/mac/Sources/Core/Desktop/WindowTiler.swift +0 -2222
- package/apps/mac/Sources/Core/Input/EventTapBreaker.swift +0 -124
- package/apps/mac/Sources/Core/Input/EventTapThread.swift +0 -54
- package/apps/mac/Sources/Core/Input/InputCaptureResetCenter.swift +0 -20
- package/apps/mac/Sources/Core/Input/KeyboardRemapConfig.swift +0 -69
- package/apps/mac/Sources/Core/Input/KeyboardRemapController.swift +0 -346
- package/apps/mac/Sources/Core/Input/KeyboardRemapStore.swift +0 -141
- package/apps/mac/Sources/Core/Input/MouseGestureConfig.swift +0 -499
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +0 -2583
- package/apps/mac/Sources/Core/Input/MouseInputDeviceStore.swift +0 -98
- package/apps/mac/Sources/Core/Input/MouseInputEventViewer.swift +0 -272
- package/apps/mac/Sources/Core/Input/MouseShortcutStore.swift +0 -170
- package/apps/mac/Sources/Core/Input/SecureEventInputMonitor.swift +0 -39
- package/apps/mac/Sources/Core/Input/ShapeRecognizer.swift +0 -624
- package/apps/mac/Sources/Core/Input/TapBudgetMeter.swift +0 -56
- package/apps/mac/Sources/Core/Overlays/AppWindowShell.swift +0 -63
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeState.swift +0 -1566
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeView.swift +0 -1927
- package/apps/mac/Sources/Core/Overlays/CommandMode/CommandModeWindow.swift +0 -196
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteView.swift +0 -307
- package/apps/mac/Sources/Core/Overlays/CommandPalette/CommandPaletteWindow.swift +0 -67
- package/apps/mac/Sources/Core/Overlays/HUD/CheatSheetHUD.swift +0 -576
- package/apps/mac/Sources/Core/Overlays/HUD/HUDBottomBar.swift +0 -279
- package/apps/mac/Sources/Core/Overlays/HUD/HUDController.swift +0 -1158
- package/apps/mac/Sources/Core/Overlays/HUD/HUDLeftBar.swift +0 -849
- package/apps/mac/Sources/Core/Overlays/HUD/HUDMinimap.swift +0 -179
- package/apps/mac/Sources/Core/Overlays/HUD/HUDRightBar.swift +0 -596
- package/apps/mac/Sources/Core/Overlays/HUD/HUDState.swift +0 -367
- package/apps/mac/Sources/Core/Overlays/HUD/HUDTopBar.swift +0 -243
- package/apps/mac/Sources/Core/Overlays/HUD/LauncherHUD.swift +0 -334
- package/apps/mac/Sources/Core/Overlays/HUD/LayerBezel.swift +0 -203
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchState.swift +0 -280
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchView.swift +0 -422
- package/apps/mac/Sources/Core/Overlays/OmniSearch/OmniSearchWindow.swift +0 -94
- package/apps/mac/Sources/Core/Overlays/OverlayPanelShell.swift +0 -241
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapState.swift +0 -3135
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapView.swift +0 -3977
- package/apps/mac/Sources/Core/Overlays/ScreenMap/ScreenMapWindowController.swift +0 -119
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +0 -1217
- package/apps/mac/Sources/Core/Overlays/Voice/VoiceCommandWindow.swift +0 -1575
- package/apps/mac/Sources/Core/Pi/PiAuthNextStepCard.swift +0 -148
- package/apps/mac/Sources/Core/Pi/PiAuthPromptCard.swift +0 -90
- package/apps/mac/Sources/Core/Pi/PiChatDock.swift +0 -564
- package/apps/mac/Sources/Core/Pi/PiChatSession.swift +0 -1948
- package/apps/mac/Sources/Core/Pi/PiInstallCallout.swift +0 -86
- package/apps/mac/Sources/Core/Pi/PiProviderSetupCallout.swift +0 -99
- package/apps/mac/Sources/Core/Pi/PiWorkspaceView.swift +0 -510
- package/apps/mac/Sources/Core/System/Capability.swift +0 -79
- package/apps/mac/Sources/Core/System/DiagnosticLog.swift +0 -373
- package/apps/mac/Sources/Core/System/EventBus.swift +0 -31
- package/apps/mac/Sources/Core/System/PermissionChecker.swift +0 -224
- package/apps/mac/Sources/Core/System/ProcessModel.swift +0 -199
- package/apps/mac/Sources/Core/System/ProcessQuery.swift +0 -151
- package/apps/mac/Sources/Core/System/SystemTelemetryMonitor.swift +0 -273
- package/apps/mac/Sources/Core/Voice/AdvisorLearningStore.swift +0 -90
- package/apps/mac/Sources/Core/Voice/AgentSession.swift +0 -377
- package/apps/mac/Sources/Core/Voice/AudioProvider.swift +0 -555
- package/apps/mac/Sources/Core/Voice/HandsOffSession.swift +0 -839
- package/apps/mac/Sources/Core/Voice/VoiceChatView.swift +0 -192
- package/apps/mac/Sources/Core/Voice/VoxClient.swift +0 -454
- package/apps/mac/Sources/Core/Workspace/Project.swift +0 -28
- package/apps/mac/Sources/Core/Workspace/ProjectScanner.swift +0 -141
- package/apps/mac/Sources/Core/Workspace/SessionLayerStore.swift +0 -285
- package/apps/mac/Sources/Core/Workspace/SessionManager.swift +0 -75
- package/apps/mac/Sources/Core/Workspace/Terminal/Terminal.swift +0 -259
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalQuery.swift +0 -156
- package/apps/mac/Sources/Core/Workspace/Terminal/TerminalSynthesizer.swift +0 -200
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxModel.swift +0 -60
- package/apps/mac/Sources/Core/Workspace/Tmux/TmuxQuery.swift +0 -105
- package/apps/mac/Sources/Core/Workspace/WorkspaceManager.swift +0 -1027
- package/apps/mac/Sources/UI/ActionRow.swift +0 -78
- package/apps/mac/Sources/UI/OrphanRow.swift +0 -129
- package/apps/mac/Sources/UI/ProjectRow.swift +0 -368
- package/apps/mac/Sources/UI/TabGroupRow.swift +0 -178
- package/apps/mac/Sources/UI/Theme.swift +0 -164
- package/apps/mac/Tests/StageDragTests.swift +0 -333
- package/apps/mac/Tests/StageJoinTests.swift +0 -313
- package/apps/mac/Tests/StageManagerTests.swift +0 -280
- package/apps/mac/Tests/StageTileTests.swift +0 -353
- package/swift/Package.swift +0 -20
- package/swift/Sources/DeckKit/DeckAction.swift +0 -51
- package/swift/Sources/DeckKit/DeckBridgeSecurity.swift +0 -152
- package/swift/Sources/DeckKit/DeckCockpit.swift +0 -82
- package/swift/Sources/DeckKit/DeckHost.swift +0 -7
- package/swift/Sources/DeckKit/DeckManifest.swift +0 -145
- package/swift/Sources/DeckKit/DeckRuntimeSnapshot.swift +0 -533
- package/swift/Sources/DeckKit/DeckTrackpad.swift +0 -63
- package/swift/Sources/DeckKit/DeckValue.swift +0 -93
- package/swift/Sources/DeckKit/DeckVoiceError.swift +0 -88
- package/swift/Tests/DeckKitTests/DeckKitTests.swift +0 -286
|
@@ -1,124 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,20 +0,0 @@
|
|
|
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
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import CoreGraphics
|
|
2
|
-
import Foundation
|
|
3
|
-
|
|
4
|
-
enum KeyboardRemapKey: String, Codable, Equatable {
|
|
5
|
-
case capsLock = "caps_lock"
|
|
6
|
-
|
|
7
|
-
var keyCode: Int64 {
|
|
8
|
-
switch self {
|
|
9
|
-
case .capsLock: return 57
|
|
10
|
-
}
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
var displayLabel: String {
|
|
14
|
-
switch self {
|
|
15
|
-
case .capsLock: return "Caps Lock"
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
enum KeyboardRemapAction: String, Codable, Equatable {
|
|
21
|
-
case escape
|
|
22
|
-
case hyper
|
|
23
|
-
|
|
24
|
-
var displayLabel: String {
|
|
25
|
-
switch self {
|
|
26
|
-
case .escape: return "Escape"
|
|
27
|
-
case .hyper: return "Hyper"
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
struct KeyboardRemapRule: Codable, Equatable, Identifiable {
|
|
33
|
-
var id: String
|
|
34
|
-
var enabled: Bool
|
|
35
|
-
var from: KeyboardRemapKey
|
|
36
|
-
var toIfHeld: KeyboardRemapAction
|
|
37
|
-
var toIfAlone: KeyboardRemapAction?
|
|
38
|
-
|
|
39
|
-
var summaryLine: String {
|
|
40
|
-
let held = "hold \(from.displayLabel) -> \(toIfHeld.displayLabel)"
|
|
41
|
-
guard let alone = toIfAlone else { return held }
|
|
42
|
-
return "\(held), tap -> \(alone.displayLabel)"
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
struct KeyboardRemapConfig: Codable, Equatable {
|
|
47
|
-
var rules: [KeyboardRemapRule]
|
|
48
|
-
|
|
49
|
-
static let defaults = KeyboardRemapConfig(
|
|
50
|
-
rules: [
|
|
51
|
-
KeyboardRemapRule(
|
|
52
|
-
id: "caps_lock_hyper_escape",
|
|
53
|
-
enabled: true,
|
|
54
|
-
from: .capsLock,
|
|
55
|
-
toIfHeld: .hyper,
|
|
56
|
-
toIfAlone: .escape
|
|
57
|
-
)
|
|
58
|
-
]
|
|
59
|
-
)
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
extension CGEventFlags {
|
|
63
|
-
static let latticesHyper: CGEventFlags = [
|
|
64
|
-
.maskCommand,
|
|
65
|
-
.maskControl,
|
|
66
|
-
.maskAlternate,
|
|
67
|
-
.maskShift,
|
|
68
|
-
]
|
|
69
|
-
}
|
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import AppKit
|
|
2
|
-
import Combine
|
|
3
|
-
import CoreGraphics
|
|
4
|
-
|
|
5
|
-
final class KeyboardRemapController: ObservableObject {
|
|
6
|
-
static let shared = KeyboardRemapController()
|
|
7
|
-
|
|
8
|
-
/// Live state of the event-tap circuit breaker. SettingsView observes
|
|
9
|
-
/// this to surface "paused" / "disabled" status and a re-arm button.
|
|
10
|
-
@Published private(set) var breakerState: EventTapBreaker.State = .armed
|
|
11
|
-
|
|
12
|
-
private static let syntheticMarker: Int64 = 0x4C4B524D
|
|
13
|
-
|
|
14
|
-
private var eventTap: CFMachPort?
|
|
15
|
-
private var runLoopSource: CFRunLoopSource?
|
|
16
|
-
private var subscriptions: Set<AnyCancellable> = []
|
|
17
|
-
private var installedObservers = false
|
|
18
|
-
private var capsLayerActive = false
|
|
19
|
-
private var capsUsedAsModifier = false
|
|
20
|
-
private var capsLayerActivatedAt: CFAbsoluteTime?
|
|
21
|
-
private var capsLayerLastEventAt: CFAbsoluteTime?
|
|
22
|
-
private var bypassUntil: CFAbsoluteTime = 0
|
|
23
|
-
private var lastCapsLayerStaleLogAt: CFAbsoluteTime = 0
|
|
24
|
-
private var pressedKeyCodes: [Int64: CFAbsoluteTime] = [:]
|
|
25
|
-
private let breaker = EventTapBreaker(label: "KeyboardRemap")
|
|
26
|
-
private let budgetMeter = TapBudgetMeter(label: "KeyboardRemap")
|
|
27
|
-
private let maxCapsLayerIdleDuration: TimeInterval = 2.0
|
|
28
|
-
private let maxCapsLayerHeldDuration: TimeInterval = 20.0
|
|
29
|
-
private let maxTrackedKeyDownDuration: TimeInterval = 120.0
|
|
30
|
-
private let emergencyBypassDuration: TimeInterval = 3.0
|
|
31
|
-
|
|
32
|
-
private init() {
|
|
33
|
-
breaker.onStateChanged = { [weak self] newState in
|
|
34
|
-
self?.breakerState = newState
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/// Re-enable the tap after a breaker trip, clearing trip history.
|
|
39
|
-
/// Settings UI calls this when the user explicitly chooses to recover
|
|
40
|
-
/// from a `disabled` state.
|
|
41
|
-
func reArmAfterBreakerTrip() {
|
|
42
|
-
dispatchPrecondition(condition: .onQueue(.main))
|
|
43
|
-
breaker.reset()
|
|
44
|
-
if let tap = eventTap {
|
|
45
|
-
CGEvent.tapEnable(tap: tap, enable: true)
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
func start() {
|
|
50
|
-
installObserversIfNeeded()
|
|
51
|
-
refresh()
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
func stop() {
|
|
55
|
-
removeEventTap()
|
|
56
|
-
clearCapsLayer()
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
func resetForSystemInputBoundary(reason: String) {
|
|
60
|
-
dispatchPrecondition(condition: .onQueue(.main))
|
|
61
|
-
clearCapsLayer()
|
|
62
|
-
pressedKeyCodes.removeAll()
|
|
63
|
-
breaker.reset()
|
|
64
|
-
if let eventTap {
|
|
65
|
-
CGEvent.tapEnable(tap: eventTap, enable: true)
|
|
66
|
-
} else {
|
|
67
|
-
refresh()
|
|
68
|
-
}
|
|
69
|
-
DiagnosticLog.shared.warn("KeyboardRemap: reset for \(reason)")
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
private func installObserversIfNeeded() {
|
|
73
|
-
guard !installedObservers else { return }
|
|
74
|
-
installedObservers = true
|
|
75
|
-
|
|
76
|
-
Preferences.shared.$keyboardRemapsEnabled
|
|
77
|
-
.receive(on: RunLoop.main)
|
|
78
|
-
.sink { [weak self] _ in self?.refresh() }
|
|
79
|
-
.store(in: &subscriptions)
|
|
80
|
-
|
|
81
|
-
PermissionChecker.shared.$accessibility
|
|
82
|
-
.receive(on: RunLoop.main)
|
|
83
|
-
.sink { [weak self] _ in self?.refresh() }
|
|
84
|
-
.store(in: &subscriptions)
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
private func refresh() {
|
|
88
|
-
guard Preferences.shared.keyboardRemapsEnabled,
|
|
89
|
-
PermissionChecker.shared.accessibility else {
|
|
90
|
-
removeEventTap()
|
|
91
|
-
return
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
KeyboardRemapStore.shared.ensureConfigFile()
|
|
95
|
-
if eventTap == nil {
|
|
96
|
-
installEventTap()
|
|
97
|
-
} else if let eventTap {
|
|
98
|
-
CGEvent.tapEnable(tap: eventTap, enable: true)
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
private func installEventTap() {
|
|
103
|
-
// Fresh install is a clean slate — drop any stale trip history so
|
|
104
|
-
// the new tap's first failure is judged on its own merits.
|
|
105
|
-
breaker.reset()
|
|
106
|
-
|
|
107
|
-
var mask = CGEventMask(0)
|
|
108
|
-
mask |= CGEventMask(1) << CGEventType.keyDown.rawValue
|
|
109
|
-
mask |= CGEventMask(1) << CGEventType.keyUp.rawValue
|
|
110
|
-
mask |= CGEventMask(1) << CGEventType.flagsChanged.rawValue
|
|
111
|
-
|
|
112
|
-
let tap = CGEvent.tapCreate(
|
|
113
|
-
tap: .cgSessionEventTap,
|
|
114
|
-
place: .headInsertEventTap,
|
|
115
|
-
options: .defaultTap,
|
|
116
|
-
eventsOfInterest: mask,
|
|
117
|
-
callback: Self.eventTapCallback,
|
|
118
|
-
userInfo: UnsafeMutableRawPointer(Unmanaged.passUnretained(self).toOpaque())
|
|
119
|
-
)
|
|
120
|
-
|
|
121
|
-
guard let tap else {
|
|
122
|
-
DiagnosticLog.shared.warn("KeyboardRemap: failed to install keyboard event tap")
|
|
123
|
-
return
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
|
|
127
|
-
eventTap = tap
|
|
128
|
-
runLoopSource = source
|
|
129
|
-
|
|
130
|
-
if let source {
|
|
131
|
-
EventTapThread.shared.add(source: source)
|
|
132
|
-
}
|
|
133
|
-
CGEvent.tapEnable(tap: tap, enable: true)
|
|
134
|
-
breaker.rearm = { [weak self] in
|
|
135
|
-
guard let self, let tap = self.eventTap else { return }
|
|
136
|
-
CGEvent.tapEnable(tap: tap, enable: true)
|
|
137
|
-
}
|
|
138
|
-
DiagnosticLog.shared.info("KeyboardRemap: keyboard event tap installed")
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
private func removeEventTap() {
|
|
142
|
-
if let source = runLoopSource {
|
|
143
|
-
EventTapThread.shared.remove(source: source)
|
|
144
|
-
}
|
|
145
|
-
runLoopSource = nil
|
|
146
|
-
if let tap = eventTap {
|
|
147
|
-
CFMachPortInvalidate(tap)
|
|
148
|
-
}
|
|
149
|
-
eventTap = nil
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
private static let eventTapCallback: CGEventTapCallBack = { _, type, event, userInfo in
|
|
153
|
-
guard let userInfo else { return Unmanaged.passUnretained(event) }
|
|
154
|
-
let controller = Unmanaged<KeyboardRemapController>.fromOpaque(userInfo).takeUnretainedValue()
|
|
155
|
-
return controller.handleEvent(type: type, event: event)
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
private func handleEvent(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
|
|
159
|
-
let started = CFAbsoluteTimeGetCurrent()
|
|
160
|
-
defer {
|
|
161
|
-
let elapsedMs = (CFAbsoluteTimeGetCurrent() - started) * 1000
|
|
162
|
-
budgetMeter.record(durationMs: elapsedMs)
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
if type == .tapDisabledByTimeout {
|
|
166
|
-
// OS killed the tap because a callback was too slow. Run through
|
|
167
|
-
// the breaker — it backs off in escalating cooldowns rather than
|
|
168
|
-
// re-enabling immediately and getting killed again.
|
|
169
|
-
clearCapsLayer()
|
|
170
|
-
breaker.recordTrip()
|
|
171
|
-
return Unmanaged.passUnretained(event)
|
|
172
|
-
}
|
|
173
|
-
if type == .tapDisabledByUserInput {
|
|
174
|
-
// User-driven disable (rare). Re-enable directly, no cooldown.
|
|
175
|
-
clearCapsLayer()
|
|
176
|
-
if let eventTap {
|
|
177
|
-
CGEvent.tapEnable(tap: eventTap, enable: true)
|
|
178
|
-
}
|
|
179
|
-
return Unmanaged.passUnretained(event)
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
if event.getIntegerValueField(.eventSourceUserData) == Self.syntheticMarker {
|
|
183
|
-
return Unmanaged.passUnretained(event)
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
expireStalePressedKeys(now: started)
|
|
187
|
-
updatePressedKeys(type: type, keyCode: event.getIntegerValueField(.keyboardEventKeycode), now: started)
|
|
188
|
-
if shouldTriggerEmergencyReset(type: type, event: event) {
|
|
189
|
-
emergencyClear(now: started)
|
|
190
|
-
InputCaptureResetCenter.reset(reason: "keyboard emergency chord")
|
|
191
|
-
return Unmanaged.passUnretained(event)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
if started < bypassUntil {
|
|
195
|
-
return Unmanaged.passUnretained(event)
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
KeyboardRemapStore.shared.scheduleReloadCheckIfNeeded()
|
|
199
|
-
guard let rule = KeyboardRemapStore.shared.capsLockRule,
|
|
200
|
-
rule.toIfHeld == .hyper else {
|
|
201
|
-
return Unmanaged.passUnretained(event)
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
|
|
205
|
-
if type == .flagsChanged, keyCode == rule.from.keyCode {
|
|
206
|
-
return handleCapsLockFlagsChanged(event, rule: rule)
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
reconcileCapsLayer(event: event, type: type, now: started)
|
|
210
|
-
guard capsLayerActive else {
|
|
211
|
-
return Unmanaged.passUnretained(event)
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
switch type {
|
|
215
|
-
case .keyDown:
|
|
216
|
-
if keyCode == 53 {
|
|
217
|
-
emergencyClear(now: started)
|
|
218
|
-
return Unmanaged.passUnretained(event)
|
|
219
|
-
}
|
|
220
|
-
capsUsedAsModifier = true
|
|
221
|
-
capsLayerLastEventAt = started
|
|
222
|
-
event.flags = normalizedFlags(event.flags).union(.latticesHyper)
|
|
223
|
-
return Unmanaged.passUnretained(event)
|
|
224
|
-
case .keyUp:
|
|
225
|
-
capsLayerLastEventAt = started
|
|
226
|
-
event.flags = normalizedFlags(event.flags).union(.latticesHyper)
|
|
227
|
-
return Unmanaged.passUnretained(event)
|
|
228
|
-
default:
|
|
229
|
-
return Unmanaged.passUnretained(event)
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
private func handleCapsLockFlagsChanged(_ event: CGEvent, rule: KeyboardRemapRule) -> Unmanaged<CGEvent>? {
|
|
234
|
-
let isDown = event.flags.contains(.maskAlphaShift)
|
|
235
|
-
if isDown {
|
|
236
|
-
capsLayerActive = true
|
|
237
|
-
capsUsedAsModifier = false
|
|
238
|
-
let now = CFAbsoluteTimeGetCurrent()
|
|
239
|
-
capsLayerActivatedAt = now
|
|
240
|
-
capsLayerLastEventAt = now
|
|
241
|
-
DiagnosticLog.shared.info("KeyboardRemap: Caps Lock layer active")
|
|
242
|
-
} else {
|
|
243
|
-
let shouldTap = capsLayerActive && !capsUsedAsModifier && rule.toIfAlone == .escape
|
|
244
|
-
clearCapsLayer()
|
|
245
|
-
if shouldTap {
|
|
246
|
-
postKeyTap(keyCode: 53)
|
|
247
|
-
}
|
|
248
|
-
DiagnosticLog.shared.info("KeyboardRemap: Caps Lock layer inactive")
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
return nil
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private func clearCapsLayer() {
|
|
255
|
-
capsLayerActive = false
|
|
256
|
-
capsUsedAsModifier = false
|
|
257
|
-
capsLayerActivatedAt = nil
|
|
258
|
-
capsLayerLastEventAt = nil
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
private func reconcileCapsLayer(event: CGEvent, type: CGEventType, now: CFAbsoluteTime) {
|
|
262
|
-
guard capsLayerActive else { return }
|
|
263
|
-
|
|
264
|
-
// If a release event was dropped, later key events often arrive
|
|
265
|
-
// without the physical Caps flag. Treat that as an input boundary and
|
|
266
|
-
// fail open before rewriting the user's key.
|
|
267
|
-
if type == .keyDown || type == .keyUp,
|
|
268
|
-
!event.flags.contains(.maskAlphaShift) {
|
|
269
|
-
clearCapsLayer(reason: "physical Caps flag cleared", now: now)
|
|
270
|
-
return
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if let lastEventAt = capsLayerLastEventAt,
|
|
274
|
-
now - lastEventAt > maxCapsLayerIdleDuration {
|
|
275
|
-
clearCapsLayer(reason: "idle", now: now)
|
|
276
|
-
return
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
if let activatedAt = capsLayerActivatedAt,
|
|
280
|
-
now - activatedAt > maxCapsLayerHeldDuration {
|
|
281
|
-
clearCapsLayer(reason: "held too long", now: now)
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
private func clearCapsLayer(reason: String, now: CFAbsoluteTime) {
|
|
286
|
-
clearCapsLayer()
|
|
287
|
-
if now - lastCapsLayerStaleLogAt > 1 {
|
|
288
|
-
lastCapsLayerStaleLogAt = now
|
|
289
|
-
DiagnosticLog.shared.warn("KeyboardRemap: Caps Lock layer cleared (\(reason))")
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
private func emergencyClear(now: CFAbsoluteTime) {
|
|
294
|
-
clearCapsLayer()
|
|
295
|
-
pressedKeyCodes.removeAll()
|
|
296
|
-
bypassUntil = now + emergencyBypassDuration
|
|
297
|
-
DiagnosticLog.shared.warn("KeyboardRemap: emergency bypass via Escape")
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
private func updatePressedKeys(type: CGEventType, keyCode: Int64, now: CFAbsoluteTime) {
|
|
301
|
-
switch type {
|
|
302
|
-
case .keyDown:
|
|
303
|
-
pressedKeyCodes[keyCode] = now
|
|
304
|
-
case .keyUp:
|
|
305
|
-
pressedKeyCodes.removeValue(forKey: keyCode)
|
|
306
|
-
default:
|
|
307
|
-
break
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
private func expireStalePressedKeys(now: CFAbsoluteTime) {
|
|
312
|
-
let staleKeys = pressedKeyCodes.filter { now - $0.value > maxTrackedKeyDownDuration }.map(\.key)
|
|
313
|
-
guard !staleKeys.isEmpty else { return }
|
|
314
|
-
for keyCode in staleKeys {
|
|
315
|
-
pressedKeyCodes.removeValue(forKey: keyCode)
|
|
316
|
-
}
|
|
317
|
-
DiagnosticLog.shared.warn("KeyboardRemap: cleared stale key-down state for \(staleKeys.count) key(s)")
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
private func shouldTriggerEmergencyReset(type: CGEventType, event: CGEvent) -> Bool {
|
|
321
|
-
guard type == .keyDown else { return false }
|
|
322
|
-
let keyCode = event.getIntegerValueField(.keyboardEventKeycode)
|
|
323
|
-
let flags = event.flags
|
|
324
|
-
return keyCode == 40
|
|
325
|
-
&& pressedKeyCodes[53] != nil
|
|
326
|
-
&& flags.contains(.maskShift)
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
private func normalizedFlags(_ flags: CGEventFlags) -> CGEventFlags {
|
|
330
|
-
var normalized = flags
|
|
331
|
-
normalized.remove(.maskAlphaShift)
|
|
332
|
-
return normalized
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
private func postKeyTap(keyCode: CGKeyCode) {
|
|
336
|
-
guard let source = CGEventSource(stateID: .combinedSessionState),
|
|
337
|
-
let down = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: true),
|
|
338
|
-
let up = CGEvent(keyboardEventSource: source, virtualKey: keyCode, keyDown: false) else {
|
|
339
|
-
return
|
|
340
|
-
}
|
|
341
|
-
down.setIntegerValueField(.eventSourceUserData, value: Self.syntheticMarker)
|
|
342
|
-
up.setIntegerValueField(.eventSourceUserData, value: Self.syntheticMarker)
|
|
343
|
-
down.post(tap: .cghidEventTap)
|
|
344
|
-
up.post(tap: .cghidEventTap)
|
|
345
|
-
}
|
|
346
|
-
}
|