KeyboardShortcuts
⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes
by sindresorhusSwift
Commit activity
Last 12 weeks · 4 commits
Community health
4 of 6 standards met
Recent PRs & issues
While a menu is open, a shortcut can fire twice for a single key press. In my app, a hotkey toggles a light: with the menu open, the light sometimes switches on and immediately off again. With the menu closed, it works as expected. In menu-open mode, key events are handled by the run-loop monitor and the AppKit monitor instead of Carbon hotkeys. Logging showed that the same (same type, key code and timestamp) reaches more than once: both monitors may see it, and the run-loop monitor can see it repeatedly even after consuming it. Each delivery called / again. I have proposed a fix which makes remember the last key event it handled (type, key code and timestamp). Repeated deliveries of that event are still consumed, so they don't reach the menu, but they no longer fire the shortcut. The next key press is a new event and is handled as usual. covers repeated deliveries of the same event, the next press, and unrelated keys passing through. is now internal instead of private so the test can call it. Here is my proposal for a fix: https://github.com/Maschina/KeyboardShortcuts/commit/1c40783
Recent fixes
The observer in runs on every pass of the run loop and each pass calls . This is expensive: it triggers a CoreAnimation commit and full layout pass. Here is a sample taken while hovering over a menu: The upshot is that the menu's highlight lags behind the cursor. Separately, every re-posted mouse-moved event is expensive. While hovering, I could get my CPU to spike to 50% (M5 Pro). One solution is to gate the observer callback with a call to and exit early if the counter hasn't incremented: There is a side-effect though: the counter doesn't increment while a menu _within the hosting app_ is open. In other words, you can't activate the shortcut while a menu in your own app is open. See https://github.com/jamdotdev/KeyboardShortcuts/tree/menu-performance
PRs are restricted to collaborators on this repo, so I'm filing this as an issue. Fixes are pushed to my fork and linked below — happy for them to be cherry-picked, or I can open a PR if you'd rather grant access. On macOS 26/27 the recorder is unusable: clicking the field focuses it, but pressing a shortcut types a character into the search field instead of recording it. ⌥⇧P inserts . The clear button does nothing at all. There are three independent defects that compound — each fix only changes the symptom, which is what made this hard to pin down. All three reproduce in a ~100 line SwiftUI app using nothing but this package, so it isn't app-specific. Verified on both 0.7.1 and 3.0.1. 1. holds its monitor token weakly https://github.com/ppardi/KeyboardShortcuts/commit/c3f86c6 returns an autoreleased object the caller must own until it's passed to . Held weakly, nothing retains it, so it's deallocated when the autorelease pool drains at the end of the current event-loop turn. arms its key monitor inside , so the monitor is gone before the user can press anything. The field looks focused and captures nothing. is unreachable for the same reason — it early-returns on a nil , so monitors are never removed either. Present since c5d135b (2021), evidently masked until now by AppKit keeping the token alive internally. 2. ends the recording session it just started https://github.com/ppardi/KeyboardShortcuts/commit/3f517c0 does this, in order: Mutating while the field editor is installed makes AppKit end and restart editing. The resulting calls unconditionally, and it can land after the monitor is armed — tearing it down microseconds later. The failure tracks the field's contents exactly, which is what makes it look random: field already holds a shortcut → → cell mutates → editing ends → monitor dies field empty → , already nil → no mutation → monitor survives So recording works on a cleared field and fails when replacing an existing shortcut. Clearing the field or refocusing appears to "fix" it because both leave it empty for the next focus. Trace — failing, then working: ``blur()` after a successful capture, so the user can keep trying combinations without clicking back in. That one is a UX preference, not a fix — it's deliberately last so the three fixes can be taken without it. Tested on macOS 27.
Repository: sindresorhus/KeyboardShortcuts. Description: ⌨️ Add user-customizable global keyboard shortcuts (hotkeys) to your macOS app in minutes Stars: 2725, Forks: 253. Primary language: Swift. Languages: Swift (100%). License: MIT. Homepage: https://swiftpackageindex.com/sindresorhus/KeyboardShortcuts/documentation/keyboardshortcuts/keyboardshortcuts Topics: carthage, cocoapods, hotkey, keyboard-shortcuts, macos, swift-package, swift-package-manager, swiftui, swiftui-components. Latest release: 3.1.0 (1mo ago). Open PRs: 0, open issues: 2. Last activity: 1mo ago. Community health: 71%. Top contributors: sindresorhus, francisfeng, pvieito, Maschina, seungwoochoe, decodism, cweider, kant, DeLoeribas, gpoitch and others.