Program Global Hotkeys in Cocoa Easily
When I first started to program QuickTunes 2, the number 1 feature I wanted to add was global hotkeys. I Googled it, I asked in chat rooms, I asked in forums. I never got a solid answer that worked for Panther or Tiger. So I decided to go about and use the various sources online and dig through the Carbon docs. So I now want to explain to you in a simplified version how to make multiple global hotkeys for your program.
What’s a Global Hotkey?
A global hotkey is a key combination that can be pressed whenever a user wants. Your app does not have to be the foreground app; it just needs to be running. When the keys are pressed, your application will receive an event, and then you can carry out a method. I use this in QuickTunes to allow global access to iTunes play/pause commands. Apple uses this in the command-space hotkey to invoke Spotlight on the new Mac OS X Tiger. Other examples would be command-shift 4, for taking screenshots (Mac OS 10.3 and 10.4).
What’s on the Agenda?
- Register our hotkey
- Register a second hotkey
- Make them customizable
Register our First Hotkey
Let me first set out the scene. I assume you are working on a already designed Cocoa application. I also assume that you are using Objective-C. This is important because we are using C code in our hotkeys section. Objective-C is a subset of C so C code will still work. To start off, import the carbon framework by selecting your project and then going to the menu item “Project.” From there click Add to Project and select /System/Library/Frameworks/Carbon.framework. Click Add.
Open up the .m file you wish to add the hotkeys to. I recommend putting it into your apps main controller class, but you can choose which class works best. We need to set up the code that will make our hotkeys. Move to the awakeFromNib: method in your class. If it does not already exist, create it. Now place this code in their.
1 2 3 4 5 6 | //Register the Hotkeys EventHotKeyRef gMyHotKeyRef; EventHotKeyID gMyHotKeyID; EventTypeSpec eventType; eventType.eventClass=kEventClassKeyboard; eventType.eventKind=kEventHotKeyPressed; |
This just creates some variables that will store some basic info about our hotkeys. EventTypeSpec is a struct and we’ve set its eventClass to the kEventClassKeyboard and its eventKind variable to kEventHotKeyPressed. There are different options, but if you are making a global hotkey, use these.
Next we want to install a handler so that our application can accept these events.
1 | InstallApplicationEventHandler(&MyHotKeyHandler,1,&eventType,NULL,NULL); |
This is a very basic method. The first parameter will be a reference to a method somewhere in your class. We will write that later. Leave the next parameter as a 1. The next parameter is our eventType variable we have already declared. The next parameter I have put to NULL, but you can pass any object in there. The object will then be available to the method that is called when the hotkey is pressed. This is very useful, and I will explain how to use it properly in the later sections. And finally, pass in NULL to the last parameter.
Now lets create an actual hotkey and get it working. Type this code next.
1 2 | gMyHotKeyID.signature='htk1'; gMyHotKeyID.id=1; |
The first line is a string for the name of the hotkey. Name it whatever you like. This is for easy access and recognition. The second line gives the hotkey an id. Give all your hotkeys different ids. I simply use 1,2,3 etc.
Our next section will actually register the hotkey itself.
1 2 | RegisterEventHotKey(49, cmdKey+optionKey, gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); |
This is the meat of the program, so I will detail how it all works. RegisterEventHotKey will do all the work to create the actual hotkey itself, and yes you can create multiple ones, but more on that later. The first parameter I passed, is 49. Now this is going to be different based on what your hotkey will actually be. 49 represents the space bar. The advantage of using this format is that although the ASCII value of some keys might change when you switch from English to Spanish, the keyboard reference number does not. Now I looked around on the net trying to figure out what keys I could use, but it was very difficult until I found an app called AsyncKeys (The file might be down at that location. I have hosted a copy on my server. This is solely for convenience. Get it here). Download this program and install it. When it opens just click the keys that you want and it will return the ASCII values as well as the Keyboard Reference Number. You can then take that keyboard ref number and place it in the place of 49 above. The next parameter is for the modifier keys of your hotkeys. Apple decided to require a hotkey to have at least one modifier key like control or command, in order to prevent key loggers. These C constants represent numbers, but you should still use the constants for future versions of OS X. The possible constants are: cmdKey, shiftKey, optionKey, and controlKey. If you wish to have multiple modifiers like my example just add the constants together. The third parameter is the hotkey name and number we just defined. All other parameters should stay as they are listed above.
The final thing we need is our method that will be called once you press the hotkey. Here is the code for that:
1 2 3 4 5 6 | OSStatus MyHotKeyHandler(EventHandlerCallRef nextHandler,EventRef theEvent, void *userData) { //Do something once the key is pressed return noErr; } |
The above method should be added somewhere in the same class file. It will be executed once your hotkey is pressed. Fill in the code necessary. Build and run your app, you should be able to press your hotkey and your function will activate whether or not your app is the front most.
Register our Second Hotkey
This section is very easy considering that we already have working hotkeys. To add a second (and as many others as you want) just add a few more line of code. This section should go right after the first RegisterEventHotkey method.
1 2 3 4 | gMyHotKeyID.signature='htk2'; gMyHotKeyID.id=2; RegisterEventHotKey(124, cmdKey+optionKey, gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); |
This is pretty much exactly the same except for 3 things. The hotkey has a different signature, id, and different button (in this case the right arrow key).
The final change will be inside the function we wrote to execute after the key is pressed: MyHotKeyHandler.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | OSStatus MyHotKeyHandler(EventHandlerCallRef nextHandler,EventRef theEvent, void *userData) { EventHotKeyID hkCom; GetEventParameter(theEvent,kEventParamDirectObject,typeEventHotKeyID,NULL, sizeof(hkCom),NULL,&hkCom); int l = hkCom.id; switch (l) { case 1: //do something break; case 2: //do something break; } return noErr; } |
This basically adds a few things. First off, we have one major difficulty. We have multiple hotkeys, but they are only calling one function. This is how we solve it. We create a EventHotKeyID called hkCom. We then call the function GetEventParameter which will help give us details about the specific event that just occurred. We then take the id variable from our event, and that means we know which hotkey was pressed. In this case, we have 2 hotkeys with ids of 1 and 2. In the integer variable l, we now have this value. We do a simple switch statement and then can execute different code based on which key was pressed. Very easy, except that took along time to figure out. An extra side note. I for one, have methods in other classes I would like to call once these hotkeys have been pressed. To solve this, pass in an object at the second to last parameter in InstallApplicationEventHandler. Then one you are in your case statement, you can do: [userData runMethod:somevalue].
Make them customizable
Now we get to the difficult part of the project, allowing the user to choose which hotkeys they want for the global hotkeys. This is how I do it. In our init: method I set some User Defaults.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | NSMutableDictionary *userDefaultsValuesDict = [NSMutableDictionary dictionary]; [userDefaultsValuesDict setObject:[NSNumber numberWithInt:49] forKey:@"hotkeyCodePlay"]; [userDefaultsValuesDict setObject:[NSNumber numberWithInt:cmdKey+optionKey] forKey:@"hotkeyModifiersPlay"]; [userDefaultsValuesDict setObject:[NSNumber numberWithInt:124] forKey:@"hotkeyCodeNext"]; [userDefaultsValuesDict setObject:[NSNumber numberWithInt:cmdKey+optionKey] forKey:@"hotkeyModifiersNext"]; [[NSUserDefaults standardUserDefaults] registerDefaults: userDefaultsValuesDict]; //Register the defaults [[NSUserDefaults standardUserDefaults] synchronize]; //And sync them |
What this does is create the default values for the two hotkeys: cmd-option space (49) and cmd-option right arrow (124). Hopefully you are familiar with user defaults and understand how this all works. Our next goal is to rewrite parts of our code to allow for the user’s choice to be inserted as the keyboard reference number rather than our specific choices. Go to the section where we were calling RegisterEventHotKey. Replace your code with:
1 2 3 4 5 6 7 8 9 | gMyHotKeyID.signature='htk1'; gMyHotKeyID.id=1; if([[NSUserDefaults standardUserDefaults] integerForKey:@"hotkeyCodePlay"] !=-999) { RegisterEventHotKey([[NSUserDefaults standardUserDefaults] integerForKey: @"hotkeyCodePlay"], [[NSUserDefaults standardUserDefaults] integerForKey: @"hotkeyModifiersPlay"], gMyHotKeyID, GetApplicationEventTarget(), 0, &gMyHotKeyRef); } |
The first two lines are the same as before. The third line is an if statement that checks to make sure that the user wants us to have a global hotkey function for a specific method. I always give the users a choice as to whether have a hotkey exist. If the user does not, then I set the preferences value to –999. This if just checks to make sure that our user wants this hotkey and then moves into the actual declaration. As you can tell, we’ve added a little more code. This just pulls from NSUserDefaults the values the user has chosen instead of the 49 and 124.
Now you can create a simple preferences box like in QuickTunes that allows your users to pick the hotkeys themselves. Simply link up those values to the NSUserDefaults page and you are done.
Conclusion
I hope this was a very helpful tutorial. I wanted to give enough detail that you can figure out what’s going on for yourself but still not overwhelm you with code. The last part where you make the actual preference box may be overwhelming to some, it actually does take some smart coding to make it work perfectly. I am going to leave that out of this tutorial and post it in an upcoming tutorial so as to not overwhelm anyone with too much content. Please feel free to leave comments about the tutorial on this blog, on digg, or email me here.
11 Replies
Anonymous on 1/4/2006 at 19:49Will the hotkey keep working when playing a game (fullscreen) ?
Jesper on 6/24/2006 at 07:46Remember to have #include in your header file.
Jerome on 8/25/2006 at 03:50Yeah, this rocks !
Thanks a lot for this tutorial, it completly fullfilled my needs !
A simple info to help futur visitors :
in a c function, even when embedded in an objectif-c class, the self variable is not available. Therefor, you may have to pass it as the 4th parameter to the InstallApplicationEventHandler function, to get it back as userData in you event handler function.
Global thanks for you work Dustin !
Chris Forsythe on 10/5/2006 at 17:36
Lora on 12/7/2006 at 05:07Hello, Dustin!
I’m really enjoy your post about global hot key.
Thank you very much!
Your post helps me with my job.
Stepan on 12/16/2006 at 14:50Thank you very much! I’ve found all I wanted!
Charlie Monroe on 5/13/2007 at 02:33Hey, cool tutorial! Thanks a lot!
To Jerome:
You either pass (void*)self as a parameter, or (much simpler) you use notifications. This way all parts of your project can receive the hot key notif.
Cheers,
Charlie
Justin on 5/30/2007 at 20:19First great write up, EXACTLY what I needed.
Here’s a question: how to catch/trigger when the button being held down?
Cocoa is for You, Indie Developers: Taste the Chocolate » Dustin Bachrach Blog on 7/24/2007 at 18:57[...] their features into it. There are still certain things that can be only done in C. For instance, my tutorial on how to do global hotkeys utilizes a Carbon API. Since we’re using Objective-C, it’s [...]
Phil on 10/1/2007 at 00:19Objective-C is a _superset_ of C. ;)
Bryan on 2/8/2008 at 09:59Thank you very much for writing this guide. I’m just starting with Cocoa (old hand with MFC and Motif though…) and I was not happy when I found out that Cocoa did not have global hotkeys. I really didn’t want to learn Carbon at all. Your guide sure saved me a lot of stress and time.
Thanks again!



