Digging into Apple's hot-key mechanism

Mac Secrets Before Christmas, I explained how to use an undocumented class within PreferencePanes.framework to easily convert key codes and modifiers into human readable string representations.

This time, I make good on my promise to describe the global hot-key mechanism used by Apple software such as Universal Access, Expose, and Dashboard.

For Cocoa developers, a number of different open source hot-key implementations can be found on the internet, one of the most popular being a set of classes developed by (I think!) Quentin Carnicelli. Type "KeyCombo.m" into Google, and you'll soon find an example.

I was never really happy with this stuff, partly because you end up adding several extra classes and source files to your application. It seemed unnecessarily complex to me. In the end, I wrote my own implementation by sub-classing NSApplication and adding just a few methods to that.

However, what had always intrigued me was the built-in hot-key mechanism used by Universal Access, Expose, and so on. Two months ago, I hadn't figured out enough of the details to make this useful, but now things are a lot clearer. The real hot-key mechanism appears to be implemented at a very low-level, probably inside the window server process. Code inside CoreGraphics.framework implements a symbolic hot-key API, which communicates directly with the windows server through Mach IPC calls.

Why "symbolic"? As I mentioned last time, Apple's global hot keys assign a unique, system-wide number to each hot key. Thus, the number eight identifies the hot key that moves focus to the Dock (normally Control-F3). The user might change the actual key-code and modifiers used for the hot-key, but it will always be identified as symbolic hot key eight within the operating system.

Suppose we want to use hot-key number 144, which in Tiger and Leopard isn't currently assigned or enabled. First of all, we'll want to enable the hot key as below: This tells the windows server that the hot key is in use.

CGSSetSymbolicHotKeyEnabled (144, YES);

Next, tell the window server to send notifications for this hot key to our application:

CGSSetSymbolicHotKey (CGSMainConnectionID(), 999, 144);

You may already be familiar with CGSMainConnectionID. If not, suffice to say that it returns a connection ID used to communicate between your application and the window server. This is needed for the call. The second parameter provides an internal, application-specific identifier by which the hot key should be known. We'll see how this works in just a moment.

By default, symbolic hot key 144 corresponds to Control-Option-1 ("1" on the number keypad). Once you've executed the above two statements, the Windows server will start sending events to your app anytime this key combination is pressed. These events can be easily intercepted by sub-classing NSApplication and overriding the sendEvent method as shown below:

There are a couple of things to note here. First, the hot-key events are received as NSSystemDefined events, not as regular key-up or key-down events. Second, notice the use of the event's subtype field in the above code: A value of six indicates that the hot key has been pressed, whereas nine would indicate that it's been released.

You'll also see that we check the value of the data1 field. This should correspond to the arbitrary value (999) that we used earlier in the call to CGSSetSymbolicHotKey. Because sendEvent: never gets to "see" the symbolic hot-key number (144 in this case), we can use the data1 field to easily identify, which hot key has been pressed. This is obviously essential for an application that uses more than one.

Since this value is completely arbitrary, you could get sneaky and store something like an NSInvocation instance into the data1 field. That way, the hot key processing code inside sendEvent: could be pretty much self contained and could invoke your target method without needing to know about any other classes in your app.

Proceed with caution: undocumented calls can change all these

Cool though this stuff is, it wouldn't be a great deal of use if you couldn't actually change the key code and modifier mask of the hot key or keys you're using. This can be done through a couple of calls as shown below:

CGSGetSymbolicHotKeyValue
CGSSetSymbolicHotKeyValue

For the full function prototypes, consult the SymbolicKeys.h file included with this month's project, below. These routines allow you to interrogate the current key combination and change it to something as specified by the user of your application. Note that when calling CGSSetSymbolicHotKeyValue, you should generally use 0xffff for the event flags parameter.

But what about persistence, I hear you say. The Core Graphics routines described above only affect OS X's current state. To persistently change a symbolic hot-key, you'll need to modify the dictionary in the "com.apple.symbolichotkeys" domain. A small program showing how to access this dictionary was included in the earlier article.