Create a variable to store which keystroke you want to type

In a big structure KMMacro I need to type a keystroke five times. These five Type a Keystroke macros hiden somewhere in this big structure. Now I need to change this keystroke to another one, I need to find these five macros then edit them one by one. It’s really a big waste of time. So I try to create a variable to store which keystroke I want to achieve.


But you cannot do this to type a keystroke macro.
I change to use AppleScript. If the keystroke is single character, it’s easy. Like this.

But if the keystroke is two-key-combined. I cannot find a way to do this. Cause I need to type “using command down” something like this(one by one in each execute an AppleScript KMM). You cannot convert multi-key-keystroke to key code.

Hope someone here can help me do this.

Possibly an XY problem ?

You can embed your keystroke simulation inside a Repeat action.

1 Like

these five item were hiden in five different position in a big structure.
the first one is in Asia.
the second one is in Europe.
the third one is in Afrira.
the fourth one is in US.
the fifth one is in Austrilia.
how can you put them in a group????????????????????????????????????

Post your macro. Nobody will understand what you mean until you do.

1 Like

Then perhaps enter a keystroke in one action and copy and paste the completed action to five places ?

But as @noisneil points out, show works where tell fails.


See:

1 Like

PS you will notice from the XML of a Simulate keystroke action

(Try Edit > Copy As > Copy As XML)

that the value stored is not the string name of a keystroke, but the hardware key code integer (not a character but a fixed position on the keyboard)

Similarly, any set of keystroke modifiers (like ^⌥⌘) is also encoded in the form of an integer.

Expand disclosure triangle to view XML Source
<?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>ActionUID</key>
        <integer>9293683</integer>
        <key>KeyCode</key>
        <integer>15</integer>
        <key>MacroActionType</key>
        <string>SimulateKeystroke</string>
        <key>Modifiers</key>
        <integer>6400</integer>
        <key>ReleaseAll</key>
        <false/>
        <key>TargetApplication</key>
        <dict/>
        <key>TargetingType</key>
        <string>Front</string>
    </dict>
</array>
</plist>

In the osascript interface (AppleScript or JS) you can use such an XML (plist) string with the Keyboard Maestro Engine's do script method to perform the encoded action.


In other words, you can write a function with (KeyCode, Modifiers) :: (Integer, Integer) parameters, interpolating the pair of integers that is passed to the function, as two integer strings, into the plist string, for use with do script


And I think you can obtain the mask integers (which need to be added up to obtain an integer code for a combined set of modifiers) like this (where AlphaShift is Caps Lock):

Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    // ---------------------- TEST -----------------------
    return [
            "AlphaShift", "Shift", "Control",
            "Alternate", "Command"
        ]
        .map(k => {
            const
                maskName = `$.NS${k}KeyMask`,
                maskValue = eval(`${maskName}`);

            return `${k} -> ${maskValue} === 2**${Math.log2(maskValue)}`;
        })
        .join("\n");
})();
AlphaShift -> 65536 === 2**16
Shift -> 131072 === 2**17
Control -> 262144 === 2**18
Alternate -> 524288 === 2**19
Command -> 1048576 === 2**20
1 Like

This actually works for me -- BBEdit will type "zzz" and "ZZZ" as expected. Did you, perhaps, change the value of "thisKey" for the sake of the example? If not, why obfuscate your own code by using key codes rather than characters?

What are you actually trying/expecting to type, and with what modifier(s), in what application?

You open a new world to me. I did some tests and find I can use key code to describe different modifier type in XML code. Then use your way to do script in KM.

ctrl+opt+cmd+shift 6912
ctrl 4096
ctrl+opt 6144
ctrl+cmd 4352
ctrl+shift 4608
ctrl+opt+cmd 6400
ctrl+cmd+shift 4864
opt 2048
opt+cmd 2304
opt+shift 2560
opt+cmd+shift 2816
cmd 256
cmd+shift 768
Shift 512
NULL 0

I make a handler macro, like a function. I create variable thisKeyCode to describe the character key you want to type. Create variable thisModifierCode to describe the modifier type you want to type.


In any KMMacro, as long as you set these two variables at the beginning, you can use execute a macro to call this handler macro(function).

When you want to change to another keystroke, you only need to change the two variables at the head of the macro.
Completely new way to me. Many thanks.
handler use variable pass keystroke v0_Backup_20220426_1031_v0.kmmacros (122.9 KB)

1 Like

I still have no idea what your macro does.

1 Like

Search and Replace

Have you experimented with putting the variable tokens

  • %Variable%thisModifierCode%
  • %Variable%thisKeyCode%

directly in the plist XML ?

(In principle at least, I think that KM should expand those tokens for you, inside the Set Variable to Text action, so that you don't have to use a search and replace)

1 Like

nice. i ll try it tomorrow.

Here FWIW, is a variant listing:

Mods Code
512
^ 4096
2048
256
⇧^ 4608
⇧⌥ 2560
⇧⌘ 768
^⌥ 6144
^⌘ 4352
⌥⌘ 2304
⇧^⌥ 6656
⇧^⌘ 4864
⇧⌥⌘ 2816
^⌥⌘ 6400
⇧^⌥⌘ 6912
Expand disclosure triangle to view JS Source
(() => {
    "use strict";

    ObjC.import("AppKit");

    const modifier = {
        "⇧": 512,
        "^": 4096,
        "⌥": 2048,
        "⌘": 256
    };

    const modifierGlyph = {
        "cmd": "⌘",
        "shift": "⇧",
        "opt": "⌥",
        "ctrl": "^"
    };

    // modifierCombinations :: () -> [String]
    const modifierCombinations = () => {
        const ks = Object.keys(modifier);

        return enumFromTo(1)(4)
            .flatMap(flip(combinations)(ks))
            .map(cs => cs.join(""));
    };

    // modCombListing :: () -> String
    const modCombListing = () =>
        unlines(
            modifierCombinations().map(s => {
                const n = `${sumFromGlyphString(s)}`;

                return `${s.padStart(4, " ")}    ${n.padStart(4, " ")}`;
            })
        );

    // modifierGlyphsFromNames :: String -> String
    const modifierGlyphsFromNames = k =>
        // e.g. ctrl+cmd+shift -> ^⌘⇧
        k.split(/[^a-z]+/u)
        .map(s => modifierGlyph[s] || "?")
        .join("");

    // sumFromGlyphString :: String -> Int
    const sumFromGlyphString = glyphString =>
        // e.g. "⇧^⌘" -> 4864
        [
            ...glyphString
        ]
        .reduce(
            (a, c) => a + (modifier[c] || 0),
            0
        );

    // modifierSumFromNameString :: String -> Int
    const modifierSumFromNameString = s =>
        // e.g. "ctrl+cmd+shift" -> 4864
        sumFromGlyphString(
            modifierGlyphsFromNames(s)
        );

    // namesMatchSum :: String -> Int -> Bool
    const namesMatchSum = nameString =>
        n => n === modifierSumFromNameString(nameString);

    const main = () => {
        const test = `  ctrl\t4096
                        ctrl+opt\t6144
                        ctrl+cmd\t4352
                        ctrl+shift\t4608
                        ctrl+opt+cmd\t6400
                        ctrl+cmd+shift\t4864
                        opt\t2048
                        opt+cmd\t2304
                        opt+shift\t2560
                        opt+cmd+shift\t2816
                        cmd\t256
                        cmd+shift\t768
                        shift\t512
                        NULL\t0;`;


        const tabulation = modCombListing();

        return (
            copyText(tabulation),
            tabulation
        );

        // return lines(test).map(
        //     s => uncurry(
        //         namesMatchSum
        //     )(
        //         bimap(
        //             k => k.trim()
        //         )(
        //             k => parseInt(k, 10)
        //         )(
        //             s.split(/\t/u)
        //         )
        //     )
        // );
    };

    // --------------------- GENERIC ---------------------

    // bimap :: (a -> b) -> (c -> d) -> (a, c) -> (b, d)
    const bimap = f =>
        // Tuple instance of bimap.
        // A tuple of the application of f and g to the
        // first and second values respectively.
        g => tpl => [
            f(tpl[0]),
            g(tpl[1])
        ];

    // combinations :: Int -> [a] -> [[a]]
    const combinations = n =>
        // Combinations, without repetition,
        // of n items drawn from xs.
        xs => {
            const go = (n, xs) =>
                1 > n ? [
                    []
                ] : 0 === xs.length ? (
                    []
                ) : ((h, tail) => go(n - 1, tail)
                    .map(t => [h].concat(t))
                    .concat(go(n, tail))
                )(xs[0], xs.slice(1));

            return (go)(n, xs);
        };


    // copyText :: String -> IO String
    const copyText = s => {
        // ObjC.import("AppKit");
        const pb = $.NSPasteboard.generalPasteboard;

        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };


    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = m =>
        n => Array.from({
            length: 1 + n - m
        }, (_, i) => m + i);


    // flip :: (a -> b -> c) -> b -> a -> c
    const flip = op =>
        // The binary function op with
        // its arguments reversed.
        1 !== op.length ? (
            (a, b) => op(b, a)
        ) : (a => b => op(b)(a));


    // lines :: String -> [String]
    const lines = s =>
        // A list of strings derived from a single string
        // which is delimited by \n or by \r\n or \r.
        Boolean(s.length) ? (
            s.split(/\r\n|\n|\r/u)
        ) : [];


    // uncurry :: (a -> b -> c) -> ((a, b) -> c)
    const uncurry = f =>
        // A function over a pair, derived
        // from a curried function.
        (...args) => {
            const [x, y] = Boolean(args.length % 2) ? (
                args[0]
            ) : args;

            return f(x)(y);
        };


    // unlines :: [String] -> String
    const unlines = xs =>
        // A single string formed by the intercalation
        // of a list of strings with the newline character.
        xs.join("\n");

    // sj :: a -> String
    const sj = (...args) =>
        // Abbreviation of showJSON for quick testing.
        // Default indent size is two, which can be
        // overriden by any integer supplied as the
        // first argument of more than one.
        JSON.stringify.apply(
            null,
            1 < args.length && !isNaN(args[0]) ? [
                args[1], null, args[0]
            ] : [args[0], null, 2]
        );

    return main();
})();
2 Likes