Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

addGlobalMonitorForEventsMatchingMask not working

Tags:

macos

cocoa

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!

like image 963
James Waldrop Avatar asked Aug 26 '14 00:08

James Waldrop


1 Answers

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
like image 128
James Waldrop Avatar answered Oct 23 '22 20:10

James Waldrop