This was initially going to be a small part of a different blog post, but somewhere along the lines I realized that this “little” part of the other post was easily large enough to be it’s own post. This is a pretty cool topic in and of itself. The post it was going to be a part of is Making the Magic Trackpad Work
In OS X there’s a pretty cool piece of magic called the Event Tap. It is essentially what it sounds like. When an event is going to happen, OS X will go through everything in the event tap to allow those handlers to do something about it.
It’s really useful if you’re trying to make global shortcuts or event handlers that you don’t want the OS to muck around with. For example, in Windows you can press Win+L to lock your computer. But in OS X, you can’t. (what the hell, Apple) So if you wanted to make a shortcut to turn on the screensaver, or put the computer to sleep or do something when you press a keyboard shortcut, you have to rely on a pretty horrible method.
You have to
- write an apple script using automator
- save it as a service
- go into System Preferences -> Keyboard -> Shortcuts
- poke around until you finally find your service
- assign a keyboard shortcut to your service
And after all that, you still only have a half working keyboard shortcut. There are some keyboard combinations you just can’t use, like alt+L, and if an application has a keyboard shortcut that conflicts with yours, good luck. So what you’re left with is a shortcut that doesn’t even work half the time. But if you want some information on how to actually make one of these shortcuts, here’s a good set of instructions.
With the event tap, you have access to the events before most things. So if you want to create a keyboard shortcut that always works, just write yourself a cocoa app and hook into the event loop. Or instead, use spark.app, then you don’t need to know any of this stuff (I know the website looks a little ghetto, but It’s the best for keyboard shortcuts).
Unfortunately the event tap is a ton of core whatever frameworks, so you’ll have to deal with quite a bit more C than Objective-C, but it’s not terrible. Write yourself something like this.
CFMachPortRef eventTap = CGEventTapCreate(kCGHIDEventTap, kCGHeadInsertEventTap, kCGEventTapOptionDefault, kCGEventMaskForAllEvents, handleCGEvent, (__bridge void *)(self));
CFRunLoopSourceRef runLoopSource = CFMachPortCreateRunLoopSource(kCFAllocatorDefault, eventTap, 0);
CFRunLoopAddSource(CFRunLoopGetMain(), runLoopSource, kCFRunLoopCommonModes);
CGEventRef handleCGEvent(CGEventTapProxy proxy, CGEventType type, CGEventRef eventRef, void *refcon)
We first create our event tap with CGEventTapCreate, with a bunch of parameters
- Which event tap to use
- HID will give you the Human Interface Device event tap. This is exactly what we want.
- There are also options for Session and Annotated Session, I have no idea what these are
- Where in the event tap to stick ourselves
- HeadInsert puts us at the beginning, TailAppend puts us at the end. Keep in mind that other apps can put themselves at the beginning after us. So if you put yourself at the head, you’ve got a pretty safe bet at getting all events, but not a guarantee.
- Active or Passive
- Default will let you change events in the event tap
- ListenOnly is great if you don’t want to change anything, you just want to know that something happened
- What kind of events to listen for
- You can create your own mask with all sorts of kCGEvent enums
- A function to execute
- What object to execute the function on
So the above chunk will create an event tap for human interface devices, its at the beginning of the event tap, it can change the events as they happen, it receives all events, and it uses the handleCGEvent function to process the events. And handleCGEvent just returns the event unmodified. If handleCGEvent were to return NULL , then you would effectively block any events from coming through the event tap until your app stops, or crashes. Remember, with great knowledge comes the ability to screw yourself over.
After creating an event tap, the rest is boiler plate code to get added to the run loop, and enable your tap.
So if you wanted to do some cool keyboard shortcuts (or even mouse shortcuts), you could add yourself to the event tap, then you just need to check and see if the events coming through fit some criteria, and if they do, execute your action and return NULL to stop the event from continuing on to other applications (unless you want the event to continue through).