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
//}
}