How to autostart an application on Mac?

Mac OSX specific forum
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

How to autostart an application on Mac?

Post 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
wilbert
PureBasic Expert
PureBasic Expert
Posts: 3870
Joined: Sun Aug 08, 2004 5:21 am
Location: Netherlands

Re: How to autostart an application on Mac?

Post by wilbert »

Windows (x64)
Raspberry Pi OS (Arm64)
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: How to autostart an application on Mac?

Post 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...
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: How to autostart an application on Mac?

Post 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?
Last edited by deseven on Fri Sep 09, 2016 12:19 am, edited 1 time in total.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: How to autostart an application on Mac?

Post 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
User avatar
Kukulkan
Addict
Addict
Posts: 1352
Joined: Mon Jun 06, 2005 2:35 pm
Location: germany
Contact:

Re: How to autostart an application on Mac?

Post 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
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: How to autostart an application on Mac?

Post 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
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: How to autostart an application on Mac?

Post 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?
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: How to autostart an application on Mac?

Post by deseven »

It isn't /Library/LaunchAgents/, it's ~/Library/LaunchAgents/ actually :)
I don't see StartUpItems in my user Library dir anyway.
User avatar
Keya
Addict
Addict
Posts: 1891
Joined: Thu Jun 04, 2015 7:10 am

Re: How to autostart an application on Mac?

Post 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/)
User avatar
deseven
Enthusiast
Enthusiast
Posts: 362
Joined: Wed Jan 12, 2011 3:48 pm
Location: Serbia
Contact:

Re: How to autostart an application on Mac?

Post 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.
StanleySWells
New User
New User
Posts: 1
Joined: Thu Dec 16, 2021 9:57 am

Re: How to autostart an application on Mac?

Post 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...
Keep going!
Post Reply