bdunagan

Brian Dunagan

September 25 2010
Cocoa Tip: Enabling "Launch on Startup"

When I start up my Mac, I have a number of apps launch, including Jing, Evernote, LaunchBar, and BusySync. Each registers itself as a “Login Item”, listed in System Preferences under Accounts. Developers have different names for this option, like “Launch on Startup” or “Launch at Login”.

In 10.4 and older, Mac OS X enabled this feature through AppleScript, and Apple has sample code for that in LoginItemsAE. In 10.5 and 10.6, the API is in LSSharedFileList.h, buried in the LaunchServices framework:

/System/Library/Frameworks/CoreServices.framework/ Frameworks/LaunchServices.framework/Headers/

I played around with this API a bit today and came up with a couple wrapper methods to abstract away the details: isLaunchAtStartup and toggleLaunchAtStartup:. Feel free to use this code.

// MIT license
- (BOOL)isLaunchAtStartup {
	// See if the app is currently in LoginItems.
	LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
	// Store away that boolean.
	BOOL isInList = itemRef != nil;
	// Release the reference if it exists.
	if (itemRef != nil) CFRelease(itemRef);

	return isInList;
}

- (IBAction)toggleLaunchAtStartup:(id)sender {
	// Toggle the state.
	BOOL shouldBeToggled = ![self isLaunchAtStartup];
	// Get the LoginItems list.
	LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
	if (loginItemsRef == nil) return;
	if (shouldBeToggled) {
		// Add the app to the LoginItems list.
		CFURLRef appUrl = (CFURLRef)[NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
		LSSharedFileListItemRef itemRef = LSSharedFileListInsertItemURL(loginItemsRef, kLSSharedFileListItemLast, NULL, NULL, appUrl, NULL, NULL);
		if (itemRef) CFRelease(itemRef);
	}
	else {
		// Remove the app from the LoginItems list.
		LSSharedFileListItemRef itemRef = [self itemRefInLoginItems];
		LSSharedFileListItemRemove(loginItemsRef,itemRef);
		if (itemRef != nil) CFRelease(itemRef);
	}
	CFRelease(loginItemsRef);
}

- (LSSharedFileListItemRef)itemRefInLoginItems {
	LSSharedFileListItemRef itemRef = nil;
	NSURL *itemUrl = nil;

	// Get the app's URL.
	NSURL *appUrl = [NSURL fileURLWithPath:[[NSBundle mainBundle] bundlePath]];
	// Get the LoginItems list.
	LSSharedFileListRef loginItemsRef = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
	if (loginItemsRef == nil) return nil;
	// Iterate over the LoginItems.
	NSArray *loginItems = (NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
	for (int currentIndex = 0; currentIndex < [loginItems count]; currentIndex++) {
		// Get the current LoginItem and resolve its URL.
		LSSharedFileListItemRef currentItemRef = (LSSharedFileListItemRef)[loginItems objectAtIndex:currentIndex];
		if (LSSharedFileListItemResolve(currentItemRef, 0, (CFURLRef *) &itemUrl, NULL) == noErr) {
			// Compare the URLs for the current LoginItem and the app.
			if ([itemUrl isEqual:appUrl]) {
				// Save the LoginItem reference.
				itemRef = currentItemRef;
			}
		}
	}
	// Retain the LoginItem reference.
	if (itemRef != nil) CFRetain(itemRef);
	// Release the LoginItems lists.
	[loginItems release];
	CFRelease(loginItemsRef);
	
	return itemRef;
}
Cocoa Tip: NSDate, to the nearest 15 minutes Remind Me Later: Quickly add events to iCal
LinkedIn GitHub Email