GithubHelp home page GithubHelp logo

Comments (11)

longseespace avatar longseespace commented on September 1, 2024 1

I tried this one and it seems to work: https://hisaac.net/how-to-detect-if-your-macos-app-was-launched-as-a-login-item/

from launchatlogin.

krishsatya avatar krishsatya commented on September 1, 2024 1

I was able to implement the solution linked to by @longseespace. Link has changed though: https://hisaac.net/blog/how-to-detect-if-your-macos-app-was-launched-as-a-login-item/.

from launchatlogin.

cbjeukendrup avatar cbjeukendrup commented on September 1, 2024

Another solution could be to launch the main app with a command line flag (for example "LaunchedAtLogin", so that it can look if it was launched with that flag. It would be great if the name of that flag is customizable.

from launchatlogin.

sindresorhus avatar sindresorhus commented on September 1, 2024

@cbjeukendrup That's the obvious solution, yes, but unfortunately doesn't work with sandboxing:

If the calling process is sandboxed, the system ignores the value of this property. - https://developer.apple.com/documentation/appkit/nsworkspace/openconfiguration/3172708-arguments

from launchatlogin.

cbjeukendrup avatar cbjeukendrup commented on September 1, 2024

Ah, sorry, missed that one. That makes it a lot more complicated...

from launchatlogin.

gpoitch avatar gpoitch commented on September 1, 2024

I was able to do this by creating an App Group and in the launcher set:

UserDefaults(suiteName: "[TeamId][AppGroupId]")?.set(true, forKey: "didLaunchAtLogin")

Then in the main app, read that value then clear it.

It might not be feasible for a drop in library like this to set that up, but at least it's a simple solution.

from launchatlogin.

sindresorhus avatar sindresorhus commented on September 1, 2024

@gpoitch That's a good workaround. Thanks for sharing. However, I don't want to require App Group for LaunchAtLogin users, so I think the more complicated solution is a better fit for LaunchAtLogin.

from launchatlogin.

technusm1 avatar technusm1 commented on September 1, 2024

I took the approach opposite to that of @sindresorhus by having the main app asking the helper app to terminate. IMO, this approach ensures that both the main and helper app are initialized properly and only then, can exchange messages.

I used the simple DistributedNotificationCenter mechanism, which seems to work well for my case so far. Here's my code:

In LaunchAtLogin enum:

// Requires AppKit to be imported
public static let wasLaunchedOnLogin: Bool = {
        let bundleIdentifier = Bundle.main.bundleIdentifier!
        let mainBundleIdentifier = "\(bundleIdentifier)-LaunchAtLoginHelper"
        // If helper is not running
        if NSRunningApplication.runningApplications(withBundleIdentifier: mainBundleIdentifier).isEmpty {
            return false
        } else {
            // Helper is running, ask it to terminate
            let nc = DistributedNotificationCenter.default()
            nc.post(name: NSNotification.Name("TerminateHelper"), object: nil)
            return true
        }
}()

AppDelegate class in LaunchAtLoginHelper:

final class AppDelegate: NSObject, NSApplicationDelegate {
	let nc = DistributedNotificationCenter.default()
	
	@objc func terminateApp() {
		NSLog("LaunchAtLogin helper terminateApp called")
		nc.removeObserver(self)
		NSApp.terminate(nil)
	}
	
	func applicationDidFinishLaunching(_ notification: Notification) {
		let bundleIdentifier = Bundle.main.bundleIdentifier!
		let mainBundleIdentifier = bundleIdentifier.replacingOccurrences(of: #"-LaunchAtLoginHelper$"#, with: "", options: .regularExpression)

		// Ensures the app is not already running.
		guard NSRunningApplication.runningApplications(withBundleIdentifier: mainBundleIdentifier).isEmpty else {
			NSApp.terminate(nil)
			return
		}

		let pathComponents = (Bundle.main.bundlePath as NSString).pathComponents
		let mainPath = NSString.path(withComponents: Array(pathComponents[0...(pathComponents.count - 5)]))
		NSWorkspace.shared.launchApplication(mainPath)
		nc.addObserver(self, selector: #selector(terminateApp), name: NSNotification.Name("TerminateHelper"), object: nil)
		NSLog("LaunchAtLogin helper exited")
	}
}

This seems to work on my sandboxed app running on my developer MacBook Pro. Any feedback is appreciated.

from launchatlogin.

sindresorhus avatar sindresorhus commented on September 1, 2024

The tricks mentioned in this thread will no longer work with the new API for launch at login in macOS 13: https://developer.apple.com/documentation/servicemanagement/smappservice/3945412-mainapp?changes=latest_minor

So we need some other ideas.

Please also submit feedback to Apple (using the Feedback Assistant app) that there needs to be an API for this. Like I have done in: feedback-assistant/reports#337

from launchatlogin.

technusm1 avatar technusm1 commented on September 1, 2024

@sindresorhus I agree that a helper app is no longer required and with new API, one could simply register mainAppService to be launched at login, but I think it would still be necessary for this use case, as the API doesn't provide any way to differentiate between automatic login launch and manual launches.

To that end, I've explored Apple's documentation on sandboxed applications and came across this peculiar bit on this link:

With App Sandbox, you can receive Apple events and respond to Apple events, but you cannot send Apple events to arbitrary apps.

I found an answer on StackOverflow related to Apple events: https://stackoverflow.com/a/19890943/4385319

And here's what I've tried so far in my main app:
In my AppDelegate, I added the following code:

func applicationWillFinishLaunching(_ notification: Notification) {
        NSAppleEventManager.shared().setEventHandler(self, andSelector: #selector(AppDelegate.handleEvent(_:withReplyEvent:)), forEventClass: AEEventClass(kCoreEventClass), andEventID: AEEventID(kAEOpenApplication))
}

Now my trouble begins:

@objc func handleEvent(_ event: NSAppleEventDescriptor!, withReplyEvent: NSAppleEventDescriptor!) {
    // I'm trying to compare the following two things (as per the answer found on StackOverflow):
    // event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue
    // keyAELaunchedAsLogInItem
}

So far, the expression event?.paramDescriptor(forKeyword: keyAEPropData)?.enumCodeValue returns nil when my app is launched at login (using LaunchAtLogin framework on macOS 12) and is non-nil optional value otherwise (when I manually open it).

To those who have deeper knowledge on this topic, here's my question: Is checking the above expression for nil and non-nil a reliable way to say that my app was launched at login or not?

Apologies if I missed something.

EDIT: Upon further testing, I can see that whenever the packaged app is launched from:

  • Finder/Autologin/Xcode: Expression is nil
  • Launchpad/Spotlight: Expression is non-nil

I have no words here... 😖

from launchatlogin.

krishsatya avatar krishsatya commented on September 1, 2024

I see this thread has gone quiet but I'll add a not-so-great option to the mix. Perhaps we could use the time since last login as a proxy for whether the app was launched at login or not. It is by no means perfect, however, if the OS doesn't emit an event when an app is started at login, what reliable options are available?

from launchatlogin.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.