Implemented Helper App But Does Not Launch on Login

SMLoginItemSetEnabled returns success but doesn’t add login item

The solution to this was to delete the DerivedData folder for both the base app and the helper app. It would seem that the login item was confused about which copy of the app to open, and was presumably trying to open one from DerivedData. Deleting these folders and leaving only the app in /Applications allowed the helper app to launch the base app from the login item.

Error when enabling auto login of macOS app using a helper

I had the exact same problem just now, and while looking for a solution found this (unanswered) question.

At least in my case, this desired functionality of the worked fine when I copied the app (exported from Xcode as a dev-id signed .app) to a fresh OS X install/account without all my development stuff on it. Of course it must also be in /Applications, as stated in the tutorial referred to in the question.

I am not sure why this feature of the app did not work on my development machine. Perhaps the problem could be due to some form of conflict with all the other near-identical copies of my app I have on disk (I have an archive of different versions of the app, plus the copies Xcode stores itself), all with the same bundle id of course.

Hope this helps in one way or another!

OS X Login helper app thinks app is already running

runningApplicationsWithBundleIdentifier: will not return nil, it will return an empty array, so this comparison always evaluates to YES.

quote from the docs:

Return Value

An array of NSRunningApplications, or an empty array if no applications match the bundle identifier.

Login Helper App creating multiple OS X app instances

The problem being that I had a few different copies of the application on my system (specifically, one on the desktop and on in the applications folder). Removing one of these application instances resolved my issue.

Mac OS X: start on launch while in app store?

I recently went through this same process, unfortunately with sandboxing it's not nearly as easy to do as it used to be. I made a test application with very detailed instructions that is now on Github

Notes

This demo application and your application will only work if they are deployed preferably in the /Applications/MyGreat.app and will not work reliably from the Xcode debug folder.

Project settings

These are the settings of my project that worked perfectly with this implementation.

  1. Create a new project with ARC enabled
  2. Sandbox both your main application and helper application (if you haven't created a helper yet we will get to it shortly) I also enabled code signing
  3. Since this was just a test application I had no active entitlements for either the main application or the helper
  4. If you haven't already, create a helper app. Go to your project settings and click "Add Target" choose a Cocoa Application. Name it something such as MyAwesomeProjectHelper also with ARC enabled. (I left its "App Store Category" blank)
  5. Now select the Target of your main application. Go to Build Phases -> Add Build Phase -> Add Copy Files.
  6. Change the Destination to Wrapper. Make the Subpath Contents/Library/LoginItems leave Copy only when installing unchecked. Drag your helper application from Products on the left into the tableview.

Main Application code setup

  1. Import ServiceManagement.framework into your main application(not your helper) and include #import <ServiceManagement/ServiceManagement.h> in your .h file
  2. Grab StartAtLoginController from Github. This is an easy to use class by Alex Zielenski to deal with the complications of adding, removing and querying login items. Import StartAtLoginController.h into your h file.
  3. Create whatever interface you want to for controlling this setting. If your application automatically enables this it will be denied from the Mac App Store(per guideline #2.26)
  4. Implement a method such as - (IBAction)checkChanged:(id)sender I made a simple checkbox tied to the StandardUserDefaults. (If you chose to do something else your implementation for this may vary.) I also bound the checkbox to IBOutlet NSButton *loginCheck; in order to determine it's state. This could also be done through [[NSUserDefaults standardUserDefaults] boolForKey:YourKey]
  5. Implement code similar to this in your .m file.

    StartAtLoginController *loginController = [[StartAtLoginController alloc] init];
    [loginController setBundle:[NSBundle bundleWithPath:[[[NSBundle mainBundle] bundlePath] stringByAppendingPathComponent:@"Contents/Library/LoginItems/HelperApp.app"]]];
    // Change "HelperApp.app" to the name of your helper

    if ([loginCheck state]) {
    if (![loginController startAtLogin]) {
    [loginController setStartAtLogin: YES];
    }
    } else {
    if ([loginController startAtLogin]) {
    [loginController setStartAtLogin:NO];
    }
    }
  6. That's it. As you can see in this project there are some other methods you may want to use such as:

    if ([loginController startAtLogin]) {
    NSLog(@"Error");
    }

    For checking after you enable or disable the setting to make sure it worked correctly. Or this:

    BOOL startsAtLogin = [loginController startAtLogin];
    if (startsAtLogin) {
    // Do stuff
    }

    To do something if the login helper is enabled.

Helper Application code setup

Make sure to test this code vigorously with your implementation.

  1. Make your helper application a UIElement by navigating to HelperApp.plist located in the Supporting Files group by default. Add a line at the bottom with the Key Application is agent (UIElement) and YES as the Value (this will suppress the application from flashing a dock icon each time the user enables launch at login) I also deleted everything except for the App Delegate in interface builder
  2. Erase the default method - (void)applicationDidFinishLaunching:(NSNotification *)aNotification and replace it with - (void)applicationWillFinishLaunching:(NSNotification *)aNotification
  3. Within this method implement code similar to this.

    NSString *appPath = [[[[[[NSBundle mainBundle] bundlePath] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent] stringByDeletingLastPathComponent];
    // This string takes you from MyGreat.App/Contents/Library/LoginItems/MyHelper.app to MyGreat.App This is an obnoxious but dynamic way to do this since that specific Subpath is required
    NSString *binaryPath = [[NSBundle bundleWithPath:appPath] executablePath]; // This gets the binary executable within your main application
    [[NSWorkspace sharedWorkspace] launchApplication:binaryPath];
    [NSApp terminate:nil];

    This code finds your main application, determines it's binary executable(required to launch the application within the sandbox) opens your application, then quits

  4. That's it.

Deploy

The last thing you should do when deploying your application for yourself or to the Mac App store is remove your Helper app from the Archived items. Do this by navigating to the Target of your HelperApp -> Build Settings -> Skip Install and set Yes for Release. Apple provides more information at (http://developer.apple.com/library/ios/#documentation/ToolsLanguages/Conceptual/Xcode4UserGuide/000-About_Xcode/about.html)

Helper mac app (Login Item), unable to communicate with

Your helper application is sandboxed. Therefore, it cannot register a mach service dynamically, although Xcode allows it for debug purpose.

However, when you add your helper application to login items (using SMLoginItemSetEnabled() ), launchd will automatically register a mach service for you named with its bundle identifier.

Now your main application is sandboxed. Therefore, random mach communication is not allowed. The only way to make it work was to add a temporary mach lookup entitlement.

Since 10.7.4. Apple introduced the application-groups entitlements as a solution for this case, where an application needs to communicate with a helper app.

Both applications have to share the same application-groups entitlement. It can be any value, but Apple requires that this value must start with your Team-ID (e.g: Team-id.myApp). Then, your helper application bundle identifier must start with that same entitlement (e.g Team-id.myApp.myHelperApp). After that, your main application can freely communicate with your helper application using a XPC communication with the service named with the helper application bundle identifier (i.e. Team-id.myApp.myHelperApp). Also, the two applications will share access to a group container folder named with the application group entitlement (e.g. ~/Library/Group Containers/Team-id.myApp), that you have to create manually if you will need it.



Related Topics



Leave a reply



Submit