20111221

Developments

Since my previous post, I have changed employers twice. First to SMSI, but I didn't feel like I was doing meaningful work there. I now work for Amazon on the Kindle Team. I find that I prefer C++ and Objective-C to Java, but I am willing to admit that it could be due to lack of understanding of the inheritance model.

20110616

Unbundled Info.plist on 10.5 Leopard

We recently added a non-privileged, unbundled (stand-alone) helper tool to our main application bundle. At first it showed up in the Dock, but then we added LSUIElement to that application's Info.plist. Naturally, we embed that plist with -sectcreate __TEXT __info_plist Info_plist_file.
However, 10.5 did not respect this setting. It insisted on displaying a duplicate Dock tile for the main app -- same name and icon. We tried several variations, but the only thing we found that works on 10.5 is to make the helper application a Cocoa app bundle and include its Info.plist there. Only then is the LSUIElement preference respected.

20110103

App Distribution

Everyone wants to have users download a small .dmg file from their website. Opening the dmg pops up a window showing a pretty background, the application's icon, and an alias file to the /Applications directory.

Finder in 10.6 creates non-backward compatible .DS_Store files, so to get the positioning and background image, you need to create a dummy dmg by hand on 10.5, copy its .DS_Store, and save it for copying into your production dmg.

No-one wants their dmg to have a .Trashes directory, and no-one wants the mounted image to have free space -- it's just wasteful, and it feels unprofessional. The key to creating just such a dmg is to populate a local directory with everything you want in your dmg, use hdituil to makehybrid from it, and then convert to the desired format (probably UDBZ format with -imagekey bzip2-level=9 for maximum compression).

If you have an SLA that you want to display, you can unflatten, add the rtf SLAs using Rainer Brockerhoff's AddLicense tool, and then flatten the dmg again.

20100902

Google Chrome

Until now, I have been using Google Chrome web browser. I manually removed its Keystone updater launchd jobs and executable. However, I stopped using it today because it stopped showing me the uri of the webpage I was viewing. This is unforgiveable.
The browser is not supposed to guess that there is some information I don't want.

20100729

Installing a Helper Tool

If your application does something with/to files that the user wouldn't normally have access to, you can use IPC (inter-process communication) with an Elevated Helper Tool. But how do you install the Helper Tool?

Prior to OS X 10.6, you would have to use a Helper Tool Installer, which you would have to launch using AuthorizationExecuteWithPrivileges(...). The (authorized) Installer would also have to install a launchd plist and register the plist using launchctl.

On 10.6 "Snow Leopard", Apple has made this process easier with the Service Management APIs. There is one API now, SMJobBless, that takes your authorization and the name of the helper tool, and installs it. Here are some of the details of using it.

Both the application and the Helper Tool must be code signed. It isn't necessarily important who is the CA.

The Helper Tool must reside inside of your application bundle in the Contents/Library/LaunchServices directory. The name of the HelperTool executable file MUST be its launchd Label. This is usually a reverse-DNS label such as com.QuantumCheese.MyApp.HelperTool .

Furthermore, SMJobBless requires two custom sections in the __TEXT segment of your executable. The first of these is the info plist. This is a normal Info.plist file, customized for your Helper Tool. It must contain an array SMAuthorizedClients of code signing requirements for authorized clients. It is also a good idea to have a version string (the key is CFBundleVersion) and a bundle name (key CFBundleIdentifier) (probably the Label).
The second is the launchd plist. This is the same plist you would have written from the Installer executable; it ends up in /Library/LaunchDaemons. See man (5) launchd.plist for information on this file; it must include the Label, and it should not include Program or ProgramArguments keys (they will be ignored and replaced).

In the Xcode project settings for your helper tool's target, add Other Linker Flags of
-sectcreate __TEXT __info_plist /path/to/your/daemon's/info.plist
and
-sectcreate __TEXT __launchd_plist /path/to/your/daemon's/launchd.plist
Note that these two lines will be split into eight lines; make sure that each 4 stay together.

The documentation for SMJobBless says that the only domain currently supported is kSMDomainSystemLaunchd. Therefore, SMJobBless will take your Helper Tool, extract its __launchd_plist to /Library/LaunchDaemons/reverse-DNS-HelperTool.plist, and copy the Helper Tool to /Library/PrivilegedHelperTools/reverse-DNS-HelperTool. It automatically version checks, and the API returns true on successful upgrade or if the tool is already installed.

Experiments indicate that it will return true even if a newer version of the Helper Tool is installed. Therefore, if your IPC demands that your bundled version exactly matches the installed version, you will still need to use the Helper Tool Installer to downgrade.

Even though it is tempting, I recommend against putting an additional copy of the executable into the Contents/MacOSX folder. While doing so would let you use the CFBundleCopyAuxiliaryExecutableURL API to create a URL to the helper executable, it increases the size of your bundle, which is a bigger problem than finagling the helper executable's URL on 10.5.
Instead, I recommend appending the Contents/Library/LaunchServices/HelperTool path to the URL obtained from CFBundleCopyBundleURL(CFBundleGetMainbundle()).

20100602

Ways to Bork Your Code

Maybe I'll make this a series of posts: ways to screw up, big time.

Preprocessor macros are very handy, but very easy to abuse. One day in college, a few of us sat around trying to think of devious macros we could include in our projects that would mess up other contributors' code.
We tried #define 1 (-12), but it wouldn't compile.
We did succeed in #define main foo to eliminate the main function, but my personal favorite takes two lines:

#define if while
#define else


And of course, to be a good hygienic programmer, you'd want to include the accompanying #undefs. Please never use this.
I can just imagine some confused code successor looking at the file and asking, "Why does this file end with #undef if? Uhh....."

20100530

NSMenu popup location

NSMenu has a class method popUpContextMenu:withEvent:forView:, which I initially thought was the only way to cause a menu to pop-up where I wanted. In order to give it a useful event, I took the event I had and created a new one from it, with a different mouse location, to try to trick the menu's position. The goal is that the menu should normally appear under the control, with the left of the menu aligned to the left of the control. If there isn't enough room under the control, the menu should appear above the control and not obscure it from view. Similarly, if there isn't enough space to the right of the control for the entire menu to display, the menu should right-align to the control.
I had gotten the above/below positioning sorted out using the NSMenuDelegate method confinementRectForMenu:onScreen:, but I could not get the left/right alignment switch to work. If I would set the menu rect's origin to the left of the control, the upper-right of the menu would be there; and if I set it to the right of the control, the menu's upper-left would be there.
I put the menu to the left of the control entirely and then moved the x field of the origin over by the width of the menu. No good.
I tried moving it over incrementally over several test runs. It eventually became clear that I could not move the x coordinate of the menu's origin past 5/6 of the width of the control without the menu aligning its left to the left of the control.

I then discovered that NSMenu has an instance method popUpMenuPositioningItem:atLocation:inView:. This method had much better documentation, saying that I could pass nil for the item and the view, and then I could control the upper-left corner of the menu rect, and it would pop-up unattached to any other window. I tried this out, and it worked, with less code (no longer needed to fake an NSEvent).
I went back and used a nil view for the class method, and this also worked.

My recommendation is to use the class method, since it provides the flexibility to position an arbitrary item in the menu or the entire menu at a certain point, and it has much better documentation.