mirror of
https://github.com/lwouis/alt-tab-macos.git
synced 2026-05-24 11:20:36 +00:00
wip trackpad
This commit is contained in:
@@ -64,6 +64,7 @@
|
||||
442E26DB1F7DA80AF794CCB6 /* Pods_unit_tests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C7E6F409CA442F83B7BE71AD /* Pods_unit_tests.framework */; };
|
||||
4807A6C623A9CD190052A53E /* SkyLight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4807A6C523A9CD190052A53E /* SkyLight.framework */; };
|
||||
48F3E16224EC0D8800A1C64B /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = D04BABDA79F8EF76E3ACDD5F /* Localizable.strings */; };
|
||||
5883F0A829A5182C0071DB65 /* TrackpadEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5883F0A729A5182C0071DB65 /* TrackpadEvents.swift */; };
|
||||
5F21C9E02C6E94240091F72F /* ShortcutsWhenActiveSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9DF2C6E94240091F72F /* ShortcutsWhenActiveSheet.swift */; };
|
||||
5F21C9E22C6E94410091F72F /* AdditionalControlsSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9E12C6E94410091F72F /* AdditionalControlsSheet.swift */; };
|
||||
5F21C9E42C6E94700091F72F /* CustomizeStyleSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5F21C9E32C6E94700091F72F /* CustomizeStyleSheet.swift */; };
|
||||
@@ -324,6 +325,7 @@
|
||||
1C961FB3DCF4906FEEF4A23D /* TableGroupView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TableGroupView.swift; sourceTree = "<group>"; };
|
||||
4807A6C523A9CD190052A53E /* SkyLight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SkyLight.framework; path = ../../../../System/Library/PrivateFrameworks/SkyLight.framework; sourceTree = "<group>"; };
|
||||
481FE54624D2D387001032F1 /* alt-tab-macos-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "alt-tab-macos-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||
5883F0A729A5182C0071DB65 /* TrackpadEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrackpadEvents.swift; sourceTree = "<group>"; };
|
||||
59DD4BE63D42EEFF1182BA7F /* Pods-alt-tab-macos.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-alt-tab-macos.release.xcconfig"; path = "Target Support Files/Pods-alt-tab-macos/Pods-alt-tab-macos.release.xcconfig"; sourceTree = "<group>"; };
|
||||
5F21C9DF2C6E94240091F72F /* ShortcutsWhenActiveSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutsWhenActiveSheet.swift; sourceTree = "<group>"; };
|
||||
5F21C9E12C6E94410091F72F /* AdditionalControlsSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalControlsSheet.swift; sourceTree = "<group>"; };
|
||||
@@ -1709,6 +1711,7 @@
|
||||
D04BA76AEE37D6656EB80126 /* RunningApplicationsEvents.swift */,
|
||||
D04BA5D9B4EBEB9E4333AE39 /* UserDefaultsEvents.swift */,
|
||||
D04BAF320F4D43F0DDFE063E /* MouseEvents.swift */,
|
||||
5883F0A729A5182C0071DB65 /* TrackpadEvents.swift */,
|
||||
BF0C870C14C20C936BA4AF40 /* DockEvents.swift */,
|
||||
BF0C8DF008F1EB5F23291A13 /* SpacesEvents.swift */,
|
||||
BF0C822D3BEDAB1F1C9F775E /* ScreensEvents.swift */,
|
||||
@@ -2226,6 +2229,7 @@
|
||||
D04BA8757C9F20F25EB50785 /* TextField.swift in Sources */,
|
||||
5F21C9E02C6E94240091F72F /* ShortcutsWhenActiveSheet.swift in Sources */,
|
||||
D04BAE8B16A06A10E2FA94DE /* AccessibilityEvents.swift in Sources */,
|
||||
5883F0A829A5182C0071DB65 /* TrackpadEvents.swift in Sources */,
|
||||
D04BAAAF5CFA991D3B078DB8 /* KeyboardEvents.swift in Sources */,
|
||||
D04BA1766FBCB2E941D081A5 /* RunningApplicationsEvents.swift in Sources */,
|
||||
D04BA0AE9865276FF8EF5DEB /* UserDefaultsEvents.swift in Sources */,
|
||||
|
||||
@@ -166,6 +166,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Dark" = "Dark";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Disabled" = "Disabled";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Do nothing" = "Do nothing";
|
||||
|
||||
@@ -193,6 +196,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"General" = "General";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Gesture" = "Gesture";
|
||||
|
||||
/* Menubar callout button */
|
||||
"Grant permission" = "Grant permission";
|
||||
|
||||
@@ -289,6 +295,9 @@
|
||||
/* No comment provided by engineer. */
|
||||
"Open Screen Recording Preferences…" = "Open Screen Recording Preferences…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Open Trackpad Preferences…" = "Open Trackpad Preferences…";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Optional: email (if you want a reply)" = "Optional: email (if you want a reply)";
|
||||
|
||||
@@ -475,6 +484,15 @@
|
||||
/* Menubar option */
|
||||
"Support this project ❤️" = "Support this project ❤️";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Swipe may conflict with system shortcuts" = "Swipe may conflict with system shortcuts";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Swipe with Four Fingers" = "Swipe with Four Fingers";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Swipe with Three Fingers" = "Swipe with Three Fingers";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"Switch between 3 different styles. You can customize them." = "Switch between 3 different styles. You can customize them.";
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class Preferences {
|
||||
"nextWindowShortcut3": "",
|
||||
"nextWindowShortcut4": "",
|
||||
"nextWindowShortcut5": "",
|
||||
"nextWindowGesture": GesturePreference.disabled.indexAsString,
|
||||
"focusWindowShortcut": "Space",
|
||||
"previousWindowShortcut": "⇧",
|
||||
"cancelShortcut": "⎋",
|
||||
@@ -32,21 +33,25 @@ class Preferences {
|
||||
"showMinimizedWindows3": ShowHowPreference.show.indexAsString,
|
||||
"showMinimizedWindows4": ShowHowPreference.show.indexAsString,
|
||||
"showMinimizedWindows5": ShowHowPreference.show.indexAsString,
|
||||
"showMinimizedWindows6": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows2": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows3": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows4": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows5": ShowHowPreference.show.indexAsString,
|
||||
"showHiddenWindows6": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows2": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows3": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows4": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows5": ShowHowPreference.show.indexAsString,
|
||||
"showFullscreenWindows6": ShowHowPreference.show.indexAsString,
|
||||
"windowOrder": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder2": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder3": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder4": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder5": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"windowOrder6": WindowOrderPreference.recentlyFocused.indexAsString,
|
||||
"showTabsAsWindows": "false",
|
||||
"hideColoredCircles": "false",
|
||||
"windowDisplayDelay": "100",
|
||||
@@ -65,16 +70,19 @@ class Preferences {
|
||||
"appsToShow3": AppsToShowPreference.all.indexAsString,
|
||||
"appsToShow4": AppsToShowPreference.all.indexAsString,
|
||||
"appsToShow5": AppsToShowPreference.all.indexAsString,
|
||||
"appsToShow6": AppsToShowPreference.all.indexAsString,
|
||||
"spacesToShow": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow2": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow3": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow4": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow5": SpacesToShowPreference.all.indexAsString,
|
||||
"spacesToShow6": SpacesToShowPreference.all.indexAsString,
|
||||
"screensToShow": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow2": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow3": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow4": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow5": ScreensToShowPreference.all.indexAsString,
|
||||
"screensToShow6": ScreensToShowPreference.all.indexAsString,
|
||||
"fadeOutAnimation": "false",
|
||||
"hideSpaceNumberLabels": "false",
|
||||
"hideStatusIcons": "false",
|
||||
@@ -90,6 +98,7 @@ class Preferences {
|
||||
"shortcutStyle3": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"shortcutStyle4": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"shortcutStyle5": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"shortcutStyle6": ShortcutStylePreference.focusOnRelease.indexAsString,
|
||||
"hideAppBadges": "false",
|
||||
"hideWindowlessApps": "false",
|
||||
"hideThumbnails": "false",
|
||||
@@ -106,6 +115,7 @@ class Preferences {
|
||||
// persisted values
|
||||
static var holdShortcut: [String] { ["holdShortcut", "holdShortcut2", "holdShortcut3", "holdShortcut4", "holdShortcut5"].map { UserDefaults.standard.string($0) } }
|
||||
static var nextWindowShortcut: [String] { ["nextWindowShortcut", "nextWindowShortcut2", "nextWindowShortcut3", "nextWindowShortcut4", "nextWindowShortcut5"].map { UserDefaults.standard.string($0) } }
|
||||
static var nextWindowGesture: GesturePreference { UserDefaults.standard.macroPref("nextWindowGesture", GesturePreference.allCases) }
|
||||
static var focusWindowShortcut: String { UserDefaults.standard.string("focusWindowShortcut") }
|
||||
static var previousWindowShortcut: String { UserDefaults.standard.string("previousWindowShortcut") }
|
||||
static var cancelShortcut: String { UserDefaults.standard.string("cancelShortcut") }
|
||||
@@ -148,14 +158,14 @@ class Preferences {
|
||||
static var showTitles: ShowTitlesPreference { UserDefaults.standard.macroPref("showTitles", ShowTitlesPreference.allCases) }
|
||||
static var updatePolicy: UpdatePolicyPreference { UserDefaults.standard.macroPref("updatePolicy", UpdatePolicyPreference.allCases) }
|
||||
static var crashPolicy: CrashPolicyPreference { UserDefaults.standard.macroPref("crashPolicy", CrashPolicyPreference.allCases) }
|
||||
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4", "appsToShow5"].map { UserDefaults.standard.macroPref($0, AppsToShowPreference.allCases) } }
|
||||
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4", "spacesToShow5"].map { UserDefaults.standard.macroPref($0, SpacesToShowPreference.allCases) } }
|
||||
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4", "screensToShow5"].map { UserDefaults.standard.macroPref($0, ScreensToShowPreference.allCases) } }
|
||||
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4", "showMinimizedWindows5"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4", "showHiddenWindows5"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4", "showFullscreenWindows5"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4", "windowOrder5"].map { UserDefaults.standard.macroPref($0, WindowOrderPreference.allCases) } }
|
||||
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4", "shortcutStyle5"].map { UserDefaults.standard.macroPref($0, ShortcutStylePreference.allCases) } }
|
||||
static var appsToShow: [AppsToShowPreference] { ["appsToShow", "appsToShow2", "appsToShow3", "appsToShow4", "appsToShow5", "appsToShow6"].map { UserDefaults.standard.macroPref($0, AppsToShowPreference.allCases) } }
|
||||
static var spacesToShow: [SpacesToShowPreference] { ["spacesToShow", "spacesToShow2", "spacesToShow3", "spacesToShow4", "spacesToShow5", "spacesToShow6"].map { UserDefaults.standard.macroPref($0, SpacesToShowPreference.allCases) } }
|
||||
static var screensToShow: [ScreensToShowPreference] { ["screensToShow", "screensToShow2", "screensToShow3", "screensToShow4", "screensToShow5", "screensToShow6"].map { UserDefaults.standard.macroPref($0, ScreensToShowPreference.allCases) } }
|
||||
static var showMinimizedWindows: [ShowHowPreference] { ["showMinimizedWindows", "showMinimizedWindows2", "showMinimizedWindows3", "showMinimizedWindows4", "showMinimizedWindows5", "showMinimizedWindows6"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showHiddenWindows: [ShowHowPreference] { ["showHiddenWindows", "showHiddenWindows2", "showHiddenWindows3", "showHiddenWindows4", "showHiddenWindows5", "showHiddenWindows6"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var showFullscreenWindows: [ShowHowPreference] { ["showFullscreenWindows", "showFullscreenWindows2", "showFullscreenWindows3", "showFullscreenWindows4", "showFullscreenWindows5", "showFullscreenWindows6"].map { UserDefaults.standard.macroPref($0, ShowHowPreference.allCases) } }
|
||||
static var windowOrder: [WindowOrderPreference] { ["windowOrder", "windowOrder2", "windowOrder3", "windowOrder4", "windowOrder5", "windowOrder6"].map { UserDefaults.standard.macroPref($0, WindowOrderPreference.allCases) } }
|
||||
static var shortcutStyle: [ShortcutStylePreference] { ["shortcutStyle", "shortcutStyle2", "shortcutStyle3", "shortcutStyle4", "shortcutStyle5", "shortcutStyle6"].map { UserDefaults.standard.macroPref($0, ShortcutStylePreference.allCases) } }
|
||||
static var menubarIcon: MenubarIconPreference { UserDefaults.standard.macroPref("menubarIcon", MenubarIconPreference.allCases) }
|
||||
static var menubarIconShown: Bool { UserDefaults.standard.bool("menubarIconShown") }
|
||||
static var language: LanguagePreference { UserDefaults.standard.macroPref("language", LanguagePreference.allCases) }
|
||||
@@ -512,6 +522,20 @@ enum MenubarIconPreference: CaseIterable, MacroPreference {
|
||||
}
|
||||
}
|
||||
|
||||
enum GesturePreference: CaseIterable, MacroPreference {
|
||||
case disabled
|
||||
case threeFingerSwipe
|
||||
case fourFingerSwipe
|
||||
|
||||
var localizedString: LocalizedString {
|
||||
switch self {
|
||||
case .disabled: return NSLocalizedString("Disabled", comment: "")
|
||||
case .threeFingerSwipe: return NSLocalizedString("Swipe with Three Fingers", comment: "")
|
||||
case .fourFingerSwipe: return NSLocalizedString("Swipe with Four Fingers", comment: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
enum LanguagePreference: CaseIterable, MacroPreference {
|
||||
case systemDefault
|
||||
case indonesian
|
||||
|
||||
@@ -182,10 +182,10 @@ class Windows {
|
||||
return list.count > focusedWindowIndex ? list[focusedWindowIndex] : nil
|
||||
}
|
||||
|
||||
static func cycleFocusedWindowIndex(_ step: Int) {
|
||||
static func cycleFocusedWindowIndex(_ step: Int, allowWrap: Bool = true) {
|
||||
let nextIndex = windowIndexAfterCycling(step)
|
||||
if ((step > 0 && nextIndex < focusedWindowIndex) || (step < 0 && nextIndex > focusedWindowIndex)) &&
|
||||
(ATShortcut.lastEventIsARepeat || KeyRepeatTimer.timer?.isValid ?? false) {
|
||||
(!allowWrap || ATShortcut.lastEventIsARepeat || KeyRepeatTimer.timer?.isValid ?? false) {
|
||||
return
|
||||
}
|
||||
updateFocusedAndHoveredWindowIndex(nextIndex)
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
import Cocoa
|
||||
|
||||
fileprivate var eventTap: CFMachPort!
|
||||
fileprivate var shouldBeEnabled: Bool!
|
||||
|
||||
//TODO: Should we add a sensitivity setting instead of these magic numbers?
|
||||
fileprivate let SHOW_UI_THRESHOLD: Float = 0.003
|
||||
fileprivate let CYCLE_THRESHOLD: Float = 0.04
|
||||
|
||||
// gesture tracking state
|
||||
fileprivate var prevTouchPositions: [String: NSPoint] = [:]
|
||||
fileprivate var totalDisplacement = (x: Float(0), y: Float(0))
|
||||
fileprivate var extendNextXThreshold = false
|
||||
|
||||
//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_()
|
||||
TrackpadEvents.toggle(Preferences.nextWindowGesture != .disabled)
|
||||
}
|
||||
|
||||
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, // we need raw data
|
||||
place: .headInsertEventTap,
|
||||
options: .defaultTap,
|
||||
eventsOfInterest: NSEvent.EventTypeMask.gesture.rawValue,
|
||||
callback: handleEvent,
|
||||
userInfo: nil)
|
||||
if let eventTap = eventTap {
|
||||
let runLoopSource = CFMachPortCreateRunLoopSource(nil, eventTap, 0)
|
||||
CFRunLoopAddSource(BackgroundWork.keyboardEventsThread.runLoop, runLoopSource, .commonModes)
|
||||
} else {
|
||||
App.app.restart()
|
||||
}
|
||||
}
|
||||
|
||||
private let handleEvent: CGEventTapCallBack = { _, type, cgEvent, _ in
|
||||
if type.rawValue == NSEvent.EventType.gesture.rawValue {
|
||||
if touchEventHandler(cgEvent) {
|
||||
return nil // focused app won't receive the event
|
||||
}
|
||||
} else if (type == .tapDisabledByUserInput || type == .tapDisabledByTimeout) && shouldBeEnabled {
|
||||
CGEvent.tapEnable(tap: eventTap!, enable: true)
|
||||
}
|
||||
return Unmanaged.passUnretained(cgEvent) // focused app will receive the event
|
||||
}
|
||||
|
||||
private func touchEventHandler(_ cgEvent: CGEvent) -> Bool {
|
||||
var nsEvent: NSEvent?
|
||||
DispatchQueue.main.sync {
|
||||
nsEvent = NSEvent(cgEvent: cgEvent)
|
||||
}
|
||||
guard let nsEvent = nsEvent else { return false }
|
||||
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 false }
|
||||
let requiredFingers = Preferences.nextWindowGesture == .fourFingerSwipe ? 4 : 3
|
||||
if touches.allSatisfy({ $0.phase == .ended }) || touches.count != requiredFingers {
|
||||
clearState()
|
||||
if App.app.appIsBeingUsed && touches.count < requiredFingers && App.app.shortcutIndex == 5
|
||||
&& Preferences.shortcutStyle[App.app.shortcutIndex] == .focusOnRelease {
|
||||
DispatchQueue.main.async { App.app.focusTarget() }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
guard let delta = calculateTouchDelta(touches) else { return false }
|
||||
let displacement = (x: totalDisplacement.x + delta.x, y: totalDisplacement.y + delta.y)
|
||||
totalDisplacement = displacement
|
||||
|
||||
// handle showing the app initially
|
||||
if !App.app.appIsBeingUsed {
|
||||
if abs(displacement.x) > SHOW_UI_THRESHOLD && abs(displacement.y) < SHOW_UI_THRESHOLD {
|
||||
resetDisplacement(x: true, y: false)
|
||||
// the SHOW_UI_THRESHOLD is much less then the CYCLE_THRESHOLD
|
||||
// so for consistency when swiping, extend the threshold for the next horizontal swipe
|
||||
extendNextXThreshold = true
|
||||
DispatchQueue.main.async { App.app.showUiOrCycleSelection(5) }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// handle swipes when the app is open
|
||||
if abs(displacement.x) > CYCLE_THRESHOLD {
|
||||
// if extendNextXThreshold is set, extend the threshold for a right swipe to account for the show ui swipe
|
||||
if !extendNextXThreshold || displacement.x < 0
|
||||
|| displacement.x > 2 * CYCLE_THRESHOLD - SHOW_UI_THRESHOLD
|
||||
{
|
||||
let direction: Direction = displacement.x < 0 ? .left : .right
|
||||
resetDisplacement(x: true, y: false)
|
||||
extendNextXThreshold = false
|
||||
DispatchQueue.main.async { App.app.cycleSelection(direction, allowWrap: false) }
|
||||
return true
|
||||
}
|
||||
}
|
||||
if abs(displacement.y) > CYCLE_THRESHOLD {
|
||||
let direction: Direction = displacement.y < 0 ? .down : .up
|
||||
resetDisplacement(x: false, y: true)
|
||||
DispatchQueue.main.async { App.app.cycleSelection(direction, allowWrap: false) }
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func calculateTouchDelta(_ touches: Set<NSTouch>) -> (x: Float, y: Float)? {
|
||||
var allRight = true
|
||||
var allLeft = true
|
||||
var allUp = true
|
||||
var allDown = true
|
||||
var sumDelta = (x: Float(0), y: Float(0))
|
||||
var count = 0
|
||||
|
||||
for touch in touches {
|
||||
let prevPosition = prevTouchPositions["\(touch.identity)"]
|
||||
let position = touch.normalizedPosition
|
||||
if touch.phase == .ended {
|
||||
prevTouchPositions.removeValue(forKey: "\(touch.identity)")
|
||||
} else {
|
||||
prevTouchPositions["\(touch.identity)"] = position
|
||||
}
|
||||
if prevPosition == nil { continue }
|
||||
|
||||
let delta = (
|
||||
x: Float(position.x - prevPosition!.x), y: Float(position.y - prevPosition!.y)
|
||||
)
|
||||
|
||||
allRight = allRight && delta.x > 0
|
||||
allLeft = allLeft && delta.x < 0
|
||||
allUp = allUp && delta.y > 0
|
||||
allDown = allDown && delta.y < 0
|
||||
|
||||
sumDelta.x += delta.x
|
||||
sumDelta.y += delta.y
|
||||
count += 1
|
||||
}
|
||||
|
||||
// All fingers should move in the same direction.
|
||||
if count == 0 || (!allRight && !allLeft && !allUp && !allDown) { return nil }
|
||||
return (x: sumDelta.x / Float(count), y: sumDelta.y / Float(count))
|
||||
}
|
||||
|
||||
private func clearState() {
|
||||
prevTouchPositions.removeAll()
|
||||
resetDisplacement()
|
||||
extendNextXThreshold = false
|
||||
}
|
||||
|
||||
private func resetDisplacement(x: Bool = true, y: Bool = true) {
|
||||
if x && y {
|
||||
totalDisplacement = (0, 0)
|
||||
} else if x {
|
||||
totalDisplacement.x = 0
|
||||
} else if y {
|
||||
totalDisplacement.y = 0
|
||||
}
|
||||
}
|
||||
+4
-3
@@ -74,6 +74,7 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
self.feedbackWindow = FeedbackWindow()
|
||||
KeyboardEvents.addEventHandlers()
|
||||
MouseEvents.observe()
|
||||
TrackpadEvents.observe()
|
||||
// TODO: undeterministic; events in the queue may still be processing; good enough for now
|
||||
DispatchQueue.main.async { () -> () in Windows.sortByLevel() }
|
||||
self.preloadWindows()
|
||||
@@ -216,11 +217,11 @@ class App: AppCenterApplication, NSApplicationDelegate {
|
||||
showPreferencesWindow()
|
||||
}
|
||||
|
||||
func cycleSelection(_ direction: Direction) {
|
||||
func cycleSelection(_ direction: Direction, allowWrap: Bool = true) {
|
||||
if direction == .up || direction == .down {
|
||||
thumbnailsPanel.thumbnailsView.navigateUpOrDown(direction)
|
||||
thumbnailsPanel.thumbnailsView.navigateUpOrDown(direction, allowWrap: allowWrap)
|
||||
} else {
|
||||
Windows.cycleFocusedWindowIndex(direction.step())
|
||||
Windows.cycleFocusedWindowIndex(direction.step(), allowWrap: allowWrap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -57,24 +57,32 @@ class ThumbnailsView: NSVisualEffectView {
|
||||
}
|
||||
}
|
||||
|
||||
func nextRow(_ direction: Direction) -> [ThumbnailView]? {
|
||||
func nextRow(_ direction: Direction, allowWrap: Bool = true) -> [ThumbnailView]? {
|
||||
let step = direction == .down ? 1 : -1
|
||||
if let currentRow = Windows.focusedWindow()?.rowIndex {
|
||||
let nextRow = (currentRow + step) % rows.count
|
||||
let nextRow_ = nextRow < 0 ? rows.count + nextRow : nextRow
|
||||
if ((step > 0 && nextRow_ < currentRow) || (step < 0 && nextRow_ > currentRow)) &&
|
||||
var nextRow = currentRow + step
|
||||
if nextRow >= rows.count {
|
||||
if allowWrap {
|
||||
nextRow = nextRow % rows.count
|
||||
} else { return nil }
|
||||
} else if nextRow < 0 {
|
||||
if allowWrap {
|
||||
nextRow = rows.count + nextRow
|
||||
} else { return nil }
|
||||
}
|
||||
if ((step > 0 && nextRow < currentRow) || (step < 0 && nextRow > currentRow)) &&
|
||||
(ATShortcut.lastEventIsARepeat || KeyRepeatTimer.timer?.isValid ?? false) {
|
||||
return nil
|
||||
}
|
||||
return rows[nextRow_]
|
||||
return rows[nextRow]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func navigateUpOrDown(_ direction: Direction) {
|
||||
func navigateUpOrDown(_ direction: Direction, allowWrap: Bool = true) {
|
||||
let focusedViewFrame = ThumbnailsView.recycledViews[Windows.focusedWindowIndex].frame
|
||||
let originCenter = NSMidX(focusedViewFrame)
|
||||
if let targetRow = nextRow(direction) {
|
||||
if let targetRow = nextRow(direction, allowWrap: allowWrap) {
|
||||
let leftSide = originCenter < NSMidX(frame)
|
||||
let leadingSide = App.shared.userInterfaceLayoutDirection == .leftToRight ? leftSide : !leftSide
|
||||
let iterable = leadingSide ? targetRow : targetRow.reversed()
|
||||
|
||||
@@ -41,12 +41,13 @@ class ControlsTab {
|
||||
static var additionalControlsSheet: AdditionalControlsSheet!
|
||||
|
||||
static func initTab() -> NSView {
|
||||
let (holdShortcut, nextWindowShortcut, tab1View) = toShowSection(0)
|
||||
let (holdShortcut2, nextWindowShortcut2, tab2View) = toShowSection(1)
|
||||
let (holdShortcut3, nextWindowShortcut3, tab3View) = toShowSection(2)
|
||||
let (holdShortcut4, nextWindowShortcut4, tab4View) = toShowSection(3)
|
||||
let (holdShortcut5, nextWindowShortcut5, tab5View) = toShowSection(4)
|
||||
tableGroupViews = [tab1View, tab2View, tab3View, tab4View, tab5View]
|
||||
let (holdShortcut, nextWindowShortcut, tab1View) = shortcutTab(0)
|
||||
let (holdShortcut2, nextWindowShortcut2, tab2View) = shortcutTab(1)
|
||||
let (holdShortcut3, nextWindowShortcut3, tab3View) = shortcutTab(2)
|
||||
let (holdShortcut4, nextWindowShortcut4, tab4View) = shortcutTab(3)
|
||||
let (holdShortcut5, nextWindowShortcut5, tab5View) = shortcutTab(4)
|
||||
let tab6View = gestureTab(5)
|
||||
tableGroupViews = [tab1View, tab2View, tab3View, tab4View, tab5View, tab6View]
|
||||
// trigger shortcutChanged for these shortcuts to trigger .restrictModifiers
|
||||
[holdShortcut, holdShortcut2, holdShortcut3, holdShortcut4, holdShortcut5].forEach { ControlsTab.shortcutChangedCallback($0[1] as! NSControl) }
|
||||
[nextWindowShortcut, nextWindowShortcut2, nextWindowShortcut3, nextWindowShortcut4, nextWindowShortcut5].forEach { ControlsTab.shortcutChangedCallback($0[0] as! NSControl) }
|
||||
@@ -62,6 +63,7 @@ class ControlsTab {
|
||||
NSLocalizedString("Shortcut 3", comment: ""),
|
||||
NSLocalizedString("Shortcut 4", comment: ""),
|
||||
NSLocalizedString("Shortcut 5", comment: ""),
|
||||
NSLocalizedString("Gesture", comment: ""),
|
||||
], trackingMode: .selectOne, target: self, action: #selector(switchTab(_:)))
|
||||
tab.selectedSegment = 0
|
||||
tab.segmentStyle = .automatic
|
||||
@@ -71,7 +73,7 @@ class ControlsTab {
|
||||
let additionalControlsButton = NSButton(title: NSLocalizedString("Additional controls…", comment: ""), target: self, action: #selector(ControlsTab.showAdditionalControlsSettings))
|
||||
let shortcutsButton = NSButton(title: NSLocalizedString("Shortcuts when active…", comment: ""), target: self, action: #selector(ControlsTab.showShortcutsSettings))
|
||||
let tools = StackView([additionalControlsButton, shortcutsButton], .horizontal)
|
||||
let view = TableGroupSetView(originalViews: [table, tab1View, tab2View, tab3View, tab4View, tab5View], toolsViews: [tools], toolsAlignment: .trailing)
|
||||
let view = TableGroupSetView(originalViews: [table, tab1View, tab2View, tab3View, tab4View, tab5View, tab6View], toolsViews: [tools], toolsAlignment: .trailing)
|
||||
view.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
shortcutsWhenActiveSheet = ShortcutsWhenActiveSheet()
|
||||
@@ -82,7 +84,28 @@ class ControlsTab {
|
||||
return view
|
||||
}
|
||||
|
||||
private static func toShowSection(_ index: Int) -> ([NSView], [NSView], TableGroupView) {
|
||||
private static func shortcutTab(_ index: Int) -> ([NSView], [NSView], TableGroupView) {
|
||||
var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), Preferences.indexToName("holdShortcut", index), Preferences.holdShortcut[index], false, labelPosition: .leftWithoutSeparator)
|
||||
holdShortcut.append(LabelAndControl.makeLabel(NSLocalizedString("and press", comment: "")))
|
||||
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), Preferences.indexToName("nextWindowShortcut", index), Preferences.nextWindowShortcut[index], labelPosition: .right)
|
||||
let tab = controlTab(index, holdShortcut + [nextWindowShortcut[0]])
|
||||
return (holdShortcut, nextWindowShortcut, tab)
|
||||
}
|
||||
|
||||
private static func gestureTab(_ index: Int) -> TableGroupView {
|
||||
let label = NSLocalizedString("Swipe may conflict with system shortcuts", comment: "")
|
||||
let button = NSButton(title: NSLocalizedString("Open Trackpad Preferences…", comment: ""), target: self, action: #selector(openSystemGestures(_:)))
|
||||
let infoBtn = LabelAndControl.makeInfoButton(onMouseEntered: { event, view in
|
||||
Popover.shared.show(event: event, positioningView: view, message: label, extraView: button)
|
||||
})
|
||||
let gesture = LabelAndControl.makeDropdown("nextWindowGesture", GesturePreference.allCases, extraAction: ControlsTab.gestureChangedCallback)
|
||||
let gestureWithTooltip = StackView([infoBtn, gesture], .horizontal)
|
||||
gestureWithTooltip.spacing = 8
|
||||
gestureWithTooltip.alignment = .centerY
|
||||
return controlTab(index, [gestureWithTooltip])
|
||||
}
|
||||
|
||||
private static func controlTab(_ index: Int, _ trigger: [NSView]) -> TableGroupView {
|
||||
let appsToShow = LabelAndControl.makeDropdown(Preferences.indexToName("appsToShow", index), AppsToShowPreference.allCases)
|
||||
let spacesToShow = LabelAndControl.makeDropdown(Preferences.indexToName("spacesToShow", index), SpacesToShowPreference.allCases)
|
||||
let screensToShow = LabelAndControl.makeDropdown(Preferences.indexToName("screensToShow", index), ScreensToShowPreference.allCases)
|
||||
@@ -90,14 +113,10 @@ class ControlsTab {
|
||||
let showHiddenWindows = LabelAndControl.makeDropdown(Preferences.indexToName("showHiddenWindows", index), ShowHowPreference.allCases)
|
||||
let showFullscreenWindows = LabelAndControl.makeDropdown(Preferences.indexToName("showFullscreenWindows", index), ShowHowPreference.allCases.filter { $0 != .showAtTheEnd })
|
||||
let windowOrder = LabelAndControl.makeDropdown(Preferences.indexToName("windowOrder", index), WindowOrderPreference.allCases)
|
||||
|
||||
var holdShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Hold", comment: ""), Preferences.indexToName("holdShortcut", index), Preferences.holdShortcut[index], false, labelPosition: .leftWithoutSeparator)
|
||||
holdShortcut.append(LabelAndControl.makeLabel(NSLocalizedString("and press", comment: "")))
|
||||
let nextWindowShortcut = LabelAndControl.makeLabelWithRecorder(NSLocalizedString("Select next window", comment: ""), Preferences.indexToName("nextWindowShortcut", index), Preferences.nextWindowShortcut[index], labelPosition: .right)
|
||||
let shortcutStyle = LabelAndControl.makeDropdown(Preferences.indexToName("shortcutStyle", index), ShortcutStylePreference.allCases)
|
||||
|
||||
let table = TableGroupView(width: PreferencesWindow.width)
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Trigger shortcut", comment: ""), rightViews: holdShortcut + [nextWindowShortcut[0]]))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Trigger shortcut", comment: ""), rightViews: trigger))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("After release", comment: ""), rightViews: [shortcutStyle]))
|
||||
|
||||
table.addNewTable()
|
||||
@@ -109,7 +128,7 @@ class ControlsTab {
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Show fullscreen windows", comment: ""), rightViews: [showFullscreenWindows]))
|
||||
table.addRow(TableGroupView.Row(leftTitle: NSLocalizedString("Order windows by", comment: ""), rightViews: [windowOrder]))
|
||||
table.fit()
|
||||
return (holdShortcut, nextWindowShortcut, table)
|
||||
return table
|
||||
}
|
||||
|
||||
@objc static func switchTab(_ sender: NSSegmentedControl) {
|
||||
@@ -293,6 +312,15 @@ class ControlsTab {
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func gestureChangedCallback(_ sender: NSControl) {
|
||||
TrackpadEvents.toggle(Preferences.nextWindowGesture != .disabled)
|
||||
}
|
||||
|
||||
@objc private static func openSystemGestures(_ sender: NSButton) {
|
||||
// Apple doesn't expose the More Gestures tab directly
|
||||
NSWorkspace.shared.open(URL(string: "x-apple.systempreferences:com.apple.Trackpad-Settings.extension")!)
|
||||
}
|
||||
|
||||
static func executeAction(_ action: String) {
|
||||
shortcutsActions[action]!()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user