Debugging Apple’s LoginItemsAE.c on Intel Macs
jake March 28th, 2007
Came across some wackiness involving the Apple Event Manager and LoginItemsAE.c (Apple’s example code for manipulating a user’s list of login items). In hopes that others dealing with similar problems will find this:
The “System Events” process can be accessed via an Apple Event (i.e. Applescript) dictionary which allow you to list, add, and delete login items. Login items are described by two parameters, a path to the application to be launched when the user logs in, and a flag which specifies whether it should be hidden once launched.
In LoginItemsAE.c, retrieved login item paths are encoded as typeUnicodeText (native-endian UTF-16) and are converted to CFURLs for the user’s consumption, in three steps:
- Get a UTF8-converted string in a char buffer using AEGetKeyPtr (#defined to AEGetParam) to “coerce” the parameter to typeUTF8Text.
- Make an FSPathRef from the character buffer using FSPathMakeRef.
- Use CFURLCreateFromFileSystemRepresentation to get a CFURLRef
On my Powerbook (PPC), this worked fine, but when I sent my beta to testers, MacBook users reported that my “Start At Login” option wasn’t working. In debugging, I found that AEGetKeyPtr was returning errAECoercionFail (-1700) — it couldn’t convert the Unicode path to UTF8.
A symmetric problem occurs when asking System Events to add a login item. Here, LoginItemsAE.c uses AECoercePtr to create it’s path property in typeUnicode form from a UTF8 character buffer, but was seeing errAECoercionFail on Intel Macs.
Well, that sucks. In Apple’s Apple Events Programming Guide, it says
Support for the following coercions was added in Mac OS X version 10.4:
Between these types:
- typeStyledText, typeUnicodeText, typeUTF8Text, and typeUTF16ExternalRepresentation
…
In searching for a solution on the web, I noticed that the Growl project also uses LoginItemsAE.c to set their helper app as a login item. The latest repository version shows no changes. Have they not seen the same problem?
The workaround: instead of asking AE to coerce our data for us, we do it ourselves using CFStrings. I wrote a function which converts a char buffer between CF string representations:
static int ConvertPathEncoding(char *path, int pathlen, int bufsize, int src_cftype, int dest_cftype)
{
int ret = 0;
CFStringRef cfpath = CFStringCreateWithBytes(NULL, (UInt8 *)path, pathlen, src_cftype, FALSE);
if (cfpath == NULL) {
ret = -1;
}
else {
int len = CFStringGetLength(cfpath);
Size actualLen;
CFStringGetBytes(cfpath, CFRangeMake(0, len), dest_cftype, 0, false,
(UInt8 *)path, bufsize, &actualLen);
CFRelease(cfpath);
ret = (int)actualLen;
}
return ret;
}
Now I can ask AEGetKeyPtr to give me a typeUnicode string in my buffer, and then call ConvertPathEncoding to convert from kCFStringEncodingUnicode and kCFStringEncodingUTF8. Similarly, instead of calling AECoercePtr to create my login item path parameter description, I call ConvertPathPathEncoding to convert from kCFStringEncodingUTF8 and kCFStringEncodingUnicode.
In AEDataModel.h, we see that typeUnicodeText and some other types are “deprecated due to their lack of explicit encoding or byte order definition. Please use typeUTF16ExternalRepresentation or typeUTF8Text instead.” typeUTF16ExternalRepresentation is defined as “big-endian 16 bit unicode with optional byte-order-mark, or little-endian 16 bit unicode with required byte-order-mark,“, i.e. it is possible to check if the string has the correct byte-order for the system. But apparently, the System Events API is still using UTf-16 with no BOM.