Reputation: 3200
I have developed a Mac app which I want to start as soon as the Mac is started. How can I achieve this functionality programmatically?
I know about launchd, but can't find a working example.
Upvotes: 3
Views: 1915
Reputation: 68
A great example of the implementation of this can be found at https://github.com/nfarina/feeds/blob/master/Feeds/LoginItems.m
#import "LoginItems.h"
@implementation LoginItems
// Copied from https://github.com/carpeaqua/Shared-File-List-Example
- (void)enableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath {
// We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list.
CFURLRef url = (CFURLRef)[NSURL fileURLWithPath:appPath];
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(theLoginItemsRefs, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
if (item)
CFRelease(item);
}
- (void)disableLoginItemWithLoginItemsReference:(LSSharedFileListRef )theLoginItemsRefs ForPath:(NSString *)appPath {
UInt32 seedValue;
CFURLRef thePath = NULL;
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue);
for (id item in (NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(NSURL *)thePath path] hasPrefix:appPath]) {
LSSharedFileListItemRemove(theLoginItemsRefs, itemRef); // Deleting the item
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
if (thePath != NULL) CFRelease(thePath);
}
}
if (loginItemsArray != NULL) CFRelease(loginItemsArray);
}
- (BOOL)loginItemExistsWithLoginItemReference:(LSSharedFileListRef)theLoginItemsRefs ForPath:(NSString *)appPath {
BOOL found = NO;
UInt32 seedValue;
CFURLRef thePath = NULL;
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue);
for (id item in (NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(NSURL *)thePath path] hasPrefix:appPath]) {
found = YES;
break;
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
if (thePath != NULL) CFRelease(thePath);
}
}
if (loginItemsArray != NULL) CFRelease(loginItemsArray);
return found;
}
// Our code
+ (LoginItems *)userLoginItems {
static LoginItems *userItems = nil;
return userItems ?: (userItems = [LoginItems new]);
}
- (BOOL)currentAppLaunchesAtStartup {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
BOOL exists = [self loginItemExistsWithLoginItemReference:loginItems ForPath:appPath];
CFRelease(loginItems);
return exists;
}
- (void)setCurrentAppLaunchesAtStartup:(BOOL)currentAppLaunchesAtStartup {
NSString * appPath = [[NSBundle mainBundle] bundlePath];
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
if (loginItems) {
if (!self.currentAppLaunchesAtStartup)
[self enableLoginItemWithLoginItemsReference:loginItems ForPath:appPath];
else
[self disableLoginItemWithLoginItemsReference:loginItems ForPath:appPath];
CFRelease(loginItems);
}
}
@end
Upvotes: 1
Reputation: 3200
I found the solution Thanks
- (void)enableLoginItemWithLoginItemsReference:(LSSharedFileListRef)theLoginItemsRefs ForPath:(NSString *)appPath {
// We call LSSharedFileListInsertItemURL to insert the item at the bottom of Login Items list.
CFURLRef url = (__bridge CFURLRef)[NSURL fileURLWithPath:appPath];
LSSharedFileListItemRef item = LSSharedFileListInsertItemURL(theLoginItemsRefs, kLSSharedFileListItemLast, NULL, NULL, url, NULL, NULL);
if (item)
CFRelease(item);
}
- (void)disableLoginItemWithLoginItems {
UInt32 seedValue;
CFURLRef thePath = NULL;
NSString * appPath = [[NSBundle mainBundle] bundlePath];
// Create a reference to the shared file list.
LSSharedFileListRef loginItems = LSSharedFileListCreate(NULL, kLSSharedFileListSessionLoginItems, NULL);
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(loginItems, &seedValue);
for (id item in (__bridge NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) {
LSSharedFileListItemRemove(loginItems, itemRef); // Deleting the item
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
if (thePath != NULL) CFRelease(thePath);
}
}
if (loginItemsArray != NULL) CFRelease(loginItems);
}
- (BOOL)loginItemExistsWithLoginItemReference:(LSSharedFileListRef)theLoginItemsRefs ForPath:(NSString *)appPath {
BOOL found = NO;
UInt32 seedValue;
CFURLRef thePath = NULL;
// We're going to grab the contents of the shared file list (LSSharedFileListItemRef objects)
// and pop it in an array so we can iterate through it to find our item.
CFArrayRef loginItemsArray = LSSharedFileListCopySnapshot(theLoginItemsRefs, &seedValue);
for (id item in (__bridge NSArray *)loginItemsArray) {
LSSharedFileListItemRef itemRef = (__bridge LSSharedFileListItemRef)item;
if (LSSharedFileListItemResolve(itemRef, 0, (CFURLRef*) &thePath, NULL) == noErr) {
if ([[(__bridge NSURL *)thePath path] hasPrefix:appPath]) {
found = YES;
break;
}
// Docs for LSSharedFileListItemResolve say we're responsible
// for releasing the CFURLRef that is returned
if (thePath != NULL) CFRelease(thePath);
}
}
if (loginItemsArray != NULL) CFRelease(loginItemsArray);
return found;
}
Upvotes: 0
Reputation: 5655
This code below was developed from an excellent blog post by Tim Schroeder: The Launch At Login Sandbox Project. Actually more or less borrowed completely from there-- it looks like I only ever had to change the log messages and strings in there, so it's pretty rock solid if I never screwed with it.
Where my opinion differs with that post is that there should be no "helper" app, even if you want the app to launch without a GUI at login. It's unnecessary. If you want your app to have a background mode that runs at login or when the use closes the GUI, you should switch the app to accessory mode as detailed in my answer here. You don't need to deal with subprojects and compiling separate executables. Just have one.
Anyways, Tim's eminently useful code. It uses a segmented control on a preferences panel to toggle the app on/off. The segmentedControl value uses cocoa-bindings to bind the control value to NSUserDefaults, so all that's seen here is registering/unregistering the app as a login item, and error checking/alerting.
- (IBAction)toggleRunAtLogin:(NSSegmentedControl*)sender {
NSLog(@"toggling run at login");
NSUInteger clickedSegment = [sender selectedSegment];
if (clickedSegment == 0) { // ON
// Turn on launch at login
NSLog(@"... to ON");
if (!SMLoginItemSetEnabled ((__bridge CFStringRef)@"com.yourCo.yourApp", YES)) {
NSAlert *alert = [NSAlert alertWithMessageText:@"An error ocurred"
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"Couldn't add App to launch at login item list."];
[alert runModal];
}
}
if (clickedSegment == 1) { // OFF
// Turn off launch at login
NSLog(@"... to OFF");
if (!SMLoginItemSetEnabled ((__bridge CFStringRef)@"com.yourCo.yourApp", NO)) {
NSAlert *alert = [NSAlert alertWithMessageText:@"An error ocurred"
defaultButton:@"OK"
alternateButton:nil
otherButton:nil
informativeTextWithFormat:@"Couldn't remove App from launch at login item list."];
[alert runModal];
}
}
}
Upvotes: 2