Checking "Is Macro Enabled and Active" via JXA?

Is there a way that I can find out if a macro is Enabled and Active (i.e. it can be executed), from JXA?

Better still, can I check to see if multiple macros are Enabled and Active, from JXA?

I’m doing something similar to “Execute Macro by Name”, and I only want to show macros that actually can be executed. And preferably, I’d like it to be fast.

I don’t mind having to parse plists, or anything else down-and-dirty.

Thanks.

Unless @peternlewis has a different solution, which I kind of doubt because I suspect this is the correct procedure, I found the answer:

I call Keyboard Maestro Engine’s “getmacros” scripting function, and parse it to check a macro’s “active” and “enabled” properties, which are up-to-date for each macro.

Dan, since the "active" status of a macro is dynamically updated by the KM Engine in real time, do you think the entire macro list would be continuously updated vs just keeping a list of active macros?

I'm sure Peter will jump in and confirm/clarify.

I think it will be a challenge to be as fast as the native "Execute Macro by Name".
Are you aware of the search filters than can be applied?

Yes, not only do I think that, experimentation shows it to be true. Remember, applications like KeyCue need to have a way to determine the same thing.

In case you're not aware, "getmacros" does not return the contents of each macro - it only returns information about the groups and macros.

OK, thanks for the update.

As you note, getmacros will return info about all currently active macros.

You can also use the Macro condition to test if a macro is active. The two sides of the If could set a variable, or otherwise inform whatever you are doing.

1 Like

Peter, I must be misunderstanding something. The way I read the results of the getmacros command, it is returning inactive macros as well, as shown by these two lines:

        <key>active</key>
        <false/>

I see quite a few lines like these.

What am I missing?

Is this supposed to be only the list of active macros, or all macros?

###Script

tell application "Keyboard Maestro Engine"
  set macroList to getmacros with asstring
end tell

###Results (partial)

  <dict>
    <key>enabled</key>
    <true/>
    <key>macros</key>
    <array>
      <dict>
        <key>active</key>
        <false/>
        <key>created</key>
        <real>481320050.79918498</real>
        <key>enabled</key>
        <true/>
        <key>lastused</key>
        <real>482097735.83228999</real>
        <key>modified</key>
        <real>481252202.47775799</real>
        <key>name</key>
        <string>[PDF] Extract Annotations from PDF</string>
        <key>saved</key>
        <real>128</real>
        <key>sort</key>
        <string>[PDF] Extract Annotations from PDF</string>
        <key>triggers</key>
        <array>
          <dict>
            <key>description</key>
            <string>The Hot Key ⌃⇧A is pressed</string>
            <key>short</key>
            <string>⌃⇧A</string>
            <key>type</key>
            <string>Hot Key Trigger</string>
          </dict>
        </array>
        <key>uid</key>
        <string>BF3BA3C4-56A7-4D62-AF5D-2CC7E5D80071</string>
        <key>used</key>
        <integer>5</integer>
      </dict>

It's all macros. You have to look at each macro's "active" and "enabled" properties.

It appears that if the group's active is false, the macro's active is also false, so you don't need to check the group's active property, unless you want to know that info.

I've got all sorts of JXA code for working with all this, if you need it. I understand the desire to write stuff myself, as well as the times I can't be bothered, so it's cool to me either way.

Dan, thanks for confirming. That is what I am seeing also, but since Peter said "all currently active macros", it made me question what I was seeing. Hopefully he will clarify.

So, according to the KM Wiki article, Macro Activation, a macro cannot be active unless both the macro and group are enabled. Is that consistent with what you are seeing?

Of course I'd love to see and use your code -- no rush though, as I don't have any immediate needs. Although, if you have any generic JXA functions for reading and converting .plist files/data to JS objects/arrays, that would be great.

I've always preferred to start as high up on the food chain as I can, building on the existing code of others rather than reinventing the wheel. While some programmers have a NIH complex, it's never been an issue for me. :wink:

Good luck with your project. I can't wait to see what you will come up with next.

I guess I lied a little. I don't think the list shows the Group's "active" state, only "enabled". However, each macro's "active" is correct, based on whether the Group should be active or not.

Here's some code. Run it through the debugger and it will stop on the "debugger" statement, and you can look at the contents of "plist".

(function() {
    'use strict';

    var KMEngine = (function() {
        var _engineApp;

        return {
            convertStringToPlist: function(str) {
                return ObjC.deepUnwrap(
                    $.NSPropertyListSerialization.propertyListWithDataOptionsFormatError(
                        $(str).dataUsingEncoding($.NSUTF8StringEncoding), 0, 0, null));
            },

            getEngineAppName: function() {
                return "Keyboard Maestro Engine";
            },

            getEngineApp: function() {
                if (!_engineApp)
                    _engineApp = Application(this.getEngineAppName());
                return _engineApp;
            },

            getMacros: function(binary) {
                return this.getEngineApp().getmacros({
                    asstring: !binary
                });
            },

            getMacrosAsPlist: function(binary) {
                return this.convertStringToPlist(this.getMacros(false));
            },

            getVariable: function(name, required) {
                var result = this.getEngineApp().getvariable(name);
                if (!result && required)
                    throw Error("Variable '" + name + "' is empty");
                return result;
            },

            setVariable: function(name, value) {
                this.getEngineApp().setvariable(name, {
                    to: value
                });
            },
        };

    })();

    function getKMMacroAndGroupByMacroUUID(macroUUID, plist) {
        var result;
        plist.find(function(group) {
            var macros = group.macros;
            if (!macros) return false;

            var macro = macros.find(function(m) {
                return m.uid === macroUUID; });
            if (macro) {
                result = { macro: macro, group: group };
                return true;
            }
            return false;
        });
        if (!result)
            throw Error("Could not find info for Macro '" + macroUUID + "'");
        return result;
    }

    function run() {
        var macroUUID = "7E71EB71-13A6-4E96-A4A5-ACD8503D1330";
        // var macroUUID = KMEngine.getVariable("kmfamMacroUUID", true);
        var plist = KMEngine.getMacrosAsPlist();
        debugger

        // var match = getKMMacroAndGroupByMacroUUID(macroUUID, plist);

        // console.log(match.group.uid)
        // KMEngine.setVariable("kmfamGroupUUID", match.group.uid);
        return "OK";
    }

    try {
        return run();
    } catch (e) {
        return "Error: " + e.message;
    }
})()

Thanks, Dan. Looks like some great stuff here, but it'll take me a while to understand it all.

One question: Is the convertStringToPlist function a generic function to convert any Plist XML to JS arrays, or is it designed specifically for the getmacros command?

I had totally forgotten that none of that code was commented - I'm re-doing how I comment things, and, well, there you go.

convertStringToPlist converts a string to a Plist object. The string can be any standard Plist XML, either a single "dict" or an array of "dict"s. You could even read a plist text file from disk and use it here, but there's other better ways of doing that.

The result isn't really a Plist object - there isn't such a thing in JXA. It's actually more like an object literal, or an array of object literals. You can think of the result as either a set of name/value pairs, possibly nested, or an array of sets of name/value pairs.

I guess I really need to get back to documenting.

Sorry, I spoke imprecisely.

It is relatively documented in the sdef.

gethotkeys will return all the active macros with hot key or typed string triggers (maybe USB device key ones too, I’m not sure).

gethotkeys with getall will return all the active macros.

getmacros will return all the macros.

Peter, thanks for the clarification.

So this should list ONLY ACTIVE Macros, correct?

tell application "Keyboard Maestro Engine"
  set activeMacros to gethotkeys asstring true ¬
    with getall
end tell

####Partial Results

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
  <dict>
    <key>macros</key>
    <array>
      <dict>
        <key>name</key>
        <string>Custom HTML Prompt Fixed Header and Footer using Flexbox Examples (Dan)</string>
        <key>sort</key>
        <string>Custom HTML Prompt Fixed Header and Footer using Flexbox Examples (Dan)</string>
        <key>uid</key>
        <string>B040B250-40D8-4C38-B813-27E34E4FDD9B</string>
      </dict>
      <dict>
        <key>name</key>
        <string>Custom HTML Prompt with Dynamic Size and Position (Dan)</string>
        <key>sort</key>
        <string>Custom HTML Prompt with Dynamic Size and Position (Dan)</string>
        <key>uid</key>
        <string>F5D06F09-E511-4201-8DD1-4A9FDB8E3AEE</string>
      </dict>
    </array>

I am surprised that you use uid rather than UUID used almost everywhere else. I think this can lead to a lot of confusion and scripting errors.

May I suggest that you provide a scripting command called "getActiveMacros".
Even though the "gethotkeys" command has a parameter of "getall" that is:

retrieves all active macros, not just hot key and typed string triggered ones

It is buried in the SDEF, and not intuitive to be part of the command "gethotkeys"

Thanks for supporting scripting. I realize that we are probably penetrating the scripting interface in more detail than ever before, so there are bound to be a few glitches.

I've often wondered why "UID" and not "UUID" in the XML. I'm sure there's a logical explanation.

I should warn you that the results from getmacros are the only results (I think) that have the key names in lowercase. And when it comes to using the results, the keys are case-sensitive (at least in JXA).

So, for instance:

macro.uid
macro.name

As opposed to something like:

macro.UID
macro.Name

gethotkeys was made for KeyCue.

Thanks, Peter. I'd still like to confirm this about "gethotkeys":

If that is correct, then the "gethotkeys" command should be the best approach for getting a list of the macros currently active, correct?

I don't think so, because doesn't this only return macros that actually have hotkeys?

I think that is the purpose of the "getall" parameter:

getall optional boolean
retrieves all active macros, not just hot key and typed string triggered ones