Drag and drop multiple URLs

As a reminder, in case you forgot from the last post, I am Jeff Johnson of the clan Johnson. I was born in the flatlands of America, and I cannot die unless you take my head…or shoot me, stab me, burn me, force feed me tainted spinach, etc. I am not immortal, and neither are you, so let’s get on with it.

In the Vienna support forum, we got a bug report from a user who was trying to drag multiple RSS feeds from Safari’s bookmarks page to Vienna’s subscription list. Instead of creating a subscription for each feed URL, Vienna created only one subscription with all the feed URLs strung together. Bummer. So much for migrating from Safari RSS to Vienna!

To fix the bug, I needed to know what exactly was in the pasteboard. Thankfully, Apple provides a little tool for just this purpose: Clipboard Viewer. I knew that Vienna had to be getting the data from NSStringPboardType, because that was the only non-custom type it accepted. In Clipboard Viewer, I could see that NSStringPboardType contained the feed URLs separated by line feeds (hexadecimal code 0a), which Clipboard Viewer displays as dots (.) for some reason. One solution, then, would be to extract the URLs from the string, perhaps using -[NSString componentsSeparatedByString:] with the argument @"\n". That might be ok if your app only accepts dragged URLs, but what if it accepts other data? The app would have to be able to distinguish between URLs and other kinds of dragged strings. Is there an easier way?

Clipboard Viewer gives us a list of available pasteboard types. There is Apple URL pasteboard type, which turns out to be just NSURLPboardType. Unfortunately, you’ll find in the docs for NSPasteboard that NSURLPboardType carries NSURL data for one file or resource. There can be only one, flatlander! The one URL for NSURLPboardType is the first of the selected Safari bookmarks. Other available pasteboard types include 'url ' and 'urln', AKA CorePasteboardFlavorType 0x75726C20 and CorePasteboardFlavorType 0x75726C6E, AKA Sammy the Snitch and Jimmy Two-Fingers. You can see in the Uniform Type Identifiers Overview that 'url ' and 'urln' are tags for, no surprise, a URL and a URL name. These pasteboard types also contain the data for only one URL, so they don’t help us here. (Incidentally, I love that pasteboard types have flavors. This suggests to me that in the future, Apple will release a lickable interface, perhaps a wireless lollipop.) Retro programmers will enjoy NeXT plain ascii pasteboard type, but its content is basically the same as NSStringPboardType.

BookmarkDictionaryListPboardType looks very interesting, because it seems to contain everything that we want, and more. However, a search for the term at ADC returns no results. My guess is that BookmarkDictionaryListPboardType and its apparent sidekick BookmarkStatisticsPBoardType (note the different capitalization, though) are private Safari-defined types. I’ll let Safari hackers figure that out. (Actually, I don’t recall these pasteboard types existing when I first investigated URL dragging. A No-Prize goes to the person who can tell us when they were introduced.)

The last available pasteboard type, but certainly not least—you knew the story was going to have a happy ending!—is WebURLsWithTitlesPboardType. Although an ADC search again turns up nothing, the name of the pasteboard type begins with Web, which should sound familiar: WebFrame, WebView, WebFrameView (when WebFrame and WebView get married), WebUndefined (huh?). In other words, WebURLsWithTitlesPboardType is probably defined in WebKit, the framework used by Safari to handle web content. WebKit is open source, so we can check it out for ourselves. Yay! A project search reveals that WebKit does indeed define WebURLsWithTitlesPboardType, in the file WebURLsWithTitles.h of all places. Go figure. The file is short, so I’ll reproduce it below for your reading pleasure.

/*
 * Copyright (C) 2005 Apple Computer, Inc.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#import <Cocoa/Cocoa.h>

#define WebURLsWithTitlesPboardType     @"WebURLsWithTitlesPboardType"

// Convenience class for getting URLs and associated titles on and off an NSPasteboard

@interface WebURLsWithTitles : NSObject

// Writes parallel arrays of URLs and titles to the pasteboard. These items can be retrieved by
// calling URLsFromPasteboard and titlesFromPasteboard. URLs must consist of NSURL objects.
// titles must consist of NSStrings, or be nil. If titles is nil, or if titles is a different
// length than URLs, empty strings will be used for all titles. If URLs is nil, this method
// returns without doing anything. You must declare an WebURLsWithTitlesPboardType data type
// for pasteboard before invoking this method, or it will return without doing anything.
+ (void)writeURLs:(NSArray *)URLs andTitles:(NSArray *)titles toPasteboard:(NSPasteboard *)pasteboard;

// Reads an array of NSURLs off the pasteboard. Returns nil if pasteboard does not contain
// data of type WebURLsWithTitlesPboardType. This array consists of the URLs that correspond to
// the titles returned from titlesFromPasteboard.
+ (NSArray *)URLsFromPasteboard:(NSPasteboard *)pasteboard;

// Reads an array of NSStrings off the pasteboard. Returns nil if pasteboard does not contain
// data of type WebURLsWithTitlesPboardType. This array consists of the titles that correspond to
// the URLs returned from URLsFromPasteboard.
+ (NSArray *)titlesFromPasteboard:(NSPasteboard *)pasteboard;

@end

P.S. It’s clear from the code in the file WebNSPasteboardExtras.m that 'url ' and 'urln' have an additional set of aliases: WebURLPboardType and WebURLNamePboardType.

One Response to “Drag and drop multiple URLs”

  1. Peter Hosey says:

    There is Apple URL pasteboard type, which turns out to be just NSURLPboardType. Unfortunately, you’ll find in the docs for NSPasteboard that NSURLPboardType carries NSURL data for one file or resource. There can be only one, flatlander!

    As I mentioned on the Vienna forum, NSURLPboardType is actually an array plist. So if you don’t mind getting your hands dirty (the format could change out from under you), it could be possible to extract multiple URLs from that type. Unfortunately, Safari behaves in accordance with the documentation, and only puts one URL there, so it wouldn’t help.

    (To be clear, I do think that that’s a good thing. Apple ignores its documentation enough as it is. :)