From 1821deaa73f97557411eaa6cb9e0954a9eb73fdf Mon Sep 17 00:00:00 2001 From: Louis Pontoise Date: Fri, 28 Oct 2022 12:44:17 +0200 Subject: [PATCH] fix: improve key repeat-rate when held (closes #2026) --- src/logic/KeyRepeatTimer.swift | 5 +++-- src/logic/Windows.swift | 1 - src/ui/main-window/ThumbnailsView.swift | 1 - src/ui/preferences-window/tabs/GeneralTab.swift | 9 ++++++++- 4 files changed, 11 insertions(+), 5 deletions(-) diff --git a/src/logic/KeyRepeatTimer.swift b/src/logic/KeyRepeatTimer.swift index 83f191458..868a124ee 100644 --- a/src/logic/KeyRepeatTimer.swift +++ b/src/logic/KeyRepeatTimer.swift @@ -7,6 +7,7 @@ class KeyRepeatTimer { static func toggleRepeatingKeyPreviousWindow() { if let shortcut = ControlsTab.shortcuts["previousWindowShortcut"], + // events already repeat when using a shortcut with a keycode; no need for artificial repeat shortcut.shortcut.keyCode == .none { toggleRepeatingKey(shortcut) { App.app.previousWindowShortcutWithRepeatingKey() @@ -23,7 +24,7 @@ class KeyRepeatTimer { } private static func toggleRepeatingKey(_ atShortcut: ATShortcut, _ block: @escaping () -> Void) { - if (timer == nil || !timer!.isValid) { + if ((timer == nil || !timer!.isValid) && atShortcut.state != .up) { let repeatRate = ticksToSeconds(defaults.string(forKey: "KeyRepeat") ?? "6") let initialDelay = ticksToSeconds(defaults.string(forKey: "InitialKeyRepeat") ?? "25") timer = Timer(fire: Date(timeIntervalSinceNow: initialDelay), interval: repeatRate, repeats: true, block: { _ in @@ -40,6 +41,6 @@ class KeyRepeatTimer { } private static func ticksToSeconds(_ appleNumber: String) -> Double { - return Double(appleNumber)! / 60 + return Double(appleNumber)! / 60 // Apple probably hard-coupled key repeat-rate with 60hz monitors } } diff --git a/src/logic/Windows.swift b/src/logic/Windows.swift index 916191982..22a4bc88e 100644 --- a/src/logic/Windows.swift +++ b/src/logic/Windows.swift @@ -118,7 +118,6 @@ class Windows { let nextIndex = windowIndexAfterCycling(step) if ((step > 0 && nextIndex < focusedWindowIndex) || (step < 0 && nextIndex > focusedWindowIndex)) && (KeyRepeatTimer.isARepeat || KeyRepeatTimer.timer?.isValid ?? false) { - KeyRepeatTimer.timer?.invalidate() return } updateFocusedWindowIndex(nextIndex) diff --git a/src/ui/main-window/ThumbnailsView.swift b/src/ui/main-window/ThumbnailsView.swift index 55befcf44..010a35e42 100644 --- a/src/ui/main-window/ThumbnailsView.swift +++ b/src/ui/main-window/ThumbnailsView.swift @@ -42,7 +42,6 @@ class ThumbnailsView: NSVisualEffectView { let nextRow_ = nextRow < 0 ? rows.count + nextRow : nextRow if ((step > 0 && nextRow_ < currentRow) || (step < 0 && nextRow_ > currentRow)) && (KeyRepeatTimer.isARepeat || KeyRepeatTimer.timer?.isValid ?? false) { - KeyRepeatTimer.timer?.invalidate() return nil } return rows[nextRow_] diff --git a/src/ui/preferences-window/tabs/GeneralTab.swift b/src/ui/preferences-window/tabs/GeneralTab.swift index b52acbee3..3bd5f5819 100644 --- a/src/ui/preferences-window/tabs/GeneralTab.swift +++ b/src/ui/preferences-window/tabs/GeneralTab.swift @@ -35,7 +35,7 @@ class GeneralTab { App.app.restart() } - // add/remove plist file in ~/Library/LaunchAgents/ depending on the checkbox state + /// add/remove plist file in ~/Library/LaunchAgents/ depending on the checkbox state static func startAtLoginCallback(_ sender: NSControl) { var launchAgentsPath = (try? FileManager.default.url(for: .libraryDirectory, in: .userDomainMask, appropriateFor: nil, create: false)) ?? URL.init(fileURLWithPath: "~/Library", isDirectory: true) launchAgentsPath.appendPathComponent("LaunchAgents", isDirectory: true) @@ -49,12 +49,19 @@ class GeneralTab { launchAgentsPath.appendPathComponent("com.lwouis.alt-tab-macos.plist", isDirectory: false) if (sender as! NSButton).state == .on { // docs: https://developer.apple.com/library/archive/technotes/tn2083/_index.html#//apple_ref/doc/uid/DTS10003794-CH1-SECTION23 + // docs: man launchd.plist let plist: NSDictionary = [ "Label": App.id, "Program": Bundle.main.executablePath ?? "/Applications/\(App.name).app/Contents/MacOS/\(App.name)", "RunAtLoad": true, "LimitLoadToSessionType": "Aqua", "AssociatedBundleIdentifiers": App.id, + // "ProcessType: If left unspecified, the system will apply light resource limits to the job, + // throttling its CPU usage and I/O bandwidth" + "ProcessType": "Interactive", + // "LegacyTimers": If this key is set to true, timers created by the job will opt into less + // efficient but more precise behavior and not be coalesced with other timers. + "LegacyTimers": true, ] plist.write(to: launchAgentsPath, atomically: true) } else {