3

I'm working on a MacOS menu bar app which needs to track some global shortcuts in order to facilitate adjusting display brightness on external monitors. However, I cannot get it to fire a handler on any keyboard related events (mouse events work just fine).

I'm checking Accessibility with the following code

NSDictionary *options = @{CFBridgingRelease(kAXTrustedCheckOptionPrompt): @YES};
BOOL accessibilityEnabled = AXIsProcessTrustedWithOptions((CFDictionaryRef)options);

Then, I'm adding a global event monitor using:

self.eventMonitor = [NSEvent addGlobalMonitorForEventsMatchingMask:NSEventMaskKeyDown handler:^(NSEvent * event) {
    NSLog(@"Some event");
}];

If I switch NSEventMaskKeyDown to NSEventMaskMouseMove or something else mouse related, everything works fine. If I try NSEventMaskAny, nothing triggers again (which seems strange, as it should trigger on mouse still).

CatalinM
  • 520
  • 1
  • 5
  • 20

1 Answers1

7

The fact that NSEventMaskMouseMove works but NSEventMaskKeyDown doesn't is a clear sign that your app currently does not fullfill all requirements needed for tracking key events. NSEventMaskMouseMove is not considered as an event that needs special security protection and even works when AXIsProcessTrustedWithOptions returns false.

Key events require more permissions as mentioned by Charles Srstka in this answer:

The reason this doesn't work is because global monitors for .keyDown events require more permissions than some of the other event handlers, including the one that somebody thought this was a duplicate of. This is mainly because global .keyDown monitors can be used for nefarious purposes, such as keyloggers. So there are additional security measures in place to make sure we're legit:

1) Your app needs to be code-signed.

2) Your app needs to not have the App Sandbox enabled, and:

3) Your app needs to be registered in the Security and Privacy preference pane, under Accessibility.

You need to enable code-signing in the General pane of your app target. Make sure that both "Team" and "Signing Certificate" are set:

Turning on code-signing

You also have to disable the "App Sandbox" in the Capabilities pane:

Turning off sandbox

Please note, that in order the meet the third requirement you als need to add Xcode itself to the Accessibility pane. Otherwise you want be able to debug your event monitoring.

Two more tips regarding key events:

Key events sent to NSSecureTextField (or NSSecureTextFieldCell) are masked so that no event monitor can intercept or read them. This is a security feature to prevent applications from stealing passwords as they're typed, and there's no API to get around it.

A note about AppStore-compatible global shortcuts: It's clear that you can't upload an app to the AppStore that has its sandbox disabled. If you plan to distribute your app via the Mac AppStore (MAS) you have to use a different API. This thread mentions a bunch of solutions that are MAS-friendly. They use the RegisterEventHotKey API from the old Carbon days. Apple promised to not reject apps that use it.

mxgzf
  • 896
  • 10
  • 12
  • Thanks Maximilian. What you're saying makes sense. However, I checked everything and all the prerequisites seem to be in order. The app is code signed, sandboxing is turned off, both the app and Xcode are checked in Security&Privacy/Accessibility, not looking for input to secure fields... Funny thing is, I tried `CGEventTapCreate()` and that failed as well (could not create tap). I'm a bit frustrated as documentation is lacking, especially in regard to failure reasons. I will try the old Carbon APIs next (as I have an example from Spectacle.app) – CatalinM Apr 08 '18 at 14:12
  • Your app is treated as not having key down permissions at all. A newly created Mac app project with the same lines of code is behaving exactly as you are describing (before activating code signing and turning off sandboxing for it): MouseMove works, KeyDown not. I would investigate this using your code in a new project. You could also change your existing your app: bundle id, code signing, AppStore entitlements, … so that it's treated as a new app. – mxgzf Apr 08 '18 at 14:40
  • Tried it with a new app that had sandboxing turned off from the beginning (they're all code signed by default). No luck. I'm ready to give up on this approach and go the Carbon route – CatalinM Apr 08 '18 at 16:22
  • Strange. A simple new Cocoa app in Xcode 9.3 with just the (Swift) line `NSEvent.addGlobalMonitorForEvents(matching: .keyDown) { print($0) }` in `applicationDidFinishLaunching` works for me after following the three steps from above. That's how i tested it before posting my answer. Are your projects really code-signed by default? I have to select a team and choose "Enable Development Signing" for every new Cocoa app. – mxgzf Apr 08 '18 at 20:29
  • Yes, by default all my projects start with signing set to Automatic. That may be because I chose a team when starting a new project (when you choose a bundle ID), so it sticks to that as a default. – CatalinM Apr 09 '18 at 08:04
  • PS: What version of MacOS are you running? I just read this interesting tidbit about `CGEventTap`s (which are lower level and seem to be the basis of what `eventMonitor`s do) not working anymore starting 10.9. And I managed to get one working with the same result: works on mouse and other events but not keyboard. `Might be worth it to note that CGEventTap for keyboard events seems to have finally been deprecated in OSX 10.9. YOu will definitely will want to go with the InstallApplicationEventHandler methods suggested. – ekscrypto Aug 29 '13 at 12:52` – CatalinM Apr 09 '18 at 08:36
  • I'm running macOS 10.13.4. CGEvents don't seem to be officially deprecated. But i've heard keyboard-related CGEventTaps will not work sandboxed. Also: CGEvents have the same accessibility requirements as mentioned in my answer. I'd recommend that you focus on a test project to investigate why your Mac doesn't allow this kind of event monitoring: Are there erros in the system log? Do 3rd party apps like TextExpander work? Do they interfere? Does it work with another user account or a another macOS instance running in a VM? It should at least work with a simple test project. – mxgzf Apr 10 '18 at 17:14
  • 1
    It should... but it doesn’t. In the end I’ve given up on that and implemented the Carbon solution which works perfectly for my need. Mind you, that didn’t work either if I tried to listen for key presses, but it worked when listening for hotkeys that I registered through my app. In the end, all I wanted was support for system-wide hotkeys, which I now have. – CatalinM Apr 10 '18 at 17:31