Thanks for being a part of WWDC25!

How did we do? We’d love to know your thoughts on this year’s conference. Take the survey here

SMAppService - How does this work?

I'm a developer using Lazarus Pascal, so converting ObjC and Swift comes with its challenges.

I'm trying to figure how to properly use SMAppService to add my application as a login item for the App Store.

I have learned that the old method (< macOS 13) uses a helper tool, included in the app bundle, which calls the now deprecated SMLoginItemSetEnabled. Now this is already quite a pain to deal with if you're not using XCode, not to mention converting the headers being rather complicated when you're not experienced with doing this.

The "new" method (as of macOS 13) is using SMAppService. Can anyone explain how to use this? The documentation (for me anyway) is a not very clear about that and neither are examples that can be found all over the Internet.

My main question is:

Can I now use the SMAppService functions to add/remove a login item straight in my application, or is a helper tool still required?

Answered by DTS Engineer in 830704022
I'm a developer using

Nice! I was one of the last Pascal holdouts on traditional Mac OS. I only moved to C when I joined Apple. I was never happy with that, and that’s one of the reasons why I’m a huge fan of Swift.

The "new" method (as of macOS 13) is using SMAppService. Can anyone explain how to use this?

Sure. I’m gonna focus on Swift, because that’ll be the most useful to a general audience, but I’ll come back to your specific tools later on.

To add your app as a login item using SMAppService:

  1. Import the framework.

  2. Create a service for your own app.

  3. Call register() on it.

Here’s what that looks like:

import ServiceManagement

func registerAppServiceAsLoginItem() throws {
    let service = SMAppService.mainApp
    try service.register()
}

You do this straight from your app; there’s no indirection required.

Indeed, there was no indirection required by SMLoginItemSetEnabled either. I suspect that this indirection was introduced by your tooling.


Speaking of which, do your tools have a mechanism for calling native C code? This is often called a foreign function interface (FFI).

If so, my advice would be to write an Objective-C version of the above code [1], wrap that in a C function, and then call that from your app’s actual code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Here’s the C header:

#ifndef SMAppServiceRegister_h
#define SMAppServiceRegister_h

extern int registerAppServiceAsLoginItem(void);

#endif /* SMAppServiceRegister_h */

And here’s the Objective-C implementation:

#include "SMAppServiceRegister.h"

@import ServiceManagement;

extern int registerAppServiceAsLoginItem(void) {
    SMAppService * service = [SMAppService mainAppService];
    BOOL success = [service registerAndReturnError:NULL];
    return success;
}
Accepted Answer
I'm a developer using

Nice! I was one of the last Pascal holdouts on traditional Mac OS. I only moved to C when I joined Apple. I was never happy with that, and that’s one of the reasons why I’m a huge fan of Swift.

The "new" method (as of macOS 13) is using SMAppService. Can anyone explain how to use this?

Sure. I’m gonna focus on Swift, because that’ll be the most useful to a general audience, but I’ll come back to your specific tools later on.

To add your app as a login item using SMAppService:

  1. Import the framework.

  2. Create a service for your own app.

  3. Call register() on it.

Here’s what that looks like:

import ServiceManagement

func registerAppServiceAsLoginItem() throws {
    let service = SMAppService.mainApp
    try service.register()
}

You do this straight from your app; there’s no indirection required.

Indeed, there was no indirection required by SMLoginItemSetEnabled either. I suspect that this indirection was introduced by your tooling.


Speaking of which, do your tools have a mechanism for calling native C code? This is often called a foreign function interface (FFI).

If so, my advice would be to write an Objective-C version of the above code [1], wrap that in a C function, and then call that from your app’s actual code.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] Here’s the C header:

#ifndef SMAppServiceRegister_h
#define SMAppServiceRegister_h

extern int registerAppServiceAsLoginItem(void);

#endif /* SMAppServiceRegister_h */

And here’s the Objective-C implementation:

#include "SMAppServiceRegister.h"

@import ServiceManagement;

extern int registerAppServiceAsLoginItem(void) {
    SMAppService * service = [SMAppService mainAppService];
    BOOL success = [service registerAndReturnError:NULL];
    return success;
}

Thank you for the very clear and helpful assist! 😊

I may be able to convert the ObjC calls to Objective Pascal so I could call these functions directly (with some luck and patience). I assume that signing the application is a requirement when testing?

If I cannot get this to work, then I'll use your suggestion - let me tinker for a bit and see what I can get to work 😊 I'll come back here if I have a working solution - as it could be useful for others.

Note: The approach with helper tools seemed rather unnecessarily complicated, so it is nice to hear that I can do this straight in the main application. Things like this make it harder and harder to enjoy developing the little tools I make for fun. 😉

Thanks again for helping! It is very much appreciated.

Got it to work thanks to your tips and suggestions.

  1. Created a unit to interact with SMAppService:
unit ServiceManagementAppService;

{$mode objfpc}{$H+}
{$modeswitch objectivec1}
{$linkframework ServiceManagement}

interface

uses Classes, SysUtils, CocoaAll;

const
  SMAppServiceStatusNotRegistered	=0;
  SMAppServiceStatusEnabled		=1; 
  SMAppServiceStatusRequiresApproval	=2; 
  SMAppServiceStatusNotFound		=3;

  SmAppServiceStatusResult :
    Array [0..3] of string = 
                            ('Not registered',
                              'Enabled',
                              'Requires Approval',
                              'Not found' );  
type
  SMAppServiceStatus = NSInteger;
  
type
  SMAppService = objcclass external (NSObject)
  public
    function  registerAndReturnError(error: NSErrorPtr): objcbool;  message 'registerAndReturnError:';
    function  unregisterAndReturnError(error: NSErrorPtr): objcbool;  message 'unregisterAndReturnError:';
    function  status:SMAppServiceStatus; message 'status';
  end;

implementation

end.
  1. And in my main application for some random testing:
procedure TForm1.Button1Click(Sender: TObject);
var
  appService: SMAppService;
  StatusReturn : SMAppServiceStatus;
  Registered:boolean;
begin
  try
    // Create a service
    appService := SMAppService.new;

    // Get current status
    StatusReturn := appService.status;
    ShowMessage('Status: '+IntToStr(Integer(StatusReturn))+LineEnding+ SmAppServiceStatusResult[StatusReturn]);

    // Resgister service
    Registered := appService.registerAndReturnError(nil);
    ShowMessage('Registering result: '+BoolToStr(Registered,'Success','Failed'));

    // Get current status
    StatusReturn := appService.status;
    ShowMessage('Status: '+IntToStr(Integer(StatusReturn))+LineEnding+ SmAppServiceStatusResult[StatusReturn]);

    // Resgister service
    Registered := appService.unregisterAndReturnError(nil);
    ShowMessage('Unregistering result: '+BoolToStr(Registered,'Success','Failed'));

    // Get current status
    StatusReturn := appService.status;
    ShowMessage('Status: '+IntToStr(Integer(StatusReturn))+LineEnding+ SmAppServiceStatusResult[StatusReturn]);
  except
    Showmessage('fail');
  end;
end; 

Note:

  • while testing it seemed I needed to sign the app bundle of my application
  • I haven't tested this yet in the App Store (I know that I need to make the user make this choice)

I hope this is useful to someone and feel free to correct me if I goofed up somehow. 😊

Thanks again!

SMAppService - How does this work?
 
 
Q