Cocoa is touted as a revolutionary application framework which wraps the miracle technologies of 1988 around the miracle technologies of 1984 while they both sit on top of the miracle technologies of the 1960s, and somehow this unholy three-way circlejerk promises to make your life as a developer easier. In practice, it's frequently clumsy, rigid and ultimately too immature for any project that's going to try to do anything genuinely interesting in its UI, and by "anything genuinely interesting" I mean "anything that Apple hasn't already done so many times over that they decided to factor the code out into a watered down framework fit for public consumption" (brushed metal windows anyone?). Sure, you can build a fully featured QuickTime movie player or rich text editor in two lines of code just like the keynote speaker did, but how many QuickTime movie players and rich text editors do you think that keynote speaker would have to write to pay for even half of the energy drinks, vegan cookies, seratonin reuptake inhibitors, mid-life crisis sports coupes and vintage Skinny Puppy t-shirts he sweats through in a given week? Probably like a million.

For every handy-dandy time-saving feature of Cocoa (90% of which are all in InterfaceBuilder) there are at least 20 time-destroying anti-features lurking behind the scenes waiting to cause you grief (and that's not including the RSI inducing bracket syntax of Objective-C itself). I cannot recall a Cocoa project I was involved with where I did not slam into a brick wall after weeks worth of steady progress only to be told by the powers that be "Oh, you can't do that. File a Radar bug and maybe we'll think about putting it in the next release if we can manage to remember who the hell you are." New releases have indeed brought new features (it only took 5 years for us to get QTKit!), but Cocoa still lags behind the rest of OS X's APIs like a battered wife at a monster truck rally. Want to grab the underlying HIView of a Cocoa menu and use some of those fancy new features in Carbon? Can't do it. Want to fire off more than one UI thread to prevent drag operations from hanging your entire application while the data is processed in a tight, unyielding loop? Can't do it. Want complete access to the events which triggered the sending of a message to one of your action handlers? Well you couldn't do that either until Tiger came along.

The Simplest Example I Can Think Of

Case in point: say you have a Cocoa menu all wired up by the book and you want to handle item selections according to the state of the modifier keys. This is simple if you want to require your users to hold the modifier key down before they even click on the menu as the mouse down event is still considered the "current" event by your NSApplication instance when it invokes your action handler. Unfortunately, the mouse up event that actually cues the item's selection has already been handled and dequeued by the event tracking loop that started up when the menu was clicked, so you lose the ability to let the user control his or her selection the way you might like them to.

Now there very well may be some means of convincing the NSEventLoop to keep these mouse up suckers in the queue, but I couldn't find any solution that lived up to my expectations sufficiently until Mr. Amit Singh (whose book is wonderful and should be purchased by everyone in the world) introduced me to the wonders of 10.4's event taps, which are essentially the replacement for jGNE filters we've all been waiting for.

I'm no expert on the subject, so I'll leave the nitty gritty details of event tap mechanics as an exercise for the reader since I'd probably get them wrong anyway. What I will do is show you how easy it is to pick up and even modify events before they ever get anywhere near Cocoa without having to throw all your work to the dogs and switch everything to straight Carbon.

Find a place in your app where you can run some startup code—the +initialize method of an application controller object would be good—and plug this on in:

ProcessSerialNumber psn; if ( GetCurrentProcess(&psn) == noErr ) { CFMachPortRef eventTap = CGEventTapCreateForPSN( &psn, kCGHeadInsertEventTap, kCGEventTapOptionListenOnly, 1 << kCGEventLeftMouseUp, MyEventTap, nil ); if ( eventTap ) { CFRunLoopSourceRef source = CFMachPortCreateRunLoopSource( kCFAllocatorDefault, eventTap, 0 ); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); CGEventTapEnable(eventTap, true); } }

Believe it or not, I'm not actually interested in hearing about how you learned to put your curly braces in different locations or indent differently than I do at CS Clown College. Surely there's some open source automated code formatting utility out there which will live up to the rigorous demands of your IRC nerd rage in much less time than it would take to email me about it.

That basically puts a "read only" tap on all mouse up events coming in to this process. We've designated a callback function to handle these events which is defined as follows:

CGEventRef MyEventTap( CGEventTapProxy proxy, CGEventType type, CGEventRef event, void *refcon) { if (type == kCGEventLeftMouseUp) { gMouseUpModifiers = CGEventGetFlags(event); } return event; }

We could easily do something incredibly fancy here, including performing some kind of hot-rod modification to the event before passing it along or routing it to a different handler entirely, but our task is a simple one and all we really need to do is record the state of the modifier keys when the mouse is released so our action method can branch appropriately. I'm simply stashing everything into a global for simplicity's sake, but if the namespace pollution is simply too much for you to bear, you can do something useful with the refcon instead.

- (void) myActionMethod: (id) sender { if ( gMouseUpModifiers & kCGEventFlagMaskCommand ) { //Do something } else { //Do something else entirely } }

Ugly? Yes. I once inherited a project that had over 60 different calls to WaitNextEvent in it, so I know all too well how horrible decentralized event processing can become. But as long as you use it wisely this is really no worse than the garbage Cocoa makes you do to calculate the bounding box of a string to be drawn on the screen or execute a shell command whose output you value these days. I can think of at least a dozen different ways this technique could have helped me tremendously back in the days before iPods (could probably even thread drag operations this way now that I think about it), so I can live with it. Ugly code is more fun to write, just don't let your friends see you doing it. Why do you think the AppKit sources are kept private? It certainly ain't because they're full of magical mystery algorithms that would bankrupt Apple if their secrets ever got out.

It's also worth noting that Apple's Quartz development mailing list is by far the best one the company has to offer, probably due in no small part to the lack of Quinn! The Douche-imo's involvement. Unlike say, Gnaegy's ColorSync list, it is populated by active, knowledgeable engineers who answer questions quickly and directly without getting all butt-hurt when somebody finds a bug. I'm beginning to think that the Quartz guys are really the only ones who give a shit anymore.