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:
A subsection of the Cocoa Scripting Guide that detailed the
open content
Apple Event sent by Mac OS.
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."
<span class="k">def</span> <span class="nf">applicationDidFinishLaunching_</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">sender</span><span class="p">):</span>
<span class="n">NSApp</span><span class="o">.</span><span class="n">setServicesProvider_</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">doString_userData_error_</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">pboard</span><span class="p">,</span><span class="n">userData</span><span class="p">,</span><span class="n">error</span><span class="p">):</span>
<span class="n">pboardString</span> <span class="o">=</span> <span class="n">pboard</span><span class="o">.</span><span class="n">stringForType_</span><span class="p">(</span><span class="n">NSStringPboardType</span><span class="p">)</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">u"</span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="n">pboardString</span><span class="p">)</span>
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."
<span class="k">def</span> <span class="nf">applicationDidFinishLaunching_</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">sender</span><span class="p">):</span>
<span class="n">NSApp</span><span class="o">.</span><span class="n">setServicesProvider_</span><span class="p">(</span><span class="bp">self</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">doString_userData_error_</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span><span class="n">pboard</span><span class="p">,</span><span class="n">userData</span><span class="p">,</span><span class="n">error</span><span class="p">):</span>
<span class="n">pboardString</span> <span class="o">=</span> <span class="n">pboard</span><span class="o">.</span><span class="n">stringForType_</span><span class="p">(</span><span class="n">NSStringPboardType</span><span class="p">)</span>
<span class="n">NSLog</span><span class="p">(</span><span class="s">u"</span><span class="si">%s</span><span class="s">"</span> <span class="o">%</span> <span class="n">pboardString</span><span class="p">)</span>
<span class="n">lookupString_userData_error_</span> <span class="o">=</span> <span class="n">serviceSelector</span><span class="p">(</span><span class="n">lookupString_userData_error_</span><span class="p">)</span>
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.
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!