feat: improve accidental cursor movement
see https://github.com/lwouis/alt-tab-macos/issues/4278#issuecomment-3733280737
This commit is contained in:
1 parent
0539a3ad49
commit
74b5a119d3
8 files changed
+96
-13
No files matched your search
@@ -101,6 +101,7 @@
|
||||
BF0C827C61136E83F43C6436 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C8913DCD0FE4C494055B6 /* InfoPlist.strings */; };
|
||||
BF0C828D53D429C2B8B22391 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C883EBA8FDA9CB61C91FD /* Localizable.strings */; };
|
||||
BF0C829583ED47E458528F3A /* accessibility@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = BF0C889529FF19620EDAC295 /* accessibility@2x.png */; };
|
||||
BF0C82E9AB495AAAA182764A /* CursorEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C81DD40D5AAD9B7C09028 /* CursorEvents.swift */; };
|
||||
BF0C832C5BF4C8E9C95F7767 /* MissionControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C82E4DE5A28800F1DA6E6 /* MissionControl.swift */; };
|
||||
BF0C83578F7292F307E30751 /* PopupButtonLikeSystemSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0C8A479D2B7FB80531F037 /* PopupButtonLikeSystemSettings.swift */; };
|
||||
BF0C836CC0D9CEE5026B8EF4 /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = BF0C83121AAE4437993B4FF3 /* InfoPlist.strings */; };
|
||||
@@ -387,6 +388,7 @@
|
||||
BF0C81BB57B297B2432C4824 /* test-release.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = "test-release.xcconfig"; sourceTree = "<group>"; };
|
||||
BF0C81BF699036148BA17B7C /* jv */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = jv; path = Localizable.strings; sourceTree = "<group>"; };
|
||||
BF0C81D602ED642A48D50B78 /* notarytool */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = notarytool; sourceTree = "<group>"; };
|
||||
BF0C81DD40D5AAD9B7C09028 /* CursorEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CursorEvents.swift; sourceTree = "<group>"; };
|
||||
BF0C82009DCCA86287730F13 /* th */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = th; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
BF0C821029C1CBAC09232B0B /* nn */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.strings; name = nn; path = InfoPlist.strings; sourceTree = "<group>"; };
|
||||
BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreensEvents.swift; sourceTree = "<group>"; };
|
||||
@@ -1898,6 +1900,7 @@
|
||||
BF0C8643F524515224BF1618 /* KeyboardEventsTestable.swift */,
|
||||
BF0C8C69316EAAF6DC9AE391 /* CliEvents.swift */,
|
||||
BF0C8FAF34022E23582797F3 /* ScrollwheelEvents.swift */,
|
||||
BF0C81DD40D5AAD9B7C09028 /* CursorEvents.swift */,
|
||||
);
|
||||
path = events;
|
||||
sourceTree = "<group>";
|
||||
@@ -2476,6 +2479,7 @@
|
||||
BF0C8E5F4E4D5F7DAEE07C20 /* ThumbnailsPanelBackgroundView.swift in Sources */,
|
||||
BF0C8AD478399D2E97AE3FB4 /* CustomRecorderControlTestable.swift in Sources */,
|
||||
BF0C8A5FA46C5E74A071FF4C /* ScrollwheelEvents.swift in Sources */,
|
||||
BF0C82E9AB495AAAA182764A /* CursorEvents.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -381,3 +381,17 @@ extension String.StringInterpolation {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension CGEvent {
|
||||
func toNSEvent() -> NSEvent? {
|
||||
if Thread.isMainThread {
|
||||
return NSEvent(cgEvent: self)
|
||||
}
|
||||
// conversion has to happen on the main-thread, or appkit will crash
|
||||
var nsEvent: NSEvent?
|
||||
DispatchQueue.main.sync {
|
||||
nsEvent = NSEvent(cgEvent: self)
|
||||
}
|
||||
return nsEvent
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import Cocoa
|
||||
import Carbon.HIToolbox.Events
|
||||
|
||||
class CursorEvents {
|
||||
private static var eventTap: CFMachPort!
|
||||
private static var shouldBeEnabled: Bool!
|
||||
static var deadZoneInitialPosition: CGPoint?
|
||||
static var isAllowedToMouseHover = true
|
||||
|
||||
static func observe() {
|
||||
observe_()
|
||||
}
|
||||
|
||||
static func toggle(_ enabled: Bool) {
|
||||
guard enabled != shouldBeEnabled else { return }
|
||||
shouldBeEnabled = enabled
|
||||
if !enabled {
|
||||
deadZoneInitialPosition = nil
|
||||
}
|
||||
if let eventTap {
|
||||
CGEvent.tapEnable(tap: eventTap, enable: enabled)
|
||||
}
|
||||
}
|
||||
|
||||
private static func observe_() {
|
||||
let eventMask = [CGEventType.mouseMoved].reduce(CGEventMask(0), { $0 | (1 << $1.rawValue) })
|
||||
// CGEvent.tapCreate returns nil if ensureAccessibilityCheckboxIsChecked() didn't pass
|
||||
eventTap = CGEvent.tapCreate(
|
||||
tap: .cgSessionEventTap,
|
||||
place: .headInsertEventTap,
|
||||
options: .defaultTap,
|
||||
eventsOfInterest: eventMask,
|
||||
callback: handleEvent,
|
||||
userInfo: nil)
|
||||
if let eventTap {
|
||||
toggle(false)
|
||||
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
|
||||
// we run on main-thread directly since all we do is check NSEvent data, which we must do on main-thread
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
|
||||
} else {
|
||||
App.app.restart()
|
||||
}
|
||||
}
|
||||
|
||||
private static let handleEvent: CGEventTapCallBack = { _, type, cgEvent, _ in
|
||||
if type == .mouseMoved {
|
||||
updateDeadzoneSituation(cgEvent)
|
||||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||
}
|
||||
return Unmanaged.passUnretained(cgEvent) // focused app will receive the event
|
||||
}
|
||||
|
||||
/// when using the trackpad, the user may swipe with a slight mistake. This will create a small cursor movement
|
||||
/// we ignore those, as they are not intended. Intended movements will be larger and not ignored
|
||||
private static func updateDeadzoneSituation(_ cgEvent: CGEvent) {
|
||||
guard let event = cgEvent.toNSEvent() else { return }
|
||||
guard let deadZoneInitialPosition else {
|
||||
deadZoneInitialPosition = event.locationInWindow
|
||||
isAllowedToMouseHover = false
|
||||
return
|
||||
}
|
||||
let deltaX = event.locationInWindow.x - deadZoneInitialPosition.x
|
||||
let deltaY = event.locationInWindow.y - deadZoneInitialPosition.y
|
||||
let d = hypot(deltaX, deltaY)
|
||||
Logger.error { d }
|
||||
isAllowedToMouseHover = d > 25
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@ class MouseEvents {
|
||||
callback: handleEvent,
|
||||
userInfo: nil)
|
||||
if let eventTap {
|
||||
MouseEvents.toggle(false)
|
||||
toggle(false)
|
||||
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
|
||||
// we run on main-thread directly since all we do is check UI coordinates, which we must do on main-thread
|
||||
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, .commonModes)
|
||||
|
||||
@@ -4,6 +4,7 @@ import Cocoa
|
||||
class TrackpadEvents {
|
||||
private static var eventTap: CFMachPort!
|
||||
private static var shouldBeEnabled: Bool!
|
||||
private static var cursorMovedDistance = CGFloat(0.0)
|
||||
|
||||
static func observe() {
|
||||
observe_()
|
||||
@@ -48,15 +49,15 @@ class TrackpadEvents {
|
||||
}
|
||||
|
||||
private static func touchEventHandler(_ cgEvent: CGEvent) -> Bool {
|
||||
guard let nsEvent = convertEvent(cgEvent) else { return false } // don't absorb the touch event
|
||||
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) }
|
||||
let requiredFingers = Preferences.nextWindowGesture.isThreeFinger() ? 3 : 4
|
||||
// Logger.error("---", "activeTouches:", activeTouches.count, "all:", touches.map { $0.phase.readable }, requiredFingers)
|
||||
if App.app.appIsBeingUsed {
|
||||
handleEventIfAppIsBeingUsed(activeTouches, requiredFingers)
|
||||
return true // absorb the touch event
|
||||
@@ -74,6 +75,7 @@ class TrackpadEvents {
|
||||
}
|
||||
}
|
||||
} else if activeTouches.count > 1 {
|
||||
CursorEvents.deadZoneInitialPosition = nil
|
||||
NavigationSwipeDetector.hasDetected(activeTouches)
|
||||
} else {
|
||||
// if activeTouches.count == 1, ignore (finger is in pointer-mode)
|
||||
@@ -232,12 +234,3 @@ fileprivate func performHapticFeedback() {
|
||||
NSHapticFeedbackManager.defaultPerformer.perform(.generic, performanceTime: .default)
|
||||
}
|
||||
}
|
||||
|
||||
fileprivate func convertEvent(_ cgEvent: CGEvent) -> NSEvent? {
|
||||
var nsEvent: NSEvent?
|
||||
// conversion has to happen on the main-thread, or appkit will crash
|
||||
DispatchQueue.main.sync {
|
||||
nsEvent = NSEvent(cgEvent: cgEvent)
|
||||
}
|
||||
return nsEvent
|
||||
}
|
||||
@@ -63,6 +63,7 @@ class App: AppCenterApplication {
|
||||
isFirstSummon = true
|
||||
forceDoNothingOnRelease = false
|
||||
MouseEvents.toggle(false)
|
||||
CursorEvents.toggle(false)
|
||||
ScrollwheelEvents.toggle(false)
|
||||
hideThumbnailPanelWithoutChangingKeyWindow()
|
||||
if !keepPreview {
|
||||
@@ -314,6 +315,7 @@ extension App: NSApplicationDelegate {
|
||||
self.feedbackWindow = FeedbackWindow()
|
||||
KeyboardEvents.addEventHandlers()
|
||||
MouseEvents.observe()
|
||||
CursorEvents.observe()
|
||||
TrackpadEvents.observe()
|
||||
CliEvents.observe()
|
||||
// login item and plist updates can be done a bit later, to accelerate launch
|
||||
|
||||
@@ -71,6 +71,7 @@ class ThumbnailsPanel: NSPanel {
|
||||
alphaValue = 1
|
||||
makeKeyAndOrderFront(nil)
|
||||
MouseEvents.toggle(true)
|
||||
CursorEvents.toggle(true)
|
||||
thumbnailsView.scrollView.flashScrollers()
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ class ScrollView: NSScrollView {
|
||||
}
|
||||
|
||||
override func mouseMoved(with event: NSEvent) {
|
||||
guard let documentView, !isCurrentlyScrolling && !ScrollwheelEvents.shouldBeEnabled else { return }
|
||||
guard let documentView, !isCurrentlyScrolling && CursorEvents.isAllowedToMouseHover else { return }
|
||||
let location = documentView.convert(App.app.thumbnailsPanel.mouseLocationOutsideOfEventStream, from: nil)
|
||||
let newTarget = findTarget(location)
|
||||
guard newTarget !== previousTarget else { return }
|
||||
|
||||
Reference in new issue
Block a user