Fix keyboard shortcuts when `NSMenu` is open (#122)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com>
This commit is contained in:
parent
8c90a95cb9
commit
3362cdbdf9
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue