Now that Keyboard Maestro 8 makes it so easy to copy an action or macro in XML (plist) format,
one way of executing actions and action lists is to specify their details in scripting language record (Applescript) or dictionary (JavaScript) format, and then pass the record, or list of records, to a function which executes them as KM actions or action lists.
In what context might this be useful ?
Simply when you want to be able to change any action settings at runtime – easier to make changes to a record or dictionary than to an XML string.
(and, of course, a little more readable).
I personally tend to do this in JavaScript, but to show how it can be done in AppleScript too, here is a "Hello world" example – a list of two Keyboard Maestro Play Sound actions, in the form of an Applescript list of two records, and executed by a kmEvalASMay function, which:
- Executes the AS list, if it can be interpreted as a sequence of KM actions,
- returns the XML version of the list (if ditto)
(The final May in the function name just means Maybe)
use framework "Foundation"
use scripting additions
-- EXECUTE APPLESCRIPT LISTS OF RECORDS AS KEYBOARD MAESTRO MACROS -----------
-- kmEvalASMay :: (Array | Record) a => a -> Maybe XML String
on kmEvalASMay(asObject)
set mbXML to plistMay(asObject)
if nothing of mbXML then
mbXML
else
set strXML to just of mbXML
-- Effects
tell application "Keyboard Maestro Engine" to do script strXML
-- Value
mbXML
end if
end kmEvalASMay
-- TEST ------------------------------------------------------------------
-- Execute two KM Play Sound actions
on run
-- First Copy as XML in KM8,
-- then ...
-- set xs to Actions of item 1 of Macros of item 1 of just of
-- asRecFromPlistMay(strXML)
-- (Check that keys like |Path| begin Titlecased)
set lstActions to {{Volume:50.0, MacroActionType:¬
"PlaySound", TimeOutAbortsMacro:true, |Path|:¬
"/System/Library/Sounds/Glass.aiff", DeviceID:"SOUNDEFFECTS"} ¬
, ¬
{Volume:100.0, MacroActionType:¬
"PlaySound", TimeOutAbortsMacro:true, |Path|:¬
"/System/Library/Sounds/Submarine.aiff", DeviceID:"SOUNDEFFECTS"}}
-- Run records as KM macro -----------------------------------------------
set mbXML to kmEvalASMay(lstActions)
-- and return XML --------------------------------------------------------
if nothing of mbXML then
"plist XML not generated ..."
else
set strXML to just of mbXML
writeFile("~/Desktop/asPlist.txt", strXML)
strXML
end if
end run
-- PLIST XML TO AND FROM AS RECORDS ------------------------------------------
-- asRecFromPlistMay :: String -> Maybe Record
on asRecFromPlistMay(s)
set ca to current application
set strTempPath to POSIX path of (path to temporary items as alias) & ¬
drop(3, (random number) as string) & ".plist"
writeFile(strTempPath, s)
try
set v to unwrap(ca's NSArray's ¬
arrayWithContentsOfFile:wrap(strTempPath))
if v is missing value then
{nothing:true, msg:"Clipboard did not contain plist XML"}
else
{nothing:false, just:v}
end if
on error e
{nothing:true, msg:"Clipboard did not contain plist XML"}
end try
end asRecFromPlistMay
-- CONVERT AN APPLESCRIPT RECORD BACK TO AN XML FORMAT WHICH
-- THE KEYBOARD MAESTRO ENGINE CAN EXECUTE
-- plistMay :: AS Object -> Maybe Plist String
on plistMay(arrayOrRec)
set ca to current application
set {v, e} to ca's NSPropertyListSerialization's ¬
¬
dataWithPropertyList_format_options_error_(arrayOrRec, (ca's NSPropertyListXMLFormat_v1_0), 0, (reference))
if v is missing value then
{nothing:true, msg:theError's localizedDescription() as text}
else
{nothing:false, just:¬
unwrap(ca's NSString's alloc()'s ¬
initWithData:v encoding:(ca's NSUTF8StringEncoding))}
end if
end plistMay
-- GENERIC FUNCTIONS ---------------------------------------------------------
-- drop :: Int -> a -> a
on drop(n, a)
if n < length of a then
if class of a is text then
text (n + 1) thru -1 of a
else
items (n + 1) thru -1 of a
end if
else
{}
end if
end drop
-- unwrap :: NSObject -> AS value
on unwrap(objCValue)
if objCValue is missing value then
return missing value
else
set ca to current application
item 1 of ((ca's NSArray's arrayWithObject:objCValue) as list)
end if
end unwrap
-- wrap :: AS value -> NSObject
on wrap(v)
set ca to current application
ca's (NSArray's arrayWithObject:v)'s objectAtIndex:0
end wrap
-- writeFile :: FilePath -> String -> IO ()
on writeFile(strPath, strText)
set ca to current application
script wrap
on |λ|(x)
ca's (NSArray's arrayWithObject:x)'s objectAtIndex:0
end |λ|
end script
tell wrap
|λ|(strText)'s writeToFile:(|λ|(strPath)'s ¬
stringByStandardizingPath()) atomically:true
end tell
end writeFile
Exercise for the reader - rewrite this so that the records are generated by the script, as variations of a single original record, and play, in sequence, each of the sounds in "/System/Library/Sounds"
One of the many ways in which we can use Keyboard Maestro 8 is as a script library for JavaScript or Applescript.