24

How do I programmatically set an application bundle on Mac OS X to run when the user logs in?

Basically, the equivalent of the HKCU\Software\Microsoft\Windows\CurrentVersion\Run registry key in Windows.

Jake Petroules
  • 23,472
  • 35
  • 144
  • 225

5 Answers5

18

You can add the application to the user's "Login Items" (under System Preferences=>Accounts=[user]) or you can add a launchd agent to the user's ~/Library/LaunchAgents folder (see man launchd.plist). Use ~/Library/LaunchDaemons/ if your app has no user-facing UI. As others point out, launchd gives you a lot of control over when the app starts, what happens if the app quits or crashes, etc. and is most appropriate for "daemon" style apps (with our without UI).

The first option (Login Items) can be manipulated programmatically (link from Gordon).

Community
  • 1
  • 1
Barry Wark
  • 107,306
  • 24
  • 181
  • 206
10

A working example is shown below. It's based on this post.

Create file ~/Library/LaunchAgents/my.everydaytasks.plist with the following contents:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>Label</key>
    <string>my.everydaytasks</string>
    <key>ProgramArguments</key>
    <array>
        <string>/Applications/EverydayTasks.app/Contents/MacOS/EverydayTasks</string>
    </array>
    <key>ProcessType</key>
    <string>Interactive</string>
    <key>RunAtLoad</key>
    <true/>
    <key>KeepAlive</key>
    <false/>
</dict>
</plist>

To test you need to run this in terminal

launchctl load -w ~/Library/LaunchAgents/my.everydaytasks.plist

To unload

launchctl unload -w ~/Library/LaunchAgents/my.everydaytasks.plist

For more information, see launchd.plist man page and Creating Launch Daemons and Agents.

A Shared File List is another way of adding your application to startup using "Login Items". See this example on how to implement it.

Y. E.
  • 687
  • 1
  • 10
  • 29
Dmitriy
  • 5,357
  • 8
  • 45
  • 57
  • Suppose that I have a parameter of the .list file as a dynamic value generated in the application install moment. How can I pass/send it? – Marcelo Sep 10 '14 at 20:03
6

The "correct" method is to create a LaunchAgent for processes you want to start at login that may have a UI and a LaunchDaemon for those that should be pure background processes. In your installer drop your plist into the correct folder, either for the user, or all users, or the system. The reason this method is superior is because you can use launchd to control how your process is run including the built-in ability to make sure it keeps running even if it crashes or is killed by the user.

Jeremy
  • 849
  • 1
  • 7
  • 11
  • This isn't a critical application that *must* be kept running, it's just for a convenience option in the preferences dialog. Are you saying it'll keep running the application if users exit it? I don't want that to happen. – Jake Petroules Jul 29 '10 at 23:02
  • No, I'm saying that's an option when you use LaunchDaemons. You can choose how you want the system to run your application. – Jeremy Jul 30 '10 at 21:02
4

Wanted to throw this out here for anyone using Qt / C++. Qt makes it super easy to use plists through the QSettings class. Check out this code snippet from a sample dummy application.

void MainWindow::readPlist()
{
    QSettings settings(appPlistPath, QSettings::NativeFormat);
    QVariant value = settings.value("mykey");
    QMessageBox::information(this, "Your Value", value.toString());
}

void MainWindow::addPlistEntry()
{
    QSettings settings(appPlistPath, QSettings::NativeFormat);
    settings.setValue("mykey", "myvalue");
}

void MainWindow::removePlistEntry()
{
    QSettings settings(appPlistPath, QSettings::NativeFormat);
    settings.remove("mykey");
}
jocull
  • 20,008
  • 22
  • 105
  • 149
1

You can also achieve this by invoking System Events via osascript. This should do the trick:

struct LaunchAtStartupHelper {
    static var isEnabled: Bool {
        get {
            shell(
                """
                osascript -e 'tell application "System Events" to get the name of every login item'
                """)
                .contains("MyAppName")
        }
        set {
            if newValue {
                shell(
                    """
                    osascript -e 'tell application "System Events" to make login item at end with properties {path:"/Applications/MyAppName.app", hidden:true}'
                    """)
            } else {
                shell(
                    """
                    osascript -e 'tell application "System Events" to delete login item "MyAppName"'
                    """)
            }
        }
    }
    // from https://stackoverflow.com/a/50035059/1072846
    @discardableResult
    private static func shell(_ command: String) -> String {
        let task = Process()
        let pipe = Pipe()

        task.standardOutput = pipe
        task.standardError = pipe
        task.arguments = ["-c", command]
        task.launchPath = "/bin/zsh"
        task.launch()

        let data = pipe.fileHandleForReading.readDataToEndOfFile()
        let output = String(data: data, encoding: .utf8)!

        return output
    }
}

Eric
  • 16,003
  • 15
  • 87
  • 139