Skip to content

Investigate a possibility to use CGEvent.tapCreate for global hotkeys #1012

@nikitabobko

Description

@nikitabobko

Right now, we use Carbon API. It's a deprecated framework, but since a looot of apps use, I don't think that Apple is gonna drop it in foreseeable future

The alternative API I recently found is
https://developer.apple.com/documentation/coregraphics/cgevent/1454426-tapcreate
https://forums.developer.apple.com/forums/thread/735223

Copying the response from apple forum in case it gets deleted:

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](https://developer.apple.com/documentation/bundleresources/information_property_list/lsuielement) 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](https://developer.apple.com/documentation/appkit/menus_cursors_and_the_dock#2871629) to present your UI.

Note If you’re using SwiftUI, there’s now a [SwiftUI equivalent](https://developer.apple.com/documentation/swiftui/scenes#creating-a-menu-bar-extra).

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](https://developer.apple.com/documentation/servicemanagement/smappservice/3945411-loginitem) to configure this.

If you need to support older system, use the [SMLoginItemSetEnabled function](https://developer.apple.com/documentation/servicemanagement/1501557-smloginitemsetenabled) 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](https://developer.apple.com/app-store/review/).

With regards B, monitoring key presses, there are a variety of APIs to monitor keyboard events, including:

  - The [CGEventTap subsystme](https://developer.apple.com/documentation/coregraphics/cgevent/1454426-tapcreate)

  - AppKit’s [global event monitor](https://developer.apple.com/documentation/appkit/nsevent/1535472-addglobalmonitorforevents)

  - 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](https://developer.apple.com/forums/thread/707680?answerId=716892022#716892022) for an example.

Maybe the alternative API allows to distinguish left and right modifiers (#28) and fn key (#1011)

Related

#1472

Follow ups

#1931

Metadata

Metadata

Assignees

No one assigned

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions