25

Google gave me: http://developer.apple.com/samplecode/LoginItemsAE/index.html

And I figured there must be a better way than using AppleScript Events.

So I downloaded the Growl sources. They use the exact sources from that Apple developer article.

Is there a better way?

(I refer to Login Items in Accounts in System Preferences, ie. making my program start when the user Logs in, programmatically)

mxcl
  • 26,392
  • 12
  • 99
  • 98
  • An unrelated Google got me the following comprehensive Apple documentation: http://developer.apple.com/documentation/MacOSX/Conceptual/BPSystemStartup/Articles/CustomLogin.html – mxcl Mar 08 '09 at 10:58
  • BTW, I had occasionally problems with using AppleEvents to get and set the list of login items, because the background service used by AppleScript for this (don't recall the name right now) was hung, and therefore my app would also hang at start when it checked whether it had to update its login item after the app may have gotten renamed or moved. I hope the LSSharedFileList solution won't suffer from this issue - I only employed this recently, so I have no data to back that up, yet, but it's likely that this avoids the issue unless it internally just uses AppleEvents). – Thomas Tempelmann Aug 30 '16 at 14:22

7 Answers7

27

There's an API that's new in Leopard called LSSharedFileList. One of the things it lets you do is view and edit the Login Items list (called Session Login Items in that API).

BTW, I'm the lead developer of Growl. We haven't switched away from AE yet because we still require Tiger, but I'm thinking of dropping that for 1.2 (haven't talked it over with the other developers yet). When we do drop Tiger, we'll drop LoginItemsAE as well, and switch to the Shared File List API.


EDIT from the year 2012: Since 2009, when I originally wrote this answer, Growl has switched to LSSharedFileList and I've left the project.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • 2
    I think you mean LSSharedFileList – Nathan Kinsinger Mar 04 '09 at 03:03
  • Did you check how it works in Lion? Sometimes LSSharedFileList* calls hang (I received one complain from user, and saw crash log at AppStore). As Growl is used by lot of ppl, you should also receive sush logs, dod you? This seems to happen when there are some not mounted network drives. – Nickolay Olshevsky Apr 01 '12 at 10:55
  • 1
    Uh, just found an answer - in LSSharedFileListItemResolve pass kLSSharedFileListDoNotMountVolumes as flags. Possibly, kLSSharedFileListNoUserInteraction should be needed as well. – Nickolay Olshevsky Apr 01 '12 at 11:05
  • That's not specific to Lion, and whether you should use those options depends on how you want your application to behave when the item is on a disk image or network share. Note that with either or both of those options, resolution may fail (or fail faster), since you can't have an FSRef to an item on a volume that isn't mounted. (Also, even with mounting and user interaction enabled, it can still fail if the user cancels the interaction.) – Peter Hosey Apr 01 '12 at 21:17
  • I'm reading this in 2012 and all I can say is: well played sir! – Bojan Babic May 24 '12 at 21:49
14

I stumbled across Ben Clark-Robinson's LaunchAtLoginController. A very elegant solution to a very common problem.

Reed Olsen
  • 9,099
  • 4
  • 37
  • 47
  • 1
    Works great. Tested in 10.6 and 10.7 – rydgaze Mar 01 '12 at 15:43
  • 3
    That code doesn't use ARC and uses `LSSharedFileListItemResolve`, which is deprecated in OS X 10.10. I resolved these issues by using Xcode's suggestions for ARC and replacing `LSSharedFileListItemResolve` with `CFURLRef currentItemURL = LSSharedFileListItemCopyResolvedURL(item, resolutionFlags, NULL)`. It now works warning-free for me. – VinceFior Jul 17 '15 at 07:57
  • The ARC version is available here: https://github.com/jklarfeld/LaunchAtLoginController/blob/master/LaunchAtLoginController.m – Stéphane Bruckert Sep 25 '15 at 22:34
10

This works on xcode 5.

- (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;
}

- (void)toggleLaunchAtStartup {
    // 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 = (__bridge 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 res = nil;

    // Get the app's URL.
    NSURL *bundleURL = [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 = (__bridge NSArray *)LSSharedFileListCopySnapshot(loginItemsRef, nil);
    for (id item in loginItems) {
        LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)(item);
        CFURLRef itemURLRef;
        if (LSSharedFileListItemResolve(itemRef, 0, &itemURLRef, NULL) == noErr) {
            // Again, use toll-free bridging.
            NSURL *itemURL = (__bridge NSURL *)itemURLRef;
            if ([itemURL isEqual:bundleURL]) {
                res = itemRef;
                break;
            }
        }
    }
    // Retain the LoginItem reference.
    if (res != nil) CFRetain(res);
    CFRelease(loginItemsRef);
    CFRelease((__bridge CFTypeRef)(loginItems));

    return res;
}
poordeveloper
  • 2,272
  • 1
  • 23
  • 36
  • Please tell us what systems it runs on and not with which IDE version you compiled the code, this info is not worth mentioning. – Julian F. Weinert Feb 06 '15 at 14:10
  • 3
    This works perfectly thanks. As of OSX 10.10 `LSSharedFileListItemResolve` is deprecated. Instead use something along the lines of: `CFURLRef itemUrlRef = LSSharedFileListItemCopyResolvedURL(itemRef, 0, &err);` – cdyer May 30 '15 at 17:07
  • Its not inserting my application in Login Items in System Preferences. – ManiaChamp Jan 15 '16 at 12:11
  • This example, like most similar in existence, does not deal with the case where the app got deleted and then re-installed in another place after having been added to the Login Items - because it will then add another entry without removing the dead one. To do this right, before adding a new one, one should first look at all entries and remove any whose names start with the app's name. – Thomas Tempelmann Aug 08 '16 at 18:10
4

I do this in an app I'm writing:

Check out UKLoginItemRegistry for an easy way to do this pragmatically. Afaik, there is no way in Tiger to do this without Apple Events; in Leopard there's a better way, but if you use UKLoginItemRegistry it really is no problem. Here's the complete code for implementing an "Open at Logon" menu item

+ (bool)isAppSetToRunAtLogon {
  int ret = [UKLoginItemRegistry indexForLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
  NSLog(@"login item index = %i", ret);
  return (ret >= 0);
}

- (IBAction)toggleOpenAtLogon:(id)sender {
  if ([PopupController isAppSetToRunAtLogon]) {
    [UKLoginItemRegistry removeLoginItemWithPath:[[NSBundle mainBundle] bundlePath]];
  } else {
    [UKLoginItemRegistry addLoginItemWithPath:[[NSBundle mainBundle] bundlePath] hideIt: NO];
  }
}
Ana Betts
  • 73,868
  • 16
  • 141
  • 209
3

I've refactored some of the answers here to provide a category on NSApplication that provides a launchAtLogin property.

https://gist.github.com/joerick/73670eba228c177bceb3

joerick
  • 16,078
  • 4
  • 53
  • 57
3

SMLoginItemSetEnabled is another modern option, see Modern Login Items article by Cory Bohon where he explains that you have to create a helper application whose sole purpose is to launch the main application. There's also a full step by step explanation in SMLoginItemSetEnabled - Start at Login with App Sandboxed on Stack Overflow.

0xced
  • 25,219
  • 10
  • 103
  • 255
OCTAGRAM
  • 618
  • 6
  • 12
-1

Check here an open source example: https://github.com/invariant/rhpnotifier (LoginItem.m, LoginItem.h)

Marius
  • 3,589
  • 3
  • 27
  • 30