Calling a Keyboard Maestro action from a script with .doScript + XML

Here’s a simple example of calling Keyboard Maestro actions from scripts.

The scripting section of the KM documentation describes how you can find the XML for an action by opening a saved .kmmacros file in a text editor, and then call the XML snippet with the .doScript() / do script method of the “Keyboard Maestro Engine” application.

http://www.keyboardmaestro.com/documentation/7/keyboardmaestro.html#scripting_control

Here are two approaches to selecting (from a script) a particular menu item in a named application.

  1. by using a KM Action from the script (easier and more reliable)
  2. by relying on the raw resources of the scripting language (harder to maintain, less reliable)
// Rob Trew (twitter @complexpoint) 2015

// TWO APPROACHES TO CLICKING View > Panes > Split Right IN THE ATOM EDITOR MENU SYSTEM

// THE KM ACTION APPROACH:

menuItemClickKM("Atom", ["View", "Panes", "Split Right"]);


// AND THE RAW SCRIPTING LANGUAGE APPROACH

// (contrasting functions below)

//menuItemClickSE("Atom", ["View", "Panes", "Split Right"]);


// VERSION ONE (BETTER) USING Keyboard Maestro Engine with custom XML

// Calling KME.doScript() on a generalised XML fragment
// (save a KM macro and inspect the XML in the .kmmacros file)
function menuItemClickKM(strAppName, lstMenuPath) {
	"use strict";

	var appSE = Application("System Events"),
		lstApps = appSE.processes.where({
			name: strAppName
		}),
		procApp = lstApps.length ? lstApps[0] : null;

	// XML copied and pasted from a .kmmacros file,
	// and then slightly generalised
	return procApp ?
		Application("Keyboard Maestro Engine").doScript(
		"<dict><key>IsActive</key><true/><key>IsDisclosed</key><true/><key>MacroActionType</key><string>SelectMenuItem</string><key>Menu</key><array><string>" + lstMenuPath.join("</string><string>") + "</string></array><key>TargetApplication</key><dict><key>BundleIdentifier</key><string>" + procApp.bundleIdentifier() + "</string><key>Name</key><string>" + strAppName + "</string><key>NewFile</key><string>" + procApp.file.posixPath() + "</string></dict><key>TargetingType</key><string>Specific</string></dict>"
	) : null;
}



// VERSION TWO ( TRADITIONAL, BUT FEW MERITS BEYOND THAT :-)

// Using System Events - harder to maintain
// less well supported by error messages
// and possibly less reliable, too.

// Exact spelling of application name
// and exact list of menu path strings
// strAppName --> [Menu item name] --> maybeClick
function menuItemClickSE(strAppName, lstMenuPath) {
	"use strict";
	var appSE = Application("System Events"),

		// RUNNING APPLICATION
		lstApps = appSE.processes.where({
			name: strAppName
		}),
		procApp = lstApps.length ? lstApps[0] : null,


		// MENU ITEM AT END OF PATH
		mnuItem = procApp ? (lstMenuPath.slice(1, -1).reduce(
			function (oMenu, strMenu) {
				return oMenu.menuItems[strMenu].menus[strMenu];
			},
			procApp.menuBars[0].menus[lstMenuPath[0]]

		).menuItems[lstMenuPath[lstMenuPath.length - 1]]) : null;


	// ATTEMPTED CLICK
	try {
		Application(strAppName).activate();
		return appSE.click(mnuItem.click()) && true;
	} catch (e) {
		return e.message +
			"\nCheck spelling of application name and menu path:" +
			lstMenuPath.join(" > ");
	}
}
2 Likes

Rob, great idea.

How about a third choice, which, if it works, might be even be easier to create and easier to read:

Use KM action directly to execute stuff like menu options.
Then call a script to further process.

Or the reverse order if that makes sense.

That's always the first choice : - )

( but occasionally useful not to have to interrupt a script )

1 Like