Inside Contextual Menu Items, Part 2
Pages: 1, 2
Handling Selections
Once the menu is built and displayed, the user will be able to interact with
it and pick an item from it. If one of our menu items is selected, the system
will call the function SampleCMPlugInHandleSelection and pass it
three parameters:
thisInstance: This, again, is a pointer to the currently executing instance of the CMI.inContext: As before, this is a pointer to anAEDescstructure. In fact, it's the same information that theSampleCMPlugInExamineContextreceived when it was called. Getting this information twice allows your CMI to act on the selection without having to remember it.inCommandID: This is an integer containing the ID value of the menu item that the user selected.
In the case of SampleCMPlugIn, nothing much happens when a menu item is selected.
(It simply writes out some debugging information, which you can view in the
Console application.) However, when writing your own CMIs, this is the place
where you would carry out your custom actions based on the ID that you get in
the inCommandID parameter.
The Glue That Holds it Together
At this point, you might be wondering what's so special about the SampleCMPlugInExamineContext
and SampleCMPlugInHandleSelection functions. Well, as has been
pointed out in another
article here on macdevcenter.com,
Mac OS X implements Microsoft's COM architecture as part of the Core Foundation
framework. The main reason for this is to support, you guessed it, plugins,
which is exactly what CMIs are.
These functions are simply implementations of two functions (generically known
as ExamineContext and HandleSelection) required by
this particular type of COM interface. A COM discussion is a bit beyond what
we're talking about here, but if you would like a better understanding of how
a CMI uses COM to interface with Mac OS X, start with a look at the SampleCMPlugInFactory
function on line 279. (We'll come back to the SampleCMPlugInFactory
function in just a bit.)
A Custom CMI
Now that we have an overview of what CMIs are and how one actually works, let's take that information and create a custom CMI that allows the user to gracefully quit any open application. Like just about every other Mac OS X CMI, we'll use the SampleCMPlugIn as our starting point. Customizing the SampleCMPlugIn project is a relatively simple, but lengthy, process, so let's look at each step individually. (Note that many of these steps could be combined with clever use of Xcode's Find and Replace functionality, but the point here is to understand everything that must be done, so we'll look at every step required.)
- Duplicate the SampleCMPlugin folder and rename it QuitCMI. Open the QuitCMI folder.
- Rename the SampleCMPlugin.pbproj file to QuitCMI.pbproj.
- Open the build folder and delete its contents.
- In the build folder, create a new folder called QuitCMI.plugin.
- Close the build folder and open the QuitCMI.pbproj file with Xcode.
- In Xcode (not the Finder!), open the "Implementation Files" group and rename the SampleCMPlugIn.c file to QuitCMI.c. (If you rename the file in the Finder, Xcode will lose track of it.)
- Open the project group (it should still be named "SampleCMPlugin" at this point) and open the "Resources" subgroup. Double-click the InfoPlist.strings file. In this file, change each occurrence of "SampleCMPlugIn" to "QuitCMI". (Edit the copyright notices as you see fit.)
- Open the "Targets" group and right-click on the "SampleCMPlugIn" target. Rename the target to "QuitCMI".
- At this point, if the Target information editor isn't visible in the project window, click the Show Editor button (it's to the left of the magnifying glass in the project window), or double-click the target to bring up the editor in its own window.
- In the editor, open the "Settings" section, and then open the
"Simple View" section. Change the product name to "QuitCMI".

- Scroll down a bit to the GCC Compiler Settings section and notice that the flag to generate debugging signals is turned on. You don't have to turn it off now, but you'll definitely want to turn it off before you release the final version of your CMI.
- Now open the "Info.plist Entries" section
of the editor. Under Basic Information, change the identifier from
com.apple.SampleCMPlugIntocom.EGOSystems.QuitCMI. (You would, of course, use your own company and product name here.) - Change the version number to "1.0".
- Under Display Information, change the Display Name to "QuitCMI".
- For the "Version Displayed in Finder info" field, enter whatever
you want to appear when the user performs a "Get Info" operation
on the CMI in the Finder.

- At the bottom of the "Info.plist Entries" section, find and click
on the "Expert View" section. This will bring up a simple pList
editor that shows the contents of Info.plist in a much more "raw"
format than the Simple View we've been working with. Scroll down to the bottom
of the pList entries and completely expand the
CFPlugInFactoriesandCFPlugInTypesentries. When you do, you should see something like this (note that I've highlighted these entries in the screen shot):
The
CFPlugInTypesentry is an array that does two things: it identifies the type of plugin this is and supplies the UUID of the plugin. In this case, it's a CMI (identified by the Apple-defined value starting with "2F6522E9-") that has a UUID that starts with "3487BB5A-". TheCFPlugInFactoriesentry ties that UUID to a particular function in the CMI that will instantiate an instance of the CMI (which is why they call it a "factory.") In this case that function is theSampleCMPluginFactoryfunction. (Which takes us back to our earlier discussion of COM and how theSampleCMPlugInExamineContextandSampleCMPlugInHandleSelectionfunctions get invoked.) - We need to change these values to represent our new CMI. So the first order
of business is to generate a new UUID. For that, fire up UUID Generator and
paste the results into a new TextEdit document so that you can get to them
later. When I ran UUID Generator, this is what I saw:
And this is what ended up in my TextEdit document:
F4054EF0-A9A4-11D8-BAE7-000393D128F2
{0xF4,0x05,0x4E,0xF0,0xA9,0xA4,0x11,0xD8,0xBA,0xE7,0x00,0x03, 0x93,0xD1,0x28,0xF2}
#define YOUR_CMPLUGINFACTORYID ( CFUUIDGetConstantUUIDWithBytes( NULL,0xF4,0x05,0x4E,0xF0,0xA9,0xA4,0x11,0xD8,0xBA,0xE7,0x00,0x03, 0x93,0xD1,0x28,0xF2 ) )
/*F4054EF0-A9A4-11D8-BAE7-000393D128F2*/ - The first line of this is exactly what's needed for
CFPlugInTypesandCFPlugInFactories. Simply copy this value into those fields and change the function name inCFPlugInFactoriesfromSampleCMPlugInFactorytoQuitCMIFactory, and you'll end up with this:
Once you verify that this is all correct, save the project and close the Target.
- Open the QuitCMI.c source code file and select the "Single File Find" command from the Find menu. In the "Find" field, type "SampleCMPlugin" and in the "Replace" field, type "QuitCMI". Click the "Replace All" button.
- Once that's complete, put "com.apple" in the "Find"
field and "com.EGOSystems" in the "Replace" field, and
click the "Replace All" button again. You should end up with line
724 looking like this:
CFStringRef bundleCFStringRef = CFSTR("com.EGOSystems.QuitCMI");(Remember that this is the line that loads our bundle resource, so we have to change it to reflect the new identifier we gave the bundle in step 12.) - Find the definition for
kQuitCMIFactoryIDand replace the old UUID with the one that you specified in theCFPlugInFactoriespList entry. When you finish, it should look something like this:
#define kQuitCMIFactoryID ( CFUUIDGetConstantUUIDWithBytes( NULL,0xF4,0x05,0x4E,0xF0,0xA9,0xA4,0x11,0xD8,0xBA,0xE7,0x00,0x03, 0x93,0xD1,0x28,0xF2 ) )(You might notice that the old value for
kSampleCMPlugInFactoryIDdoesn't match what was in the Info.plist file. This is an error in the SampleCMPlugin code.) - Finally (hurray!) look just below the
kQuitCMIFactoryIDdefinition and you'll see an enum definition. This definition (for a couple of contextual menu constants) has become redundant (these constants are now defined in the Menu.h header file) since SampleCMPlugIn was released, so comment it out. (If you leave it in, the code won't compile.)
At this point, save the project and build it. If all goes well, you can install and test it, and you should see something like this:
Whew! That's a lot of work just to change the name of the thing, isn't it?
Quit That!
With all that behind us, we can finally add our custom code to create our CMI. I certainly don't expect you to type all of this code, so I'm making the entire project available here. Download it and then follow along in the QuitCMI.c file as I go over the steps below:
- Every CMI will be somewhat different, so the first thing you need to do is figure out which additional headers your project will need. In this case, we'll need access to process information (to figure out which process are active and therefore, "quittable"), and we'll need to fiddle with Core Foundation (CF) style strings, so I added includes for Process.h, CFBase.h, and CFString.h. (Lines 16, 18 and 19.)
- Since I'll be juggling process, I also need some way to keep a list of
them in memory. Towards that end, I also created a new structure called
pInfo. (Lines 74 to 78.) - You'll also notice that there are several things missing from QuitCMI.c
that were in SampleCMPlugin.c. Namely:
- The
gNumCommandsvariable. I'll be using process numbers to identify my menu items, so this ID holder isn't needed. - The following functions are also gone:
Copy_CFStringRefToCString,CreateFileSubmenu,HandleFileSubmenu,CreateSampleSubMenu, andHandleSampleSubMenu. I'm not building lists of files or anything like that (and most of my string manipulation is of CF strings), so none of these functions is necessary. (Note, however, that theCreateFileSubmenufunction actually lives on as theCreateProcessSubmenufunction, discussed below.)
- The
- I've also changed the
AddCommandToAEDescListfunction so that it no longer adds ID values to the menu item strings that it stuffs intooutCommandPairs. (Lines 298 to 300.) - Finally, these three functions make up the "meat" of QuitCMI:
QuitCMIExamineContext: This is very similar toSampleCMPluginExamineContext. One difference is that since we are providing a "Quit" service, we don't really care about the context. All we want to do is build our list of processes and return that to the operating system. This is done by theCreateProcessSubmenufunction (discussed below). One other trick in this function is an example of how to determine the name of the host application in which the CMI was invoked. In this case, it's checking for the Finder, but it could be any application that you wish. Nothing is done with this information; I've just left it in as an example of how it's done.CreateProcessSubmenu: This function (a derivative of the oldCreateFileSubmenufunction) receives the name of our CMI (in a parameter namedtheSupercommandText) and uses it to build a menu item with a submenu that contains the names of all of our active applications. As mentioned before, the menu IDs that are used are the actual process IDs of each application. (You'll note that a simple bubble sort is used to sort the names of the applications alphabetically. Bubble sorts are slow for large numbers of items, but, hopefully, no one out there will have 1,000 applications open at any given time!) For the truly gory details of what's going on in this function, refer to Apple's online Interapplication Communication documentation.QuitCMIHandleSelection: This function receives the ID of the menu item that was selected (which, again, is the ID of the corresponding process) and then builds a "Quit" Apple Event (typekAEQuitApplication) and sends that to the selected process. That's all there is to it!
Final Tweaks
There are a few more things to do before the project is finished. Since SampleCMPlugIn was intended to be a comprehensive example, it comes complete with a lot of debugging statements that write information out to the console. To remove these, search for "DEBUGSTR" and "printf" and comment out each line that you find. (You should also go back and turn off debug code generation in the compiler as well.)
Finally, if you build the project as it stands at this point, you'll notice
that the menu item that appears in the contextual menu is named "QuitCMI".
While this is fine for an internal name, it would be better if the menu item
were simply named Quit. To make this change, return to the Expert View of the
Info.plist editor and change the CFBundleName from
"QuitCMI" to just "Quit". Then rebuild the project. When
you install and test it, you should see something like this:
That's It!
At first glance, it might seem that creating a CMI is a lot of work. Well, at first, it is!
However, once you get a couple of them under your belt, you'll find yourself breezing through the tedious bits more and more quickly. Once that's done, you can get right to creating your own "Why didn't Apple include this?" CMI.
I hope that you've enjoyed these two articles and found the information useful. I look forward to seeing your contextual menu items pop up when I right-click my mouse!
Steven Disbrow is the President of EGO Systems, Inc., a computer consulting firm in Chattanooga, TN.
Return to the Mac DevCenter
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 8 of 8.
-
Add an icon to contextual menu item
2006-02-01 23:48:55 piti118 [Reply | View]
-
Add an icon to contextual menu item
2006-02-02 07:14:51 EGOSysDiz [Reply | View]
Tee,
Well, the link you show (to the Folder Contents CMI) certainly seems to show that this can be done. Unfortunately, it's not something I've actually done at this point.
But, I think that the functions you mention here are a great place to start!
As for getting hold of the menuref... there's gotta be a way, but I don't know it off the top of my head. Anyone else know this?
-
Are the old Apple docs still useful?
2005-03-23 21:57:09 Matthew Russell |
[Reply | View]
Nice developer article...
As a fairly new Mac developer, I haven't waded too far out of the Cocoa frameworks, but have touched on a little bit of Carbon from time to time. I've run across the old Apple docs like the one you reference here about Interprocess communication, but sort of just tossed them to the side, because I figured any document that's dated "Jul 1996" must have been so many OS releases ago that it's no longer useful (especially given the big changes with OS X). As a general rule of thumb, is this the case, or not? Can I confidently learn relevant development info from the old docs that are dated pre 1999? I know they're out there for a reason, but thought it might have been for historical purposes or something. -
Are the old Apple docs still useful?
2005-03-25 12:14:33 EGOSysDiz [Reply | View]
(Un)fortunately, yes.
One of my main dissapointments with Mac OS X programming is how much Carbon knowledge is needed in order to create useful things like CMIs. This is slowly changing for the better, but until Apple kills it off completely, you'll need to ask yourself, "MUST I do this with Carbon?" way more often than you'd like.
Of course, now that OS X (and Carbon) has been around so long, it's doubtful that the Carbon API's will EVER really go away, but the day we can create CMI's in Cocoa will a day for great rejoicing. (If anyone already knows how to do this, let us know!)
-
Copy & Paste
2004-07-02 08:07:39 GlobalMouser [Reply | View]
Is it possible to write some code that will enable me to copy and paste text when renaming files in the Finder?
Why hasn't Apple included this functionality into the OS?
A Chupa Chup to the first person to make a plugin available for download! -
Copy & Paste
2004-07-02 20:36:36 EGOSysDiz [Reply | View]
Contextual Menu Items seem to be completely disabled when renaming a file in the Finder, so a CMI solution to this is pretty unlikely (at least in Panther, and I haven't seen Tiger, so I don't know about that...)
But, you CAN use Command-C and Command-V to copy and paste text from a filename while renaming.
-
text selection?
2004-06-13 10:22:48 edalytical [Reply | View]
Is there any way to get the current text selection from a application using a custom CMI? I have tried it, but I don't think it's supported. I can only get file selections in Finder. If it's not possible to get a text selection is there any way to invoke a service for the current application from within the CMI?
-
text selection?
2004-06-14 07:14:41 EGOSysDiz [Reply | View]
Well, supposedly, applications will report a "TEXT" type of selection to your CMI (if you look at the sample CMI source code from Apple, you can see code to trap this), but in practice, I've found that only a few applications do this. In fact, I haven't come accross a single Apple application that does, but all the Office v.X applications do! (Don't know about Office X 2004...)
As for invoking a selection from a CMI, I'm sure it's possible, but I've not had a chance to try it yet.
Steve Disbrow
EGO Systems, Inc.






Do you know how to add an icon to a contextual menu item? I strongly believe that there is a way to do that. There are two places I found in which it might suggest a way to do that.
1)In the attribute record there is a flag for disableIcon. But I do not know how to set icon for such a thing though.
2)In Apple menu manager reference, I found that there is a function called
OSErr SetMenuItemIconHandle (
MenuRef inMenu,
SInt16 inItem,
UInt8 inIconType,
Handle inIconHandle
);
The bad news is that I do know know how to get hold of menuref of the contextual menu.
I also saw that some program ex:http://www.naratt.com/FolderContents.html. Has icon attach to the menu.
Thank you very much in advance,
Tee