fix: better handle palm/fingers resting on the trackpad (closes #5191)
This commit is contained in:
1 parent
a0c9feefca
commit
9ed8a199b0
2 files changed
+32
-20
No files matched your search
@@ -20,6 +20,14 @@ class TrackpadEvents {
|
||||
}
|
||||
}
|
||||
|
||||
static func reset() {
|
||||
ScrollwheelEvents.toggle(false)
|
||||
NavigationSwipeDetector.reset()
|
||||
NonFreshGestureDetector.reset()
|
||||
// no need to call TriggerSwipeDetector.reset; it does it itself when triggering
|
||||
TriggerSwipeDetector.maxFingersDownDuringTrigger = 0
|
||||
}
|
||||
|
||||
private static func observe_() {
|
||||
// CGEvent.tapCreate returns null if ensureAccessibilityCheckboxIsChecked() didn't pass
|
||||
eventTap = CGEvent.tapCreate(
|
||||
@@ -51,39 +59,40 @@ class TrackpadEvents {
|
||||
private static func touchEventHandler(_ cgEvent: CGEvent) -> Bool {
|
||||
guard let nsEvent = cgEvent.toNSEvent() else { return false } // don't absorb the touch event
|
||||
let touches = nsEvent.allTouches()
|
||||
// Logger.error { (touches.count, touches.map { $0.phase.readable}) }
|
||||
// sometimes the os sends events with no touches; we ignore these as they could break our gesture logic
|
||||
if touches.count == 0 {
|
||||
return false
|
||||
}
|
||||
let activeTouches = touches.filter { !$0.isResting && ($0.phase == .began || $0.phase == .moved || $0.phase == .stationary) }
|
||||
// Logger.error { (touches.count, touches.map { $0.phase.readable }) }
|
||||
// macOS often sends faulty events with no touches between valid events; we ignore these as they would break our gesture logic
|
||||
guard touches.count > 0 else { return false }
|
||||
// isResting seems to always return false. It's not doing its job to detect resting thumb/palm/finger
|
||||
// on macOS, the finger contact surface is not exposed in NSTouch, so we can't detect big contact == palm, for example
|
||||
// the closest thing we can do to detect resting inputs is remove touches which have .phase == .stationary
|
||||
let activeTouches = touches.filter { !$0.isResting && ($0.phase == .began || $0.phase == .moved) }
|
||||
let fingersDown = touches.count { $0.phase == .began || $0.phase == .moved || $0.phase == .stationary }
|
||||
let requiredFingers = Preferences.nextWindowGesture.isThreeFinger() ? 3 : 4
|
||||
if App.app.appIsBeingUsed {
|
||||
handleEventIfAppIsBeingUsed(activeTouches, requiredFingers)
|
||||
handleEventIfAppIsBeingUsed(fingersDown, activeTouches, requiredFingers)
|
||||
return true // absorb the touch event
|
||||
}
|
||||
return handleEventIfAppIsNotBeingUsed(touches, activeTouches, requiredFingers) // absorb or not the touch event, depending on the situation
|
||||
return handleEventIfAppIsNotBeingUsed(fingersDown, activeTouches, requiredFingers) // absorb or not the touch event, depending on the situation
|
||||
}
|
||||
|
||||
private static func handleEventIfAppIsBeingUsed(_ activeTouches: Set<NSTouch>, _ requiredFingers: Int) {
|
||||
if activeTouches.count == 0 {
|
||||
NavigationSwipeDetector.reset()
|
||||
private static func handleEventIfAppIsBeingUsed(_ fingersDown: Int, _ activeTouches: Set<NSTouch>, _ requiredFingers: Int) {
|
||||
if fingersDown <= TriggerSwipeDetector.maxFingersDownDuringTrigger - requiredFingers {
|
||||
if App.app.shortcutIndex == Preferences.gestureIndex && !App.app.forceDoNothingOnRelease && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease {
|
||||
DispatchQueue.main.async {
|
||||
ScrollwheelEvents.toggle(false)
|
||||
App.app.focusTarget()
|
||||
}
|
||||
}
|
||||
} else if activeTouches.count > 1 {
|
||||
return
|
||||
}
|
||||
if activeTouches.count > 1 {
|
||||
CursorEvents.deadZoneInitialPosition = nil
|
||||
NavigationSwipeDetector.hasDetected(activeTouches)
|
||||
} else {
|
||||
// if activeTouches.count == 1, ignore (finger is in pointer-mode)
|
||||
}
|
||||
// if activeTouches.count == 1, ignore (finger is in pointer-mode)
|
||||
}
|
||||
|
||||
private static func handleEventIfAppIsNotBeingUsed(_ touches: Set<NSTouch>, _ activeTouches: Set<NSTouch>, _ requiredFingers: Int) -> Bool {
|
||||
if touches.count <= 1 {
|
||||
private static func handleEventIfAppIsNotBeingUsed(_ fingersDown: Int, _ activeTouches: Set<NSTouch>, _ requiredFingers: Int) -> Bool {
|
||||
if fingersDown <= 1 {
|
||||
NonFreshGestureDetector.reset()
|
||||
return false // don't absorb the touch event
|
||||
}
|
||||
@@ -94,7 +103,7 @@ class TrackpadEvents {
|
||||
TriggerSwipeDetector.reset()
|
||||
return false // don't absorb the touch event
|
||||
} else {
|
||||
return TriggerSwipeDetector.hasDetected(activeTouches) // absorb or not the touch event, depending on the situation
|
||||
return TriggerSwipeDetector.hasDetected(fingersDown, activeTouches) // absorb or not the touch event, depending on the situation
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -127,12 +136,15 @@ class TriggerSwipeDetector {
|
||||
// when the native using 3-finger swipe to shift Space, macOS will prevent a swipe until the fingers are raised,
|
||||
// if the user moves too much in the vertical direction. We imitate this behavior
|
||||
static let MAX_SWIPE_DISTANCE_IN_WRONG_DIRECTION: Double = 0.1 // % of trackpad surface traveled
|
||||
// if there are extra non-active fingers during the gesture trigger, we remember it so we can ignore those extra fingers to detect intent to focus on release
|
||||
static var maxFingersDownDuringTrigger = 0
|
||||
|
||||
private static var gestureTracker = GestureTracker()
|
||||
private static var swipeStillPossible = true
|
||||
|
||||
static func hasDetected(_ activeTouches: Set<NSTouch>) -> Bool {
|
||||
static func hasDetected(_ fingersDown: Int, _ activeTouches: Set<NSTouch>) -> Bool {
|
||||
guard swipeStillPossible && !gestureTracker.isNewGesture(activeTouches) else { return false }
|
||||
maxFingersDownDuringTrigger = max(maxFingersDownDuringTrigger, fingersDown)
|
||||
let distances = gestureTracker.computeDistance(activeTouches)
|
||||
for distance in distances {
|
||||
let (absX, absY) = (abs(distance.x), abs(distance.y))
|
||||
|
||||
+1
-1
@@ -64,7 +64,7 @@ class App: AppCenterApplication {
|
||||
forceDoNothingOnRelease = false
|
||||
MouseEvents.toggle(false)
|
||||
CursorEvents.toggle(false)
|
||||
ScrollwheelEvents.toggle(false)
|
||||
TrackpadEvents.reset()
|
||||
hideThumbnailPanelWithoutChangingKeyWindow()
|
||||
if !keepPreview {
|
||||
previewPanel.orderOut(nil)
|
||||
|
||||
Reference in new issue
Block a user