Hello there, I am building an app that's going to be keyboard oriented, meaning that the UI will be minimal and only live n the menu bar; all core functions will be performed from the keyboard hotkeys that should be available from wherever in the system. I know about a Swift library called Hotkey that's doing it and it seems to work, however it uses the Carbon API which is deprecated for many years, plus its code is double dutch to me and, since it relies on a legacy code I wish I could atleast understand it to maintain my version of it in case MacOS finally sheds of the Carbon API completely. Is there a way to realize global hotkey in a more modern way?
How to properly realize global hotkeys on MacOS?
I was recently asked a very similar question in a DTS incident. Pasted in below are the relevant snippets from my response.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"
There are two parts to creating an app like this:
-
Ensuring your app runs in the background (A)
-
Monitoring key presses (B)
I’ll cover each in turn.
With regards point A, running in the background, the approach I recommend is to split the core of your app off into a separate app that you then nest within your main app. Set the LSUIElement
property on the nested app, so it doesn’t show up in the Dock or have its own menu bar.
In that nested app, use a menu bar status item to present your UI.
Note If you’re using SwiftUI, there’s now a SwiftUI equivalent.
In your main app, have a UI to control whether the nested app runs as a login item, that is, runs while the user is logged in. Use the SMAppService.loginItem(identifier:)
method to configure this.
If you need to support older system, use the SMLoginItemSetEnabled
function instead..
IMPORTANT App Review has specific constraints about the use of login items. See clause 2.4.5(iii) of the App Store Review Guidelines.
With regards B, monitoring key presses, there are a variety of APIs to monitor keyboard events, including:
-
AppKit’s global event monitor
-
RegisterEventHotKey
Of these the one I like the most is RegisterEventHotKey
. However, it’s intimately tied to the legacy Carbon toolbox and thus I can’t honestly recommend it.
Of the remaining options, I prefer CGEventTap
because of its interactions with TCC. More on this below.
Note This is called CGEventTap
because of the API name in C-based languages, CGEventTapCreate
.
TCC stands for Transparency, Consent, and Control. It’s the subsystem behind the privileges in System Settings > Privacy and Security. To listen for keyboard events you’ll need the Input Monitoring privilege.
One reason I like CGEventTap
is that it’s clearly associated with the APIs to determine whether you have that privilege (CGPreflightListenEventAccess
) and to request that privilege (CGRequestListenEventAccess
).
CGEventTap
is compatible with the App Sandbox, starting with macOS 10.15.
CGEventTap
is a bit tricky to use from Swift. See this post for an example.