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.
20110103
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.
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()).
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
We did succeed in
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....."
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.
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.
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):
UPDATE:
There is a much more intuitive interface:
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}".
Subscribe to:
Posts (Atom)