You can provide system services (items that appear in the Services menu) in two ways: through an application (YourApp.app) or through a standalone service (YourService.service). The first way is well documented, but I've found it hard to find instructions for the second way.
The first way: a regular .app
Your Cocoa application can provide services that appear in the Services menu. Apple's "Services Implementation Guide" explains how to do this. Basically:
- Create a class (or use an existing class) with one method for each service. Each service receives input and optionally returns output by way of NSPasteboard.
- Add an "NSServices" key to Info.plist with info about each service.
- At some point during application startup, call NSRegisterServicesProvider().
Put the application in /Applications, or a subfolder thereof, to get the system to detect the new service(s).
When an application service is invoked, the application becomes active. There seems to be no way to avoid this. At best you can immediately hide the application, but there will still be a flicker as windows appear and disappear.
UPDATE: Mark Munz offered this tip on the cocoa-dev mailing list:
[T]he solution I used to prevent Services from bringing the app forward is to have a background (LSUIElement) helper app that acts as the NSServices provider. Depending on what you need to do, you could either support the service directly in the helper app or use it to talk to the parent app to perform the necessary work (without requiring it to activate). I use the second approach.
The second way: a .service bundle
If you want a service that is purely a background operation, you can create a standalone service that has no UI. It's very simple once you know how, but I haven't found a lot of help online, although some books discuss it and you can find examples on GitHub by searching for NSRegisterServicesProvider.
I expected a "System Service" template in Xcode, but there is none. Instead, you use a "Cocoa Application" project and code it just like a regular application with services except:
- Change the target's wrapper extension from "app" to "service": Project > Target > Build Settings > Wrapper Extension.
- Make it a background application by adding to Info.plist: LSUIElement = YES. (This is the "Application is background only" item in the popup menu that appears in Xcode's plist editor.)
- Remove resources you won't need, like MainMenu.xib, AppDelegate, and Credits.rtf.
- Edit main.m to instantiate the service-provider object and enter a run loop. Here's an example.
Build the project and copy YourService.service to ~/Library/Services. You may have to relaunch apps to get them to see the service.
Instead of editing Info.plist to add the NSServices entries, you might prefer to use Xcode's UI. I don't know when this was added to Xcode, but you can go to Project > Target > Info and there's a "Services" area at the bottom. It's easy to miss if you don't know it's there.
To uninstall the service, remove YourService.service from ~/Library/Services. You won't be able to delete it if the service has a running process, so you might have to kill the process first.
If the service doesn't appear in the Services menu, you may have to force the menu to be reconstructed. Unfortunately there's no command you can call from the command line. It's trival to write one though — all it has to do is call NSUpdateDynamicServices().
As I mentioned, Apple's "Services Implementation Guide" has plenty of detail on writing application services, but as far as I can tell the only help it gives on standalone services is this one line:
> To build a standalone service, use the extension .service and store it in Library/Services.
The breakthrough that got me going was when I found example code on the Timac blog. I already had implemented a service in an application. By looking at Timac's code and doing monkey-see-monkey-do, I got my first standalone service working.
I then realized I could find plenty more examples by searching GitHub for "NSRegisterServicesProvider".
Here's the service I wrote. It copies method names to the clipboard so you can paste them in emails, code comments, docs, and so forth.
Pingback: Michael Tsai - Blog - Writing a .service Bundle
This was extremely helpful and exactly what I was looking for. I searched quite a bit for implementing as .service bundle and eventually ended up here. Thanks for the write-up and clear explanation.
Brian, I'm glad this was useful. Sorry for the delay in approving your comment.
Very helpful article!
One small addition: There is a command line tool to rescan services:
Apple writes: When you test your program, it may be useful to trigger an immediate rescan of Services, as if NSUpdateDynamicServices had been called. You can do this from the command line using the pbs tool:
sorry, the Apple docs were outdated. The correct command is: