@lattices/cli 0.4.11 → 0.4.12
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/apps/mac/Lattices.app/Contents/Info.plist +4 -4
- package/apps/mac/Lattices.app/Contents/MacOS/Lattices +0 -0
- package/apps/mac/Sources/Core/Input/MouseGestureController.swift +93 -81
- package/apps/mac/Sources/Core/Overlays/ScreenOverlayCanvasController.swift +24 -0
- package/docs/reference/dewey.config.ts +1 -1
- package/package.json +1 -1
|
@@ -26,17 +26,17 @@
|
|
|
26
26
|
</dict>
|
|
27
27
|
</array>
|
|
28
28
|
<key>CFBundleVersion</key>
|
|
29
|
-
<string>0.4.
|
|
29
|
+
<string>0.4.12</string>
|
|
30
30
|
<key>CFBundleShortVersionString</key>
|
|
31
|
-
<string>0.4.
|
|
31
|
+
<string>0.4.12</string>
|
|
32
32
|
<key>LatticesBuildChannel</key>
|
|
33
33
|
<string>dev</string>
|
|
34
34
|
<key>LatticesBuildTrack</key>
|
|
35
35
|
<string>latest</string>
|
|
36
36
|
<key>LatticesBuildRevision</key>
|
|
37
|
-
<string>
|
|
37
|
+
<string>b84f24f</string>
|
|
38
38
|
<key>LatticesBuildTimestamp</key>
|
|
39
|
-
<string>2026-05-
|
|
39
|
+
<string>2026-05-04T16:17:39Z</string>
|
|
40
40
|
<key>LSMinimumSystemVersion</key>
|
|
41
41
|
<string>13.0</string>
|
|
42
42
|
<key>LSUIElement</key>
|
|
Binary file
|
|
@@ -93,6 +93,7 @@ final class MouseGestureController: ObservableObject {
|
|
|
93
93
|
|
|
94
94
|
private var eventTap: CFMachPort?
|
|
95
95
|
private var runLoopSource: CFRunLoopSource?
|
|
96
|
+
private var installedEventMask: CGEventMask = 0
|
|
96
97
|
private var session: GestureSession?
|
|
97
98
|
private var retainedOverlays: [ObjectIdentifier: MouseGestureOverlay] = [:]
|
|
98
99
|
private var staleSessionTimer: Timer?
|
|
@@ -106,6 +107,9 @@ final class MouseGestureController: ObservableObject {
|
|
|
106
107
|
let buttonNumber: Int64
|
|
107
108
|
let startPoint: CGPoint
|
|
108
109
|
let nativeClickPassthrough: Bool
|
|
110
|
+
var nativeClickBalanced: Bool
|
|
111
|
+
let dragThreshold: CGFloat
|
|
112
|
+
let axisBias: CGFloat
|
|
109
113
|
let startedAt: CFAbsoluteTime
|
|
110
114
|
}
|
|
111
115
|
|
|
@@ -139,14 +143,11 @@ final class MouseGestureController: ObservableObject {
|
|
|
139
143
|
return tapTrackingState
|
|
140
144
|
}
|
|
141
145
|
|
|
142
|
-
private func currentTrackingButton() -> Int64? {
|
|
143
|
-
currentTrackingState()?.buttonNumber
|
|
144
|
-
}
|
|
145
|
-
|
|
146
146
|
private func setTrackingButton(
|
|
147
147
|
_ value: Int64?,
|
|
148
148
|
startPoint: CGPoint = .zero,
|
|
149
|
-
nativeClickPassthrough: Bool = false
|
|
149
|
+
nativeClickPassthrough: Bool = false,
|
|
150
|
+
tuning: MouseShortcutTuning = .defaults
|
|
150
151
|
) {
|
|
151
152
|
trackingLock.lock()
|
|
152
153
|
if let value {
|
|
@@ -154,6 +155,9 @@ final class MouseGestureController: ObservableObject {
|
|
|
154
155
|
buttonNumber: value,
|
|
155
156
|
startPoint: startPoint,
|
|
156
157
|
nativeClickPassthrough: nativeClickPassthrough,
|
|
158
|
+
nativeClickBalanced: !nativeClickPassthrough,
|
|
159
|
+
dragThreshold: tuning.dragThreshold,
|
|
160
|
+
axisBias: tuning.axisBias,
|
|
157
161
|
startedAt: CFAbsoluteTimeGetCurrent()
|
|
158
162
|
)
|
|
159
163
|
} else {
|
|
@@ -162,6 +166,21 @@ final class MouseGestureController: ObservableObject {
|
|
|
162
166
|
trackingLock.unlock()
|
|
163
167
|
}
|
|
164
168
|
|
|
169
|
+
private func markNativeClickBalanced(buttonNumber: Int64) -> Bool {
|
|
170
|
+
trackingLock.lock()
|
|
171
|
+
defer { trackingLock.unlock() }
|
|
172
|
+
guard var state = tapTrackingState,
|
|
173
|
+
state.buttonNumber == buttonNumber,
|
|
174
|
+
state.nativeClickPassthrough,
|
|
175
|
+
!state.nativeClickBalanced else {
|
|
176
|
+
return false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
state.nativeClickBalanced = true
|
|
180
|
+
tapTrackingState = state
|
|
181
|
+
return true
|
|
182
|
+
}
|
|
183
|
+
|
|
165
184
|
private init() {
|
|
166
185
|
breaker.onStateChanged = { [weak self] newState in
|
|
167
186
|
self?.breakerState = newState
|
|
@@ -238,10 +257,16 @@ final class MouseGestureController: ObservableObject {
|
|
|
238
257
|
return
|
|
239
258
|
}
|
|
240
259
|
|
|
260
|
+
let desiredMask = desiredEventMask()
|
|
241
261
|
if eventTap == nil {
|
|
242
|
-
installEventTap()
|
|
262
|
+
installEventTap(mask: desiredMask)
|
|
243
263
|
} else if let eventTap {
|
|
244
|
-
|
|
264
|
+
if installedEventMask != desiredMask {
|
|
265
|
+
removeEventTap()
|
|
266
|
+
installEventTap(mask: desiredMask)
|
|
267
|
+
} else {
|
|
268
|
+
CGEvent.tapEnable(tap: eventTap, enable: true)
|
|
269
|
+
}
|
|
245
270
|
}
|
|
246
271
|
}
|
|
247
272
|
|
|
@@ -256,20 +281,18 @@ final class MouseGestureController: ObservableObject {
|
|
|
256
281
|
}
|
|
257
282
|
}
|
|
258
283
|
|
|
259
|
-
private func
|
|
260
|
-
// Fresh install is a clean slate — drop any stale trip history so
|
|
261
|
-
// the new tap's first failure is judged on its own merits.
|
|
262
|
-
breaker.reset()
|
|
263
|
-
|
|
284
|
+
private func desiredEventMask() -> CGEventMask {
|
|
264
285
|
var mask = CGEventMask(0)
|
|
265
|
-
mask |= CGEventMask(1) << CGEventType.leftMouseDown.rawValue
|
|
266
|
-
mask |= CGEventMask(1) << CGEventType.leftMouseUp.rawValue
|
|
267
|
-
mask |= CGEventMask(1) << CGEventType.rightMouseDown.rawValue
|
|
268
|
-
mask |= CGEventMask(1) << CGEventType.rightMouseDragged.rawValue
|
|
269
|
-
mask |= CGEventMask(1) << CGEventType.rightMouseUp.rawValue
|
|
270
286
|
mask |= CGEventMask(1) << CGEventType.otherMouseDown.rawValue
|
|
271
287
|
mask |= CGEventMask(1) << CGEventType.otherMouseDragged.rawValue
|
|
272
288
|
mask |= CGEventMask(1) << CGEventType.otherMouseUp.rawValue
|
|
289
|
+
return mask
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
private func installEventTap(mask: CGEventMask) {
|
|
293
|
+
// Fresh install is a clean slate — drop any stale trip history so
|
|
294
|
+
// the new tap's first failure is judged on its own merits.
|
|
295
|
+
breaker.reset()
|
|
273
296
|
|
|
274
297
|
let tap = CGEvent.tapCreate(
|
|
275
298
|
tap: .cgSessionEventTap,
|
|
@@ -288,6 +311,7 @@ final class MouseGestureController: ObservableObject {
|
|
|
288
311
|
let source = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, tap, 0)
|
|
289
312
|
eventTap = tap
|
|
290
313
|
runLoopSource = source
|
|
314
|
+
installedEventMask = mask
|
|
291
315
|
|
|
292
316
|
if let source {
|
|
293
317
|
EventTapThread.shared.add(source: source)
|
|
@@ -309,6 +333,7 @@ final class MouseGestureController: ObservableObject {
|
|
|
309
333
|
CFMachPortInvalidate(tap)
|
|
310
334
|
}
|
|
311
335
|
eventTap = nil
|
|
336
|
+
installedEventMask = 0
|
|
312
337
|
}
|
|
313
338
|
|
|
314
339
|
private static let eventTapCallback: CGEventTapCallBack = { _, type, event, userInfo in
|
|
@@ -352,19 +377,6 @@ final class MouseGestureController: ObservableObject {
|
|
|
352
377
|
return Unmanaged.passUnretained(event)
|
|
353
378
|
}
|
|
354
379
|
|
|
355
|
-
switch type {
|
|
356
|
-
case .leftMouseDown, .leftMouseUp:
|
|
357
|
-
return handlePassiveMouseButtonEvent(type: type, event: event)
|
|
358
|
-
case .rightMouseDown:
|
|
359
|
-
return handleMouseDown(event, buttonNumber: Int64(CGMouseButton.right.rawValue))
|
|
360
|
-
case .rightMouseDragged:
|
|
361
|
-
return handleMouseDragged(event, buttonNumber: Int64(CGMouseButton.right.rawValue))
|
|
362
|
-
case .rightMouseUp:
|
|
363
|
-
return handleMouseUp(event, buttonNumber: Int64(CGMouseButton.right.rawValue))
|
|
364
|
-
default:
|
|
365
|
-
break
|
|
366
|
-
}
|
|
367
|
-
|
|
368
380
|
let buttonNumber = event.getIntegerValueField(.mouseEventButtonNumber)
|
|
369
381
|
if buttonNumber < 2 {
|
|
370
382
|
return Unmanaged.passUnretained(event)
|
|
@@ -389,54 +401,15 @@ final class MouseGestureController: ObservableObject {
|
|
|
389
401
|
// MouseEventSnapshot, and hand the heavy work to main async — so a slow
|
|
390
402
|
// main thread never adds latency to mouse events at the head-insert tap.
|
|
391
403
|
|
|
392
|
-
private func handlePassiveMouseButtonEvent(type: CGEventType, event: CGEvent) -> Unmanaged<CGEvent>? {
|
|
393
|
-
let snapshot = MouseEventSnapshot(
|
|
394
|
-
location: event.location,
|
|
395
|
-
flags: event.flags,
|
|
396
|
-
buttonNumber: event.getIntegerValueField(.mouseEventButtonNumber)
|
|
397
|
-
)
|
|
398
|
-
DispatchQueue.main.async { [weak self] in
|
|
399
|
-
self?.processPassiveMouseButton(type: type, snapshot: snapshot)
|
|
400
|
-
}
|
|
401
|
-
return Unmanaged.passUnretained(event)
|
|
402
|
-
}
|
|
403
|
-
|
|
404
404
|
private func isEmergencyMouseReset(type: CGEventType, event: CGEvent) -> Bool {
|
|
405
405
|
switch type {
|
|
406
|
-
case .
|
|
406
|
+
case .otherMouseDown:
|
|
407
407
|
return event.flags.intersection(.latticesHyper) == .latticesHyper
|
|
408
408
|
default:
|
|
409
409
|
return false
|
|
410
410
|
}
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
-
private func processPassiveMouseButton(type: CGEventType, snapshot: MouseEventSnapshot) {
|
|
414
|
-
dispatchPrecondition(condition: .onQueue(.main))
|
|
415
|
-
guard MouseInputEventViewer.shared.isCaptureActive else { return }
|
|
416
|
-
|
|
417
|
-
let phase: String
|
|
418
|
-
switch type {
|
|
419
|
-
case .leftMouseDown, .rightMouseDown:
|
|
420
|
-
phase = "down"
|
|
421
|
-
case .leftMouseUp, .rightMouseUp:
|
|
422
|
-
phase = "up"
|
|
423
|
-
default:
|
|
424
|
-
return
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
recordObservedEvent(
|
|
428
|
-
phase: phase,
|
|
429
|
-
button: MouseShortcutButton(rawButtonNumber: Int(snapshot.buttonNumber)),
|
|
430
|
-
location: snapshot.location,
|
|
431
|
-
delta: .zero,
|
|
432
|
-
modifiers: snapshot.flags,
|
|
433
|
-
candidate: nil,
|
|
434
|
-
match: nil,
|
|
435
|
-
note: "pass-through primary button",
|
|
436
|
-
appInfo: currentAppInfo()
|
|
437
|
-
)
|
|
438
|
-
}
|
|
439
|
-
|
|
440
413
|
private func handleMouseDown(_ event: CGEvent, buttonNumber: Int64) -> Unmanaged<CGEvent>? {
|
|
441
414
|
let snapshot = MouseEventSnapshot(
|
|
442
415
|
location: event.location,
|
|
@@ -448,6 +421,7 @@ final class MouseGestureController: ObservableObject {
|
|
|
448
421
|
let button = MouseShortcutButton(rawButtonNumber: Int(buttonNumber))
|
|
449
422
|
let needsNativeClickCapture = MouseShortcutStore.shared.hasEnabledRule(button: button, kind: .click)
|
|
450
423
|
|| MouseShortcutStore.shared.hasEnabledRule(button: button, kind: .shape)
|
|
424
|
+
let tuning = MouseShortcutStore.shared.tuning
|
|
451
425
|
let nativeClickPassthrough = buttonNumber >= 2 && !needsNativeClickCapture
|
|
452
426
|
let canRecognize = Preferences.shared.mouseGesturesEnabled
|
|
453
427
|
&& MouseShortcutStore.shared.watchedButtonNumbers.contains(buttonNumber)
|
|
@@ -472,7 +446,8 @@ final class MouseGestureController: ObservableObject {
|
|
|
472
446
|
setTrackingButton(
|
|
473
447
|
buttonNumber,
|
|
474
448
|
startPoint: snapshot.location,
|
|
475
|
-
nativeClickPassthrough: nativeClickPassthrough
|
|
449
|
+
nativeClickPassthrough: nativeClickPassthrough,
|
|
450
|
+
tuning: tuning
|
|
476
451
|
)
|
|
477
452
|
DispatchQueue.main.async { [weak self] in
|
|
478
453
|
self?.processMouseDownConsume(
|
|
@@ -572,7 +547,8 @@ final class MouseGestureController: ObservableObject {
|
|
|
572
547
|
}
|
|
573
548
|
|
|
574
549
|
private func handleMouseDragged(_ event: CGEvent, buttonNumber: Int64) -> Unmanaged<CGEvent>? {
|
|
575
|
-
guard
|
|
550
|
+
guard let trackingState = currentTrackingState(),
|
|
551
|
+
trackingState.buttonNumber == buttonNumber else {
|
|
576
552
|
return Unmanaged.passUnretained(event)
|
|
577
553
|
}
|
|
578
554
|
let snapshot = MouseEventSnapshot(
|
|
@@ -580,6 +556,28 @@ final class MouseGestureController: ObservableObject {
|
|
|
580
556
|
flags: event.flags,
|
|
581
557
|
buttonNumber: buttonNumber
|
|
582
558
|
)
|
|
559
|
+
|
|
560
|
+
let delta = CGPoint(
|
|
561
|
+
x: snapshot.location.x - trackingState.startPoint.x,
|
|
562
|
+
y: snapshot.location.y - trackingState.startPoint.y
|
|
563
|
+
)
|
|
564
|
+
let direction = Self.resolveDirection(
|
|
565
|
+
delta: delta,
|
|
566
|
+
threshold: trackingState.dragThreshold,
|
|
567
|
+
axisBias: trackingState.axisBias
|
|
568
|
+
)
|
|
569
|
+
if direction != nil,
|
|
570
|
+
markNativeClickBalanced(buttonNumber: buttonNumber) {
|
|
571
|
+
postSyntheticMouseUp(
|
|
572
|
+
buttonNumber: buttonNumber,
|
|
573
|
+
at: snapshot.location,
|
|
574
|
+
flags: snapshot.flags
|
|
575
|
+
)
|
|
576
|
+
DispatchQueue.main.async {
|
|
577
|
+
DiagnosticLog.shared.info("MouseGesture: balanced native mouseUp for claimed gesture button=\(buttonNumber)")
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
|
|
583
581
|
DispatchQueue.main.async { [weak self] in
|
|
584
582
|
self?.processMouseDragged(snapshot: snapshot)
|
|
585
583
|
}
|
|
@@ -677,8 +675,11 @@ final class MouseGestureController: ObservableObject {
|
|
|
677
675
|
x: snapshot.location.x - trackingState.startPoint.x,
|
|
678
676
|
y: snapshot.location.y - trackingState.startPoint.y
|
|
679
677
|
)
|
|
680
|
-
let
|
|
681
|
-
|
|
678
|
+
let direction = Self.resolveDirection(
|
|
679
|
+
delta: delta,
|
|
680
|
+
threshold: trackingState.dragThreshold,
|
|
681
|
+
axisBias: trackingState.axisBias
|
|
682
|
+
)
|
|
682
683
|
if direction == nil {
|
|
683
684
|
setTrackingButton(nil)
|
|
684
685
|
DispatchQueue.main.async { [weak self] in
|
|
@@ -982,13 +983,7 @@ final class MouseGestureController: ObservableObject {
|
|
|
982
983
|
}
|
|
983
984
|
|
|
984
985
|
private func replayMouseClick(buttonNumber: Int64, at point: CGPoint, flags: CGEventFlags) {
|
|
985
|
-
|
|
986
|
-
if buttonNumber == Int64(CGMouseButton.right.rawValue) {
|
|
987
|
-
events = [.rightMouseDown, .rightMouseUp]
|
|
988
|
-
} else {
|
|
989
|
-
events = [.otherMouseDown, .otherMouseUp]
|
|
990
|
-
}
|
|
991
|
-
for type in events {
|
|
986
|
+
for type in [CGEventType.otherMouseDown, .otherMouseUp] {
|
|
992
987
|
guard let mouseButton = CGMouseButton(rawValue: UInt32(buttonNumber)) else { continue }
|
|
993
988
|
guard let event = CGEvent(
|
|
994
989
|
mouseEventSource: nil,
|
|
@@ -1004,6 +999,23 @@ final class MouseGestureController: ObservableObject {
|
|
|
1004
999
|
}
|
|
1005
1000
|
}
|
|
1006
1001
|
|
|
1002
|
+
private func postSyntheticMouseUp(buttonNumber: Int64, at point: CGPoint, flags: CGEventFlags) {
|
|
1003
|
+
guard let mouseButton = CGMouseButton(rawValue: UInt32(buttonNumber)),
|
|
1004
|
+
let event = CGEvent(
|
|
1005
|
+
mouseEventSource: nil,
|
|
1006
|
+
mouseType: .otherMouseUp,
|
|
1007
|
+
mouseCursorPosition: point,
|
|
1008
|
+
mouseButton: mouseButton
|
|
1009
|
+
) else {
|
|
1010
|
+
return
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
event.setIntegerValueField(CGEventField.mouseEventButtonNumber, value: buttonNumber)
|
|
1014
|
+
event.setIntegerValueField(CGEventField.eventSourceUserData, value: Self.syntheticMarker)
|
|
1015
|
+
event.flags = flags
|
|
1016
|
+
event.post(tap: CGEventTapLocation.cghidEventTap)
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1007
1019
|
private func screen(containing cgPoint: CGPoint) -> NSScreen? {
|
|
1008
1020
|
let primaryHeight = NSScreen.screens.first?.frame.height ?? 0
|
|
1009
1021
|
let nsPoint = NSPoint(x: cgPoint.x, y: primaryHeight - cgPoint.y)
|
|
@@ -125,6 +125,7 @@ final class ScreenOverlayCanvasController {
|
|
|
125
125
|
private var globalDismissMonitor: Any?
|
|
126
126
|
private var localDismissMonitor: Any?
|
|
127
127
|
private var dragState: OverlayActorDragState?
|
|
128
|
+
private var actorDragTimeoutTimer: Timer?
|
|
128
129
|
private var agentActorsHidden = false
|
|
129
130
|
private let maxActorDragDuration: TimeInterval = 8.0
|
|
130
131
|
|
|
@@ -176,6 +177,7 @@ final class ScreenOverlayCanvasController {
|
|
|
176
177
|
motionsByLayerID.removeValue(forKey: id)
|
|
177
178
|
if dragState?.id == id {
|
|
178
179
|
dragState = nil
|
|
180
|
+
cancelActorDragTimeout()
|
|
179
181
|
resetPointerCapture()
|
|
180
182
|
}
|
|
181
183
|
render()
|
|
@@ -188,6 +190,7 @@ final class ScreenOverlayCanvasController {
|
|
|
188
190
|
motionsByLayerID = motionsByLayerID.filter { id, _ in layersByID[id] != nil }
|
|
189
191
|
if let dragState, removedIDs.contains(dragState.id) {
|
|
190
192
|
self.dragState = nil
|
|
193
|
+
cancelActorDragTimeout()
|
|
191
194
|
resetPointerCapture()
|
|
192
195
|
}
|
|
193
196
|
render()
|
|
@@ -198,6 +201,7 @@ final class ScreenOverlayCanvasController {
|
|
|
198
201
|
agentActorsHidden.toggle()
|
|
199
202
|
if agentActorsHidden {
|
|
200
203
|
dragState = nil
|
|
204
|
+
cancelActorDragTimeout()
|
|
201
205
|
resetPointerCapture()
|
|
202
206
|
}
|
|
203
207
|
render()
|
|
@@ -206,6 +210,7 @@ final class ScreenOverlayCanvasController {
|
|
|
206
210
|
|
|
207
211
|
func resetInputCapture(reason: String) {
|
|
208
212
|
dragState = nil
|
|
213
|
+
cancelActorDragTimeout()
|
|
209
214
|
resetPointerCapture()
|
|
210
215
|
DiagnosticLog.shared.warn("ScreenOverlay: input capture reset for \(reason)")
|
|
211
216
|
}
|
|
@@ -403,6 +408,7 @@ final class ScreenOverlayCanvasController {
|
|
|
403
408
|
self.localDismissMonitor = nil
|
|
404
409
|
}
|
|
405
410
|
dragState = nil
|
|
411
|
+
cancelActorDragTimeout()
|
|
406
412
|
resetPointerCapture()
|
|
407
413
|
}
|
|
408
414
|
}
|
|
@@ -472,6 +478,7 @@ final class ScreenOverlayCanvasController {
|
|
|
472
478
|
lastPoint: currentPoint,
|
|
473
479
|
startedAt: Date()
|
|
474
480
|
)
|
|
481
|
+
scheduleActorDragTimeout()
|
|
475
482
|
layersByID[hit.id] = layer.replacingPayload(.pet(payload.moved(to: currentPoint, state: "idle", isDragging: true)))
|
|
476
483
|
render()
|
|
477
484
|
updateLifecycleMonitors()
|
|
@@ -510,6 +517,7 @@ final class ScreenOverlayCanvasController {
|
|
|
510
517
|
}
|
|
511
518
|
layersByID[dragState.id] = layer.replacingPayload(.pet(payload.moved(to: dragState.lastPoint, state: "idle", isDragging: false)))
|
|
512
519
|
self.dragState = nil
|
|
520
|
+
cancelActorDragTimeout()
|
|
513
521
|
render()
|
|
514
522
|
updateLifecycleMonitors()
|
|
515
523
|
updatePointerCapture(at: NSEvent.mouseLocation)
|
|
@@ -522,6 +530,20 @@ final class ScreenOverlayCanvasController {
|
|
|
522
530
|
endActorDrag()
|
|
523
531
|
}
|
|
524
532
|
|
|
533
|
+
private func scheduleActorDragTimeout() {
|
|
534
|
+
cancelActorDragTimeout()
|
|
535
|
+
actorDragTimeoutTimer = Timer.scheduledTimer(withTimeInterval: maxActorDragDuration, repeats: false) { [weak self] _ in
|
|
536
|
+
guard let self, self.dragState != nil else { return }
|
|
537
|
+
DiagnosticLog.shared.warn("ScreenOverlay: actor drag timed out; releasing pointer capture")
|
|
538
|
+
self.endActorDrag()
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
private func cancelActorDragTimeout() {
|
|
543
|
+
actorDragTimeoutTimer?.invalidate()
|
|
544
|
+
actorDragTimeoutTimer = nil
|
|
545
|
+
}
|
|
546
|
+
|
|
525
547
|
private func resetPointerCapture() {
|
|
526
548
|
for window in windowsByScreenID.values {
|
|
527
549
|
window.ignoresMouseEvents = true
|
|
@@ -536,6 +558,7 @@ final class ScreenOverlayCanvasController {
|
|
|
536
558
|
motionsByLayerID.removeValue(forKey: hit.id)
|
|
537
559
|
if dragState?.id == hit.id {
|
|
538
560
|
dragState = nil
|
|
561
|
+
cancelActorDragTimeout()
|
|
539
562
|
}
|
|
540
563
|
render()
|
|
541
564
|
updateLifecycleMonitors()
|
|
@@ -569,6 +592,7 @@ final class ScreenOverlayCanvasController {
|
|
|
569
592
|
motionsByLayerID = motionsByLayerID.filter { id, _ in layersByID[id] != nil }
|
|
570
593
|
if let dragState, layersByID[dragState.id] == nil {
|
|
571
594
|
self.dragState = nil
|
|
595
|
+
cancelActorDragTimeout()
|
|
572
596
|
resetPointerCapture()
|
|
573
597
|
}
|
|
574
598
|
guard layersByID.count != before else { return }
|