Cocoa Drag and Drop text into the Dock Icon

I spent much of the afternoon trying to do something that seems like it should be dead simple: allowing a Cocoa application to accept text dropped onto its Dock icon (and its Application icon too, although that wasn't my primary concern). Its pretty easy to get it to accept files, and I was able to get it accepting txt files without much hassle, but getting it to accept strings of highlighted text was an entirely different matter.

After recombining search terms and trying out various ideas, I finally fell upon the two crucial resources:

  1. A subsection of the Cocoa Scripting Guide that detailed the open content Apple Event sent by Mac OS.

  2. The documentation for providing a Systems Service.

If I was a helpful ghost in the Cocoa programming archives I would leave you there and let you piece it together, but I'm not quite so cruel, so we'll look at the details a bit more closely. I happened to be working using PyObjC, so I'll start with the Python examples, but I'll port the juicy bits to ObjectiveC as well.

Adding to your AppDelegate

First, you need to create your implementation of a one-way systems service in your application's delegate. A simple service that uses NSLog to log the incoming text would look like this:

def doString_userData_error_(self,pboard,userData,error):
    pboardString = pboard.stringForType_(NSStringPboardType)
    NSLog(u"%s" % pboardString)

And then you would need to register your service in your application delegate's applicationDidFinishLaunching_ method:

def applicationDidFinishLaunching_(self,sender):
    NSApp.setServicesProvider_(self)

Put that together and it looks like this:

from AppKit import *
import objc

class MyAppDelegate(NSObject):
    "My Application Delegate."

    def applicationDidFinishLaunching_(self,sender):
        NSApp.setServicesProvider_(self)

    def doString_userData_error_(self,pboard,userData,error):
        pboardString = pboard.stringForType_(NSStringPboardType)
        NSLog(u"%s" % pboardString)

This almost works, but is missing once piece of PyObjC specific sauce to correctly handle things. A no-longer-crashing-with-an-incoherent-error-message implementation will look like this:

from AppKit import *
import objc

def serviceSelector(fn):
    # this is the signature of service selectors
    return objc.selector(fn, signature="v@:@@o^@")

class MyAppDelegate(NSObject):
    "My Application Delegate."

    def applicationDidFinishLaunching_(self,sender):
        NSApp.setServicesProvider_(self)

    def doString_userData_error_(self,pboard,userData,error):
        pboardString = pboard.stringForType_(NSStringPboardType)
        NSLog(u"%s" % pboardString)

    lookupString_userData_error_ = serviceSelector(lookupString_userData_error_)

Porting this code to ObjectiveC is pretty straight forward. The two method implementations will look something like this:

-(void)applicationDidFinishLaunching:(NSNotification *)aNotification {
    [NSApp setServicesProvider:self];
}

-(void)doString:(NSPasteboard *)pboard userData:(NSString *)userData error:(NSString **)error {
    NSString * pboardString = [pboard stringForType:NSStringPboardType];
    NSLog(@"%@",pboardString);
}

Modifying Info.plist

The last thing you need to do is add a few simple settings to your Info.plist file to register your systems service. First, open up your Info.plist file for editing. Next, add a new key named Services (autocomplete should help you here), whose value will be an array.

In the array for the Services key, XCode will create another key named, very intuitively, Item 1. The item for key Item 1 will be an array as well. It will have already created a key named Menu, which has a child named default. The value of default should be an intuitive name for your service perhaps it might be Search dictionary or Add to list depending on what your service will do.

Now, you just need two more children of Item 1 and we'll be all set. First you need the Instance method name key which would have the value doString for the above code. Second, you need the Send Types key, whose value is an array, whose first key is Item 1 with the value NSStringPboardType.

If that explanation was a few steps below perfect, here is a picture to help clarify.

A picture of the Info.plist file's entries need to register a Cocoa System Service.

Now if you build your application, select some text and drag it onto the app's icon in the Dock, it should light up welcomingly, merrily accepting your text.

Let me know if there are any problems or questions!

All Rights Reserved, Will Larson 2007 - 2014.