diff --git a/src/api-wrappers/PrivateApis.swift b/src/api-wrappers/PrivateApis.swift index 1bbc1bfe0..60aa3fcfc 100644 --- a/src/api-wrappers/PrivateApis.swift +++ b/src/api-wrappers/PrivateApis.swift @@ -203,15 +203,22 @@ func SLSRequestScreenCaptureAccess() -> UInt8 let kAXFullscreenAttribute = "AXFullScreen" let kAXStatusLabelAttribute = "AXStatusLabel" +enum CGSSymbolicHotKey: Int, CaseIterable { + case commandTab = 1 + case commandShiftTab = 2 + case commandKeyAboveTab = 6 // see keyAboveTabDependingOnInputSource +} + // enables/disables a symbolic hotkeys. These are system shortcuts such as command+tab or Spotlight // it is possible to find all the existing hotkey IDs by using CGSGetSymbolicHotKeyValue on the first few hundred numbers // note: the effect of enabling/disabling persists after the app is quit @_silgen_name("CGSSetSymbolicHotKeyEnabled") @discardableResult -func CGSSetSymbolicHotKeyEnabled(_ hotKey: Int, _ isEnabled: Bool) -> CGError +func CGSSetSymbolicHotKeyEnabled(_ hotKey: CGSSymbolicHotKey.RawValue, _ isEnabled: Bool) -> CGError -func setNativeCommandTabEnabled(_ isEnabled: Bool) { - CGSSetSymbolicHotKeyEnabled(1, isEnabled) // command+tab - CGSSetSymbolicHotKeyEnabled(2, isEnabled) // command+shift+tab +func setNativeCommandTabEnabled(_ isEnabled: Bool, _ hotkeys: [CGSSymbolicHotKey] = CGSSymbolicHotKey.allCases) { + for hotkey in hotkeys { + CGSSetSymbolicHotKeyEnabled(hotkey.rawValue, isEnabled) + } } // returns info about a given psn diff --git a/src/logic/Preferences.swift b/src/logic/Preferences.swift index dcc17c13d..e9f13cf9e 100644 --- a/src/logic/Preferences.swift +++ b/src/logic/Preferences.swift @@ -370,6 +370,7 @@ class Preferences { return "6" } + /// key-above-tab is ` on US keyboard, but can be different on other keyboards static func keyAboveTabDependingOnInputSource() -> String { return LiteralKeyCodeTransformer.shared.transformedValue(NSNumber(value: kVK_ANSI_Grave)) ?? "`" } diff --git a/src/ui/preferences-window/tabs/ControlsTab.swift b/src/ui/preferences-window/tabs/ControlsTab.swift index 89aa4cb20..0a78d164e 100644 --- a/src/ui/preferences-window/tabs/ControlsTab.swift +++ b/src/ui/preferences-window/tabs/ControlsTab.swift @@ -136,15 +136,27 @@ class ControlsTab { toggleNativeCommandTabIfNeeded() } - private static func toggleNativeCommandTabIfNeeded() { - for atShortcut in shortcuts.values { - let shortcut = atShortcut.shortcut - if (shortcut.carbonModifierFlags == cmdKey || shortcut.carbonModifierFlags == (cmdKey | shiftKey)) && shortcut.carbonKeyCode == kVK_Tab { - setNativeCommandTabEnabled(false) - return + /// commandTab and commandKeyAboveTab are self-contained in the "nextWindowShortcut" shortcuts + /// but the keys of commandShiftTab can be spread between holdShortcut and a local shortcut + static func combinedModifiersMatch(_ modifiers1: UInt32, _ modifiers2: UInt32) -> Bool { + return (0..<5).contains { + if let holdShortcut = shortcuts[Preferences.indexToName("holdShortcut", $0)] { + return (holdShortcut.shortcut.carbonModifierFlags | modifiers1) == (holdShortcut.shortcut.carbonModifierFlags | modifiers2) } + return false } - setNativeCommandTabEnabled(true) + } + + private static func toggleNativeCommandTabIfNeeded() { + let nativeHotkeys: [CGSSymbolicHotKey: (Shortcut) -> Bool] = [ + .commandTab: { (shortcut) in shortcut.carbonKeyCode == kVK_Tab && shortcut.carbonModifierFlags == cmdKey }, + .commandShiftTab: { (shortcut) in shortcut.carbonKeyCode == kVK_Tab && combinedModifiersMatch(shortcut.carbonModifierFlags, UInt32(cmdKey | shiftKey)) }, + .commandKeyAboveTab: { (shortcut) in shortcut.carbonModifierFlags == cmdKey && shortcut.carbonKeyCode == kVK_ANSI_Grave }, + ] + let overlappingHotkeys = shortcuts.values.compactMap { (atShortcut) in nativeHotkeys.first { $1(atShortcut.shortcut) }?.key } + let nonOverlappingHotkeys: [CGSSymbolicHotKey] = Array(Set(nativeHotkeys.keys).symmetricDifference(Set(overlappingHotkeys))) + setNativeCommandTabEnabled(false, overlappingHotkeys) + setNativeCommandTabEnabled(true, nonOverlappingHotkeys) } @objc static func shortcutChangedCallback(_ sender: NSControl) {