CocoaDev

Edit AllPages

MakingServices and StandardService describes how to make a small tool that simply vends a service. But often you want to just have an application that vends services to other apps without going through a middle man. For example, TextEdit.app vends the OpenFile and OpenSelection services. Configuring your app to vend and handle a service is easy. You need to:

For a practical example of this you can refer to:

/Developer/Examples/AppKit/TextEdit

We’ll discuss TextEdit here.

Modifying the app’s Info.plist

Open /Developer/Examples/AppKit/TextEdit in ProjectBuilder. Edit the TextEdit target, click on the “Application Settings” tab, and click the “Expert” button. You will see an item in the plist called NSServices. This key tells the system that TextEdit vends two services, openFile and openSelection. Open up all the disclosure triangles and browse through the data. The Important keys are:

When the app is built, all of this information will be compiled into the application’s Info.plist. TextEdit.app’s Info.plist contains this information:

    <key>NSServices</key>
    <array>
            <dict>
                    <key>NSMenuItem</key>
                    <dict>
                            <key>default</key>
                            <string>TextEdit/Open File</string>
                    </dict>
                    <key>NSMessage</key>
                    <string>openFile</string>
                    <key>NSPortName</key>
                    <string>TextEdit</string>
                    <key>NSSendTypes</key>
                    <array>
                            <string>NSStringPboardType</string>
                    </array>
            </dict>
            <dict>
                    <key>NSMenuItem</key>
                    <dict>
                            <key>default</key>
                            <string>TextEdit/Open Selection</string>
                    </dict>
                    <key>NSMessage</key>
                    <string>openSelection</string>
                    <key>NSPortName</key>
                    <string>TextEdit</string>
                    <key>NSSendTypes</key>
                    <array>
                            <string>NSRTFDPboardType</string>
                            <string>NSRTFPboardType</string>
                            <string>NSStringPboardType</string>
                    </array>
            </dict>
    </array>

This information is read by the system when the user logs in. The first time the user logs in the system finds TextEdit installed in the /Applications directory and registers this information with the services system. Currently Mac OS X will only recognize changes to an application’s Info.plist or the addition/removal of an application when the user logs in (IIRC).

More information about these keys can be found here: file:///Developer/Documentation/Cocoa/TasksAndConcepts/ProgrammingTopics/SysServices/Concepts/properties.html

Registering Your Service Provider

Your app will define an object as the service provider. Often this is the main application controller or NSApplication delegate, but that need not be the case. TextEdit simply implements the service code in the main controller.

Regardless of where this code lives, someone must register the handler with the main NSApplication. Again, TextEdit simply does this in the main app controller. You register the service provider with the NSApplication by calling the app’s “setServicesProvider:” method.

In TextEdit’s Controller.m:

Handling the Service Request

Simply implement the methods you name in your app’s Info.plist. The signature for the method handler is:

This handler should make sure the data on the pasteboard matches what your app is expecting. Then it can simply get the data from the pastepoard and do whatever with it. TextEdit’s openFile method demonstrates this clearly:

It’s that easy!


Warning: Handling services around the same time you are loading in a nib file can cause your app to crash (I’m not sure if this is a bug or what). In particular, make sure you do not try to load any nib files between the call to setServicesProvider: and your service handler. Loading nibs before calling setServicesProvider: and once you’re in your service handler is fine.


– MikeTrent


Ok, am I totally missing something here? I can’t seem to get this to work. My code works with BBEdit, but when it comes to Safari, TextEdit, etc. it doesn’t work. Any input would be appreciated.

Here’s the main code:

#import “iRotController.h”

@implementation iRotController

// Handle remoteROT service

@end

I have tried it both with and without the validRequestorForSendType function.

Here’s the info.plist

<!DOCTYPE plist PUBLIC “-//Apple Computer//DTD PLIST 1.0//EN” “http://www.apple.com/DTDs/PropertyList-1.0.dtd”>

CFBundleDevelopmentRegion English CFBundleExecutable iROT13 CFBundleIconFile irot13 CFBundleIdentifier com.davtri.iRot13 CFBundleInfoDictionaryVersion 6.0 CFBundlePackageType APPL CFBundleSignature ???? CFBundleVersion 1.01 NSMainNibFile MainMenu NSPrincipalClass NSApplication NSServices NSMenuItem default iRot13/Remote ROT13 NSMessage remoteROT13 NSPortName iROT13 NSSendTypes NSStringPboardType NSReturnTypes NSStringPboardType NSMenuItem default iRot13/ROT13 NSMessage doMSGROT13 NSPortName iROT13 NSSendTypes NSStringPboardType

What am I missing exactly?

Thanks,

DaveGiffin


Well, I’m still stumped on this one. I haven’t been able to get it to work yet and I haven’t found any articles, blogs or developer’s docs that point me in any direction to find an answer.

I have kept pounding at this and it’s really weird. The services work fine in BBEdit, but not in any other apps that I have tried. I noticed when testing with text edit, when you call either service, it launches my app, but you get a beachball on the calling app (text edit in this cast) for a good 15 or 20 seconds. If during this beachball time, I quit my app and immediately relaunch it, then the service runs as it should. During the initial launch by the caller, the debug info isn’t even running. It’s like the app isn’t being properly initialized or the service function isn’t being called.

Any ideas?


Still trying to get this one worked out. I will play with it some more tonight when I have time, but am not holding much hope for this. Hopefully lightning will strike and shed some light on the problem :)


Hello. I’m having the same problem as above… except that up until fairly recently, it was working just fine. I wonder if it’s possible there’s a bug in Services in 10.3.6 and 10.3.7. I have no idea how to fix it though, unless applying the combo updater will fix it. It might be worth a shot. -Daniel DeCovnick, SoftYards Software


When I was trying to write a filter service, I ran into a similar problem with a filter service that translated images between different types. It’s an infinite loop: Your service is consuming its own output as input.

I don’t know how to solve that, but hopefully somebody can use that as a lead.

boredzo