-
-
Notifications
You must be signed in to change notification settings - Fork 344
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Swipe gestures support PoC #2355
Open
lwouis
wants to merge
15
commits into
lwouis:master
Choose a base branch
from
ris58h:swipes
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
15 commits
Select commit
Hold shift + click to select a range
ddf00d1
Swipe gestures support PoC
ris58h bc44216
Don't use dedicated class for swipe velocity.
ris58h 8693867
Gesture settings tab
ris58h 4219056
add TODO
ris58h 8f7a602
Support 'On release do nothing' for activation via swipe.
ris58h c5398be
Ignore end of swipe if UI wasn't activated via swipe.
ris58h 689e9c1
Add TrackpadEvents.swift to project
ris58h 58fa111
Use CGEvent.tapCreate instead of M5MultitouchSupport pod
ris58h c154a9c
Ignore empty touch events.
ris58h 663c791
Don't use variable for eventMask
ris58h da88de6
touchesCount
ris58h 1222323
Event tap with .cghidEventTap
ris58h 8777f61
Rename Swipe to Gesture
ris58h 459d379
Remove pod 'M5MultitouchSupport'
ris58h 64b5532
rephrase TODO comment
ris58h File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,150 @@ | ||
import Cocoa | ||
|
||
fileprivate var eventTap: CFMachPort! | ||
fileprivate var shouldBeEnabled: Bool! | ||
|
||
//TODO: Should we add a sensetivity setting instead of these magic numbers? | ||
fileprivate let accVelXThreshold: Float = 0.05 | ||
fileprivate let accVelYThreshold: Float = 0.075 | ||
fileprivate var accVelX: Float = 0 | ||
fileprivate var accVelY: Float = 0 | ||
//TODO: Don't use string as key. Maybe we should use other data-sructure. | ||
fileprivate var prevTouchPositions: [String: NSPoint] = [:] | ||
|
||
//TODO: underlying content scrolls if both Mission Control and App Expose use 4-finger swipes or are off in Trackpad settings. It doesn't scroll if any of them use 3-finger swipe though. | ||
class TrackpadEvents { | ||
static func observe() { | ||
observe_() | ||
} | ||
|
||
static func toggle(_ enabled: Bool) { | ||
shouldBeEnabled = enabled | ||
if let eventTap = eventTap { | ||
CGEvent.tapEnable(tap: eventTap, enable: enabled) | ||
} | ||
} | ||
} | ||
|
||
private func observe_() { | ||
// CGEvent.tapCreate returns null if ensureAccessibilityCheckboxIsChecked() didn't pass | ||
eventTap = CGEvent.tapCreate( | ||
tap: .cghidEventTap, | ||
place: .headInsertEventTap, | ||
options: .listenOnly, | ||
eventsOfInterest: NSEvent.EventTypeMask.gesture.rawValue, | ||
callback: eventHandler, | ||
userInfo: nil) | ||
if let eventTap = eventTap { | ||
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0) | ||
//TODO: Is CFRunLoopGetCurrent OK or do we need yet another thread with runLoop in BackgroundWork? | ||
CFRunLoopAddSource(CFRunLoopGetCurrent(), runLoopSource, CFRunLoopMode.commonModes) | ||
} else { | ||
App.app.restart() | ||
} | ||
} | ||
|
||
private func eventHandler(proxy: CGEventTapProxy, type: CGEventType, cgEvent: CGEvent, userInfo: UnsafeMutableRawPointer?) -> Unmanaged<CGEvent>? { | ||
if type.rawValue == NSEvent.EventType.gesture.rawValue, let nsEvent = NSEvent(cgEvent: cgEvent) { | ||
touchEventHandler(nsEvent) | ||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled { | ||
CGEvent.tapEnable(tap: eventTap!, enable: true) | ||
} | ||
return nil | ||
} | ||
|
||
private func touchEventHandler(_ nsEvent: NSEvent) { | ||
let touches = nsEvent.allTouches() | ||
|
||
// Sometimes there are empty touch events that we have to skip. There are no empty touch events if Mission Control or App Expose use 3-finger swipes though. | ||
if touches.isEmpty { | ||
return | ||
} | ||
let touchesCount = touches.allSatisfy({ $0.phase == .ended }) ? 0 : touches.count | ||
|
||
// We don't care about non-3-fingers swipes. | ||
if touchesCount != 3 { | ||
// Except when we already started a gesture, so we need to end it. | ||
if App.app.appIsBeingUsed && App.app.shortcutIndex == 5 && Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease { | ||
DispatchQueue.main.async { | ||
App.app.focusTarget() | ||
} | ||
} | ||
clearState() | ||
return | ||
} | ||
|
||
let velocity = swipeVelocity(touches) | ||
// We don't care about gestures other than horizontal or vertical swipes. | ||
if velocity == nil { | ||
return | ||
} | ||
|
||
accVelX += velocity!.x | ||
accVelY += velocity!.y | ||
// Not enough swiping. | ||
if abs(accVelX) < accVelXThreshold && abs(accVelY) < accVelYThreshold { | ||
return | ||
} | ||
|
||
let isHorizontal = abs(velocity!.x) > abs(velocity!.y) | ||
if App.app.appIsBeingUsed { | ||
let direction: Direction = isHorizontal | ||
? accVelX < 0 ? .left : .right | ||
: accVelY < 0 ? .down : .up | ||
DispatchQueue.main.async { App.app.cycleSelection(direction) } | ||
} else { | ||
if isHorizontal { | ||
DispatchQueue.main.async { | ||
App.app.appIsBeingUsed = true | ||
App.app.showUiOrCycleSelection(5) | ||
} | ||
} | ||
} | ||
clearState() | ||
} | ||
|
||
private func clearState() { | ||
accVelX = 0 | ||
accVelY = 0 | ||
prevTouchPositions.removeAll() | ||
} | ||
|
||
private func swipeVelocity(_ touches: Set<NSTouch>) -> (x: Float, y: Float)? { | ||
var allRight = true | ||
var allLeft = true | ||
var allUp = true | ||
var allDown = true | ||
var sumVelX = Float(0) | ||
var sumVelY = Float(0) | ||
for touch in touches { | ||
let (velX, velY) = touchVelocity(touch) | ||
allRight = allRight && velX >= 0 | ||
allLeft = allLeft && velX <= 0 | ||
allUp = allUp && velY >= 0 | ||
allDown = allDown && velY <= 0 | ||
sumVelX += velX | ||
sumVelY += velY | ||
|
||
if touch.phase == .ended { | ||
prevTouchPositions.removeValue(forKey: "\(touch.identity)") | ||
} else { | ||
prevTouchPositions["\(touch.identity)"] = touch.normalizedPosition | ||
} | ||
} | ||
// All fingers should move in the same direction. | ||
if !allRight && !allLeft && !allUp && !allDown { | ||
return nil | ||
} | ||
|
||
let velX = sumVelX / Float(touches.count) | ||
let velY = sumVelY / Float(touches.count) | ||
return (velX, velY) | ||
} | ||
|
||
private func touchVelocity(_ touch: NSTouch) -> (Float, Float) { | ||
guard let prevPosition = prevTouchPositions["\(touch.identity)"] else { | ||
return (0, 0) | ||
} | ||
let position = touch.normalizedPosition | ||
return (Float(position.x - prevPosition.x), Float(position.y - prevPosition.y)) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @lwouis for your work! I also tried your touch-pad and found the same problem with this PR. The sensitivity is not matched with Windows (in my experience). I tried different numbers and found that 0.03, 0.06, 0.35, and 0.125 are more sensitive and like how Windows works.