14

I am having trouble with getting an assistive-enabled application (XCode in the development case) to capture global keyDown events. I've seen lots of code examples like the below, but this doesn't work for me on 10.9.4.

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
  NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
  return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}

int main(int argc, const char * argv[])
{
  @autoreleasepool {
    if (checkAccessibility()) {
        NSLog(@"Accessibility Enabled");
    }
    else {
        NSLog(@"Accessibility Disabled");
    }

    NSLog(@"registering keydown mask");
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                           handler:^(NSEvent *event){
                                               NSLog(@"keydown: %@", event.characters);

                                           }];

      NSLog(@"entering run loop.");
      [[NSRunLoop currentRunLoop] run];
    }
    return 0;
}

The output received is:

2014-08-25 17:26:36.054 test[64725:303] Accessibility Enabled
2014-08-25 17:26:36.055 test[64725:303] registering keydown mask
2014-08-25 17:26:36.067 test[64725:303] entering run loop.

Once here, no other logging occurs, regardless of which keys I hit or what application has focus when I hit them.

FWIW, I'm trying to write an assistive application, not a key-logger or other evil thing. I've looked at the other instances of this question, but they seem to deal with either 1) the application not being assistive-enabled or 2) not receiving certain 'special' command keys that one would need CGEvents to receive. I am not seeing any keys, even simple ones (it's been running through my typing of this post and nothing was logged). TIA!

James Waldrop
  • 545
  • 4
  • 17

1 Answers1

16

So, thanks to Ken Thomases' question above, I was able to work out how to do this. The key detail is that I am using a command line application template (I don't have any need for a UI, so I was trying to keep things minimal). News to me, but obvious in hindsight, just creating a run loop doesn't create an event loop. In order to replicate the creation of an event loop within a command line application, more of the guts of a typical cocoa application have to be brought into play. First, you have to have a class implementing the NSApplicationDelegate protocol, and that delegate will be where the application code lives, leaving the main method to simply do the following:

#import <Foundation/Foundation.h>
#include "AppDelegate.h"

int main(int argc, const char * argv[])
{
    @autoreleasepool {
        AppDelegate *delegate = [[AppDelegate alloc] init];

        NSApplication * application = [NSApplication sharedApplication];
        [application setDelegate:delegate];
        [NSApp run];
    }
}

This is a nib-less, menubar-less application, just like the usual command line application template, but it does have a true event loop, due to the [NSApp run] call. Then the application code that I used to have in my main method above moved into the app delegate:

#import "AppDelegate.h"

@implementation AppDelegate

// 10.9+ only, see this url for compatibility:
// http://stackoverflow.com/questions/17693408/enable-access-for-assistive-devices-programmatically-on-10-9
BOOL checkAccessibility()
{
    NSDictionary* opts = @{(__bridge id)kAXTrustedCheckOptionPrompt: @YES};
    return AXIsProcessTrustedWithOptions((__bridge CFDictionaryRef)opts);
}

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    if (checkAccessibility()) {
        NSLog(@"Accessibility Enabled");
    }
    else {
        NSLog(@"Accessibility Disabled");
    }

    NSLog(@"registering keydown mask");
    [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
                                           handler:^(NSEvent *event){
                                               NSLog(@"keydown: %@", event.characters);

                                           }];
}

@end

And just for completeness sake and future readers, the header file looks like this:

#import <AppKit/AppKit.h>
#import <Foundation/Foundation.h>

@interface AppDelegate : NSObject <NSApplicationDelegate>

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification;

@end
Duck
  • 34,902
  • 47
  • 248
  • 470
James Waldrop
  • 545
  • 4
  • 17
  • Hey! How did you manage to give accessibility permissions to your command line tool? The settings screen really wants a real .app. – boztalay Jan 07 '16 at 19:32
  • @boztalay, you have to export your app via Product > Archive > Export... > Export as a Mac Application, then move the app to wherever you want it (i.e., your Applications folder). Then, you'll be able to find the app in Security & Privacy to give it accessibility permissions. :) – VinceFior Feb 12 '16 at 10:25
  • Guys, what is "AppDelegate.h", and how to implement it. Sorry for this question, I'm the newbie. – freeze Mar 22 '16 at 21:15
  • @VinceFior - in 10.11.6 this action does not help either. – mirap Aug 13 '16 at 19:06
  • 1
    I'm late to the question, but you just give accessibility permissions to XCode. – James Waldrop Oct 05 '16 at 05:06