UI Scripting with Applescript
June 24, 2005

It’s amazing how easily I can get distracted.

Currently my main distraction is playing with Feed Me, my bayesian news filter, which is a thinly veiled excuse to mess about with Python.

However, yesterday I managed to get distracted from that distraction.

I’ve been thinking that I really need to sort out some productivity tools for myself, to deal with the sort of repetitive administrative tasks that I find myself doing again and again.

To that end, I installed FastScripts, and set about writing some trivial scripts to do some simple things.

One of the scripts I wanted was to find all the recent mails received from a particular person, since this is something that I do manually quite often. The script would only save a few keystrokes and mouse clicks, but I figured that if I could link it to a hot key with FastScripts, it would be handy.

Unfortunately, it turns out that Apple Mail’s scripting support doesn’t allow you to perform searches.

Luckily, there is a work around, in the form of the new GUI scripting capabilities of MacOS X.

Essentially, this allows you to ‘fake’ any user interface operation by scripting it in terms of the menus, buttons and or places that you would click, and the keys that you would press.

Presented below, for your delight and delectation, is a little example of how I used this to script a search in Mail.

The hardest part is working out exactly how to specify the interface widget that you’re trying to activate. A valuable helper here is the Prefab UI Browser which allows you to browse another application’s user interface whilst it’s running, so that you can discover the hierarchy of interface widgets.

The bad thing with all of this, of course, is that it’s a very fragile way to program. By relying on the names and layout of particular widgets, the script becomes very likely to break when a new version of Mail comes along with a different user interface. So I wouldn’t recommend it if there is a useable Applescript interface available instead, but it’s bloody handy when there isn’t one!

The key routine here is searchMailboxForMailFrom() which takes the name of a user and a mailbox, and uses them to perform a find in Mail. The assistive devices support that makes all this possible is only available in OS X 10.3 or higher, and the user has to have turned it on in the system preferences, so it’s necessary to check for that if you want to be a well behaved script and fail gracefully when there’s a problem.


on run
	if gotInterfaceScripting() then
		set mailIcon to "/Applications/Mail.app/Contents/Resources/app.icns"
		display dialog "Search for mail from:" with title "Search Inbox" default answer "" with icon POSIX file mailIcon
		if the result's button returned is "OK" then
			searchMailboxForMailFrom("Inbox", the result's text returned)
		end if
	end if
	
end run


on searchMailboxForMailFrom(searchIn, searchFrom)
	tell application "Mail"
		activate
	end tell
	
	tell application "System Events"
		tell process "Mail"
			-- select first mailbox in list - should be the inbox
			select (first row of outline 1 of scroll area 1 of window 1 whose value of static text 1 is searchIn)
			
			-- select the "mailbox search" menu item
			click menu item "Mailbox Search" of menu "Find" of menu item "Find" of menu "Edit" of menu bar item "Edit" of menu bar 1
			
			-- type the search term and hit return
			keystroke searchFrom
			keystroke return
			
			repeat until exists button searchIn of window 1
				-- can get errors if the script runs before the button has been created...
				-- so we wait here
			end repeat
			
			-- click the mailbox button (as opposed to searching in all mailboxes)
			click button searchIn of window 1
			
			-- click the from button to search in the From: field
			click button "From" of window 1
		end tell
	end tell
end searchMailboxForMailFrom


on gotInterfaceScripting()
	if gotPanther() then
		-- check to see if assistive devices is enabled
		tell application "System Events"
			if UI elements enabled then
				return true
			end if
		end tell
		
		tell application "System Preferences"
			activate
			set current pane to ¬
				pane "com.apple.preference.universalaccess"
			set the dialog_message to "This script utilizes " & ¬
				"the built-in Graphic User Interface Scripting " & ¬
				"architecture of Mac OS X " & ¬
				"which is currently disabled." & return & return & ¬
				"You can activate GUI Scripting by selecting the " & ¬
				"checkbox “Enable access for assistive devices” " & ¬
				"in the Universal Access preference pane."
			display dialog dialog_message buttons {"Cancel"} ¬
				default button 1 with icon 1
		end tell
	end if
	return false
end gotInterfaceScripting


on gotPanther()
	-- get the system version
	set the hexData to system attribute "sysv"
	set hexString to {}
	repeat 4 times
		set hexString to ((hexData mod 16) as string) & hexString
		set hexData to hexData div 16
	end repeat
	set the OS_version to the hexString as string
	if the OS_version is less than "1030" then
		display dialog "This script requires the installation of " & ¬
			"Mac OS X 10.3 or higher." buttons {"Cancel"} ¬
			default button 1 with icon 2
		return false
	end if
	
	return true
end gotPanther

« New Theme Endian issues »
Got a comment on this post? Let us know at @elegantchaoscom.