Read application specific clipboard item as text

I’m a noob with ApplescriptObjC and I’m struggling to understand how to read non-standard clipboard items.

There’s an item on the clipboard with a type called “com.apple.flexo.proFFPasteboardUTI” (I know this from using the ClipboardViewer from the Apple Developer website). I’d like to get that item and assume it is an ASCII string so that my script can read it (it has some XML elements). After reading some from Shane Stanley’s ‘Everyday AppleScriptObjC’ and googling around a bit, I have this script (which doesn’t work):

use framework "Foundation"
use framework "AppKit"
use scripting additions

on readClipboard(theClipboard)
	set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
	set theClipboard to theNSPasteboard's readObjectsForClasses:({current application's NSPasteboardItem}) options:(missing value)
	return theClipboard as text
end readClipboard

set theClipboard to missing value
its readClipboard:theClipboard

What am I missing?

You can’t assume you can read someone’s proprietary clipboard format. In some cases you can figure it out, but it’s entirely up to the app in question – it’s generally designed for internal consumption only.

It’s unlikely to be an ASCII string, especially if it contains XML. It might be UTF8, but it could also be a property list – you’ll only find out by trial and error. What app is putting it there?

Something like this should get it for you if it’s UTF8:

set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
	set theNSData to theNSPasteboard's dataForType:"com.apple.flexo.proFFPasteboardUTI"
	set theNSString to current application's NSString's alloc()'s initWithData:theNSData encoding:(current application's NSUTF8StringEncoding)

If it’s a property list, you could use -propertyListForType:, and see if it returns an array or dictionary.

The app is Final Cut Pro X. I’ve used a clipboard manager called CopyLess to save the clipboard history and assign hotkeys to frequently used overlays, generators, transitions, and clips. CopyLess stores each “clipboard snapshot” as a separate file in a folder in ~Library/Application Support. This has allowed me to access and modify the clipboard and the “clipboard snapshots”.

Perhaps the coolest application of this approach is that I can copy a video clip to the clipboard, read it’s duration, and paste an overlay or title which matches the duration of the clip. Since I work with a lot of overlays, this is an enormous time saver. However this method is a little bit slow because my script has to wait for CopyLess to get the Clipboard and write it to a file before it can read it. I’d like to just read the clipboard directly.

Unfortunately your method didn’t quite do it and I’m not yet savvy enough to know how to trial and error other methods.

This is what I tried using your suggestion:

use framework "Foundation"
use framework "AppKit"
use scripting additions

on readClipboard(theClipboard)
	set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
	if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
		set theNSData to theNSPasteboard's dataForType:"com.apple.flexo.proFFPasteboardUTI"
		set theClipboard to current application's NSString's alloc()'s initWithData:theNSData encoding:(current application's NSUTF8StringEncoding)
	end if
	return theClipboard as text
end readClipboard

set theClipboard to missing value
its readClipboard:theClipboard

and it returns:

error "*** -[BAGenericObjectNoDeleteOSAID readClipboard:]: unrecognized selector sent to object <BAGenericObjectNoDeleteOSAID @0x61000023ac60: OSAID(1) ComponentInstance(0x830002)>" number -10000

As for the property list, I don’t know how write that. Do I just replace dataForType with propertyListForType?

Those two don’t match.

Try this:

use framework "Foundation"
use framework "AppKit"
use scripting additions

on readClipboard()
	set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
	if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
		set theNSData to theNSPasteboard's dataForType:"com.apple.flexo.proFFPasteboardUTI"
		set theClipboard to current application's NSString's alloc()'s initWithData:theNSData encoding:(current application's NSUTF8StringEncoding)
		return theClipboard as text
	end if
	return missing value
end readClipboard

its readClipboard()

And if that fails, my next guess would be a property list, so you could try something like:

on readClipboard()
	set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
	if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
		set theThing to theNSPasteboard's propertyListForType:"com.apple.flexo.proFFPasteboardUTI"
		current application's NSLog("Class is %@", theThing's |class|())
		return theThing as record -- best guess
	end if
	return missing value
end readClipboard

And see what gets logged in Console.

Cool. So it won’t return it as text with your first script, but the second returns this:

{ffpasteboardcopiedtypes:{pb_anchoredObject:{|count|:1}}, ffpasteboardobject:«class ocid» id «data optr00000000002E0575E77F0000», kffmodelobjectIDs:{}}

I assume that is telling me what type of objects are on the clipboard?

If that’s what’s returned, that is what’s on the clipboard. In which case the next step is to try to find out what that ffpasteboardobject value is. So start with this:

on readClipboard()
	set theNSPasteboard to current application's NSPasteboard's generalPasteboard()
	if (theNSPasteboard's canReadItemWithDataConformingToTypes:{"com.apple.flexo.proFFPasteboardUTI"}) as boolean then
		set theThing to theNSPasteboard's propertyListForType:"com.apple.flexo.proFFPasteboardUTI"
		set theObj to theThing's objectForKey:"ffpasteboardobject"
		current application's NSLog("Class is %@", theObj's |class|())
	end if
	return missing value
end readClipboard

Hmm, that returns missing value.

At this stage, what’s returned doesn’t matter – it’s what the NSLog() output that matters. You can find it in Console.app.

Okay, it says:

Does this give us a clue about how to access ffpasteboardobject?

Unfortunately it’s telling us it’s just a blob of data. Try changing the NSLog statement to:

       current application's NSLog("Data is %@", theObj)

That gives me:

I think you’ve left the |class|() in there…

You should also try:

		current application's NSLog("Object is %@", current application's NSUnarchiver's unarchiveObjectWithData:theObj)

Alright, your second line of code:

current application's NSLog("Object is %@", current application's NSUnarchiver's unarchiveObjectWithData:theObj)

gives me:

But your first line of code…

current application's NSLog("Data is %@", theObj)

gives me something promising (I have to truncate it because it’s too long to post it all):


I’m afraid you’ve run into a dead-end.

The clipboard is stored as a property list. One of the limitations of property lists is that they can only store objects from a limited range of classes. But one of those classes is NSData, so programmers can get around the limitation by converting whatever they want to store to raw data. This can be done various ways – using NSArchiver and then getting it back with NSUnarchiver is common, but by no means the only way. Unfortunately it looks like they’re using something else, and I’m not sure where you’d begin trying to work it out beyond what we’ve done here.

Sorry I don’t have better news.

Even if not successful, I’ve learned a lot. Thanks for taking the time to work on this.

Is simply writing the data to a file a viable option?

It’s easy enough to do, if that’s what you mean:

theObj's writeToFile:"path/to/file" atomically:true

So I wrote it to a file and opened it in TextEdit. It’s the same half-jumbled file that CopyLess was giving me. A fair amount of it looks like a corrupted XML but it’s sill readable enough for me to get some key values. Maybe this is a little wonky, but I think it might work for my purposes.

Send me a copy via email. There might be something that can be done.