Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Make Swift Cocoa app launch on startup on OS X 10.11

Tags:

I need to write a function that adds my application to Startup items on OS X 10.11. That's what I found at the moment:

func applicationIsInStartUpItems() -> Bool {     return (itemReferencesInLoginItems().existingReference != nil) }  func itemReferencesInLoginItems() -> (existingReference: LSSharedFileListItemRef?, lastReference: LSSharedFileListItemRef?) {      if let appUrl : NSURL = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {         let loginItemsRef = LSSharedFileListCreate(nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue() as LSSharedFileListRef?         if loginItemsRef != nil {             let loginItems: NSArray = LSSharedFileListCopySnapshot(loginItemsRef, nil).takeRetainedValue() as NSArray             if(loginItems.count > 0) {                 let lastItemRef: LSSharedFileListItemRef = loginItems.lastObject as! LSSharedFileListItemRef                 for var i = 0; i < loginItems.count; ++i {                     let currentItemRef: LSSharedFileListItemRef = loginItems.objectAtIndex(i) as! LSSharedFileListItemRef                     if let itemURL = LSSharedFileListItemCopyResolvedURL(currentItemRef, 0, nil) {                         if (itemURL.takeRetainedValue() as NSURL).isEqual(appUrl) {                             return (currentItemRef, lastItemRef)                         }                     }                 }                 return (nil, lastItemRef)             } else {                 let addatstart: LSSharedFileListItemRef = kLSSharedFileListItemBeforeFirst.takeRetainedValue()                 return(nil,addatstart)             }         }     }     return (nil, nil) }  func toggleLaunchAtStartup() {     let itemReferences = itemReferencesInLoginItems()     let shouldBeToggled = (itemReferences.existingReference == nil)     if let loginItemsRef = LSSharedFileListCreate( nil, kLSSharedFileListSessionLoginItems.takeRetainedValue(), nil).takeRetainedValue() as LSSharedFileListRef? {         if shouldBeToggled {             if let appUrl : CFURLRef = NSURL.fileURLWithPath(NSBundle.mainBundle().bundlePath) {                 LSSharedFileListInsertItemURL(loginItemsRef, itemReferences.lastReference, nil, nil, appUrl, nil, nil)             }         } else {             if let itemRef = itemReferences.existingReference {                 LSSharedFileListItemRemove(loginItemsRef,itemRef);             }         }     } } 

But LSSharedFileListCreate, LSSharedFileListInsertItemURL, LSSharedFileListItemRemove, kLSSharedFileListItemBeforeFirst, LSSharedFileListItemCopyResolvedURL, LSSharedFileListCopySnapshot, kLSSharedFileListSessionLoginItems were deprecated in OS X 10.11. How to make this work on latest version of Mac OS? How to change or rewrite this code?

like image 925
pomo_mondreganto Avatar asked Feb 11 '16 12:02

pomo_mondreganto


People also ask

How do I make my Mac not open apps automatically?

Right-click on the app and hover over Options in the menu. Apps that are set to open automatically will have a check mark next to Open at Login. Click that option to uncheck it and disable it from opening.

How do I stop apps opening when I log into my Mac?

Temporarily prevent items from opening automatically when you log in. If you see the login window, press and hold the Shift key while you click the Log In button, then release the Shift key when the Dock opens.


Video Answer


2 Answers

In Swift 3.0 it looks like this:

In your main application AppDelegate:

func applicationDidFinishLaunching(_ aNotification: Notification) {     // Check if the launcher app is started     var startedAtLogin = false     for app in NSWorkspace.shared().runningApplications {         if app.bundleIdentifier == NCConstants.launcherApplicationIdentifier {             startedAtLogin = true         }     }      // If the app's started, post to the notification center to kill the launcher app     if startedAtLogin {         DistributedNotificationCenter.default().postNotificationName(NCConstants.KILLME, object: Bundle.main.bundleIdentifier, userInfo: nil, options: DistributedNotificationCenter.Options.deliverImmediately)     } } 

In the Launcher application AppDelegate:

func applicationDidFinishLaunching(_ aNotification: Notification) {      let mainAppIdentifier = "<main-app-bundle-id>"     let running = NSWorkspace.shared().runningApplications     var alreadyRunning = false      // loop through running apps - check if the Main application is running     for app in running {         if app.bundleIdentifier == mainAppIdentifier {             alreadyRunning = true             break         }     }      if !alreadyRunning {         // Register for the notification killme         DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: NCConstants.KILLME, object: mainAppIdentifier)          // Get the path of the current app and navigate through them to find the Main Application         let path = Bundle.main.bundlePath as NSString         var components = path.pathComponents         components.removeLast(3)         components.append("MacOS")         components.append("<your-app-name>")          let newPath = NSString.path(withComponents: components)          // Launch the Main application         NSWorkspace.shared().launchApplication(newPath)     }     else {         // Main application is already running         self.terminate()     }  }  func terminate() {     print("Terminate application")     NSApp.terminate(nil) } 

Eventually, in the main application I added a user interface with a toggle button. The user can choose to launch the app at login or not. The choice is stored into the UserDefaults. In the View Controller:

@IBAction func toggleLaunchAtLogin(_ sender: Any) {     if toggleOpenAppLogin.selectedSegment == 0 {         if !SMLoginItemSetEnabled(NCConstants.launcherApplicationIdentifier as CFString, true) {             print("The login item was not successfull")             toggleOpenAppLogin.setSelected(true, forSegment: 1)         }         else {             UserDefaults.standard.set("true", forKey: "appLoginStart")         }     }     else {         if !SMLoginItemSetEnabled(NCConstants.launcherApplicationIdentifier as CFString, false) {             print("The login item was not successfull")             toggleOpenAppLogin.setSelected(true, forSegment: 0)         }         else {             UserDefaults.standard.set("false", forKey: "appLoginStart")         }     }  } 

I hope this can help somebody.

like image 123
Thomas Avatar answered Sep 24 '22 14:09

Thomas


You have to use the Service Management framework now. You create a helper application that you add to your application bundle and its job is to run code to launch your main application. Some resources for you:

  • Tim Schröder has an excellent blog post on how this is done
  • Alex Zielenski has an open source project to aid with this
  • I've made a video tutorial on creating it manually
like image 40
Lucas Derraugh Avatar answered Sep 20 '22 14:09

Lucas Derraugh