Minimal Scriptability

June 26th, 2006

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.” 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:

tell application "Preview"
	get name of document 1
end tell

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.

18 Responses to “Minimal Scriptability”

  1. Non Stop Mac Says:

    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.” …

  2. glasspusher Says:

    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.

  3. Don Briggs Says:

    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

  4. Red Sweater Blog - We Need a Hero Says:

    […] 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? […]

  5. Peter Hosey Says:

    In the last example, why not “alias (path of thisDoc)”? Did that not work?

    (Sad that I have to ask that latter question…)

  6. Daniel Jalkut Says:

    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.

  7. Peter Hosey Says:

    Ah, right. I got it mixed up with “file of document…”; sorry.

  8. Sal Soghoian Says:

    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

  9. Doug Adams Says:

    Sal says it’s OK, and that’s good enough for me.

  10. Nehemiah Says:

    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)

  11. Daniel Jalkut Says:

    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.

  12. Nehemiah Says:

    Thank you for your help, I certianly wiill do that from now on.
    俺輪馬鹿です
    Ore wa baka desu

  13. Fred Says:

    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.

  14. Mike B Says:

    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.

  15. Daniel Jalkut Says:

    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.

  16. christian boyce Says:

    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/

  17. Joel Says:

    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?

  18. Daniel Jalkut Says:

    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:

    codesign -f -s - /Applications/HackPreview.app
    

    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.

Comments are Closed.

Follow the Conversation

Stay up-to-date by subscribing to the Comments RSS Feed for this entry.