A Reverse Engineering Question

Hi everyone, I created gads of trigger files that I use for this and that. I made the mistake of renaming a few such that their current names are different from the original macro name from whence it was created. And with 1 or 2, I forgot which original macro it refers to! :grinning:

I was wondering: Is there a way to un-trigger a trigger file such that it reverts to its original bare KM macro format? Thank you much.....

I'd never heard of a trigger file! Thanks for educating me!

But that's probably why I don't understand your question. It's a new concept for me.

They're a very cool useful part of KM. If you select any macro and go to the file menu, you'll see 'Export as Trigger File.' Pull down to it and the macro gets exported (to wherever you want). Whenever you click on thetrigger file, the macro executes. I use them to organize my macros.

What do you mean?

I have a few trigger files that were exported from KM macros awhile ago. After exporting them, I renamed the triggers: For example, one trigger was renamed 'Clear.' If I execute Clear, apps, finder boxes and other items are cleared from the desktop - and so, I know Clear was derived from one of a dozen or so macros that perform these functions. But I don;t recall which macro it was originally.

My question is, can I look at the contents of a trigger file like 'Clear' to see exactly what actions and other elements were used to create it. Sorry if my question isn't articulated well enough. Let me know if you want me to restate it. Given that you're not sure what I mean, I'm guessing that the answer to my Q is "no."...thx

You shouldn't need to reverse engineer the trigger file. I believe you should just be able to look in the KM engine log (Help > Open Logs Folder > Engine.log) after running the macro to see what macro was run.

Thank you for that gem...I never used that before. The last item in engine.log is getting me closer...but me thinks that the last macro is probably an 'execute macro' in another macro (I hope you understand my meaning). anyway, I'm on my way to figuring it out now. Thx again for that.... :slight_smile:

1 Like

.kmtrigger files simply contain the UUID of the triggered macro.

You should be able to obtain the name of that macro by selecting (one or more) .kmtrigger files in the Finder, and running the macro below:

Macro names for selected KM trigger files Macro

Macro names for selected KM trigger files.kmmacros (23 KB)

JavaScript Source
(() => {
    'use strict';

    // Name(s) of macro(s) for .kmtrigger file(s) selected in Finder

    // Rob Trew 2019
    // Ver 0.1

    // main :: IO ()
    const main = () =>
        unlines(concatMap(
            fp => fp.endsWith('.kmtrigger') ? [
                takeFileName(fp) + ' -> ' +
                kmMacroNameFromUUID(strip(readFile(fp)))
            ] : [],
            selectedPaths()
        ));


    // KEYBOARD MAESTRO FUNCTIONS -------------------------

    // kmMacroFromUUIDLR :: String -> String -> Either String Dict
    const kmMacroFromUUIDLR = strUUID => {
        const
            js = ObjC.unwrap,
            ms = concatMap(
                group => maybe(
                    [],
                    x => x,
                    foldl(
                        (m, x) => m.Nothing ? (
                            strUUID === x.UID ? Just(x) : m
                        ) : m,
                        Nothing(),
                        (group.Macros || [])
                    )
                ), ObjC.deepUnwrap(
                    $.NSDictionary.dictionaryWithContentsOfFile(
                        js(js($.NSFileManager.defaultManager
                            .URLsForDirectoryInDomains(
                                $.NSApplicationSupportDirectory,
                                $.NSUserDomainMask
                            ))[0].path) +
                        '/Keyboard Maestro/Keyboard Maestro Macros.plist'
                    )
                )
                .MacroGroups
            );
        return 0 < ms.length ? (
            Right(ms[0])
        ) : Left('No match found for ' + strUUID);
    };

    // kmMacroNameFromUUID :: UUID String -> Either Name Message
    const kmMacroNameFromUUID = uuid =>
        either(
            x => 'Error :: ' + x,
            x => x,
            bindLR(
                kmMacroFromUUIDLR(uuid),
                m => Right(m.Name)
            )
        );

    // JAVASCRIPT FOR AUTOMATION --------------------------

    // selectedPaths :: () -> [pathString]
    const selectedPaths = () =>
        Application('Finder')
        .selection()
        .map(x => decodeURI(x.url())
            .slice(7));


    // GENERIC FUNCTIONS ----------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Just :: a -> Maybe a
    const Just = x => ({
        type: 'Maybe',
        Nothing: false,
        Just: x
    });

    // Left :: a -> Either a b
    const Left = x => ({
        type: 'Either',
        Left: x
    });

    // Nothing :: Maybe a
    const Nothing = () => ({
        type: 'Maybe',
        Nothing: true,
    });

    // Right :: b -> Either a b
    const Right = x => ({
        type: 'Either',
        Right: x
    });

    // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b
    const bindLR = (m, mf) =>
        undefined !== m.Left ? (
            m
        ) : mf(m.Right);

    // concatMap :: (a -> [b]) -> [a] -> [b]
    const concatMap = (f, xs) =>
        xs.reduce((a, x) => a.concat(f(x)), []);

    // Briefer but slower:
    // concatMap :: (a -> [b]) -> [a] -> [b]
    // const concatMap = (f, xs) =>
    //     [].concat(...xs.map(f))

    // either :: (a -> c) -> (b -> c) -> Either a b -> c
    const either = (fl, fr, e) =>
        'Either' === e.type ? (
            undefined !== e.Left ? (
                fl(e.Left)
            ) : fr(e.Right)
        ) : undefined;

    // foldl :: (a -> b -> a) -> a -> [b] -> a
    const foldl = (f, a, xs) => xs.reduce(f, a);

    // maybe :: b -> (a -> b) -> Maybe a -> b
    const maybe = (v, f, m) =>
        m.Nothing ? v : f(m.Just);

    // readFile :: FilePath -> IO String
    const readFile = fp => {
        const
            e = $(),
            uw = ObjC.unwrap,
            s = uw(
                $.NSString.stringWithContentsOfFileEncodingError(
                    $(fp)
                    .stringByStandardizingPath,
                    $.NSUTF8StringEncoding,
                    e
                )
            );
        return undefined !== s ? (
            s
        ) : uw(e.localizedDescription);
    };

    // strip :: String -> String
    const strip = s => s.trim();

    // takeFileName :: FilePath -> FilePath
    const takeFileName = strPath =>
        '' !== strPath ? (
            ('/' !== strPath[strPath.length - 1]) ? (
                strPath.split('/')
                .slice(-1)[0]
            ) : ''
        ) : '';

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // MAIN ---
    return main();
})();

Thank you for this. Would you mind elaborating how to use your macro? I took your instructions at face value. I selected a trigger file and then, ran your macro. But nothing happened.

All my trigger files are in a folder outside of KM, one or two levels above my startup drive. I placed your macro into one of my own macro groups (also called 'Finder') and added the status menu as a trigger.

After selecting my trigger file (in the folder where I usually keep them), I pulled down to your macro (using the status menu) but (as far as I can see) nothing happened. I look forward to your reply.

What version of macOS are you running ?

A useful test would be to:

  • copy (all) of the JS code into Script Editor
  • select one or more .kmtrigger files in Finder
  • Run the code from Script Editor (with the language tab at top left set to JavaScript)

What message do you see when you do this ?

If I try to compile or run it, it immed brings up a syntax error in a dialog box:

Error on line 1: SyntaxError: Unexpected token ')'

and it selects the first line:

(() => {

Hi Peter, Did my answer clarify? There's one trigger in particular - the one I referred to earlier, 'Clear.'

It's strange...the macro is just a simple toggle:I have an icon that executes the trigger file. Click the icon once and everything (including palettes, get info, finder boxes, apps...)immediately hide. Click it again, and they reappear. I'm sure I can reconstruct it.

Today, I was thinking, I somehow used the F11 or F12 key to switch to another desktop but the thing is, all my desktop icons remain stay in tact and so, I' must be in the same desktop. I'll figure it out....

That's what prompted me to ask the Q. I wondered, can I take the trigger file and somehow retrace my steps to find the macro....but that's turning out to be more work than just creating a new one.

Any thoughts, mind if I ask?

My OS: Yosemite: 10.10.5

Is my OS too ancient for your macro?

Addendum: I figured out the one trigger file that was bugging me, that I couldn't trace to a macro (Named 'Clear'). The macro clears everything that's hiding the desktop (except DT icons). Also, it's a toggle: The second time you execute it, everything reappears.

I feel silly: It's just one simple simulate-keystroke: Command/F8 is part of the Yosemite OS--Clear Desktop. Anyway, at least I learned a ton, thx again for the help.

PS
...it would be interesting to know, is it possible to recreate a macro from the trigger file but I'm not as concerned as before....thx again.

A kmtrigger file is just a UUID of a macro to execute. It contains no actions to reverse engineer. It is just another way to execute a macro.

thanks...someone else said that as well...that makes sense. Is there a way to get the UUID from the trigger file?

I think so : -)

(It assumes macOS Sierra onwards)

Here is an automatic translation to a Classical ES5 JS by Babel – if you find that it works on your system in Script Editor, you could paste it into the JS action in that macro:

(function() {
    'use strict';

    // Name(s) of macro(s) for .kmtrigger file(s) selected in Finder
    // Rob Trew 2019
    // Ver 0.1B (Automatic translation to Classical ES5 by Babel JS)
    // main :: IO ()

    var main = function main() {
        return unlines(concatMap(function(fp) {
            return fp.endsWith('.kmtrigger') ? [takeFileName(fp) + ' -> ' +
                kmMacroNameFromUUID(strip(readFile(fp)))
            ] : [];
        }, selectedPaths()));
    };

    // KEYBOARD MAESTRO FUNCTIONS -------------------------
    // kmMacroFromUUIDLR :: String -> String -> Either String Dict
    var kmMacroFromUUIDLR = function kmMacroFromUUIDLR(strUUID) {
        var js = ObjC.unwrap,
            ms = concatMap(function(group) {
                return maybe([], function(x) {
                    return x;
                }, foldl(function(m, x) {
                    return m.Nothing ? strUUID === x.UID ? Just(x) : m : m;
                }, Nothing(), group.Macros || []));
            }, ObjC.deepUnwrap(
                $.NSDictionary.dictionaryWithContentsOfFile(
                    js(js($.NSFileManager.defaultManager
                        .URLsForDirectoryInDomains(
                            $.NSApplicationSupportDirectory,
                            $.NSUserDomainMask))[0].path) +
                    '/Keyboard Maestro/Keyboard Maestro Macros.plist')
            ).MacroGroups);
        return 0 < ms.length ? (
            Right(ms[0])
        ) : Left('No match found for ' + strUUID);
    };

    // kmMacroNameFromUUID :: UUID String -> Either Name Message
    var kmMacroNameFromUUID = function kmMacroNameFromUUID(uuid) {
        return either(function(x) {
            return 'Error :: ' + x;
        }, function(x) {
            return x;
        }, bindLR(kmMacroFromUUIDLR(uuid), function(m) {
            return Right(m.Name);
        }));
    };

    // JAVASCRIPT FOR AUTOMATION --------------------------

    // selectedPaths :: () -> [pathString]
    var selectedPaths = function selectedPaths() {
        return Application('Finder').selection().map(function(x) {
            return decodeURI(x.url()).slice(7);
        });
    };

    // GENERIC FUNCTIONS ----------------------------

    // https://github.com/RobTrew/prelude-jxa
    // Just :: a -> Maybe a
    var Just = function Just(x) {
        return {
            type: 'Maybe',
            Nothing: false,
            Just: x
        };
    }; // Left :: a -> Either a b


    var Left = function Left(x) {
        return {
            type: 'Either',
            Left: x
        };
    }; // Nothing :: Maybe a


    var Nothing = function Nothing() {
        return {
            type: 'Maybe',
            Nothing: true
        };
    }; // Right :: b -> Either a b


    var Right = function Right(x) {
        return {
            type: 'Either',
            Right: x
        };
    }; // bindLR (>>=) :: Either a -> (a -> Either b) -> Either b


    var bindLR = function bindLR(m, mf) {
        return undefined !== m.Left ? m : mf(m.Right);
    }; // concatMap :: (a -> [b]) -> [a] -> [b]


    var concatMap = function concatMap(f, xs) {
        return xs.reduce(function(a, x) {
            return a.concat(f(x));
        }, []);
    }; // Briefer but slower:
    // concatMap :: (a -> [b]) -> [a] -> [b]
    // const concatMap = (f, xs) =>
    //     [].concat(...xs.map(f))
    // either :: (a -> c) -> (b -> c) -> Either a b -> c


    var either = function either(fl, fr, e) {
        return 'Either' === e.type ? undefined !== e.Left ? (
            fl(e.Left)
        ) : fr(e.Right) : undefined;
    }; // foldl :: (a -> b -> a) -> a -> [b] -> a


    var foldl = function foldl(f, a, xs) {
        return xs.reduce(f, a);
    }; // maybe :: b -> (a -> b) -> Maybe a -> b


    var maybe = function maybe(v, f, m) {
        return m.Nothing ? v : f(m.Just);
    }; // readFile :: FilePath -> IO String


    var readFile = function readFile(fp) {
        var e = $(),
            uw = ObjC.unwrap,
            s = uw($.NSString.stringWithContentsOfFileEncodingError($(fp)
                .stringByStandardizingPath, $.NSUTF8StringEncoding, e));
        return undefined !== s ? s : uw(e.localizedDescription);
    }; // strip :: String -> String


    var strip = function strip(s) {
        return s.trim();
    }; // takeFileName :: FilePath -> FilePath


    var takeFileName = function takeFileName(strPath) {
        return '' !== strPath ? '/' !== strPath[strPath.length - 1] ? (
            strPath.split('/').slice(-1)[0]
        ) : '' : '';
    }; // unlines :: [String] -> String


    var unlines = function unlines(xs) {
        return xs.join('\n');
    }; // MAIN ---


    return main();
})();

(It assumes macOS Sierra onwards)

out of my league...thanks tho...I'll stash it away... :yum: