In this post, I will explain, how you can add the Launch at Login (launch application on system startup) feature to your MacOS Applications. Adding this feature is way more complex than you would expect. I use MeetingBar for managing my work/personal meetings over Google Meet and Zoom. MeetingBar is an open-source MacOS App. Recently, I added the Launch at Login feature to MeetingBar. Initially, I thought that there will be a simple solution to it and it will take minutes only. But when I started implementing it, I found out that it is not really that straight forward. You can find the implementation source code below.

Link to Pull Request: Add Launch at Login feature

Source Code for this post: https://github.com/jogendra/launch-at-login-macos

For the users, a straightforward solution is going to system preference and enable auto launching for applications (Preferences -> Users & Groups -> Login Items). But it is always good if the application itself offers the feature from its preference. For application developers, it’s a bit tricky.

There are a couple of ways to add a login item to the application. The most used/useful way is using the Service Management framework. Login items installed using the Service Management framework are not visible in System Preferences and can only be removed by the application that installed them. The other way is using a shared file list. Login items installed using a shared file list are visible in System Preferences; users have direct control over them. If you use this API, your login item can be disabled by the user.

In this post, I will explain how to add login items to MacOS Applications using the Service Management framework. The idea is to create an application inside the main application that handles the auto launching for the main application.

Little bit about Apple App Sandboxing

According to Apple documentation, to distribute a macOS app through the Mac App Store, you must enable the App Sandbox capability. App Sandbox provides protection to system resources and user data by limiting your app’s access to resources requested through entitlements. Apps signed and distributed outside of the Mac App Store with Developer ID can (and in most cases should) use App Sandbox as well. App Sandbox enables you to describe how your app interacts with the system. The system then grants your app the access it needs to get its job done and no more. It is always best to design your apps with App Sandbox in mind. You can check out this detailed article by Appcoda on Mac App Sandbox.

Every application is given a sandbox, a directory it can use to store data. An application cannot access the sandbox of another application. If the application needs access to data on the device that isn’t located in the application’s sandbox, it needs to request the data through a system interface. System interfaces add a layer of security and protect the user’s privacy.

apple app sandboxing

To adopt App Sandbox for a target in an Xcode project, apply the <true/> value to the com.apple.security.app-sandbox entitlement key for that target. Do this in the Xcode target editor by selecting the Enable App Sandboxing checkbox.

entitlement

Generally, when you create macOS App, it is already added, you don’t have to worry about it.

:zap: Initial Setups

In this post, we will use MainApplication for the main application (using SwiftUI) in which we are going to add Launch at Login functionality and the other one will be AutoLauncher, which is our helper application that is responsible for registering our main application for login items.

main application general settings

:hammer_and_wrench: Adding Helper Target

To add helper target AutoLauncher to our MainApplication, go to File -> New -> Target and choose macOS template with App selected (under Application section) and click Next button to choose options for helper application.

select target templete

In next screen, choose ‘AutoLauncher’ for Product Name (or whatever name you want to give), select ‘Storyboard’ option for the User Interface option (we will delete this interface later anyway), make sure the Project option has ‘MainApplication’ target selected and finish the setup.

:gear: Helper Target Configurations

  • Since our helper target AutoLauncher should be background only as we don’t need user interface for AutoLauncher. So to make it background only service, go to Info.plist of AutoLauncher, set Application is background only key to YES.

AutoLauncher Info.plist

  • Go to Targets, select ‘AutoLauncher’ Target’s Build Settings, and search for “skip install”. Set Skip Install to Yes. Otherwise we would end up with too many bundles in our final app archive.

Skip Install Settings

  • Go to AppDelegate.swift of AutoLauncher, rename class name AppDelegate to AutoLauncherAppDelegate (or whatever name you want to give) because it is conflicting with AppDelegate class of our MainApplication. We will configure this Delegate class method applicationDidFinishLaunching(_ :) like below. It check if MainApplication is running, if not, launch application using openApplication(at:_:_:). This instance method launches the app at the specified path URL.

App delegate code

This AutoLauncher app target is actually embedded inside the MainApplication bundle under the subdirectory Contents/Library/LoginItems (we are going to do this later). So including the helper app target name (AutoLauncher) there will be a total of 4 path components to be deleted. That’s why we are running loop 4 times.

:anger: Cleaning Up Helper Target UI Elements

Helper target app (AutoLauncher) should not have any UI elements. We will remove all the visual elements from the app, even the Main.storyboard. Now, got ahead and:

  • Delete ViewController.swift file.
  • Delete Assets.xcassets folder.
  • Delete Main.storyboard.

Since we have deleted entry point of AutoLauncher target which was earlier configured in deleted Main.storyboard file. To make that up, follow:

  • Create and add main.swift file (please note it has to be ”main” with lowercased ”m”, otherwise it won’t work) to AutoLauncher.
  • Remove @NSApplicationMain from AutoLauncherAppDelegate class. @NSApplicationMain is no longer needed because in-general @NSApplicationMain implicit generate the main.swift file for us which we have already created.
  • Set the Application delegate like below in main.swift:

main.swift code

Clean? We are done with all the setups for helper. Now, let’s configure our Main Application.

:electric_plug: Adding Helper to Main Application

We have to copy the helper application AutoLauncher inside our MainApplication. To do so, go to Project Setting, have MainApplication target selected, select Build Phases, click + icon shown below, and select New Copy Files Phase.

New Copy Files Phase

Apply Settings as below:

Select Wrapper as Destination, put Contents/Library/LoginItems in Subpath and finally click on + icon to add AutoLauncher.app there (it will be inside Product folder).

New Copy Files Phase

Now, go to General tab and Add ServiceManagement framework to Frameworks, Libraries, and Embedded Content section.

You can now import ServiceManagement to any of your swift file and use the SMLoginItemSetEnabled(_:_:) function to enable a helper application. It takes two arguments, a CFStringRef containing the bundle identifier of the helper application, and a Boolean specifying the desired state. This value is effective only for the currently logged-in user. Pass true to start the helper application immediately and indicate that it should be started every time the user logs in. Pass false to terminate the helper application and indicate that it should no longer be launched when the user logs in. This function returns true if the requested change has taken effect; otherwise, it returns false. This function can be used to manage any number of helper applications.

Example UI

Example UI for Launch at Login

In this example, I just added a simple toggle using SwiftUI. On toggle, it’s calling SMLoginItemSetEnabled function with helper app identifier and current state of toggle. Further, you can store current state in UserDefaults.

Example UI for Launch at Login

NOTE: If multiple applications (for example, several applications from the same organization) contain a helper application with the same bundle identifier, only the one with the greatest bundle version number is launched. Any of the applications that contain a copy of the helper application can enable and disable it. You should keep this in mind if you are shipping applications under the same organization. You can read more in detail about this here.

  1. App Sandbox in Depth
  2. Creating a macOS App in SwiftUI
  3. SMLoginItemSetEnabled Method Documentations
  4. Beyond App Sandbox: Going outside of the macOS app container
  5. Using App Groups for communication between macOS/iOS apps from the Same Vendor

:memo: Closing Notes

We have seen how we can add login items to any MacOS application. You can Download Full Source Code Here. Adding login items to macOS applications isn’t that straight-forward. You need to follow all the setps carefully. I hope this post helped you in some way. Do share and drop me suggestions/questions on Twitter. Thanks for read :)