ScriptingBridge

One of the new ‘developer’ features (i.e. a new API) in Leopard is ScriptingBridge. It’s a significant improvement to the mechanics of driving one application from another using AppleScript/AppleEvents.It’s always been possible to use NSAppleScript in Cocoa to send AppleScript to another application basically using a simple string :

NSString *appleScriptSource = [NSString stringWithFormat:@"tell iTunes to add (POSIX file \"%@\")", moviePath];

NSAppleScript *aScript = [[NSAppleScript alloc] initWithSource:appleScriptSource];

NSAppleEventDescriptor *aDescriptor = [aScript executeAndReturnError:&anError];

Not too bad, and you do get the returned data and in theory you can use that in subsequent scripts to affect the added track (adding metadata, setting it’s kind etc). However it’s slow and still really rather clumsy, and if you want to do any real argument munging you may be forced to revert to NSAppleEvent directly which is really painful and exceedingly longwinded.ScriptingBridge in Leopard makes this much, much more straightforward and friendly. Using the command line sdef and sdp tools you can create an Objective-C 2.0 header file which contains all the scriptable classes and elements of an application. Then you can include the header file and ScriptingBridge framework and the above code becomes:

iTunesApplication *iTunes = [SBApplication applicationWithBundleIdentifier:@"com.apple.iTunes"];

[iTunes setDelegate:self];

NSArray *filesArray = [NSArray arrayWithObject:[NSURL fileURLWithPath:moviePath]];

iTunesTrack *theTrack = [iTunes add:filesArray to:nil];

At first glance it looks like this is about the same code, but it executes much faster (no script parsing/compilation to AppleEvents). The returned object is a real Objective-C object so in the next line you can do :

theTrack.videoKind = iTunesEVdKTVShow;

theTrack.show = movieTitle;

Note – you can use Objective-C 2.0 style properties. Much neater than lines and lines of coded AppleScript. There is one catch – the header file is built from parsing the scripting definitions in the application (the sdef). These can still tend to be rather opaque – and in particular rather ‘type free’. AppleScript generally is a very weak typing language, and if you’re used to strong typing (C like) languages it can be a bit of a mind bending experience. In the iTunes case I struggled for a couple of days with the add: to: message – the generated header says :

- (iTunesTrack *) add:(NSArray *)x to:(SBObject *)to; // add one or more files to a playlist

There’s no indication of type for the contents of the array. Experience with ScriptEditor (and the initial NSAppleScript solution) suggested I should be able to just use an NSString in the array with the POSIX style path. However that failed with a ‘not found’ error when the message was sent. I initially thought it was a problem with the to argument needing to be a valid playlist and I spent quite some time convincing myself that I had a valid playlist argument (basically iterating things in the iTunesApplication object to get down to a playlist). Finally I went back to ScriptEditor and launched it from Terminal with the AEDebug=1 and AEDebugSends=1 environment variable set. This shows on stdout a debug of the AppleEvents that ScriptEditor sends. Looking at the log I could see that ScriptEditor was converting my POSIX path into an ‘furl’ (File URL) ! The lightbulb came on and I adjusted my code to do likewise with an NSURL and everything ‘just worked’ as I’d hoped it would. It is odd however – the definition in the scripting def file actually refers to an ‘alias’ an old world MacOS 7 concept that’s pretty much fallen out of use. I expected the path string to be converted correctly, after all I’d already proven that other AppleEvents in other applications (Sketch2 in Leopard is a good test case) that take alias get converted, however it appears as though there’s something more specific in the iTunes case that needs the file URL.

Posted under Development, Leopard

This post was written by awk on October 26, 2007

3 Comments so far

  1. Jamie Kirkpatrick February 3, 2008 9:51 am

    You are a genius!!!!! I just spent 2 hours trying to work out what to pass to that method under PyObjC. That’s pretty good work having the idea of turning on the debugging env variables….would never have thought of it.

    Another tool in the armory anyway.

    Thanks

    Jamie

  2. awk February 3, 2008 10:27 am

    The AEDebug environment variables are really helpful for things like this. I can’t take total credit – Daniel Jalkut author of the excellent FastScripts (and MarsEdit) reminded me about them in a CocoaHeads presentation a month or two before I ran into this problem.

  3. Gabe April 26, 2011 8:47 pm

    It’s more than three years since you wrote this post, and it just helped me out tremendously. Scripting Bridge is confusing and poorly documented. Note also that searchFor:only: claims to return an iTunesTrack* and in fact returns an SBElementArray of them, among other fun inaccuracies in the header file. Putting this here for future Googlers like myself. Many thanks for the post.

Leave a Comment

Name (required)

Email (required)

Website

Comments

More Blog Post

Previose Post: