wip trackpad

This commit is contained in:
lwouis
2024-12-04 00:10:35 +01:00
parent 68d24919b1
commit a42ef80a39
8 changed files with 285 additions and 34 deletions
+4
View File
@@ -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 */,
+18
View File
@@ -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.";
+32 -8
View File
@@ -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
+2 -2
View File
@@ -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)
+168
View File
@@ -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
View File
@@ -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)
}
}
+15 -7
View File
@@ -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]!()
}