Warning: programmer nerdiness ahead.
Working on a Mac OS X app for a client. The product is being reworked as a “status item” (also known as “menu extras”, “menulets”, NSStatusItem, NSMenuExtra, and a host of other names), those handy little icons on the right-hand side of your menubar. While status items can be part of a larger application (OpenOffice does this for extensions management), many times they are simple stand-alone utilities. Do you use Dropbox? It’s all managed through that status item app. There’s a host of others out there, and I’m sure the right side of your menubar is decorated with more than a few useful ones.
But while they are apps, they have special behavior, because they have a limited UI. Notice these apps don’t show up in your Dock? Notice they don’t have a traditional menu bar (there’s no “File”, and “Edit”). This is because in the Info.plist for the app they have the “LSUIElement” flag set to true. This tells the OS to treat them a little differently. But yet, the apps can still have UI.
So in this app, there’s a Preferences window. I don’t use the mouse as much as I do the keyboard, so keyboard shortcuts matter to me. While testing, I would press “command-W” to close the window, and it wouldn’t work. Why not? I knew the traditional way was due to having a “Close” menu item with a “cmd-W” shortcut, but this is an agent app! there is no menubar! What to do?
I searched around for numerous approaches, like trying to intercept the keystrokes. But the trick is that the responder chain doesn’t really come into play here because the cmd-key is held down. There’s talk of NSApplication subclassing and intercepting events, but that just didn’t seem right. Plus, when I looked at the source code for other NSStatusItem-based apps out there, I saw they handled cmd-W just fine but had no special handlers. What gives?
And alas, Google turned up nothing, Stack Overflow was nothing other than false leads.
But I figured it out, and it was right under my nose the whole time.
When I converted the prouct to a .app, I deleted the menubar from the MainMenu.xib. It was a UI agent! it didn’t have a menubar so why have that in the xib, right?
Well, you still need one. Or at least, the menubar, while not shown, is still active and part of the command responder chain.
I recreated the MainMenu.xib from the “MainMenu” Xcode template. Now with a proper menubar, with a Close menu item that had a cmd-W shortcut and posted the -performClose: action… suddenly everything worked. No, the menubar was never displayed, but the keystroke worked.
While good, it also meant every other keystroke worked too! While the Preferences window was displayed, I could cmd-Q and quit the app. Not quite what I wanted, but made sense since because now the app was technically frontmost and responder, and all the cmd-keystrokes were passed up the command chain properly.
So there you go. In agent apps (LSUIElement: true) apps, you likely still need a menubar with proper commands to have cmd-keystroke behaviors respond correctly. But be mindful that you might want to edit your menus down to just the functionality you need.
Recorded here for Google, Bing, and whomever else to index, since I found nothing on the topic and maybe it’ll be useful to others. And in case it may be relevant, this is working on Mac OS X 10.9.4 with Xcode 5.1.1.