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.

20100429

Apple and Adobe

My response to Steve Jobs' mostly well-written reasons for not allowing Flash on his mobile devices.

Jobs is spot-on that Flash is a closed, proprietary system. It renders video (and other content) in software, not hardware; he claims that all mobile devices decode H.264 in hardware, and can play 10 hours of H.264 video content vs. 5 hours of software-decoded video. I haven't tested his claim, but specialized hardware seems like it ought to do the job better.

The part that really bothers me is where he attacks Adobe's iPhone programming interface: "We know from painful experience that letting a third party layer of software come between the platform and the developer ultimately results in sub-standard apps and hinders the enhancement and progress of the platform."
The "progress of the platform" is nonsense. It is up to the developers to use whatever tools they chose to create the products they desire. It is not relevant to Apple what methods we, the developers, use to create our content.

Furthermore, Steve Jobs has no right to complain that Flash is a closed system when his App Store is even more restrictive. It would be better to have an Open app store and allow developers to use whatever tools we want!

Jobs complains that "[t]he third party may not adopt enhancements from one platform unless they are available on all of their supported platforms. Hence developers only have access to the lowest common denominator set of features. Again, we cannot accept an outcome where developers are blocked from using our innovations and enhancements because they are not available on our competitor’s platforms."
Again, this is better than Apple's practice of changing the APIs out from under developers. Their attitude is one of "We're so very glad that you used our APIs, but we've deprecated them all, so you'll have to rewrite your code, at least to have dynamic OS-testing," but they say it differently (from the article): "We want to continually enhance the platform so developers can create even more amazing, powerful, fun and useful applications. "
This is a terrible methodology, and it's a terrible way to treat programmers.

20100414

Creating a DMG with an icon and background image

[Posted with permission from my client.]

Our Release mode build is designed to package up the final app bundle into a dmg. We wanted to make a nice user experience, similar to the images of Google Chrome and Firefox, so we added an alias to /Applications and set the alias' name to " ".
We use an applescript to set the position and size of the icons in icon view, and also to set the background image. However, we could not figure out how to set the icon for the mounted volume in Finder's sidebar. I knew that it involed .VolumeIcon.icns, but that was insufficient.
After much searching, we realized that we had to set extended Finder attributes for the volume's root directory, but couldn't figure out how to do so from the shell / applescript. Finally, we found xattr, but there was no documentation to indicate what names or attributes to use. However, Google Chrome and Firefox both had the same set of 32 4-byte hex flags, so we simply copied them.

Solution: with read/write dmg mounted (and a disk name of VOLUME_NAME), use the following command from the Terminal (or shell script):

xattr -wx com.apple.FinderInfo 00000000000000000400FFFFFFFF000000000000000000000000000000000000 "/Volumes/${VOLUME_NAME}"


UPDATE:
There is a much more intuitive interface: SetFile -a C "/Volumes/${VOLUME_NAME}".

20100224

Specifying a port on IPv6

My colleagues at Javacool Software got a feature request today for an existing product, that it should run on a custom port, presumably due to the client's unusual network setup.
While doing that, we decided that it makes sense to support this feature for IPv6 as well. However, it turns out that specifying a port on IPv6 is much more annoying that on v4. In v4, the format is like so: dot-separated-IP:port. In v6, like so: [colon-separated-IP]:port.

20100212

Source Control

Until now, the CFRaii library has been available from my website. Now it is also publicly available through github. This is a trial for me, to see if I like git and github. I'll let you know.

20100127

NSThread

The project I'm working on uses threads. Like a good Cocoa app, the main thread is in charge of the GUI, and the worker threads run in the background so that we don't pinwheel.
In order to know when the thread has finished its work, we registered for notification:

[[NSNotificationCenter defaultCenter] addObserver:self
selector:aSel
name:NSThreadWillExitNotification
object:thread];


and in the aSel method, use ivars to update an NSTableView's dataSource.

We had a really pesky race condition that, after two days, we realized was caused by aSel running in the same worker thread! It turns out that this is in the documentation, that notifications are served to the same thread that posts them. However, this is a fairly useless implementation decision.

Our workaround is not to register for the notification, but rather to have the thread performSelectorOnMainThread:withObject:waitUntilDone: at the end of its execution. There is still a problem that we'd like to release the thread at that point, but we don't know what will happen if we try that!

20100108

Apple, part II

The same day I received the rejection email from Apple, a different Apple recruiter contacted me on LinkedIn and asked me if I would be interested in a job at Apple. I wanted to see where it would go, so I said sure, tell me about the job. He described a position with the security testing team for all Apple products, but only very broadly. When I asked some detailed questions, he couldn't answer them, so he offered to set up a phone call with the team leader.
About 1.5 weeks after being contacted on LinkedIn, I got a call from the team leader. He described the position, and it sounded like something I'd enjoy, so I asked him how to apply for it; should I send a resume, etc. He said he had something like a resume in front of him (which I assume the recruiter copied from LinkedIn), and that I didn't have the experience or background they wanted, unless there was something I'd left off my resume.
I asked him why, if he had already decided that I didn't have the background he wanted, did he call me? He told me that he'd called me because the recruited had asked him to, because the recruiter couldn't answer my questions.