Minimal Scriptability
June 26th, 2006The Cocoa Scripting layer makes it extremely easy to expose manipulation of your application’s model to users via AppleScript. That said, for most developers the learning curve associated with this can be far from “easy.” The psychological burden of tackling scripting support seems to prevent many developers, even those within Apple, from providing even the barest of scripting support for their applications.
The promise of Cocoa Scripting is essentially that some robust AppleEvent parsing code in the Cocoa frameworks will translate the “yucky, raw events” into method dispatches that “just work” with your application’s attribute-based data model. Then, a different chunk of robust code will take the Cocoa objects that your application works with and automatically translate them into suitable AppleScript representations. For instance if you expose an NSString attribute from your application’s data model, the text contents are automatically converted to an AppleScript-native format suitable to the scripter’s needs. Similar bridging is done for objects as complex as windows and documents.
The power of this adaptive layer can be witnessed by taking an otherwise unscriptable application and “throwing the switch” that gives Cocoa permission to take over scripting. If you’ve got an unscriptable Cocoa-based application, why not use this as an opportunity to get your feet wet? In fact, Apple ships a good case study for this in Preview, so I’ll use that as a guinea pig while you follow along with whatever app you like. (thanks to John Gruber for pointing out this shortcoming and inspiring this article). Take the following script and paste it into Script Editor:
When you run the script, even if you can see an open document in Preview, you’ll be met with a rather rude reply: “Can’t get name of document 1.” Please! You’re telling me you can’t even offer me that tiny glimpse into the application’s personal details? Unfortunately Apple didn’t “flip the switch” on Preview, so none of the easy freebies from Cocoa Scripting are exposed through it.
So let’s flip the switch for them. Let’s pretend we work for Apple in the Preview group and our manager has finally given us the go ahead to investigate AppleScript support for Preview. Heck, let’s assume we’re the new intern, it’s day one, and we haven’t even been given source code access yet!
By navigating into the Preview.app package and opening the Info.plist document in Property List Editor (or an editor of your choice), we can add the requisite flag that instructs Cocoa Scripting to take over:
Now quit and relaunch both Preview and the Script Editor. Paste your example “name of document 1” script again and observe that the frontmost document’s name is returned!
Now you may be thinking, “Who cares? It’s just the title of the document, that’s not very useful.” But that’s not for you to decide! Let the script author decide. They’re the ones who have to use your program to get something done. The big deal here is that we turned an unscriptable application into a “scriptable beta” with the addition of a single Info.plist attribute. We didn’t even have to add a scripting dictionary!
Lest you think that the standard Cocoa Scripting functionality is only the name of the document, I will share a slightly more complex (albeit still contrived) example. Let’s suppose we have a hundred documents to review by hand with Preview. Based on some human-observable trait we want to open 25% of those documents later in Safari for printing or whatever. One option would be to go through and save a copy of each such document to a separate directory. When we’re done with the grueling day of work, we’ll drag the contents of that separate directory to Safari (or whatever application).
Using AppleScript and the “newly scriptable” Preview application, we can simplify this workflow by making the assumption that “any document with a tiny window” should be opened in Safari. Now as we go through the open documents one by one, we simply drag-resize the window to be less than 400 pixels wide. Later on, we run the following script, which makes a list of all such windows and opens the corresponding documents in Safari:
All of this for free! We make a kick-ass intern! And all on our first day with no source code.
By no means am I suggesting that developers should just flip the switch and ship. It’s very frustrating to see unscriptable apps, but even worse when a product claims scriptability but for all practical purposes is not. The point of this entry is merely to point out that Cocoa gives us a huge head start towards a decent scripting implementation, and to choose not to take advantage of that is cruel and unusual punishment to your users.
Just think how cool Preview will be when we add a dictionary that describes its document object model’s custom attributes.
June 26th, 2006 at 4:00 pm
Minimal scriptability…
Daniel Jalkut writes: “The Cocoa Scripting layer makes it extremely easy to expose manipulation of your application”™s model to users via AppleScript. That said, for most developers the learning curve associated with this can be far from “easy.” …
June 27th, 2006 at 7:35 am
Dan,
Good show. AppleScript is one of the most powerful and at the same time most daunting things in which a developer can engage! Have you checked out Don Briggs’ SuiteModeler? It simplified things enormously for me.
June 27th, 2006 at 11:41 am
Hello, Cocoa developers –
Suite Modeler is now free and universal.
(It used to be a licensed application.)
Try it at: “http://homepage.mac.com/donbriggs/”.
Compose and edit your “dictionary,” then emit prototype Objective-C code to support it.
Use Apple’s “desdp” to convert to *.sdef.
Enjoy!
Don
September 27th, 2006 at 8:14 am
[…] Why is it so painful? With Cocoa Scripting it’s common to hear that adding support is almost as simple as flipping a switch, and it sort of can be. But it’s also true that skydiving is as easy as finding something tall to jump off of. Now how do we go about ensuring a safe landing? […]
September 29th, 2006 at 11:39 pm
In the last example, why not “alias (path of thisDoc)”? Did that not work?
(Sad that I have to ask that latter question…)
September 30th, 2006 at 6:14 am
Peter: heh… the first rule of AppleScript is … you don’t talk about whether there’s a better way to write something :) Just kidding, but really, I just tweak things until they work.
In this case I think your suggested approach won’t work because the path is a POSIX path, not an HFS path, which is what “alias” would naturally expect as a parameter.
October 1st, 2006 at 3:24 am
Ah, right. I got it mixed up with “file of document…”; sorry.
October 2nd, 2006 at 9:22 am
try
tell application “Finder”
set the Preview_app to (application file id “com.apple.Preview”) as alias
end tell
set the plist_filepath to the quoted form of ((POSIX path of the Preview_app) & “Contents/Info”)
do shell script “defaults write ” & the plist_filepath & space & “NSAppleScriptEnabled -bool YES”
end try
October 2nd, 2006 at 5:12 pm
Sal says it’s OK, and that’s good enough for me.
January 10th, 2007 at 2:12 pm
When i tried this a few times, it didn’t work. Now Preview doesn’t launch. if I edit something unintentionally in the Info.plist file, could that stop the app from launching. if it does, how might i come by a replacement original for Preview.app? (aside from reinstalling from the install disk? i am prepared to do that)
January 10th, 2007 at 2:20 pm
Nehemiah: yes, in particular one of the items in the Info.plist,
“CFBundleExecutable”, tells the operating system where to find the code that actually runs the app. In Preview.app it should be called “Preview”. Is that still there?
I’m sure it goes without saying that you should always make a backup of such files in the future, before editing them. But yeah, if you need to replace it, you’ll just want to get a fresh copy of Preview.app, either from another computer or by reinstalling.
January 11th, 2007 at 1:08 pm
Thank you for your help, I certianly wiill do that from now on.
俺輪馬鹿ã§ã™
Ore wa baka desu
January 24th, 2007 at 1:44 am
Is it possible to make a script to automatically convert a whole folder full of PSD files to JPGs?
I have enabled AppleScript on Preview, but the limited scripting may not be enough.
What about and Automator Action?
Any ideas?
GraphicConverter is way to buggy. It left many files corrupted after a loit of work. I ended up deleting them all because I couldn’t trust the output.
Preview can read and convert everything with no problem.
It’s just tedious to have to do it file by file.
February 1st, 2007 at 10:40 pm
Just curious to know if anyone has written a simple Preview script that will place a piece of text on each page of the document opened? Even adding text to the first page would be a plus. I work in a lab where lots of people use the same printer and it would help sort things out a bit.
February 2nd, 2007 at 8:01 am
Mike: I think Preview is still so relatively unscriptable this might be hard. But in your position I would look at Automator and some of the “PDF” actions. For instance, “Watermark PDF” will allow you to add just about anything (text, a picture, etc) over all the pages of a PDF.
February 16th, 2007 at 1:24 am
Fred, use Image Events to convert the folder of PSD files to JPGs. Here’s a link for you.
http://www.apple.com/applescript/imageevents/
September 28th, 2011 at 6:13 pm
I hate to dig up an ancient post, but this is the best instructable I could find on this issue. However, it seems not to work for Lion (info.plist doesn’t have these parameters). Is there an alternative for those of us using this “improved” operating system?
September 28th, 2011 at 8:42 pm
Hi Joel – it still works in Lion, though there are some complications.
First, bear in mind that the Info.plist never had the NSAppleScriptEnabled parameter to begin with. You have to add it manually and set the value to true.
Second, since Apple now makes copious use of code signing, modifying the Info.plist will “break” the Preview app and cause it to stop launching entirely.
So if you wanted to do this trick, a safer way to do it would be:
1. Make a copy of Preview.app and call it “HackPreview.app”
2. Open the HackPreview Info.plist, and add the requisite NSAppleScriptEnabled flag
3. Open Terminal and re-sign the hacked copy:
4. Launch HackPreview and now try writing a script asking for documents of application “HackPreview”.
The point of this post was mostly to show what kind of sneaky stuff you can do if you go poking around in Plists. It doesn’t really have a lot of value for real scripting tasks. Probably Automator is a better solution for whatever PDF or images automation you might want to do.