iTunes Scripting Seizure

December 5th, 2006

I love iTunes, but it has a number of rough edges that leave me constantly praying that a major revision will come along one day soon. Most of my complaints have to do with it being simultaneously the “only way to manage music and podcasts on the Mac” and yet being frustratingly feature-incomplete in some important ways. For instance, it does a great job of downloading my podcasts, but neglects to put them on my iPod until I either unplug and replug it, or else manually select “Sync.” Inevitably, I end up thinking that since iTunes and my iPod have been making out all weekend, surely those new podcasts are on the iPod. It’s not until I’m at the gym with nothing to listen to that I realize I’ve been sync-jacked!

But that’s a complaint for another time. Today I’d like to highlight an infuriating behavior that iTunes has, of blocking the processing of all incoming AppleEvents while its preferences dialog is being displayed. What does this mean in most practical terms? iTunes can’t respond to AppleScript while the Preferences window is open. So if you run some script that asks for the name of the current track, for instance, it will hang and give the running app a spinning beachball while you wonder what the heck is going on. Finally you switch to iTunes and realize that you were fiddling with preferences yesterday when you got distracted and switched to another application. Close the dialog, and life goes on as usual.

This is bad enough when it’s inflicted on yourself as the script author and user, but it’s worse when your script is either shared with other users, or included as part of the functional infrastructure for an application. With iTunes integration popping up everywhere, it stands to reason that any developer who uses AppleScript to achieve the integration runs the risk of answering support call from users who have run into this iTunes freeze-out behavior.

The solution? Apple should fix iTunes so it doesn’t freeze-out AppleEvent processing while displaying the Preferences dialog.

The solution for people who live in the real world? We have a few techniques to choose from.

Time Out Gracefully

The first is to use AppleScript’s “with timeout” clause to seriously shorten the length of time it will hang out waiting for iTunes to wake up. We can pick a relatively short period of time in seconds that still constitutes an eternity in scripting time:

try
	with timeout of 2 seconds
		activate application "iTunes"
	end timeout
on error
	display dialog "iTunes is Frozen, try closing the Preferences window!"
end try

Go ahead, try it with the iTunes Preferences window open. This eliminates the outright freeze that users would experience otherwise.

But it feels risky to estimate with confidence the precise number of seconds that is “long enough, but not too long.” Maybe the user just has a really slow Mac, or something unusual happens to slow things down one out of ten times. For many iTunes scripting purposes, activating iTunes and bringing it to the front is part of the procedure. If iTunes is frontmost, then the user will see that the Preferences dialog is open and (hopefully) intuit that they must dismiss the dialog to let the scripted action carry on. The problem is, merely activating iTunes with AppleScript requires that it respond to AppleEvents! As the example script above shows, we can’t even bring it forward when it’s frozen. Or can we?

Blame It On iTunes

We know the user can bring it forward, so how do we simulate that from a script? If we keep iTunes itself out of the scripting loop, we can enlist the services of the system to change the frontmost application:

tell application "System Events"
	set frontmost of application process "iTunes" to true
end tell

Try this script with iTunes Preferences open and you’ll see that it zips to the fore, regardless of the frozen state. By putting this script command first in your iTunes-activating script, you can ensure that users will at least be confronted by the offending Preferences window, instead of staring at a spinning beachball in your application.

Not Give A Damn

Finally, we have the option of sort of throwing our scripting requests out to the world with little regard for their success or failure. For programmer types you can think of this as roughly equivalent to weak-linking a particular system API. If something neat can be done, fine we want it. Otherwise, we’ll keep running business as usual. The “ignoring application responses” clause tells AppleScript to send events but to disregard responses. For instance, the following script asks iTunes to skip to the next track, but shed no tears if that can’t be done right this second:

ignoring application responses
	tell application "iTunes" to next track
end ignoring

The big negative here of course is the script gives no feedback to the user about the success or failure of the intended action. It also silently queues up an action that might disturb the user later. For instance, when she switches to iTunes and closes the Preferences dialog, suddenly her favorite song gets skipped over.

In Closing

As I said, the solution here is for iTunes to grow up and be a real 21st Century application. At least in terms of modelessness. But for anybody trying to work around the failings of iTunes or other event-blocking applications, I hope these tips will come in handy.

Stay Responsive

November 30th, 2006

In general, a user’s changes to a Cocoa NSTextField are saved when they finish editing (by tabbing or clicking to another field) or press return. This is fine, and it works 95% of the time. But on occasion we may find good reason to saved a user’s changes regardless of whether they’ve performed one of these completing actions.

For example, in the “Send to iTunes” feature I’m working on for FlexTime, I decided it was quite possible that a user would fine-tune an activity’s details, say change the text of a “Speak Text” cue, and then immediately select “Send to iTunes.” What should happen in this case? Certainly the user’s changes should be committed and included in the exported audio. The user hasn’t “finished editing” in a conventional way, but by choosing to export the routine, they’ve let me know they are expecting to hear what they’ve just typed.

The cleanest way to end editing for a particular window was explained to me a few years ago by Erik Buck, who kindly explained on a mailing list that it’s best to first ask the window to become firstResponder (thus taking firstReponder away from the focused field), and if that failed, to resort to the crude “endEditingFor:” method on NSWindow.

This works! But it has the side-effect of causing there to no longer be a reasonable first responder for the window. In the scenario I’m dealing with, I want to:

  1. Commit Edits.
  2. Send to iTunes.
  3. Put things back how they were.

I figured if I could be responsible for taking first responder status away from some field in my window, I could just give it back after I was done. But things don’t work out so easily in a world where the mysterious field editor is always being injected into the view hierarchy. My first naive solution resulted in my essentially saving and restoring the field editor as the first responder for the window. But after the editing has ended, the field editor isn’t even on the screen anymore. So setting it to first responder just causes a weird focus-free situation that you can’t even tab out of.

The solution, I realized, was to detect the field editor being the first responder and, in that situation, treat its delegate as the responder that needs to be restored after editing. Now, I’m pretty sure that the delegate of a field editor will always be an NSResponder, but just to be sure, I do some runtime checking. The resulting code for “commit edits while saving and restoring the first responder” looks something like this:


// Save the current first responder, respecting the fact
// that it might conceptually be the delegate of the 
// field editor that is "first responder."
id oldFirstResponder = [oMainDocumentWindow firstResponder];
if ((oldFirstResponder != nil) &&
     [oldFirstResponder isKindOfClass:[NSTextView class]] &&
     [(NSTextView*)oldFirstResponder isFieldEditor])
{   
   // A field editor's delegate is the view we're editing
   oldFirstResponder = [oldFirstResponder delegate];
   if ([oldFirstResponder isKindOfClass:[NSResponder class]] == NO)
   {
      // Eh ... we'd better back off if 
      // this thing isn't a responder at all
      oldFirstResponder = nil;
   }
} 

// Gracefully end all editing in our window (from Erik Buck).
// This will cause the user's changes to be committed.
if([oMainDocumentWindow makeFirstResponder:oMainDocumentWindow])
{
   // All editing is now ended and delegate messages sent etc.
}
else
{
   // For some reason the text object being edited will
   // not resign first responder status so force an 
   /// end to editing anyway
   [oMainDocumentWindow endEditingFor:nil];
}

// If we had a first responder before, restore it
if (oldFirstResponder != nil)
{
   [oMainDocumentWindow makeFirstResponder:oldFirstResponder];
}

This works perfectly for my needs and hopefully it will at least put you on the right path towards making your app work perfectly, too.

Learn Quartz With Scott

November 29th, 2006

Scott Stevenson has posted the second part of his Intro to Quartz tutorial at Cocoa Dev Central.

The visual production of his tutorials is always really impressive, because he identifies (by instinct, I assume) the best graphical illustrations for any given point. This second Quartz tutorial is especially notable for the scope of techniques it covers, from simple paths to complex cutouts, transparent overlays, and drawing directly to an image. These concepts are covered by Apple’s documentation, but never with so much lush imagery as on Scott’s site.

Most of us just blog on a whim, with little attention to overall production value, but Scott’s articles on Cocoa Dev Central are clearly well-planned works of art. He’s accumulated quite a collection of articles that cover many of the basics, and some of the more advanced techniques involved in programming a Mac. Check them out!

Scripting The Hard Way

November 28th, 2006

I’ve been working on iTunes integration for FlexTime. My model for this feature is roughly approximated by GarageBand’s “Send To iTunes” feature, which packages up the current song into an M4A format, sends it off to iTunes, starts playing it, and makes sure the playlist into which it was added becomes visible in the iTunes browser window.

It’s pretty obvious to me that the way to get this done (at least after converting the audio to M4A) is via AppleScript. iTunes is respectably scriptable, but getting specific things done can be infuriatingly cryptic. The first several steps of the workflow are easy, being either standard AppleScript commands, or commands that are easily found in iTunes’s AppleScript dictionary:

  1. Activate iTunes. Just use the “activate” command.
  2. Make a new playlist. Not entirely obvious, but you get used to this in AppleScript. Just “make new user playlist with properties” works well.
  3. Add the new track. iTunes offers “add” to “add one or more file to a playlist.” Perfect!
  4. Set details on track. Simple attributes exposed as writable on the “track” object in iTunes’s dictionary. “set artist of,” “set album of,” etc.
  5. Play the track. Easy! “play” is exposed to “play the current track or the specified track or file.”
  6. Select the track’s playlist. Umm. Help? There’s a bunch of false positives here: “selection,” “current playlist,” etc. But they’re all marked as read-only attributes, so we’re out of luck.

The user experience if you don’t show the track’s playlist is greatly reduced, because the track just starts playing, but iTunes is still pointed at whatever the user was looking at before. To find it, they’ve got to intuit the name you gave it, and search for it. Ugh! (Note, it could be argued that just taking control away from whatever the iTunes focus was is rude, but I think it’s fair to say that it’s non-destructive and that the user will just “get over it” or else stop using this feature).

But GarageBand does it! How does GarageBand do it? The smart thing to do now is to throw up your arms and race off to some site where they’ve already figured all of this junk out. Doug’s AppleScripts is a treasure chest of scripts specifically for iTunes, and of course it includes several that show exactly how the selected playlist can be changed. The naming is tricky, but it’s simple.

Of course, I’m a dumb technophile, so my first instinct is not to run off to some site where the solutions are obvious. Instead, I decide to play detective and figure out what the hell is going on. In situations like this, I can almost guarantee you that AppleScript (or at worst, a raw AppleEvent) is being used to achieve the desired inter-application control. There is such a rich legacy of AppleEvent based interaction between applications, that even if Apple were to add a super-secret “for GarageBand only” control mechanism, it would likely be in the form of an AppleEvent.

So we break out the AppleEvent spycam. You can observe all the incoming or outgoing events for an application by setting the AEDebugReceives and AEDebugSends environment variables, respectively. From Terminal:

% cd /Application/iTunes.app/Contents/MacOS
% AEDebugSends=1 AEDebugReceives=1 ./iTunes

Now when you “Send To iTunes” from GarageBand you’ll see an avalanche of event data racing across the terminal window. This is pretty overwhelming. In fact we could have just looked at “Receives” to cut down on the chatter, but even that would give us a lot material to sift through. Take a deep breath and start scanning for demarcation points.

AE2000 (6804): Received an event:
------oo start of event oo------
{ 1 } 'aevt':  core/setd (i386){

The first three lines of an incoming event’s content are pretty easy to parse, once you know what to look for. The first line tells you it’s a “received” event, so something is asking the target application (iTunes) to do something. The third line is the highest level summary of what that something is, containing information about the event’s class and ID. This one’s an AppleEvent (aevt). It’s class is kAECoreSuite (core) and its ID is “kAESetData” (setd). In plain English? This event corresponds to an AppleScript “set” command.

In AppleScript, events usually carry a command to do something as well as a bunch of cargo representing the target of the command as well as any other parameters that are pertinent. Once you’ve deciphered what a particular event’s high level meaning is (the command to be performed), the next most interesting thing to scan for is the collection of parameters that are attached to it. The log is pretty daunting, but it helps to focus on things hierarchically and just make sense of each parameter “object” one at a time. I’ve color coded the event data to make the hierarchical structure pop out a bit more:

 
 event data:
    { 1 } 'aevt':  - 2 items {
      key 'data' - 
        { 1 } 'obj ':  - 4 items {
          key 'form' - 
            { 1 } 'enum':  4 bytes {
              'name'
            }
          key 'want' - 
            { 1 } 'type':  4 bytes {
              'cPly'
            }
          key 'seld' - 
            { 1 } 'utxt':  48 bytes {
              "........................"
            }
          key 'from' - 
            { -1 } 'null':  null descriptor
        }
      key '----' - 
        { 1 } 'obj ':  - 4 items {
          key 'form' - 
            { 1 } 'enum':  4 bytes {
              'prop'
            }
          key 'want' - 
            { 1 } 'type':  4 bytes {
              'prop'
            }
          key 'seld' - 
            { 1 } 'type':  4 bytes {
              'pPly'
            }
          key 'from' -
            { 1 } 'obj ':  - 4 items {
              key 'form' - 
                { 1 } 'enum':  4 bytes {
                  'indx'
                }
              key 'want' - 
                { 1 } 'type':  4 bytes {
                  'cwin'
                }
              key 'seld' - 
                { 1 } 'long':  4 bytes {
                  1 (0x1)
                }
              key 'from' - 
                { -1 } 'null':  null descriptor
            }
        }
    }

You can see that this event has two fundamental parameters (in blue) attached to it, each identified by a unique 4-character code. Parameters with a type of ‘----’ are sort of ubiquitous in AppleScript – they are referred to as “the direct parameter,” which is the “thing” on which any given command has its effect. In this case, it’s what we’re setting the value of. The other object here is identified as ‘data’, which should be pretty easy to figure out. That’s the cargo that represents the actual new value. For example in the AppleScript: ‘set title of window 1 to “bob”‘, the direct parameter is “title of window 1” and the data is “bob”.

You don’t have to be a genius to know where we’re going next. What the heck is the direct parameter, and what the heck is the data? We have to dig a level deeper. In color-coded terms, we’re now curious to decipher the meaning of the green and purple stuff.

These portions of the AppleEvent are composed of magically coded data that instructs the “Object Support Library” portion of the Apple Event Manager to resolve them in well-defined ways. The most common codes are defined in AEObjects.h. While you’re investigating a situation like this, it can be handy to keep that header file open. Codes not found there are probably defined by scripting definitions, which expand upon the basic built-in functionality by providing support for new properties, commands, etc.

Let’s look at the direct parameter first. It’s an object composed of four directives, identified by the codes ‘form’, ‘seld’, ‘want’, and ‘from’. These codes are all defined in AEObjects.h, and explained more thoroughly in the constants section of the Apple Event Manager Reference. All the same, a bit of plain-speakin’ would probably be appreciated right now. But before I do, let me add the disclaimer that I don’t really get all this. I only get it well enough to meet the task at hand. So with that said, and the real possibility of my making subtle technical mistakes, let’s get moving.

Basically, the ‘form’ is identifying what kind of object we’re talking about here. In this case, it’s a property (‘prop’). OK, this a property reference. Kind of like “name of window 1” is a reference to the “name” property of a given window.

Next up we’ve got ‘want’, which appropriately enough identifies the type of object we’re trying to identify. I think this is generally useful for coercions, situations where you say “count of windows as string.” You can tell the Apple Event Manager that what you’re looking for is a property, but you’d like it as a string instead. In this case, we’re happy to have the object remain a property reference, because we’re just trying to point at it for AppleScript’s sake.

The first really intriguing key is ‘seld’, which means “the unique interesting data,” i.e. the meat. The content is a type ‘pPly’. Hmm, that’s nothing standard. We’ll have to file that away for later.

Finally, we have the ‘from’ key, which is also mnemonically appropriate, in that it identifies the container from which this item (property in this case) should be fetched. If you look at the contents of the ‘from’ data, you’ll see that it’s basically a recursive exercise in applying the same code interpretation we’re doing here. So what do we have so far? If we try to mimic what we see in pseudo-AppleScript it looks something like this:

set 'pPly' of '????' to '????'

Still a lot of variables, but we know how to figure out the details. Just keep looking at codes and making sense of them. So that I don’t spend all day typing, maybe I better skip ahead and say that the purple stuff is an indexed object specifier that refers to “window 1” of the application (a null ‘from’ means the top-level container). For the ‘data’ parameter’s contents, the ‘????’, we run into many of the same referential tags. This time it’s a named object with a type code of the mysterious ‘cPly’ variety. You can probably make sense of that now by studying the details carefully. With our newly decoded information we’re left with the following:

tell application "iTunes"
    set [property 'pPly'] of window 1 to  [class 'cPly'] "......"
end tell

Hey! This is starting to look like AppleScript. I’m going to assume the ‘utxt’ with all the “……” is just the AppleEvent log failing to read the actual text out. The real mysteries now boil down to this property ‘pPly’ and this class ‘cPly’. What’s a hacker to do? If we’re lucky, and it turns out that we are, we’ll find these constants defined in the iTunes scripting dictionary. There are two ways to go about this. We could always examine the target application’s resources. In this case iTunes stores the scripting information inside its iTunes.rsrc file. Lots of newer applications use more text-friendly browsing formats such as script suites or sdef files. But we can use a little hack involving the Script Editor to take advantage of its parsing the scripting information and converting it to an sdef file.

The best thing about this hack is you’ve probably already got the target application’s dictionary open in Script Editor. The beauty is, the dictionary window in Script Editor is an opened document to an sdef file in a temporary directory. Just cmd-click the title bar to reveal its location, and select it:

Now you can open the sdef with a standard text editor and search for whatever raw codes you like. When I searched iTunes for our mystery codes, I discovered the following interesting lines:

<property name="view" code="pPly" type="playlist"
  description="the playlist currently displayed in the window"/>
<class name="playlist" code="cPly"
  description="a list of songs/streams" 
  inherits="item" plural="playlists">

Cool! Those are our mystery codes. And their definitions above lead us exactly to the answer, giving clear AppleScript names and meaning to the codes we’re examining. We could have found this by browsing the dictionary with a fine-toothed comb, but when I was looking for the desired attribute, I was fooled by all the false positives. I never considered that the property might be named “view” and that it might be an attribute of the window instead of the application itself. Heck, the code is more intuitive for “playlist” than the actual property name. View?! Anyway, the resulting victory script is as follows:

tell application "iTunes"
    set view of window 1 to  playlist "My Favorite Songs"
end tell

Next time you’re sure there’s a way to script something, but can’t for the life of you figure it out, go find a really useful site like Doug’s AppleScripts. Or if you’re a masochist maybe you’ll find a way to put some of these techniques to work, instead.