I use a bit of UI scripting to automate some of the screenshots used in Marked marketing and documentation. One (significant) part of that scripting is sending keyboard commands, so I’ve built a few routines to help out.

The script includes 3 different routines. The base is keyCmd(_string). It takes a single key, with modifiers either typed out or defined using DefaultKeyBindings-style shortcuts, where “$” is Shift, “~” is Option, “^” is Ctrl, and “@” is Command. So you can send either “cmd i” or “@i”. Named keys are recognized, so you can also use “shift opt left” to hit ⇧⌥←. When using named keys, you need to either have a space after shortcuts (e.g. “$~ left”), or use the string versions, as in the previous example.

keySeq is a shortcut for sending a batch of keyCmds at once, e.g. keySeq("@n $~@s",1) to send Command-N followed by Shift-Option-Command-S. The second parameter is the delay between keys, 0 for none.

keyType takes a string and a delay and types each character out, simulating keyboard typing. This command doesn’t allow modifiers, just characters, and the modifier shortcuts are typed as characters. Running keyType("This will type out in whatever field is focused",.01) will simulate typing the sentence out with a very brief delay between each keystroke.

I’m just sharing these in case they’re of use in your own scripting. If I get around to it, I’ll share more of my routines for window manipulation, menu clicking, and screen capture. I’m certain I can name several readers who will know of more elegant ways to accomplish all of these. I’d be delighted to update with your genius if you share it.

-- set the application to type into
-- you need to activate and focus a window prior to calling keyCmd
property process_name : "Finder"

on lowercase(_text)
	set _output to do shell script "echo " & quoted form of (_text) & " | tr A-Z a-z"
	return _output
end lowercase

(* Trigger a hotkey or character
Usage: keyCmd("hotkey to trigger")

$ : shift
~ : option
@ : command
^ : Control


Trigger "k"
Trigger "K"

Trigger Command-down arrow
	keyCmd("cmd down")

Trigger Command-I
	keyCmd("cmd i")

Trigger Shift-Option-Command-S
	keyCmd("shift opt cmd s")
on keyCmd(_string)
	set _mod to {}
	set _codes to {{name:"left", code:123}, {name:"right", code:124}, {name:"down", code:125}, {name:"up", code:126}, {name:"escape", code:53}, {name:"esc", code:53}, {name:"pgdown", code:115}, {name:"pgup", code:119}, {name:"home", code:116}, {name:"end", code:121}, {name:"f1", code:122}, {name:"f2", code:120}, {name:"f3", code:99}, {name:"f4", code:118}, {name:"f5", code:96}, {name:"f6", code:97}, {name:"f7", code:98}, {name:"f8", code:100}, {name:"f9", code:101}, {name:"f10", code:109}, {name:"f11", code:103}, {name:"f12", code:111}, {name:"f13", code:105}, {name:"f14", code:107}, {name:"f15", code:113}, {name:"f16", code:106}, {name:"f17", code:64}, {name:"f18", code:79}, {name:"f19", code:80}, {name:"f20", code:90}, {name:"delete", code:51}, {name:"del", code:51}, {name:"tab", code:48}, {name:"return", code:36}, {name:"enter", code:76}, {name:"space", code:49}, {name:"shift", code:60}, {name:"option", code:61}, {name:"opt", code:61}, {name:"control", code:62}, {name:"ctrl", code:62}, {name:"command", code:55}, {name:"cmd", code:55}, {name:"capslock", code:48}}
	if _string contains "cmd" or _string contains "command" or _string contains "⌘" or _string contains "@" then set end of _mod to command down
	if _string contains "shift" or _string contains "⇧" or _string contains "$" then set end of _mod to shift down
	if _string contains "opt" or _string contains "option" or _string contains "⌥" or _string contains "~" then set end of _mod to option down
	if _string contains "ctrl" or _string contains "control" or _string contains "⌃" or _string contains "^" then set end of _mod to control down
	log (_string)
		set _key to last item of words of _string
	on error errMsg number errNum -- errors if string is punctuation
		set _key to _string
	end try
	if _key contains "$" or _key contains "@" or _key contains "^" or _key contains "~" then
		set _key to last item of characters of _key
	end if
	set _code to 0
	set _maybe_sys_key to my lowercase(_key)
	repeat with _keycode in _codes
		if name of _keycode is equal to _maybe_sys_key then
			set _code to code of _keycode
		end if
	end repeat
	log (_code)
	tell application "System Events"
		tell process process_name
			if _code > 0 then
				key code _code using _mod
				keystroke _key using _mod
			end if
		end tell
	end tell
end keyCmd

(* trigger a sequence of keystrokes
_sequence: Pass either a space separated string or a list of quoted keys
_delay: delay between characters, 0 for none

Usage: keySeq("character list with shortcut modifers", 0-9)

Hit ⇧⌘K and type "test"
	keySeq("$@k t e s t")
	keySeq({"shift cmd k","t","e","s","t")

Select two words left
	keySeq({"shift opt left", "shift opt left"})

on keySeq(_sequence, _delay)
	if class of _sequence is not list then
		set {astid, AppleScript's text item delimiters} to {AppleScript's text item delimiters, " "}
		set _sequence to every text item of _sequence
		set AppleScript's text item delimiters to astid
	end if
	repeat with _key in _sequence
		if _delay > 0 then delay _delay
	end repeat
end keySeq

(* Type a string, no modifiers, with delay
_string: characters to type
_delay: float delay (0 for none) 

Usage: keyType("testing something out", 0.1)
on keyType(_string, _delay)
	tell application "System Events"
		tell process process_name
			repeat with _chr in characters of _string
				if _delay > 0 then delay _delay
				keystroke _key
			end repeat
		end tell
	end tell
end keyType

-- example
set process_name to "TextEdit"

tell application process_name to activate

-- create a new doc, convert to rich text, type tab
keySeq("@n $@t tab", 1)
-- type out a sentence at a decent simulated typing speed
keyType("This is going to type out in TextEdit", 0.02)