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
Written by Hansaplast in 777520021
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.

Written by Hansaplast in 777520021
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
Written by Hansaplast in 777520021
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.

Written by Hansaplast in 777520021
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