Calling KM actions from script, with run-time option changes

KM's excellent GUI provides the best way to do most things, but if, in particular cases, you need to change the options settings of an action at run-time, you can do that by calling the action from a script.

Application("Keyboard Maestro Engine").doScript(strXML) can directly execute the XML string for a particular action, and you can:

  1. Find the XML you need by saving a macro and inspecting the .kmMacros or .kmActions file
  2. Adjust the options in the XML before calling the kme.doScript(strXML) (or Applescript do script strXML

Here is an example which simply calls the 'Speak Text' action once for each voice available on the system, varying the text spoken and the speed, as well as the voice that is used.

Listen to all installed system voices.kmmacros (6.6 KB)

// EXECUTING KEYBOARD MAESTRO ACTIONS FROM SCRIPTS,
// WITH RUN-TIME ADJUSTMENTS TO THEIR OPTIONS.

// READING OUT MESSAGES IN THE FULL SET OF SYSTEM VOICES
// AT RANDOMLY VARYING SPEEDS
(function () {
  "use strict";
  // Rob Trew Twitter @ComplexPoint MIT License 2015
  // Ver 0.1

  ObjC.import('AppKit');

  var jsoDoScript = function (g) {
      var h = /&/g,
        k = /</g,
        l = />/g,
        d = {
          array: function (a) {
            return a.length ? "<array>" + a.reduce(function (a, e) {
              return a + d[typeof e](e)
            }, "") + "</array>" : "<array/>"
          },
          "boolean": function (a) {
            return a ? "<true/>" : "<false/>"
          },
          dict: function (a) {
            var b = Object.keys(a);
            return "<dict>" + (b.sort() && b).reduce(function (b, f) {
              var c = a[f];
              return b + "<key>" + f + "</key>" + d[typeof c](c)
            }, "") + "</dict>"
          },
          number: function (a) {
            return "<real>" + (a ? a : "0.0") + "</real>"
          },
          object: function (a) {
            return d[a instanceof Array ? "array" : "dict"](a)
          },
          string: function (a) {
            return "<string>" + a.replace(h, "&amp;").replace(k, "&lt;").replace(l,
              "&gt;") + "</string>"
          }
        },
        m = function (a) {
          var b = typeof a;
          return "object" === b ? a instanceof Array ? "array" : "dict" : b
        },
        c = function (a) {
          var b = a ? m(a) : "";
          return b && "dict" === b ? a.MacroActionType ? [a] : a.Actions || c(a.Macros) :
            "array" === b ? a.reduce(function (a, b) {
              return a.concat(c(b))
            }, []) : []
        },
        n = Application("Keyboard Maestro Engine");

      // EXECUTE EACH XML SNIPPET WITH THE KEYBOARD MAESTRO ENGINE
      // kme.doScript(strActionXML)
      return c(g).reduce(function (a, b) {
        var c = d.dict(b);
        return n.doScript(c), a.push(c), a
      }, [])
    },

    a = Application.currentApplication(),
    sa = (a.includeStandardAdditions = true && a),

    rgxSample = /\s+\w{2}[_-]\w{2,}\s+\#\s/,

    dctSpeeches = sa.doShellScript(
      "say -v '?'"
    ).split(/[\n\r]+/).reduce(
      function (dct, strLine) {
        var lst = strLine.split(rgxSample);

        dct[lst[0]] = lst[1];

        return dct;
      }, {}
    ),

    lstVoices = ObjC.deepUnwrap($.NSSpeechSynthesizer.availableVoices),
    lstLCVoices = lstVoices.map(function (v) {
      return v.toLowerCase();
    }),
    voiceID = function (v) {
      var l = v.toLowerCase(),
        sl = "com.apple.speech.synthesis.voice." + l,
        i = lstLCVoices.indexOf(sl),
        j = (i === -1) ? lstLCVoices.indexOf(sl + '.premium') : i;

      return lstVoices[j] || '';
    },

    lstRates = 'Default Moderate Normal Quick Fast'.split(' '),
    lngRates = lstRates.length,

    kmSpeak = {
      "MacroActionType": "SpeakText",
      "Rate": "Quick",
      "Text": "Hello world !",
      "Voice": ""
    },

    /************************* MAIN ******************************/

    strVoiceList = Object.keys(dctSpeeches).reduce(
      function (strList, strVoice, i) {
        var strText = dctSpeeches[strVoice],
          strVoiceID = voiceID(strVoice);

        // MODIFY THE PARAMETERS OF THE KM SAY ACTION
        kmSpeak.Voice = voiceID(strVoice);
        kmSpeak.Text = strText;
        kmSpeak.Rate = lstRates[Math.floor(
          Math.random() * lngRates
        )];

        // CONVERT JS OBJECT TO .kmActions XML AND EXECUTE
        jsoDoScript(kmSpeak);

        return strList + strVoice + '\t' + strVoiceID + '\t' + strText + '\n';
      }, ''
    );

  sa.setTheClipboardTo(strVoiceList);


  // EXECUTE A FINAL SERIES OF KEYBOARD MAESTRO ACTIONS,
  // WITH OR WITHOUT RUN-TIME ADJUSTMENTS TO THEIR OPTIONS

  // KM 'Play Sound' action
  jsoDoScript({
    "Volume": 80,
    "MacroActionType": "PlaySound",
    "Path": "/System/Library/Sounds/Glass.aiff",
    "DeviceID": "SOUNDEFFECTS"
  });

  // KM 'Speak Text' action
  kmSpeak.Voice = ''; // Finally, at default voice and speed ...
  kmSpeak.Rate = '';
  kmSpeak.Text =
    "A complete list of voices, with identifiers and text samples, is now in the clipboard";
  jsoDoScript(kmSpeak);

  // KM 'Notification' action
  jsoDoScript({
    "MacroActionType": "Notification",
    "Title": "Voice list",
    "Subtitle": "Now in clipboard",
    "Text": "A complete list of system voices is now in the clipboard"
  });

  return strVoiceList;
})();
1 Like

Simpler example of calling a series of two KM actions from script:

// Executing a (JSON) JavaScript object version of a Keyboard Maestro action

// Rob Trew Twitter @ComplexPoint MIT License 2015
// Ver 0.1


jsoDoScript([
  {
    "Rate": "Default",
    "IsDisclosed": true,
    "MacroActionType": "SpeakText",
    "TimeOutAbortsMacro": true,
    "Text": "This is an example of executing a JavaScript object which represents one or more Keyboard Maestro actions or macros",
    "IsActive": true,
    "Voice": ""
      },
  {
    "Volume": 74,
    "IsDisclosed": true,
    "MacroActionType": "PlaySound",
    "TimeOutAbortsMacro": true,
    "IsActive": true,
    "Path": "/System/Library/Sounds/Glass.aiff",
    "DeviceID": "SOUNDEFFECTS"
      }
]);

function jsoDoScript(g) {
  var h = /&/g,
    k = /</g,
    l = />/g,
    d = {
      array: function (a) {
        return a.length ? "<array>" + a.reduce(function (a, e) {
          return a + d[typeof e](e);
        }, "") + "</array>" : "<array/>";
      },
      'boolean': function (a) {
        return a ? "<true/>" : "<false/>";
      },
      dict: function (a) {
        var b = Object.keys(a);
        return "<dict>" + (b.sort() && b).reduce(function (b, f) {
          var c = a[f];
          return b + "<key>" + f + "</key>" + d[typeof c](c);
        }, "") + "</dict>";
      },
      number: function (a) {
        return "<real>" + (a ? a : "0.0") + "</real>";
      },
      object: function (a) {
        return d[a instanceof Array ? "array" : "dict"](a);
      },
      string: function (a) {
        return "<string>" + a.replace(h, "&amp;").replace(k, "&lt;").replace(l,
          "&gt;") + "</string>";
      }
    },
    m = function (a) {
      var b = typeof a;
      return "object" === b ? a instanceof Array ? "array" : "dict" : b;
    },
    c = function (a) {
      var b = a ? m(a) : "";
      return b && "dict" === b ? a.MacroActionType ? [a] : a.Actions || c(a.Macros) :
        "array" === b ? a.reduce(function (a, b) {
          return a.concat(c(b));
        }, []) : [];
    },
    n = Application("Keyboard Maestro Engine");
    
  return c(g).reduce(function (a, b) {
    var c = d.dict(b);
    return n.doScript(c), a.push(c), a;
  }, []);
}