Global hotkeys in Cocoa on Snow Leopard
Oct14
So I got this new MacBook Pro with Snow Leopard. One granny said that carbon won’t be supported on 64 bit platforms on Snow Leopard. So I was really interested how do I implement global hotkeys on 64 bit platform without using carbon.
NSEvent class addGlobalMonitorForEventsMatchingMask:handler: method provides this functionality. General declaration for this method looks like this:
+ (id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block
Since we’re monitoring the global event, block passed to method as event handler returns void (i.e. it can’t modify passed in event). But we can do anything we please inside the block. Most common usage pattern would be to activate an app and show it’s key window. It’s possible implement it in two ways: via window controller object or notifications.
Here how it’d look using window controller object (assuming MainWindowController is created before adding event monitor):
// AppDelegate.m - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { ... [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^{ [NSApp activateIgnoringOtherApps:YES]; [mainWindowController showWindow:self]; }]; ... }
In example above – any key down while your app is inactive will activate your app and show window mainWindowController is responsible for.
The second one IMHO is cleaner and has wider application: using NSNotification. It requires code in application controller and application delegate. Both code pieces are independent.
// AppDelegate.m - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^{ ... int flags = [event modifierFlags]; int altDown = flags & NSOptionKeyMask; if (([event keyCode] == 7) && altDown) { [[NSNotificationCenter defaultCenter] postNotificationName:@"OpenMainWindow" object:nil]; }; ... }]; }; // AppController.m - (void)awakeFromNib { // Let's register for "OpenMainWindow" notification in AppController. NSNotificationCenter *nc = [NSNotificationCenter defaultCenter]; [nc addObserver:self selector:@selector(showMainWindow:) name:@"OpenMainWindow" object:nil]; } - (void)showMainWindow:(id)sender { // Open main window. Simple. [mainWindowController showWindow:sender]; }
UPDATE: as Antonio stated in the comments you need enable access for assistive devices via System Preferences app.
Reference: addGlobalMonitorForEventsMatchingMask:handler: @ Mac Dev Center
Enjoy this article?
Consider subscribing to our RSS feed!
12:37 on October 23rd, 2009
hello andrei,
thanks for sharing this information.
the code in the handler is never been called. Some ideas?
Regards
14:03 on October 23rd, 2009
You should check in your MainMenu.xib file if AppDelegate object is created and is referenced as “delegate” for File Owner.
P.S. I’m not andrei ,)
14:16 on October 23rd, 2009
oh, i am sorry. i saw the name below.
yes, it is.
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
id tmp = [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask
handler:^(NSEvent *event) {
NSLog(@"Test");
}];
NSLog(@”%@”, tmp);
}
tmp was successfully logged. the other log statement is always ignored.
very strange…
14:54 on October 23rd, 2009
it’s because the next log statement is ran only once, after app has finished launching (i.e. it does not belong to the handler).
02:52 on November 9th, 2009
Do note that you need to have access for assistive devices enabled in the Unviersal Access prefpane for this to work. Alternatively, you need to have your app marked as a trusted accessibility app, the process for which I’m not sure of.
22:14 on November 9th, 2009
I am trying to setup my app to have user configurable hot keys…however, when I use the above method the system makes the alert noise. Is there anything I can do to make my app ‘consume’ the key press(s) and not cause the alert noise?
Also, is there an easy way to translate the keys codes into their printable characters? For example, I want to have a text box display ‘⌘ + X’ whenever ⌘ + X is pressed.
23:42 on November 9th, 2009
Regarding the alert noises – it’s probably because the keystroke is already taken by an active app or system (in which case my code would have mistake regarding the activated keystroke. thanks for noticing).
And regarding the display of the characters, I’d suggest shortcut recorder from wafflesoft.
16:55 on November 10th, 2009
I tried using F11 as my hot key (as this is the key I use with another app without problems). I made sure to remove the default functionality (expose) in system preferences. My app only seems to make the alert noise whenever Xcode or Finder has the focus, however, I’m wondering how the other app that I use gets around this.
As for the second part of my post here is the code I wrote to show what is being pressed:
myHotKey = [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
NSString *keysPressed = @"";
if ([event modifierFlags] & NSControlKeyMask)
keysPressed = [NSString stringWithFormat:@"%@^", keysPressed];
if ([event modifierFlags] & NSAlternateKeyMask)
keysPressed = [NSString stringWithFormat:@"%@?", keysPressed];
if ([event modifierFlags] & NSShiftKeyMask)
keysPressed = [NSString stringWithFormat:@"%@?", keysPressed];
if ([event modifierFlags] & NSCommandKeyMask)
keysPressed = [NSString stringWithFormat:@"%@?", keysPressed];
keysPressed = [NSString stringWithFormat:@"%@%@", keysPressed, [[event charactersIgnoringModifiers] uppercaseString]];
NSLog(@”%@”, keysPressed);
if ([event keyCode] == 103) //F11
[[NSNotificationCenter defaultCenter] postNotificationName:@”myNotification” object:nil];
}];
20:54 on November 10th, 2009
Mkay
Now i found another error in my code.
When you add local event monitor – event handler block returns NSEvent object so you can modify it. In case of global events – your handler returns void since you can’t modify global events. This might be the cause of your beep.
Ergo, you should change your code from this: [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
to this: [NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^{
I guess i should reread my post a few times before posting it ,)
21:22 on November 10th, 2009
The API does confirm that addGlobalMonitorForEventsMatchingMask returns void, however, we still need to include (NSEvent *event) because it is the parameter, without it you are not able to access the event to get the modifierFlags or keyCode.
+ (id)addGlobalMonitorForEventsMatchingMask:(NSEventMask)mask handler:(void (^)(NSEvent*))block
The weird thing is that my program does respond to the hotkey, it just also makes the alert sound…it’s like its not telling the system that it responsible for handling this key press.
21:51 on November 10th, 2009
You’re right again. I created test app with your code and it works nicely for me. The app makes beep only when it’s active. It happens because global monitor events does not route from your own app. Since you added event handler for any NSKeyDown event – it will make beeps when it’s active. On any key press, that is.
22:49 on November 10th, 2009
I see. My app is a background app so it shouldn’t ever be active :(
22:43 on January 25th, 2010
Are you sure this works? Whenever my shortcuts includes pressing the spacebar and I’m currently in the Finder, my event handling is done, but also Quicklook is shown.
I fear one has to use Carbon to really achieve the desired effect…
13:18 on January 26th, 2010
Alice – since you can’t modify global event – it gets passed further, hence triggering the quick look shortcut.