User Interfaces for ThisService Scripts

ThisService is arguably — and appropriately — oriented towards text-processing and other “faceless” services. However, with a little imagination it is easy to conjure uses for the basic user interaction capabilities provided by AppleScript’s Standard Additions. Incorporating these commands in a service script requires a little extra sleight of hand, as it may be necessary to direct them to whatever application is active when the service is invoked.

I have had variable success in my various attempts to do this. Here is a summary of how it can be done, combined with an account of problems I’ve encountered with certain configurations.

Finding the Name of the Frontmost Application

In his example word count service, John Gruber demonstrates how to use System Events to determine the name of the frontmost application:

tell application "System Events"
    set _app to the name of the first process whose frontmost is true
end tell

Another method discussed at the Mac OS X Hints forums also seems to work, even though the .app extension is included in the application name:

set _app to name of (info for (path to frontmost application))

Now the frontmost application (whatever it may be) can be directed to do something.

tell application _app to ...

This is useful because we don’t necessarily know what application will be active when our service is invoked.

An Interface for Input Services: A Predictable Success

ThisService can generate three kinds of scripts: those that act on input, those that produce output, and those that do both (filters). Gruber’s word count service acts on input, as does my save selection service. The AppleScript that powers these kinds of services looks something like this:

on process(_input)
    tell application "System Events" to set _app to the name of the first process whose frontmost is true
    tell application _app to display alert "Input: " & _input
end process

Example Input Service

When text is selected and the service is invoked, an alert reports the selected text.

An Interface for Output Services: A Disappointing Surprise

Here is the code for a simple service that produces output. Via the same mechanism as the input service, it attempts to report the name of the frontmost application and return that string:

on process()
    tell application "System Events" to set _app to the name of the first process whose frontmost is true
    tell application _app to display alert "Output: " & _app
    return _app
end process

Example Output Service

Running this service causes the frontmost application to become unresponsive for an abnormal length of time. Spin Control indicates that the service executable also hangs, although I don’t know how to interpret the report. The alert subsequently appears, but the service fails: the script’s return value is not inserted. A relevant message appears in the Console:

2007-09-12 21:45:28.621 TextEdit[2721] tossing reply message sequence 9 on thread 0x306a90

An Interface for Filter Services: Beating a Dead Horse

This filter service exhibits the same problem as the example output service.

on process(_input)
    tell application "System Events" to set _app to the name of the first process whose frontmost is true
    tell application _app to display alert "Input: " & _input & return & "Output: " & _app
    return _app
end process

Example Filter Service

The alert appears after an extended delay, but no output is received.

An Interface Compromise for Output and Filter Services

One way to circumvent the issue is to tell System Events to display the interface itself. This eliminates the delay and allows the service to return successfully.

on process()
    tell application "System Events"
        set _app to the name of the first process whose frontmost is true
        activate
        display alert "Output: " & _app
    end tell
    return _app
end process

Example Output Service Hack

Unfortunately, this approach has the unresolved side effect of stealing focus from the frontmost application’s windows. Explicitly restoring focus would require something like tell application _app to activate, which exhibits the problematic behavior.

on process(_input)
    tell application "System Events"
        set _app to the name of the first process whose frontmost is true
        activate
        display alert "Input: " & _input & return & "Output: " & _app
    end tell
    return _app
end process

Example Filter Service Hack

Without activate, System Event’s alert wouldn’t receive focus, either.

Inconclusive Conclusions

The method described above allows output and filter services to present simple user interfaces, but it is an imperfect and somewhat annoying solution. If you want to provide a robust interface for an arbitrary service, you should probably write the service from scratch. On the other hand, tell application _app’s compatibility with input services suggests the same idiom ought to be possible with the other kinds of services produced by ThisService.

It is also possible that I have overlooked or misunderstood some aspect of AppleScript, services, or Mac OS X which explains the perceived discrepancy between service types. Elucidation is welcome.

Posted on Wednesday, September 12th, 2007. Tags: .