Applescript via Python

Controlling Mac applications with appscript

Over the years, Apple has fallen in and out of love with Applescript, its "official" scripting language for MacOS. True, Applescript isn't going to go away any day now and true, it is a very simple language and easy to use. But if you don't want to have to learn a new language just to manipulate MacOS, and want to something fully-powered, there is a choice. appscript is a Python interface to Applescript, giving all the power of AS in neat pythonic code::

app('TextEdit').documents['Read Me'].paragraphs[1].get()

appscript is - for the most part - well documented. The developer also supplies two utilities for interrogating application dictionaries (the Applescript interface they provide) and translating Applescript into appscript Python. Unfortunately:

  • Some applications don't implement an Applescript interface.
  • The translation utility doesn't recognise some common Applescript commands and idioms.
  • Some of the advice found on the web for solving some appscript problems is flat-out wrong. (My guess is that the advisors either didn't test their solutions or the API has changed.)

This short script demonstrates appscript and shows

The problem

I have a list of "to do" items, extracted from an organiser program into a different program, Things. Unfortunately, Things doesn't have an Applescript interface. Nor does it have an import facility.

The solution

On the Things forums, Karel provided two Applescripts for converting mail messages into Things todos and was kind enough to supply the source. The below is largely based upon them. I munged my input into a simple text file, with an item per line. This is the file 'igtd.out' below:

from appscript import *
import os, time

# build the list of input items
infile = open ('igtd.out', 'r')
items = [x.strip() for x in infile]

# get the application and bring it to the front
things_app = app('Things')
things_app.activate()
# move selection to the inbox
time.sleep (1)
app('System Events').keystroke('0', using=[k.command_down, k.option_down])
for item in items:
   # create a new item
   app('System Events').keystroke('n', using=k.command_down)
   time.sleep(1)
   # copy text to clipboard
   os.popen('pbcopy','w').write(item)
   # paste into new item
   app('System Events').keystroke('v', using=k.command_down)
   time.sleep(1)
   # hit return to close item
   app('System Events').keystroke('r')
   time.sleep(1)

Some explanations:

  • This mimics an user creating a todo item in Things by:

    • Moving to Things inbox (command-option-0)
    • Creating a new item (command-N)
    • Pasting some text in (command-V)
    • Hitting return to close the item.
  • Between actions, a wait may be required to allow the previous action to taken effect. In the original script, this was accomplished with the Applescript command delay 1 and a timeout. appscript can't translate delay but the python equivalent is time.sleep. The 1 second given is plenty, and probably can be reduced or even eliminated.

  • Sending keystrokes to the front application can be done with app('System Events').keystroke(KEY, using=MODS) where KEY is the keystroke and MODS is either a single or sequence of key modifiers. The key modifiers are constants in the k subpackage of appscript.

  • Text can be written to the system clipboard with:

    os.popen('pbcopy', 'w').write(item)
    

    where ITEM is the text in question.

  • app('System Events').keystroke('r') is the equivalent of hitting the return key.

References