Fix keyboard shortcuts when `NSMenu` is open (#122)

Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
decodism 2023-02-28 22:11:45 +01:00 committed by GitHub
parent 8c90a95cb9
commit 3362cdbdf9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 190 additions and 10 deletions

View File

@ -8,7 +8,7 @@ enum CarbonKeyboardShortcuts {
private final class HotKey {
let shortcut: KeyboardShortcuts.Shortcut
let carbonHotKeyId: Int
let carbonHotKey: EventHotKeyRef
var carbonHotKey: EventHotKeyRef?
let onKeyDown: (KeyboardShortcuts.Shortcut) -> Void
let onKeyUp: (KeyboardShortcuts.Shortcut) -> Void
@ -37,6 +37,15 @@ enum CarbonKeyboardShortcuts {
private static var hotKeyId = 0
private static var eventHandler: EventHandlerRef?
private static let hotKeyEventTypes = [
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
]
private static let rawKeyEventTypes = [
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventRawKeyDown)),
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventRawKeyUp))
]
private static func setUpEventHandlerIfNeeded() {
guard
eventHandler == nil,
@ -45,19 +54,48 @@ enum CarbonKeyboardShortcuts {
return
}
let eventSpecs = [
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyPressed)),
EventTypeSpec(eventClass: OSType(kEventClassKeyboard), eventKind: UInt32(kEventHotKeyReleased))
]
InstallEventHandler(
var handler: EventHandlerRef?
let error = InstallEventHandler(
dispatcher,
carbonKeyboardShortcutsEventHandler,
eventSpecs.count,
eventSpecs,
0,
nil,
&eventHandler
nil,
&handler
)
guard
error == noErr,
let handler
else {
return
}
eventHandler = handler
updateEventHandler()
}
static func updateEventHandler() {
guard eventHandler != nil else {
return
}
if KeyboardShortcuts.isEnabled {
if KeyboardShortcuts.isMenuOpen {
softUnregisterAll()
RemoveEventTypesFromHandler(eventHandler, hotKeyEventTypes.count, hotKeyEventTypes)
AddEventTypesToHandler(eventHandler, rawKeyEventTypes.count, rawKeyEventTypes)
} else {
softRegisterAll()
RemoveEventTypesFromHandler(eventHandler, rawKeyEventTypes.count, rawKeyEventTypes)
AddEventTypesToHandler(eventHandler, hotKeyEventTypes.count, hotKeyEventTypes)
}
} else {
softUnregisterAll()
RemoveEventTypesFromHandler(eventHandler, hotKeyEventTypes.count, hotKeyEventTypes)
RemoveEventTypesFromHandler(eventHandler, rawKeyEventTypes.count, rawKeyEventTypes)
}
}
static func register(
@ -95,6 +133,34 @@ enum CarbonKeyboardShortcuts {
setUpEventHandlerIfNeeded()
}
private static func softRegisterAll() {
for hotKey in hotKeys.values {
guard hotKey.carbonHotKey == nil else {
continue
}
var eventHotKey: EventHotKeyRef?
let error = RegisterEventHotKey(
UInt32(hotKey.shortcut.carbonKeyCode),
UInt32(hotKey.shortcut.carbonModifiers),
EventHotKeyID(signature: hotKeySignature, id: UInt32(hotKey.carbonHotKeyId)),
GetEventDispatcherTarget(),
0,
&eventHotKey
)
guard
error == noErr,
let eventHotKey
else {
hotKeys.removeValue(forKey: hotKey.carbonHotKeyId)
continue
}
hotKey.carbonHotKey = eventHotKey
}
}
private static func unregisterHotKey(_ hotKey: HotKey) {
UnregisterEventHotKey(hotKey.carbonHotKey)
hotKeys.removeValue(forKey: hotKey.carbonHotKeyId)
@ -112,11 +178,31 @@ enum CarbonKeyboardShortcuts {
}
}
private static func softUnregisterAll() {
for hotKey in hotKeys.values {
UnregisterEventHotKey(hotKey.carbonHotKey)
hotKey.carbonHotKey = nil
}
}
fileprivate static func handleEvent(_ event: EventRef?) -> OSStatus {
guard let event else {
return OSStatus(eventNotHandledErr)
}
switch Int(GetEventKind(event)) {
case kEventHotKeyPressed, kEventHotKeyReleased:
return handleHotKeyEvent(event)
case kEventRawKeyDown, kEventRawKeyUp:
return handleRawKeyEvent(event)
default:
break
}
return OSStatus(eventNotHandledErr)
}
private static func handleHotKeyEvent(_ event: EventRef) -> OSStatus {
var eventHotKeyId = EventHotKeyID()
let error = GetEventParameter(
event,
@ -152,6 +238,57 @@ enum CarbonKeyboardShortcuts {
return OSStatus(eventNotHandledErr)
}
private static func handleRawKeyEvent(_ event: EventRef) -> OSStatus {
var eventKeyCode = UInt32()
let keyCodeError = GetEventParameter(
event,
UInt32(kEventParamKeyCode),
typeUInt32,
nil,
MemoryLayout<UInt32>.size,
nil,
&eventKeyCode
)
guard keyCodeError == noErr else {
return keyCodeError
}
var eventKeyModifiers = UInt32()
let keyModifiersError = GetEventParameter(
event,
UInt32(kEventParamKeyModifiers),
typeUInt32,
nil,
MemoryLayout<UInt32>.size,
nil,
&eventKeyModifiers
)
guard keyModifiersError == noErr else {
return keyModifiersError
}
let shortcut = KeyboardShortcuts.Shortcut(carbonKeyCode: Int(eventKeyCode), carbonModifiers: Int(eventKeyModifiers))
guard let hotKey = (hotKeys.values.first { $0.shortcut == shortcut }) else {
return OSStatus(eventNotHandledErr)
}
switch Int(GetEventKind(event)) {
case kEventRawKeyDown:
hotKey.onKeyDown(hotKey.shortcut)
return noErr
case kEventRawKeyUp:
hotKey.onKeyUp(hotKey.shortcut)
return noErr
default:
break
}
return OSStatus(eventNotHandledErr)
}
}
extension CarbonKeyboardShortcuts {

View File

@ -37,6 +37,48 @@ public enum KeyboardShortcuts {
*/
static var isPaused = false
/**
Enable/disable monitoring of all keyboard shortcuts.
*/
public static var isEnabled = true {
didSet {
guard isEnabled != oldValue else {
return
}
CarbonKeyboardShortcuts.updateEventHandler()
}
}
/**
Set according to the opening state of your NSMenu if you want your keyboard shortcuts to work when it is open.
```swift
let menu = NSMenu()
let menuDelegate = MenuDelegate()
menu.delegate = menuDelegate
class MenuDelegate: NSObject, NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
KeyboardShortcuts.isMenuOpen = true
}
func menuDidClose(_ menu: NSMenu) {
KeyboardShortcuts.isMenuOpen = false
}
}
```
*/
public static var isMenuOpen = false {
didSet {
guard isMenuOpen != oldValue else {
return
}
CarbonKeyboardShortcuts.updateEventHandler()
}
}
private static func register(_ shortcut: Shortcut) {
guard !registeredShortcuts.contains(shortcut) else {
return

View File

@ -193,6 +193,7 @@ This package:
- Support for listening to key down, not just key up.
- Swift Package Manager support.
- Connect a shortcut to an `NSMenuItem`.
- Works when [`NSMenu` is open](https://github.com/sindresorhus/KeyboardShortcuts/issues/1) (e.g. menu bar apps).
`MASShortcut`:
- More mature.