What is the best method to launch automatically an app after login of users. From macOS 11 and with API 2 recent version of Xojo.
If you open System Preferences and open the “Users and Groups” item. Click on your account and then the Login Items tab. You can add applications to that list and they will run when you login.
Right click on the dock icon,
Select Options/Open at login
I’m looking to do it programmatically.
You would have to create a launchDemon plist file. Permission write it outside of an installer would be tricky.
If you use MBS Xojo Plugins, check the LSSharedFileListMBS class.
I expect it to still work for login items.
The easiest way to create a LaunchDemon is with the app Lingon (Lingon - Peter Borg Apps). It costs 10 $. Afterwards you can create the file in code.
I think that starting with Ventura LaunchDemons can come within the app. LaunchDemons are loaded and unloaded with the Terminal command launchctl. The command changed ages ago but most websites still describe the old version and not the new one.
It’s also trivial to make a Xojo app to handle all this (additionally showing the authentication dialog with the MBS plugin).
I made long time ago with the help of guys on a French forum an AppleScript which I call from Xojo with this code:
If CheckBoxLogItems.Value Then ' I put my App in the Logins items
PosLogItemSt = AjEffLoginItems(CetAppShPath, "Le_Creer") ' Il n'existait pas, on le crée
Else ' I remove my App from the Logins items
PosLogItemSt = AjEffLoginItems(CetAppShPath, "L_Effacer") ' Il existait, on l'efface
End If
.
With
CetAppShPath = App.ExecutableFile.Parent.Parent.Parent.NativePath
.
.
The AppleScript:
-- Même procédures dans MemoDate et LaunchOnceAday pour AjEffLoginItems et LogItExists
on run {item1T, QueFaireT} -- C'est du texte, "Le_Creer" créera le Login Item si n'existe pas et "L_Effacer" l'effacera s'il existe, si vide ne fera que renvoyer si existe ou non par sa position (-1 n'existe pas) , "Test" le créera si n'existe pas et l'effacera sinon
(* Pour Test
tell application "Finder"
set item1 to choose file of type "APPL" with prompt "Choose the Application :" default location (path to applications folder)
if item1 exists then
-- set item1Nom to displayed name of item1 as text
set item1T to (POSIX path of item1) as text -- Note : item1 est un alias quoted form of
if ((characters -1 thru -1 of item1T) as text) is "/" then set item1T to (characters 1 thru -2 of item1T) as text
end if
set QueFaireT to "Test"
end tell -- Finder
activate -- Editeur AppleScript
*)
set CeLogItNoT to (AjEffLoginItems(item1T, QueFaireT) of me) as text
CeLogItNoT -- A pour effet de renvoyer cette valeur à RealBasic On est obligé de retourner du texte
end run
on AjEffLoginItems(CeShPath, QueFaire) -- ATTENTION : parfois (pour les applications notemment), il y a un '/' à la fin du path et parfois non. Ca pose problème car pas identique
tell application "System Events"
try
set NbLoginItems to count of login items
on error
set NbLoginItems to -1
end try
end tell
set CeLogItNo to LogItExists(CeShPath, NbLoginItems) of me -- , item1Nom
-- display dialog "Ce Login Item existe au n° : " & CeLogItNo & " (-1 si n'existe pas)." & return & "Path : '" & CeShPath & "' , class : " & (class of CeShPath) & return & "QueFaire : " & QueFaire -- & return & "Nom : '" & item1Nom & "'"
if CeLogItNo is -1 then -- Le Login Item n'existe pas
if (QueFaire is "Le_Creer") or (QueFaire is "Test") then
tell application "System Events"
try
make new login item at end of login items with properties {path:CeShPath, hidden:false} -- , kind:volume}
set CeLogItNo to NbLoginItems + 1 -- Puisque créé en dernière position
on error
-- CeLogItNoreste Ă -1
end try
end tell
-- else alors c'est "L_Effacer" et on pourrait faire une alerte pour dire que pas logique puisque n'existait pas
end if
else -- Le Login Item existe
if (QueFaire is "L_Effacer") or (QueFaire is "Test") then
tell application "System Events"
try
delete login item CeLogItNo
on error
-- CeLogItNoreste est quand mĂŞme mis Ă -1
end try
set CeLogItNo to -1
end tell
-- else alors c'est "Le_Creer" et on pourrait faire une alerte pour dire que pas logique puisque existait dĂ©jĂ
end if
end if
if QueFaire is "Test" then
tell application "System Preferences" to activate
delay 1 -- secondes
activate -- Finder
end if
return CeLogItNo
end AjEffLoginItems
on LogItExists(CeShPath, NbLogIt) -- , CeNom
set NoLogIt to -1 -- N'existe pas
tell application "System Events"
-- set TsNomLogin to the name of every login item
-- TsNomLogin
-- set TsPathLogin to the path of every login item
-- TsPathLogin
-- tell application "Finder" to display dialog "Nb Log Items = " & NbLogIt as text
repeat with iNbre from 1 to NbLogIt
-- display dialog ("'" & (name of login item iNbre) as text) & "'" & return & "'" & ((path of login item iNbre) as text) & "'"
if (((path of login item iNbre) as text) is CeShPath) then
-- display dialog ("'" & (name of login item iNbre) as text) & "' existe." & return & "iNbre = " & (iNbre as text)
set NoLogIt to iNbre
exit repeat
end if
end repeat
(* Je ne fais pas ça car si jamais il y a 2 éléments avec le même nom mais un path différent
if login item CeNom exists then
-- display dialog ((path of login item CeNom) as text) & return & CeShPath
set DrapLogItExists to (((path of login item CeNom) as text) is CeShPath) as boolean
else
set DrapLogItExists to false
end if *)
end tell
return NoLogIt
end LogItExists
(*
tell application "System Events"
-- LOGIN ITEMS
get the properties of every login item
-- {{class:login item, path:"/Users/sal/Desktop/View Remote Screen.app", hidden:false, kind:"Application", name:"View Remote Screen"}}
-- Adding a login item for the current user
make new login item at end of login items with properties {path:"/Applications/Dictionary.app", hidden:false}
end tell
*)
Yep, this still works. I use it in a couple of applications to make launch on login available as a preference within the app itself.
I’ve been looking into the LSSharedFileListMBS class - but how do I tell where the app is, if it’s not in the Applications folder? If it’s launched from the Downloads or Desktop folder, or the user moves it, the first line of code below won’t work.
Is there a way for the app to find out where it resides?
Dim app As FolderItem = SpecialFolder.Applications.Child("MyApplication.app")
// get list object
Dim l As New LSSharedFileListMBS(LSSharedFileListMBS.kSessionLoginItems)
// insert file
Dim item As LSSharedFileListItemMBS = l.InsertFile(l.kLSSharedFileListItemBeforeFirst, "MyApplication", Nil, app)
// check error
If l.Lasterror = 0 Then
MsgBox "OK"
Else
MsgBox "Failed: "+Str(l.Lasterror)
End If
See app.executableFile, where you may need to use .parent on to find the app bundle.
I missed the “.parent” part until now. In my case, it was “app.executableFile.parent.parent.parent” to get to the main app.
Terminal is no longer launching on startup, and the app is.
Is this still the preferred method of doing this?
Understanding that I could very much be wrong, I’m going to say that I thought using ServiceManagement was the official Apple “blessed” method of launching an app at login and that’s what I’ve been using in my app. Unfortunately it does not seem to work anymore in Ventura (I don’t know if it doesn’t actually work, but it’s throwing errors for me).
Using LSSharedFileList works, but always shows the notification to the user (which I understand is intentional on Apple’s part, but if a user turns my preference off and then back on again, it shows the notification again).
I’m not trying to get people going on Apple’s choices here, I just want to know what the officially “blessed” method of doing this is.
Thank you!
I have some apps in System Settings that use LSSharedFileList and there was no notification.
The officially blessed Apple way is to use SMAppServiceMBS. But only SMAppServiceMBS.openSystemSettingsLoginItems works fine.
I will continue to use the LaunchAgent written to the LaunchAgent folder with the caveat that the LaunchAgent needs a reference to the parent app. Otherwise, the app may show up with the name of the developer and not the app name.
I’m using SMAppService via declares (not a plugin) and it’s working for me.
It’s actually a really nice API, and a clear example of where charge does actually equal progress (unlike 2.0 All The Things). It is a massive improvement in terms of required code and situations to handle, when compared to the method deprecated in 10.11 or the kludgy workaround of using a LaunchAgent.
There are some things to note.
- It requires macOS 13.
- It doesn’t report an error if the application is Translocated, but it won’t work. So check this yourself.
- It does provide a notification to the user when the application is added.
- Apps get added to the Login Items list, not the background apps list.
Below is a code snippet from the action event of the OAKLaunchOnLogin checkbox class I’m using.
Dim nsError as integer
if me.value then
if SMAppService_registerAndReturnError( mySMAppServiceApp, nserror ) = false then
MsgBoxError "Unable to register for login", NSError_localizedDescription( nsError ), currentMethodName
end if
else
if SMAppService_unregisterAndReturnError( mySMAppServiceApp, nserror ) = false then
MsgBoxError "Unable to unregister for login", NSError_localizedDescription( nsError ), currentmethodName
end if
end if
updating = true
select case SMAppService_status( mySMAppServiceApp )
case SMAppServiceStatus.enabled
me.value = true
case SMAppServiceStatus.notFound
me.value = false
MsgBox "Service Not found"
case SMAppServiceStatus.notRegistered
// --- This fires when app is unregistered, but we only need to alert user if we're not expecting this message.
if me.value then
me.value = false
msgbox "Service Not Registered"
end if
case SMAppServiceStatus.requireApproval
SMAppService_openSystemSettingsLoginItems( NSClassFromString( "SMAppService" ) )
else
break
end select
updating = false
Have you a public sample code for this ?
I no longer make my code public since a bunch of people got really angry with me for not providing a API 2.0 / DesktopControls version of my code. The translation and support of a API 1.0 version and a API 2.0 / DesktopControls version simply isn’t worth the time.
Sigh… it’s not that complicated to switch to API 2. We can wish as much as we want that this is not necessary. I commented the code out I don’t use and converted the rest. I have started preparing for DesktopControls.
DesktopControls are the main problem.