We Need a Hero

September 27th, 2006

You’re a good developer. You’ve tackled lots of tough problems they’ve thrown your way. You are respected by your peers. You’ve done a great job with your life! And then there’s that nagging in the back of your head. You can’t kill the guilt of that glaring omission – the horror of horrors. The place where your code just doesn’t stack up.

You’ve got sucky AppleScript support.

Don’t beat yourself up. Apple is to blame for this, and they should step up and start taking responsibility. Adding AppleScript support to an application, even with the pleasure/pain of Cocoa scripting, is as much of a rite of passage for developers as learning to write AppleScripts is for users. That is, it’s a nearly universally agreed upon royal pain in the ass. And that’s wrong.

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?

I talk to a lot of developers through mailing lists, IRC, and even live in person. I’ve never met one who was completely confident about adding scripting support to an app. Even the smartest Mac programmers I’ve ever met tend to confess that they “just tweaked things until they magically worked, then stopped touching it.”

This mirrors my own experience. So let me vent some things that I think are wrong with Apple’s handling of “the AppleScript situation.”

  1. Inadequate Documentation. The Cocoa Scripting Guide is pretty frickin’ good but it falls short on some frustrating levels. The step-by-step is encouraging, but it fails to mention key points like “for some features, an sdef just totally won’t work, and you’ll want to resort to using old-fashioned script suites or even an ‘aete’ resource.”

    Worse are the misleading details about indexed array operators. I ran into this with FlexTime. I need the scripter to be able to manipulate the list of activities associated with a given document. My code-level name for these objects is “TimedEvent.” The documentation suggests that methods of this form should do the trick:

    - (void)insertObject:(TimedEvent *)newEvent
    		inTimedEventsAtIndex:(int)index
    

    But in practice, I had to google my way through blood, sweat and tears to discover that this form was required for scripting:

    - (void) insertInTimedEvents:(TimedEvent*)newEvent
    		atIndex:(int)index
    

    Ugh! Kill! Kill!

  2. Buggy Examples. Like any good programmer, you’re looking to stand on the backs of giants. In the case of scripting, you’re going to want a good “scripting definition” file, consisting of all the standard Cocoa-supplied handlers, and any custom goodies you add on yourself. So that “standard handlers” stuff should be easy to copy and paste, right? If you look for such a starting point, you’ll find several options. And they all seem to be buggy. It’s not easy for me to advise a developer starting with AppleScript whether they should use Skeleton.sdef, NSCoreSuite.sdef, or some other less scientific approach. The documentation is equally vague:

    If your application supports any of the commands, classes, or other scriptability information defined in the Standard and Text suites, as most do, you should copy the suite elements for those suites into your sdef from an existing sdef file, such as Sketch.sdef.

    It doesn’t matter which path you take, you’ll end up banging your head bloody against the keyboard as you try to decipher endless, frustratingly nuanced bugs. Script results will come back with confusing chevron values instead of class names:

    «class » «constant ****all » 
    	of script item "Text Input - 2 Btns"
    

    Confusing console log messages will appear, complaining that methods like “scriptingAnyDescriptor” are not implemented in your objects. You’ll wonder aloud what the hell that method is for, and even ask around on mailing lists and search google, only to find the plaintive cries of other frustrated developers.

    There should be no “hunt-and-gather” phase in adding Scripting support to an application. Apple should include a perfectly functioning standard sdef as part of the Xcode templates for all application targets. If they want good scripting support, they need to throw us a bone.

  3. Spotty e-Mail Support. There’s a mailing list dedicated to allegedly solving the problems of frustrated scripting support implementors. The problem is, nobody from Apple reliably steps up to answer the most vexing questions posed there. Inevitably the tough questions either go unanswered, or are eventually acknowledged by Dustin Voss or Bill Cheeseman. The two most reliable sources for AppleScript implementation support are non-Apple employees who usually have to painfully admit they only figured it out after hours of scientific experimentation. When Dustin or Bill doesn’t chime in, occasionally an Apple representative does, but it’s highly unpredictable, and more often than not silence reigns. This sucks!

    OK, I know, it’s not their “job” at Apple to monitor and reply to issues on the free mailing lists. But maybe it should be. The fact is, if there was anybody on that list replying to questions with the accuracy and authority of somebody like Eric Schlegel on Carbon-Dev, then AppleScript support would be a lot better. We’re treading water, here!

    Other groups at Apple also provide stellar support through their lists. Core Audio leaps to mind. USB, Quartz Composer, Cocoa, and Networking also have real experts – the developers themselves – providing quick and friendly support to developers on a daily basis. Most of these groups in fact have 2 or more team members actively tackling problems on the lists. And when something is undocumented or broken, they admit it and we move on. I’m sure there are others. At Apple, helping developers is the rule rather than the exception, and I’m grateful for that.

    Thank you, Eric! Thank you Jeff Moore, Bill Stewart, Fernando Urbina, Barry Twycross, David Ferguson, Pierre-Olivier Latour, Chris Kane, Douglas Davidson, Ali Ozer, Becky Willrich, and Quinn! I’m sure there are dozens more that don’t spring to mind or whose lists I don’t read. You’re all heroes, and we appreciate it. Truly.

    But we need a Cocoa Scripting hero.

Contained By What?

One of the biggest hurdles in adding Cocoa Scripting support to an application seems to be coming to terms with AppleScript’s “containment hierarchy.” It requires that any scripted object be able to fully specify its location in the containment hierarchy. For instance, in MarsEdit this text paragraph I’m typing right now needs to know that it’s the:

last paragraph of current text of post window “We Need a Hero” of application “MarsEdit”

See, that’s how AppleScript refers to things. And Cocoa Scripting exposes objects directly to AppleScript, so that scripters can manipulate them directly. Long story short, is your Cocoa objects need to know how to tell AppleScript where they live. The way this happens is by way of the “objectSpecifier” method, which most of your scriptable objects will end up needing to implement.

This was a big mental block to me, because I’m not used to “back-referencing” from my objects to their containers. Maybe it’s my application design naiveté, but I find it awkward that in FlexTime for instance, my “cue action” object should know that it’s owned by a particular activity in a particular document. This just seems clunky to me. But as far as I know we’re stuck with it.

But just because the object exposes the objectSpecifier method, doesn’t mean it has to be in charge of it. I’m more comfortable in general with the containing object being responsible for claiming and disavowing ownership of a given object. So I came up with a generic solution that works pretty well for me.

RSContainableObject (Free, MIT License) is a generic NSObject subclass that can have its owning object and relevant key set on it. Then, when somebody asks it for its objectSpecifier, it uses that information to provide the required directions. The actual specification is accomplished by asking the containing object to reckon the object relative to itself, so the parent must itself respond to “objectSpecifier,” either because it’s also an RSContainableObject, or because it implements the method itself.

This approach works for me because I discovered that the vast majority of scriptable objects in my applications are in fact direct subclasses of NSObject. Those that are not, like my NSDocument subclass, inherit the objectSpecifier magic directly from Cocoa. So my typical set up is an NSDocument subclass that contains trees of various RSContainableObject subclasses, which all know how to reckon themselves recursively back up the chain in terms of their container.

These custom model objects end up with declarations that look something like this:

@interface TimedEvent : RSContainableObject <NSCopying>

Then when such a model object is set as a property or added as an element of a containing object, the container takes responsibility by setting the pertinent ownership terms on the object:

// Associate the object with us
[newEvent setObjectContainer:self 
			containedByKeyName:@"timedEvents"
			asToManyRelationship:YES];	

The above example is for a “to many” relationship, meaning it’s a member of a list of items. But the same event object could be associated as a property relationship with a similar type of pattern. Here I pretend there’s an attribute of this object called “best damn timer” with an associated Cocoa method of “bestDamnTimer”:

// Disassociate from the old object
[mBestDamnTimer setObjectContainer:nil
		containedByKeyName:nil
		asToManyRelationship:NO];

// Retain & Release
[mBestDamnTimer release];
mBestDamnTimer = [newTimer retain];

// Associate
[newTimer setObjectContainer:self 
		containedByKeyName:@"bestDamnTimer"
		asToManyRelationship:NO];

By factoring the messy “objectSpecifier” code into one place for a wide variety of use scenarios, I avoid having to engage too often in the (for me, at least) bug-prone ritual of writing that method from scratch.

You might observe that a problem with this approach is that objects can’t be owned by more than one object. That’s true, but that’s also a basic limitation of AppleScript. Objects have “one true specifier” even if there are multiple legal specifiers that lead to it. For instance “default timer of document 1” might resolve to the same object as “timer 3 of application ‘ClockThing'”, but the RSContainableObject relationship should only be between the application and the object. A given object at one time can have only one “canonical” object specifier, and that is the relationship that this class affords.

Hope this helps…. but we still need a hero!

Update: A couple readers have pointed out that I might as well link to some good resources for AppleScript debugging and design:

TN 2106 – Scripting Interface Guidelines. This has a good discussion of the containment hierarchy, what it means for your application, and how it relates to the world-view of the scripter. Thanks, Erik Wrenholt.

TN 2124 – Mac OS X Debugging Magic. This talks about general techniques for debugging AppleEvents and also reminds us of the Cocoa Scripting debugging “defaults” setting NSScriptingDebugLogLevel. I always forget about that and maybe I wouldn’t be quite so tightly wound up about AppleScript if I remembered it! Thanks, Jonathan Wight.

FastScripts 2.3

September 26th, 2006

Today I released FastScripts 2.3 (and an updated FastScripts Lite!). FastScripts 2.3 contains the following significant changes:

  • A refined grayscale menu bar icon
  • Support for unmodified keys as script shortcuts (10.3 and later only)
  • New preference for toggling “Launch at Login” feature
  • Reorganized Preferences dialogs
  • Improved AppleScript access to FastScripts script hierarchies
  • Miscellaneous bug fixes

The new grayscale menu bar icon was basically saved by the help of Bryan Bell, an awesome icon designer who agreed to help me out with it. I’m not sure what’s more exciting, the fact that FastScripts has a great looking icon, or the fact that such a prestigious art is shipping in one of my applications! Thanks, Bryan.

Credit is also due to John Gruber who complained almost a year ago about the old icon, when I promised I’d look into improving it “within a few weeks.” He finally recommended Bryan to help me set things right.

FlexLog: Logging For FlexTime

September 23rd, 2006

One of the requests that has come in for FlexTime is that it should support some kind of logging mechanism. That is, you should be able to keep statistics on certain activities. This is especially important to people using FlexTime to improve their productivity or adherence to beneficial habits like stretching or exercise.

I do hope to fulfill this requirement in a future release of FlexTime, but in a stunning example of customer ingenuity, Ryan Ballantyne has beat me to the punch and implemented just such a mechanism, by leveraging FlexTime’s scripting support. When I see things like this I have to take a step back and say, “whoah!” It’s gratifying to see somebody seize on the functionality of FlexTime with such enthusiasm. Sure, the enthusiasm was toward filling a shortcoming of FlexTime, but the fact that he had the vision and followed through on it is at least a testament to its … flexibility.

Ryan also had some general observations about FlexTime, and how he uses it:

“I was once given a stretch routine by a chiropractor to help my back and neck. It really does help, but it’s hard to stay dedicated and do it with frequency. Also, each stretch calls for 20 seconds, and it can be hard to count out the time on my own.”

Another interesting use he describes is using it as a timer in a group gaming scenario:

“I once used it to time a game of Rummikub. In that game, everyone is allowed only 2 minutes per turn. I set flextime up to speak text at 30-second intervals, reminding people of how much time they had left. It was great.”

Interesting stuff, Ryan! I hope that your example will inspire other people to see what Flextime can do for them.

Ten Free Ideas

September 23rd, 2006

I meant it when I downplayed the value of ideas in my critique of the My Dream App contest:

“Ideas are practically free. They run like flood-water through every conceivable channel of the internet.”

In the month since I wrote that article, those words have been the most controversial. It seems most people still think ideas are golden. Ideas are important, yes. Necessary, yes! But they’re nothing special. Like water, they’re critical to life but at easy reach for most of us.

They’re mainly significant to the person who thought of them, because they represent an “inspiration bookmark” for that person. They are reminders of something that satisfies you. In the same way that you might make a note of a brand of wine you particularly enjoyed, or a movie you want to rent, or a museum you want to visit. Ideas are personal. Somebody else’s idea is only valuable if it happens to be something you were inevitably going to be passionate about, yourself.

“No one wants to hear what you dreamt about unless you dreamt about them. Don’t let that stop you. Tell them anyway.” — Built to Spill (iTunes Song Link)

So I’ll tell you about my dreams. Here are ten ideas from my years-old idea file, in no particular order.

Most of this ideas won’t inspire you, because they’re mine. You’ll know them because they’ll sound like a bad idea to you. But if you like an idea you read here, if it really fires you up, then pursue it! No permission necessary. No credit necessary. I won’t sue you. You’ve got a right to be inspired, too. Just pick your own app’s name. Maybe I’ll even buy the product. Also, let me know if any of these ideas already exist. I’ve summarized what my take is on the “state of the market” for each idea is, but I might be missing some awesome product that I just haven’t heard about.

To make it even easier for you to steal my ideas, I’m including a proposed “next action” for each idea (this approach comes from GTD and is also consistent with my Easy Programming philosophy). What simple step can you take to come closer to realizing the dream?

  1. Privacy Guard is an elegant, simple UI wrapper on some private web-browsing technology. Probably Tor, the powerful and popular solution from the EFF. Privacy Guard runs in the background and offers a menu bar icon to easily reflect your privacy status, and allow you to flip between protected and unprotected modes.

    State of the market: Vidalia is an open source UI wrapper available from EFF itself. It’s a good first step but has cross-platform clunkiness. It needs a Mac overhaul, but could prove to be a good arena in which to realize this “product.”

    Next action: Look into adding a menu bar icon to Vidalia.

  2. Snapshots examines any folder or disk on your Mac and produce a reference description against which future “snapshots” can be compared. This is particularly useful for “before and after” tests, for example when installing software. Taking a snapshot of your disk before and after a Software Update would show you in precise detail every file that the update has changed on your disk. No contents of files are actually saved, just a checksum or something to show you whether something is the same or different.

    State of the market: I don’t know of any product like this, but I haven’t looked too hard.

    Next action: Write a shell-tool proof of concept that accumulates checksums for a directory of files.

  3. Google Book Reader is a desktop application aimed at leveraging the growing availability of public domain books via Google. The UI consists of a stylish window designed as a “book metaphor.” The front cover consists of a search box that allows the user to easily bring up Google book results. When a result is clicked, the window animates as if opening a book, and presents the contents of the PDF file. The current page of this book is saved between launches so a user can always easily resume reading whichever “books they have open.”


    (Click for full-size)

    State of the market: specialized PDF readers seem like a ripe market to me, yet everybody still reads with Acrobat or Preview. With the PDF capabilities in OS X, there should be many more specialized reading apps. This would just be one of many.

    Next action: Investigate viability of searching Google’s public domain offerings programatically.

  4. Font Spy makes it easy for users to instantaneously examine attributes of any font displayed on the screen. Similarly to the Cmd-Ctrl-D “dictionary lookup” feature in Tiger, Font Spy offers a global hotkey that, when pressed, pops up a handy reference window including the text’s font name, color, and other style considerations.

    State of the market: Nothing like this exists, that I know of. It might be appealing to designers, though it’s possible that most designers are familiar enough with fonts that the information provided by Font Spy would be useless.

    Next action: Come up with a reliable way of detecting font information for whatever is pointed to on screen. This is not so easy, even with the Accessibility API.

  5. Focus is a productivity aid that embraces the concept of “work modes.” The user defines any number of modes which instruct Focus to performing an arbitrary list of instructions as appropriate. Focus understands a number of common instructions such as hiding or launching applications, and can also run arbitrary scripts. Focus triggers these actions when a mode is entered or left. For example if the user switches to “play mode,” Focus could open the web browser, start iTunes, etc. When the user switches to “writing mode” Focus could quit or hide distracting apps like Mail, iChat, and Safari, and open up something like WriteRoom.

    In addition to one-time actions upon mode switch, Focus enforces certain policies of the mode. For instance a user can configure “writing mode” such that Safari cannot be used at all, or such that it can only be used 5% of the time. Focus would strongly encourage the user to stick to their mode policies, by presenting dialogs that inform them of the violation and offer to return them to a “mode compliant” application.

    State of the market: There are a number of “sort of like that” utilities out there, but they tend to, ahem, focus on a specific method of focusing. Whatever the author in particular thought would be most useful. Focus unleashes the power of variable work modes for the user to describe as they see fit.

    Next action: Maybe a proof-of-concept based on AppleScripts alone, triggered by FastScripts or another launcher.

  6. Figure Bot is a graphic-generation application that streamlines the production of consistently styled “annotation figures” for graphics production. Designers use Figure Bot instead of tediously composing in PhotoShop or Illustrator stylistically repetitive figures such as numerals or letters in circles:

    With Figure Bot, the user selects an enclosing shape, font, colors, etc., and a starting number or letter. A global hotkey then copies the next figure onto the clipboard where it can be easily pasted into whatever graphics application the user is working in.

    State of the market: I’ve never seen anything like this. I’m curious to know whether the field of “generated graphics” is larger than I know. I expect lots of stuff like this is achieved through scripting of a particular application.

    Next action: Write a command-line tool to prove concept of copying a generated graphic to the clipboard.

  7. Hancock is the world’s simplest “PDF signature applicator.” It’s a million times simpler than PDFPen, and strives only to be a signing agent, not a utility for general PDF manipulation. Hancock provides a simple “signature capture” UI, using a similar motif to what you see on ATM checkouts at the supermarket. Any number of these captured signatures can be stored, optionally in the keychain to preserve the illusion of signatures being secure. The signature can then be overlaid on any PDF or other graphical document, nudging and scaling the graphic to fit appropriately into the designated space. The resulting document can then be printed or saved as a separate PDF for further transmission.

    State of the market: PDFPen probably does this fairly well, but it’s too complicated and too expensive. But there might not really be that many people who want or need to apply signatures to PDF documents on a regular basis.

    Next action: Develop a simple signature capturing subclass of NSView.

  8. Sign Shop leverages the graphical powers of Mac OS X to provide users with a number styled “beyond fonts” graphic generation modules. The user types in whatever text they desire and sees a live preview of their text in a given preset style. For instance, I select “Wavy Ripples” and type “Red Sweater Blog”:

    Other built-in styles include “Engraved Wood” and “Stitched Embroidery,” producing textual graphics that are impossible with plain fonts. A published plugin format allows 3rd parties to provide modules

    State of the market: Don’t know of anything like this. Could be a hit especially if some of the effects are really fun to play with, as in Apple’s Photo Booth.

    Next action: Build a few example plugins and work towards a standardized format.

  9. Be My Guest establishes a “shared files zone” between computers on a LAN, without requiring any special configuration. Users who are interested in sharing files each run a copy of the application, whereupon they each see a shared Finder-like window onto which any files or clippings they drag are visible to all other users on the LAN. Files remain visible and available as long as the providing user is running Be My Guest.

    State of the market: There are some “simple file transfer” apps out there, Drop Copy being perhaps the closest in simplicity and functionality to my vision. But they all require a “target” which adds mental noise to the equation. The idea of a shared zone makes it easy to pop things “into the commons” where all users on the LAN can be assured of having access to it.

    Next action: Develop a Bonjour-enabled server/client that pays attention to the comings and goings of peers on the LAN.

  10. Page Flipper uses a built-in or external iSight camera to detect user gestures (head nods and shakes) as instructions to turn pages of a PDF document forward or backward. Page Flipper is especially useful for pianists and other musicians who store sheet music in digital form on their Mac. With Page Flipper and a laptop, users can practice pieces all the way through without pausing to turn the pages.

    State of the market: This could be a lifestyle-changing product for technically adept musicians who are considering “going digital.” Current solutions involve printing longer pieces and taping them up in a long strip of sheets, or having an assistant on hand to turn the pages for you. After evaluating response in the music market, other uses might be considered. Hands-free page turning could be handy for the kitchen, auto shops, etc, where other triggers such as voice recognition might be suitable alternatives to head gestures.

    Next action: Research the viability of detecting head movements reliably.

There you have it, ideas are not golden. And I put my ideas where my mouth is. So rip me off!

Incidentally, while combing this file, I found these notes:

- Stretch Buddy
   - Simply utility to cue the recital of a timed
     series of actions, such as a stretching routine.
   - Features:
      - Manages any number of "routine" documents
         - An array of timed stages - sort of like 
           "Automator" in design
         - Plays a custom "start sound" when triggered
            - Built In Sounds
            - Text to Speech
            - Custom sound file
      - Supports the ability to export a "routine," 
        as an audio file to iTunes

and

- FAST Script Menu Implementation
   - keep scripts cached in memory for rapid execution!
   - Can be a faceless background app with NSStatusBar

Good to know that at least some of these ideas have become a reality!