How to properly realize global hotkeys on MacOS?

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?

Replies

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:

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.

  • Wow, it all seems so complicated. Here are some more questions to clarify.

    Why is CGEventTap a part of CoreGraphics? How does it impact me as a developer?My nested app should be only used to control the setting whether my main software runs in the background?If I make a CGEventTap and my hotkey is realized, should I return nil in order to suppress it, just like I was doing in Windows Hooks?
  • The documentation for CGEventTapCallBack does say that your callback can return NULL to delete the event, which would be reasonable to do if you have fully handled it.

Add a Comment