How to make app start at login or add to login items after install like dropbox? Xcode 11, Swift 5, MacOS 10.13+

1k Views Asked by At

I am trying to have my app start automatically when a user logs out and back in or after a reboot. The objective is to have the app there indefinitely unless a user closes out, but, to have it start back up when they reboot.

There are several tutorials I have followed and threads I have read but they all seem outdated. I am using this app on 3 different OS' including 10.13, 10.14 and 10.15. I seem to have most of the issues on 10.15 machines. I can't figure out why it is hit or miss whether the app starts on login and why sometimes it does not.

https://martiancraft.com/blog/2015/01/login-items/

https://theswiftdev.com/how-to-launch-a-macos-app-at-login/

I have code signed the application, sandbox is enabled, but maybe since the tutorials and information I have found is outdated I am missing something outside the code. This app is for internal company use only, and thus, I will not be submitting to the Apple. I intend to deploy to machines using our management software, and have built a .pkg to deploy the application + launcher into the applications folder and to run automatically after install.

Any help, cleanup, explanations or suggestions are welcome and appreciated.

Main App:

extension Notification.Name{
static let killLauncher = Notification.Name("killLauncher")
}

extension AppDelegate: NSApplicationDelegate{
func applicationDidFinishLaunching(_ aNotification: Notification) {
      // Insert code here to initialize your application

          //assign variables for launcherhelper
          let launcherAppId = "Kinetic.KTGHelperLauncher"
          let runningApps = NSWorkspace.shared.runningApplications
          let isRunning = !runningApps.filter {$0.bundleIdentifier == launcherAppId }.isEmpty
          //set launcher to login item
          SMLoginItemSetEnabled(launcherAppId as CFString, true)
          //status check if running or not running
          if isRunning {
              DistributedNotificationCenter.default().post(name: .killLauncher, object: Bundle.main.bundleIdentifier!)
          }
      
      
      
      //configure button to display button in assets
      if let button = statusItem.button {
          button.image = NSImage(named:NSImage.Name("kinetic_websitemain_red"))
          
      }
      //builds menu on start
      constructMenu()
  }
}

@NSApplicationMain
class AppDelegate: NSObject {


let statusItem = NSStatusBar.system.statusItem(withLength:NSStatusItem.squareLength)



func applicationWillTerminate(_ aNotification: Notification) {
    // Insert code here to tear down your application
}


@objc func TakeScreenshot(_ sender: Any){
    
    //get path to user download folder
    let dirPath = FileManager().urls(for:.downloadsDirectory, in:.userDomainMask)[0]
     
    //create time stamp of when picture is taken
    func CreateTimeStamp() -> Int32
    {
        return Int32(Date().timeIntervalSince1970)
    }
    
    
    var displayCount: UInt32 = 0;
    var result = CGGetActiveDisplayList(0, nil, &displayCount)
        if (result != CGError.success) {
            print("error: \(result)")
            return
        }
        let allocated = Int(displayCount)
        let activeDisplays = UnsafeMutablePointer<CGDirectDisplayID>.allocate(capacity: allocated)
        result = CGGetActiveDisplayList(displayCount, activeDisplays, &displayCount)

        if (result != CGError.success) {
            print("error: \(result)")
            return
        }

        for i in 1...displayCount {
            let unixTimestamp = CreateTimeStamp()
            let fileUrl = dirPath.appendingPathComponent("\(unixTimestamp)" + "_" + "\(i)" + ".jpg", isDirectory:false)
            let screenShot:CGImage = CGDisplayCreateImage(activeDisplays[Int(i-1)])!
            let bitmapRep = NSBitmapImageRep(cgImage: screenShot)
            let jpegData = bitmapRep.representation(using: NSBitmapImageRep.FileType.jpeg, properties: [:])!


            do {
                try jpegData.write(to: fileUrl, options: .atomic)
            }
            catch {print("error: \(error)")}
        }
    
}

@objc func kineticSelf(_ sender: Any){
    let kineticSelfUrl = URL(string: "/Library/Addigy/macmanage/MacManage.app")
    NSWorkspace.shared.openFile(kineticSelfUrl!.path)
    // NSWorkspace.shared.open(URL(fileURLWithPath: "/Library/Addigy/macmanage/MacManage.app"))
    
}


//function that opens kinetic helpdesk website
@objc func kineticHelpdesk(_ sender: Any){
    let kineticHelpdeskUrl = URL(string: "http://helpdesk.kinetictg.com")!
    NSWorkspace.shared.open(kineticHelpdeskUrl)
    
}
//function that takes user to teamviewer ktg site
@objc func kineticRemote(_ sender: Any){
    let kineticRemoteUrl = URL(string: "https://get.teamviewer.com/ktgsupport")!
    NSWorkspace.shared.open(kineticRemoteUrl)
    
}
//call kinetic
@objc func kineticHomepage(_ sender: Any){
    let url = URL(string: "https://kinetictg.com")!
    NSWorkspace.shared.open(url)
}


//function to build menu
func constructMenu(){
    let menu = NSMenu()
    
    
    
    //section for "Request Support"
    menu.addItem(NSMenuItem.separator())
    menu.addItem(NSMenuItem(title: "Request Support", action: nil, keyEquivalent:""))
        //support ticket
    menu.addItem(NSMenuItem(title: "Support Ticket", action:
        #selector(AppDelegate.kineticHelpdesk(_:)), keyEquivalent: ""))
        //remote support
    menu.addItem(NSMenuItem(title: "Remote Support", action:
         #selector(AppDelegate.kineticRemote(_:)), keyEquivalent: ""))
    
    //section for "Tools"
    menu.addItem(NSMenuItem.separator( ))
    menu.addItem(NSMenuItem(title: "Tools", action: nil, keyEquivalent:""))
        //start agent installation audit
    menu.addItem(NSMenuItem(title: "Take Screenshot", action:
        #selector(AppDelegate.TakeScreenshot(_:)), keyEquivalent: ""))
        //open self service
    menu.addItem(NSMenuItem(title: "Open Self Service", action:
        #selector(AppDelegate.kineticSelf(_:)), keyEquivalent: ""))
    
    //Section for "Info"
    menu.addItem(NSMenuItem.separator( ))
    menu.addItem(NSMenuItem(title: "Info", action: nil, keyEquivalent:""))
        //contact info
    menu.addItem(NSMenuItem(title: "Kinetic Homepage", action:
        #selector(AppDelegate.kineticHomepage(_:)), keyEquivalent: ""))
        //quit app
    menu.addItem(NSMenuItem(title: "Quit", action:
        #selector(NSApplication.terminate(_:)), keyEquivalent: "q"))
    
    statusItem.menu = menu
    
    
}
}

Launcher Application:

import Cocoa
//extension variable for launcher to kill launcher

extension Notification.Name {
static let killLauncher = Notification.Name("killLauncher")
}

@NSApplicationMain
class HelperAppDelegate: NSObject {

//terminate object
@objc func terminate(){
    NSApp.terminate(nil)
}

}

extension HelperAppDelegate: NSApplicationDelegate{

func applicationDidFinishLaunching(_ aNotification: Notification) {
    // Insert code here to initialize your application
 //main app identifier
    let mainAppIdentifier = "Kinetic.KTG-Helper"
    let runningApps = NSWorkspace.shared.runningApplications
    let isRunning = !runningApps.filter { $0.bundleIdentifier == mainAppIdentifier }.isEmpty
    //if app is running kill launcher entity and reset status of killlauncher
    if !isRunning {
        DistributedNotificationCenter.default().addObserver(self, selector: #selector(self.terminate), name: .killLauncher, object: mainAppIdentifier)
        
        let path = Bundle.main.bundlePath as NSString
        var components = path.pathComponents
        components.removeLast(3)
        components.append("MacOS")
        components.append("KTG Helper")
        
        let newPath = NSString.path(withComponents: components)
        
        NSWorkspace.shared.launchApplication(newPath)
    }
    else{
        self.terminate()
    }
}

//func applicationWillTerminate(_ aNotification: Notification) {
    // Insert code here to tear down your application
//}
}
0

There are 0 best solutions below