Page 1 of 1

How to autostart an application on Mac?

Posted: Fri Nov 20, 2015 10:19 am
by Kukulkan
Hi,

I created a sync tool using PB and this should get started each time a user is logging in to his MacOS. I found several articles about Creating "Launch Daemons and Agents" or "Login Items" but they seem to initiate during startup without a user logged in. But I want it to run after login.

Does anyone know how I make my program autostart with user login? And, best, closing if user logs off? Maybe by simply adding a start script to some dedicated folder?

I'm using the MacOS PackageMaker to create a PKG package. Can I do it from there or should I do it using PB code?

Kukulkan

Re: How to autostart an application on Mac?

Posted: Fri Nov 20, 2015 10:28 am
by wilbert

Re: How to autostart an application on Mac?

Posted: Fri Nov 20, 2015 12:34 pm
by Kukulkan
Thanks, but currently I'm playing with ~/Library/LaunchDaemons/myFile.plist for launchd usage.

I think I can create this file from my applications user context during the first start. I'll see...

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 12:08 am
by deseven
I'm currently trying to solve it.

First, i created a helper application (nothing too complicated here):

Code: Select all

launchApp.s = GetPathPart(ProgramFilename()) + "../../../../../../"
SetCurrentDirectory(launchApp)
launchApp = GetCurrentDirectory()
RunProgram("open",~"-a \"" + launchApp + ~"\"","")
I edited the plist for it like that:

Code: Select all

  <key>LSUIElement</key>
  <true/>
  <key>LSBackgroundOnly</key>
  <true/>
And of course put it into Contents/Library/LoginItems of the main app.

The helper application launches main app correctly if i start it manually.

Then, in main app:

Code: Select all

ImportC "/System/Library/Frameworks/ServiceManagement.framework/ServiceManagement"
  CFStringCreateWithCharacters(allocator,chars.p-unicode,numChars)
  SMLoginItemSetEnabled(*identifier,enabled.b)
EndImport

Procedure CFStringCreate(s.s)
  ProcedureReturn CFStringCreateWithCharacters(#Null,s,Len(s))
EndProcedure

SMLoginItemSetEnabled(CFStringCreate("bundle.id.of.helper.app"),1)
It returns 1 as a result, but there is no new login item in system preferences and nothing is launching when i'm logging in.

Can anyone help?

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 12:12 am
by Keya
good question, i'd like to know how too!
could it simply be a matter of copying the main executable (or a tiny executable or script that does nothing more than execute the main one and quit) to /Library/StartUpItems ?
http://macpaw.com/how-to/remove-startup-items-in-osx

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 7:14 am
by Kukulkan
This is just an extract and untested, but we managed it using this code:

Code: Select all

Procedure GlobalLog(LogType.i, LogMessage.s)
  ; Just a placeholder for our extended logging
  Debug LogMessage.s
EndProcedure

; run an executable and return its console output
Procedure.s runCmd(program.s, params.s, wd.s = "", lf.s = #LineBreak, 
                   Hidden.b = #False)
  Protected Compiler.i
  If Hidden.b = #True
    Compiler.i = RunProgram(program.s, params.s, wd.s, 
                            #PB_Program_Open|#PB_Program_Read|#PB_Program_Hide)
  Else
    Compiler.i = RunProgram(program.s, params.s, wd.s, 
                            #PB_Program_Open|#PB_Program_Read)
  EndIf
  Protected Output.s = ""
  If Compiler.i
    While ProgramRunning(Compiler.i)
      Protected LineLength.i = AvailableProgramOutput(Compiler.i)
      If LineLength.i > 0
        Protected *buffer = AllocateMemory(LineLength.i)
        ReadProgramData(Compiler.i, *buffer, LineLength.i)
        Protected Line.s = PeekS(*buffer, LineLength.i, #PB_Ascii)
        FreeMemory(*buffer)
        Output.s + Line.s + lf.s
      EndIf
    Wend
    CloseProgram(Compiler.i) ; Close the connection to the program
  Else
    GlobalLog(#RF_LOG_CRIT, "Failed running ["+program.s+"].")
  EndIf
  ProcedureReturn Output.s
EndProcedure

Procedure EnableAutostart(ProgramName.s, Param1.s = "", Param2.s = "", Param3.s = "")
  CompilerIf #PB_Compiler_Debugger = 1
    ProcedureReturn
  CompilerEndIf 
  
  ; dont try this in debug mode as the ide executable should not start
  Protected Path.s = GetHomeDirectory() + "Library/LaunchAgents/"
  If FileSize(Path.s) <> -2
    GlobalLog(#RF_LOG_INFO, "Create autostart folder [" + Path.s + "].")
    CreateDirectory(Path.s)
  EndIf
  Path.s = Path.s + ProgramName.s + ".plist"
  If FileSize(Path.s) > 0
    GlobalLog(#RF_LOG_VERB, "Autostart entry allready created. Leave EnableAuostart() function. Path: [" + Path.s + "]")
    ProcedureReturn
  EndIf
  Protected Content.s = "<?xml version=\q1.0\q encoding=\qUTF-8\q?>"+#LineBreak+
                        "<!DOCTYPE plist PUBLIC \q-//Apple//DTD PLIST 1.0//EN\q \qhttp://www.apple.com/DTDs/PropertyList-1.0.dtd\q>"+#LineBreak+
                        "<plist version=\q1.0\q>"+#LineBreak+
                        "<dict>"+#LineBreak+
                        "<key>Disabled</key>"+#LineBreak+
                        "<false/>"+#LineBreak+
                        "<key>KeepAlive</key>"+#LineBreak+
                        "<false/>"+#LineBreak+
                        "<key>Label</key>"+#LineBreak+
                        "<string>"+ProgramName.s+"</string>"+#LineBreak+
                        "<key>LimitLoadToSessionType</key>"+#LineBreak+
                        "<array>"+#LineBreak+
                        "        <string>Aqua</string>"+#LineBreak+
                        "</array>"+#LineBreak+
                        "<key>ProgramArguments</key>"+#LineBreak+
                        "<array>"+#LineBreak+
                        "        <string>"+ProgramFilename()+"</string>"+#LineBreak
  If Param1.s <> "": Content.s + "        <string>"+Param1.s+"</string>"+#LineBreak : EndIf
  If Param2.s <> "": Content.s + "        <string>"+Param2.s+"</string>"+#LineBreak : EndIf
  If Param3.s <> "": Content.s + "        <string>"+Param3.s+"</string>"+#LineBreak : EndIf
            Content.s + "</array>"+#LineBreak+
                        "<key>RunAtLoad</key>"+#LineBreak+
                        "<true/>"+#LineBreak+
                        "<key>WorkingDirectory</key>"+#LineBreak+
                        "<string>"+GetPathPart(ProgramFilename())+"</string>"+#LineBreak+
                        "</dict>"+#LineBreak+
                        "</plist>"
  
  Content.s = ReplaceString(Content.s, "\q", Chr(34))
  
  Protected f.i = OpenFile(#PB_Any, Path.s)
  If f.i <> 0
    WriteString(f.i, Content.s, #PB_UTF8)
    CloseFile(f.i)
    GlobalLog(#RF_LOG_VERB, "Wrote autostart entry to ["+Path.s+"].")
    Protected res.s = runCmd("launchctl", "load " + Chr(34) + Path.s + Chr(34), "")
    GlobalLog(#RF_LOG_VERB, "launchctl result: ["+res.s+"].")
  Else
    GlobalLog(#RF_LOG_CRIT, "Failed writing autostart entry to ["+Path.s+"].")
  EndIf
  
EndProcedure

Procedure DisableAutostart(ProgramName.s)
  CompilerIf #PB_Compiler_Debugger = 0
    Protected Path.s = GetHomeDirectory() + "Library/LaunchAgents/"
    Path.s = Path.s + ProgramName.s + ".plist"
    If FileSize(Path.s) < 0
      ProcedureReturn
    EndIf
    
    RunProgram("launchctl", "unload " + Chr(34) + Path.s + Chr(34), "")
    
    DeleteFile(Path.s)
  CompilerEndIf 
EndProcedure
But I remember there was a permissions problem. It does not work in some case if there are multiple users created on the mac or so...

Best,

Kukulkan

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 12:01 pm
by deseven
Thanks Kukulkan, it works!

I've arranged your code to a procedure with some small additions and moved plist out of the code to be able to edit it in a more convenient way.

Code: Select all

#loginItemPlist = ~"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
~"<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n" +
~"<plist version=\"1.0\">\n" +
~"<dict>\n" +
~"\t<key>Label</key>\n" +
~"\t<string>{appid}</string>\n" +
~"\t<key>ProgramArguments</key>\n" +
~"\t<array>\n" +
~"\t\t<string>/usr/bin/open</string>\n" +
~"\t\t<string>{apppath}</string>\n" +
~"\t</array>\n" +
~"\t<key>RunAtLoad</key>\n" +
~"\t<true/>\n" +
~"\t<key>KeepAlive</key>\n" +
~"\t<false/>\n" +
~"\t<key>LimitLoadToSessionType</key>\n" +
~"\t<string>Aqua</string></dict>\n" +
~"</plist>"

Procedure.b enableLoginItem(bundleID.s,state.b)
  Protected loginItemsPath.s = GetHomeDirectory() + "Library/LaunchAgents/"
  Protected loginItemPath.s = loginItemsPath + bundleID + ".plist"
  Protected bundlePathPtr = CocoaMessage(0,CocoaMessage(0,CocoaMessage(0,0,"NSBundle mainBundle"),"bundlePath"),"UTF8String")
  If bundlePathPtr
    Protected bundlePath.s = PeekS(bundlePathPtr,-1,#PB_UTF8)
  Else
    ProcedureReturn #False
  EndIf
  If state
    If FileSize(loginItemsPath) <> -2
      If Not CreateDirectory(loginItemsPath)
        ProcedureReturn #False
      EndIf
    EndIf
    Protected loginItemFile = CreateFile(#PB_Any,loginItemPath)
    If IsFile(loginItemFile)
      Protected loginItemPlist.s = ReplaceString(#loginItemPlist,"{appid}",bundleID)
      loginItemPlist = ReplaceString(loginItemPlist,"{apppath}",bundlePath)
      WriteString(loginItemFile,loginItemPlist,#PB_UTF8)
      CloseFile(loginItemFile)
      RunProgram("launchctl",~"load \"" + loginItemPath + ~"\"","")
    Else
      ProcedureReturn #False
    EndIf
  Else
    If FileSize(loginItemPath) <> -1
      RunProgram("launchctl",~"unload \"" + loginItemPath + ~"\"","")
      If Not DeleteFile(loginItemPath,#PB_FileSystem_Force)
        ProcedureReturn #False
      EndIf
    EndIf
  EndIf
  ProcedureReturn #True
EndProcedure

; usage: enableLoginItem("com.example.myapp",#True)
tested on 10.8 and 10.11

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 2:45 pm
by Keya
awesome! from my reading though (disclaimer: mac newbie) i would've thought /Library/StartUpItems/ would be better for general apps than /Library/LaunchAgents/ ? LaunchAgents seemed more for low-level daemons?

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 3:10 pm
by deseven
It isn't /Library/LaunchAgents/, it's ~/Library/LaunchAgents/ actually :)
I don't see StartUpItems in my user Library dir anyway.

Re: How to autostart an application on Mac?

Posted: Fri Sep 09, 2016 3:39 pm
by Keya
on El Capitan i have:
/Library/LaunchAgents, with just com.vmware.launchd.vmware-tools-userd.plist
/Library/LaunchDaemons, with just com.vmware.launchd.tools.plist
/Library/StartupItems, which is empty
but none of those directories seem to exist in my ~/Library/ (/Users/admin/Library/)

Re: How to autostart an application on Mac?

Posted: Sun Sep 11, 2016 11:45 am
by deseven
Image

Anyway, i think that the SMLoginItemSetEnabled() method is the most preferable way to do that. But i wasn't able to solve it yet.

Re: How to autostart an application on Mac?

Posted: Thu Dec 16, 2021 10:32 am
by StanleySWells
I want to learn it as well! I am always afraid that removing these startup programs by /Library/StartUpItems all by myself may result in broken login links because I am not highly technical...