|Home » Learning Curve
OS X Menus 1
Smashing Cocoa for fun and profit part one: the Services menu.
|For these exercises you will need:|
- the ADC developers tools, specifically Interface Builder;
- a good file browser, such as this one, or a certain dexterity with the Finder or Terminal; and
- a bit of patience.
Services are a great thing, but wouldn't it be nice to be able to decide what's being offered - to customise the contents of the Services menu? It can be done - it's just that it's not immediately obvious how to go about it.
We're going to take one easy example - the Stickies application.
Every Cocoa application has an 'Info.plist' file in its 'Contents' subfolder. This property list file has basic configuration information for running the application.
In the event the application will be offering services it will also have the 'NSServices' key somewhere inside this file.
A lot of the data in the 'NSServices' section of Info.plist can be taken at literal value, but the presence of files named 'ServicesMenu.strings' in the localised subdirectories to 'Resources' will override this information, and in the event these files exist, any and all changes should be made in these files instead.
The 'strings' files have the added feature of supporting comments fields exactly as in C - '/* */' - so you can easily enclose old values in comments fields for easier reference when your work is to be undone.
There are two keys of interest in these files when it comes to services: NSKeyEquivalent and NSMenuItem. If you have any uncertainties about how property list files work, consult an online reference and get it down pat before you return here. And if you're still with us at this point, it's time to move on - we're going to start smashing the menus.
One of the irksome things about the OS X Services menu going back several generations now is that the author of Stickies decided, of all people, to tack a period ('.') onto the end of a menu item where no one else ever did.
This period is so prevalent it's even printed in David Pogue's missing manuals, yet no one at Apple has ever done anything about it. If you wait for someone to correct it, you might end up waiting forever. So now you're going to do it yourself instead.
Begin by navigating to your /Applications folder. Use either our Xfile or the Finder or the command line (Terminal), whichever seems most comfortable for you. You're going to have to do some behind the scenes spelunking here.
Find the Stickies.app folder in /Applications and go behind it. The folder immediately behind Stickies.app is Contents, and in Contents you will find a file called Info.plist. This is the first place you'll stop. This is the repository for all centralised configuration information for all Cocoa OS X applications.
Info.plist is a text file. It's constructed as an XML preferences file, but it's text all the same. Open this file with a text editor (and make sure you save your changes in the same plain text format).
Search for the string 'NSServices' in Info.plist. This is the actual Services menu configuration for the Stickies app.
Note the key 'NSKeyEquivalent'. This is something Stickies will be able to put on the menu as a keyboard shortcut. Keep this in mind.
Note next the key 'NSMenuItem'. This is a reference to the actual menu text. Obviously it's not the same as you see, as it says only 'Make Sticky', but it's the first place to start looking.
[The key 'NSMessage', FWIW, contains the actual code method your services invocation will call. The key 'NSSendTypes' indicates that Stickies will recognise both plain text and RTF-formatted text.]
Back on track: we now have the default identifier of the Stickies Services menu item ('Make Sticky') and so we can move on.
Remember that OS X is a localisable operating system: you can switch languages in it with the greatest of ease. Especially Apple are intent on offering as many languages as possible. The mechanism for this localisation is already built into the Cocoa frameworks - it's not always used, or used intelligently, but it's there.
From your current working directory (/Applications/Stickies.app/Contents) navigate further down to Resources/English.lproj. (If you are running a different language, you'll probably want to choose the language folder pertaining to you - then again, you might not have the 'period' problem with Stickies in such case. If so, just follow along.)
In English.lproj you will find a file called 'ServicesMenu.strings'. This is a Unicode text file and will open with either the Project Builder editor, the Xcode editor, TextEdit, or Rixedit - just make sure you save it with the same encoding.
The content of 'ServicesMenu.strings' is eminently self-explanatory. It's a 'strings' file. It's comparable to property list files, but has only strings, and its keys and values are separated (connected) by an equals sign and the statements these connections make are always ended with a C-statement semicolon. Strings files also observe C syntax for comments fields, as will be shown by the examples below.
// Services Menu content for Make Sticky
"Make Sticky" = "Make New Sticky Note.";
// Command key equivalent for Make Sticky in Services Menu
/* The above comment could have been written with these characters */
"Y" = "Y";
Remember: the NSMenuItem value was 'Make Sticky'. Its value - as set by this file - is 'Make New Sticky Note.' - and it most assuredly has the period at the end.
So go ahead - delete the period, save the file, and you're ready to regenerate your Services menu.
NeXTSTEP used to have a command line tool named 'make_services' which automatically regenerated the Services menu, but it's been removed in OS X.
Rixstep today have a comparable tool with the same name, but in lieu of that you'll have to either log out and log in again or reboot your computer.
When a user logs in to OS X or starts it up, the Services menu is regenerated - it's that 'crunching' sound you hear right after the system seems to go idle: it's searching far and wide for all available services on your mounted hard drives.
But whenever next you view your Services menu, you will notice the period at the end of the Stickies menu item is gone.
Services menu items can also have keyboard shortcuts, as evidenced by the contents of Info.plist. This isn't necessarily a good thing, as no service can ever know what keyboard shortcuts other services will attempt to lay claim to.
The smart service might think twice about usurping a keyboard shortcut for this very reason, and if OS X finds the same keyboard shortcut more than once, it will allot it only once.
So it might be interesting to remove the Stickies keyboard shortcut.
Refer back to Info.plist and the key 'NSKeyEquivalent'. Simply put the character 'x' in front and regenerate your Services menu again - and it's gone.
Other applications offering services fall for the same temptation. One is the Java Browser, another is Safari, which wants a lovely ⇧⌘L beside its menu item. Again, the trick is to go into the Safari package, open Info.plist, search for 'NSServices' and 'NSKeyEquivalent' inside that, and add an 'x'.
The Java Browser item will only appear when you have the developers tools installed; it is found in /Developer/Applications and the modus operandi for removing its keyboard shortcut is the same.
What happens if you want to remove Services menu items altogether? For starters, make backups of all the Info.plist files you plan to alter and devise a naming scheme so you know what file goes back where. Then delete the 'NSServices' key and its values or the portions thereof you no longer want around.
Note the structure of the Services menu information: it's all pairs of keys and values (the entire Info.plist is just a so-called 'dictionary' ('<dict></dict>') with all this information within.
Each Services menu item that an application can offer is enclosed in 'dict' tags, and these dictionaries are in turn enclosed in 'array' tags - they're an array of dictionaries, one for each menu item.
If you want to remove a Services menu item entirely, remove the entire dictionary for its menu item - all the way from '<dict>' and to and including the corresponding closing '</dict>' tag.
Services are a great thing, but you should be able to determine what services are going to be on your application menus. By checking behind the scenes for 'NSServices' keys in application Info.plist files you can modify their on-screen representation or remove them altogether.