Fix option+letter shortcuts being intercepted by IME layer#236
Fix option+letter shortcuts being intercepted by IME layer#236BashkaMen wants to merge 2 commits intosindresorhus:mainfrom
Conversation
Carbon's RegisterEventHotKey doesn't reliably fire for option+letter shortcuts because macOS processes them through the IME layer first (e.g. option+q → œ). This causes such shortcuts to stop working intermittently depending on the active application's input handling. The existing RunLoopLocalEventMonitor with .eventTracking already solves this for the menu-open case. This fix extends that approach by adding an NSEvent.addGlobalMonitorForEvents listener whenever there are registered shortcuts that use Option without Command or Control — the only combinations affected by IME interception. The global monitor is enabled/disabled alongside normal hotkey mode, and is torn down automatically when no option-only shortcuts are registered. Fixes sindresorhus#235
| /** | ||
| Returns true if any registered shortcut uses Option as the only modifier (no Command or Control). | ||
| Such shortcuts are prone to being intercepted by the IME layer before Carbon receives them. | ||
| */ | ||
| private var hasOptionOnlyShortcuts: Bool { | ||
| hotKeys.values.compactMap(\.value).contains { hotKey in | ||
| let modifiers = NSEvent.ModifierFlags(carbon: hotKey.carbonModifiers) | ||
| return modifiers.contains(.option) | ||
| && !modifiers.contains(.command) | ||
| && !modifiers.contains(.control) | ||
| } | ||
| } |
There was a problem hiding this comment.
It won't work if a user has also shortcuts with different modifiers. It would be best to combine both the current solution and the new one, I guess.
There was a problem hiding this comment.
I believe your application is more important and should run globally.
I removed all the hotkeys I found while setting up the workspace.
| return modifiers.contains(.option) | ||
| && !modifiers.contains(.command) | ||
| && !modifiers.contains(.control) |
There was a problem hiding this comment.
Probably a more appropriate check would be:
modifiers == [.option]Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
|
||
| private func setGlobalOptionKeyMonitorEnabled(_ isEnabled: Bool) { | ||
| if isEnabled, globalEventMonitor == nil { | ||
| globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: [.keyDown, .keyUp]) { [weak self] event in |
There was a problem hiding this comment.
This requires accessibility access, so it's a no-go. From docs:
Key-related events may only be monitored if accessibility is enabled or if your application is trusted for accessibility access (see AXIsProcessTrusted()).
There was a problem hiding this comment.
Many apps use accessibility API. How about making it optional? Clients could decide wheter they want to use this API or not.
Fixes #235
Problem
Shortcuts using
option + letter(e.g.option + q) stop working intermittently.option + numberworks reliably. Re-opening the app's settings window temporarily restores them.Root cause: Carbon's
RegisterEventHotKeydoesn't receive events foroption + lettercombinations because macOS processes them through the Text Services Manager / IME layer first (e.g.option + q→œ). The Carbon event never fires. This doesn't affectoption + numberbecause digits don't participate in IME character composition.The existing
RunLoopLocalEventMonitorwith.eventTrackingalready solves this correctly for the menu-open case, since it intercepts raw key events before IME. This PR extends the same approach to the normal (non-menu) mode.Fix
When at least one registered shortcut uses
optionwithoutcommandorcontrol(the only combinations affected by IME), aNSEvent.addGlobalMonitorForEventslistener is activated alongside the normal Carbon hotkey handler.The global monitor handles key matching via the existing
handleRawKeyEventpath — no new matching logic needed. It is automatically enabled/disabled as shortcuts are registered/unregistered, and torn down entirely when no option-only shortcuts remain.Scope
Only
option-only shortcuts (nocmd/ctrl) are affected. Addingcommandorcontrolprevents IME interception — those shortcuts continue using Carbon exclusively.