Hot Key Cheat Sheet?

I'm loving BTT and currently using it to run AppleScripts that trigger Keyboard Maestro macros.

I'm using BTT Keyboard Shortcut triggers because, unlike Keyboard Maestro Hot Keys, they can be configured to differentiate left/right modifier keys.

Since I've created many action/trigger combinations, I'm now in need of a cheat sheet.

Is there a way to get a table of the BTT actions that are triggered with BTT Keyboard Shortcuts? KeyCue does a very nice job displaying the Hot Key triggers for Keyboard Maestro, but unfortunately this third-party app does not do the same for BTT Keyboard Shortcuts.

I did open the BTT AppleScript Dictionary and poked around, but I could not figure out a way to:

  • list the BTT actions that include Keyboard Shortcut triggers (constrained for the Keyboard Maestro app and within Preset:Default), or

  • open the BTT gui to the section where these actions are defined (constrained as aforementioned).

Thanks in advance for any suggestions that you might have.

1 Like

Getting a list of the triggers would be possible using the get_triggers script call, however there is currently no way to open the UI with a specific trigger selected.

I'm have planned something like "CheatSheet" (CheatSheet - Know your shortcuts) but I have not yet started working on it.

3 Likes

Wow, that was quick. Thanks, @Andreas_Hegenberg!

Yes, this is what I created; but is there a way to add the two other constraints I mentioned: Keyboard Maestro app and within Preset:Default?

tell application "BetterTouchTool"
	set result_Text to get_triggers trigger_type "BTTTriggerTypeKeyboardShortcut"
end tell

(Aside: Based on the appearance of the above, it seems that the Discourse feature that provides syntax highlighting for AppleScript code has not enabled.)

I'm not surprised, but I thought it was worth asking. I'm spoiled by Keyboard Maestro; many features of the KM editor can be scripted.

Great! I specifically purchased KeyCue because it includes the feature to display Keyboard Maestro Hot Keys.


Thanks again, @Andreas_Hegenberg! BTT is incredible.

Hi @jim-sauer Not exactly what you want, just a option. I know you're a KM expert, so I'll limit what I say to a minimum.. :slightly_smiling_face:

Depending on what exactly you want to do, you can use a KM palette as a "cheat sheet". eg. press and hold right cmd, a KM palette opens and shows you all the macros that can be executed with right cmd + letter, app specific or global. The macro could then be triggered either with BTT or with KM. When you release right cmd the KM palette disappears again.

This way each left and right modifier can be differentiated to trigger different macros (and you have a cheat sheet). But it requires a permanent interaction of KM and BTT, which is not really complicated, but maybe you don't want that. :slightly_smiling_face:

1 Like

Hi, @Frank1. Thanks so much for the suggestion. I had thought of something similar but dismissed the idea (temporarily) as I was seeking a tool like CheatSheet or KeyCue. I've also put a bug in the ear of ergonis, the developers of KeyCue, since I'm a licensed user (as I am with BTT, although via Setapp).

Your kind suggestion, @Frank1, has me revisiting this idea and now I'm thinking :thinking::

For BTT-triggered KM macros, the pros and cons of:

  • one palette for all, or
  • one palette for each right-modifier.

I could create one or more KM groups that include simple macros that have two purposes:

  • The name could be the Hot Key guide, e.g.,
    ⌥M Magenta
  • They include the actions for two simple options:
    • if clicked, run the target macro, or
    • if clicked with ⌥ and/or ⌘ down, open the macro in the KM editor.

So far, I have these macros trigger in BTT by right-modifiers. (At this point they are all related to automating the Keyboard Maestro editor.):

  • ⌥A Aqua
  • ⌥E Engroup (Group)
  • ⌥F Select 1st Action
  • ⌥G Green
  • ⌥L Select Last Action
  • ⌥M Magenta
  • ⌥N (none)
  • ⌥O Orange
  • ⌥P Purple
  • ⌥R Red
  • ⌥T Teal
  • ⌥Y Yellow
  • ⌘E Enable/Disable
  • ⌘F Go to Macro by Name
  • ⌘G Move Macro to a Group
  • ⌘H Header Comment Builder
  • ⌘L Macro Two-variant MD Link to Clipboard
  • ⌘N Navigate to Configured Macro or Subroutine
  • ⌘S Sort Macro Conditionally
  • ⌘T Try Action
  • ⌘V Paste Above
  • ⌥⌘F Search All Macros

This will take a little time to set up, but in the end it will be worth it. Thanks again, @Frank1.

1 Like

This looks interesting and you've given me some new ideas. Thanks a lot for that. :+1: But I will wait a bit more, because:

BTT is getting new floating menus very soon that will be similar to the KM palettes, maybe even more powerful. Andreas already has an (alpha) version on his Mac. Whether that is relevant to your project, I don't know. If you mainly trigger KM macros, then maybe not. But you might want to wait a bit until you can judge it better.

By the way, good to know: If you already use a shortcut in KM, say ⌘6, then this trigger with left ⌘ will remain if you use right ⌘6 for something else in BTT. This can be very useful because you don't have to change the KM shortcuts.

This is a question I have asked myself as well. Currently I do it like this. I have a global palette and additional app specific palettes that are only shown when the right app is in front. So there are always two different palettes open. I have discarded the idea of a global macro palette after a few attempts. I will think about your approach. :smiley:

1 Like

Thanks for the heads-up on that.

Thanks. I assumed this was the case, but I'm not sure I I had tested it yet. Now I don't have to. :grinning:

Just a quick demo of what is already possible with existing functionality (might require current alpha version). Maybe useful as reference.

Putting this into a floating webview action, will list all of the shortcuts and their actions like this:


It could easily be styled with some CSS.

<html>
  <head>
    <script>
     async function BTTWindowWillBecomeVisible() {
        console.log("loading triggers");
        const allKeyboardShortcutsJSONString = await callBTT("get_triggers", {
          trigger_type: "BTTTriggerTypeKeyboardShortcut",
        });
        const allTriggersJSONArray = JSON.parse(allKeyboardShortcutsJSONString);
        let allKeyboardShortcuts = "";
        for (const trigger of allTriggersJSONArray) {
          if (trigger.BTTShortcutModifierKeys) {
            const shortcut = `<div><span><b>${
              trigger?.BTTTriggerConfig?.BTTLeftRightModifierDifferentiation
                ? getModifiersForBitmask(
                    trigger.BTTAdditionalConfiguration,
                    true
                  )
                : getModifiersForBitmask(trigger.BTTShortcutModifierKeys, false)
            }${getKeycodeStringForKeycode(
              trigger.BTTShortcutKeyCode
            )}</b></span> <span>${getActionNameForID(
              trigger.BTTPredefinedActionType,
              trigger
            )}</span></div>`;
            allKeyboardShortcuts = allKeyboardShortcuts + shortcut;
          }
        }
        document.getElementById("triggerList").innerHTML = allKeyboardShortcuts;
      }

  function getKeycodeStringForKeycode(keycode) {
        switch (keycode) {
          case 0x00: {
            return "A";
          }
          case 0x01: {
            return "S";
          }
          case 0x02: {
            return "D";
          }
          case 0x03: {
            return "F";
          }
          case 0x04: {
            return "H";
          }
          case 0x05: {
            return "G";
          }
          case 0x06: {
            return "Z";
          }
          case 0x07: {
            return "X";
          }
          case 0x08: {
            return "C";
          }
          case 0x09: {
            return "V";
          }
          case 0x0b: {
            return "B";
          }
          case 0x0c: {
            return "Q";
          }
          case 0x0d: {
            return "W";
          }
          case 0x0e: {
            return "E";
          }
          case 0x0f: {
            return "R";
          }
          case 0x10: {
            return "Y";
          }
          case 0x11: {
            return "T";
          }
          case 0x12: {
            return "1";
          }
          case 0x13: {
            return "2";
          }
          case 0x14: {
            return "3";
          }
          case 0x15: {
            return "4";
          }
          case 0x16: {
            return "6";
          }
          case 0x17: {
            return "5";
          }
          case 0x18: {
            return "=";
          }
          case 0x19: {
            return "9";
          }
          case 0x1a: {
            return "7";
          }
          case 0x1b: {
            return "-";
          }
          case 0x1c: {
            return "8";
          }
          case 0x1d: {
            return "0";
          }
          case 0x1e: {
            return "]";
          }
          case 0x1f: {
            return "O";
          }
          case 0x20: {
            return "U";
          }
          case 0x21: {
            return "[";
          }
          case 0x22: {
            return "I";
          }
          case 0x23: {
            return "P";
          }
          case 0x25: {
            return "L";
          }
          case 0x26: {
            return "J";
          }
          case 0x27: {
            return '"';
          }
          case 0x28: {
            return "K";
          }
          case 0x29: {
            return ";";
          }
          case 0x2a: {
            return "\\";
          }
          case 0x2b: {
            return ",";
          }
          case 0x2c: {
            return "/";
          }
          case 0x2d: {
            return "N";
          }
          case 0x2e: {
            return "M";
          }
          case 0x2f: {
            return ".";
          }
          case 0x32: {
            return "?";
          }
          case 0x41: {
            return ".";
          }
          case 0x43: {
            return "*";
          }
          case 0x45: {
            return "+";
          }
          case 0x47: {
            return "?";
          }
          case 0x4b: {
            return "%";
          }
          case 0x4c: {
            return "return";
          }
          case 0x4e: {
            return "-";
          }
          case 0x51: {
            return "=";
          }
          case 0x52: {
            return "0";
          }
          case 0x53: {
            return "1";
          }
          case 0x54: {
            return "2";
          }
          case 0x55: {
            return "3";
          }
          case 0x56: {
            return "4";
          }
          case 0x57: {
            return "5";
          }
          case 0x58: {
            return "6";
          }
          case 0x59: {
            return "7";
          }
          case 0x5b: {
            return "8";
          }
          case 0x5c: {
            return "9";
          }
          case 0x24: {
            return "Return";
          }
          case 0x30: {
            return "Tab";
          }
          case 0x31: {
            return "Space";
          }
          case 0x33: {
            return "Delete";
          }
          case 0x35: {
            return "Escape";
          }
          case 0x37: {
            return "Command";
          }
          case 0x38: {
            return "Shift";
          }
          case 0x39: {
            return "CapsLock";
          }
          case 0x3a: {
            return "Option";
          }
          case 0x3b: {
            return "Control";
          }
          case 0x3c: {
            return "RightShift";
          }
          case 0x3d: {
            return "RightOption";
          }
          case 0x3e: {
            return "RightControl";
          }
          case 0x3f: {
            return "Function";
          }
          case 0x40: {
            return "F17";
          }
          case 0x48: {
            return "VolumeUp";
          }
          case 0x49: {
            return "VolumeDown";
          }
          case 0x4a: {
            return "Mute";
          }
          case 0x4f: {
            return "F18";
          }
          case 0x50: {
            return "F19";
          }
          case 0x5a: {
            return "F20";
          }
          case 0x60: {
            return "F5";
          }
          case 0x61: {
            return "F6";
          }
          case 0x62: {
            return "F7";
          }
          case 0x63: {
            return "F3";
          }
          case 0x64: {
            return "F8";
          }
          case 0x65: {
            return "F9";
          }
          case 0x67: {
            return "F11";
          }
          case 0x69: {
            return "F13";
          }
          case 0x6a: {
            return "F16";
          }
          case 0x6b: {
            return "F14";
          }
          case 0x6d: {
            return "F10";
          }
          case 0x6f: {
            return "F12";
          }
          case 0x71: {
            return "F15";
          }
          case 0x72: {
            return "Help";
          }
          case 0x73: {
            return "Home";
          }
          case 0x74: {
            return "PageUp";
          }
          case 0x75: {
            return "ForwardDelete";
          }
          case 0x76: {
            return "F4";
          }
          case 0x77: {
            return "End";
          }
          case 0x78: {
            return "F2";
          }
          case 0x79: {
            return "PageDown";
          }
          case 0x7a: {
            return "F1";
          }
          case 0x7b: {
            return "LeftArrow";
          }
          case 0x7c: {
            return "RightArrow";
          }
          case 0x7d: {
            return "DownArrow";
          }
          case 0x7e: {
            return "UpArrow";
          }
          default: {
            return "??";
          }
        }
      }

      function getModifiersForBitmask(bitMask, leftRight) {
        console.log("modifier", bitMask);
        if (bitMask === 0 || bitMask === -1) {
          return "";
        }
        var modifierString = "";

        if (leftRight && (bitMask & 0x00000001 || bitMask & 0x00002000)) {
          if (bitMask & 0x00000001) {
            modifierString += "⌃(L)";
          }
          if (bitMask & 0x00002000) {
            modifierString += "⌃(R)";
          }
        } else {
          if (bitMask & (1 << 18)) {
            modifierString += "⌃";
          }
        }

        if (leftRight && (bitMask & 0x00000002 || bitMask & 0x00000004)) {
          if (bitMask & 0x00000002) {
            modifierString += "⇧(L)";
          }
          if (bitMask & 0x00000004) {
            modifierString += "⇧(R)";
          }
        } else {
          if (bitMask & (1 << 17)) {
            modifierString += "⇧";
          }
        }

        if (leftRight && (bitMask & 0x00000010 || bitMask & 0x00000008)) {
          if (bitMask & 0x00000010) {
            modifierString += "⌘(R)";
          }
          if (bitMask & 0x00000008) {
            modifierString += "⌘(L)";
          }
        } else {
          if (bitMask & (1 << 20)) {
            modifierString += "⌘";
          }
        }

        if (leftRight && (bitMask & 0x00000020 || bitMask & 0x00000040)) {
          if (bitMask & 0x00000020) {
            modifierString += "⌥(L)";
          }
          if (bitMask & 0x00000040) {
            modifierString += "⌥(R)";
          }
        } else {
          if (bitMask & (1 << 19)) {
            modifierString += "⌥";
          }
        }

        if (bitMask & (1 << 23)) {
          modifierString += "fn";
        }

        console.log("modifierstring", modifierString);
        return modifierString;
      }

      function getShortcutString(value) {
        var kCommand = 0x37;
        var kShift = 0x38;
        var kOption = 0x3a;
        var kControl = 0x3b;
        var kRightCommand = 0x36;
        var kRightShift = 0x3c;
        var kRightOption = 0x3d;
        var kRightControl = 0x3e;
        var kFunction = 0x3f;
        var modsString = "";
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kFunction
        ) {
          modsString = "fn " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kControl
        ) {
          modsString += "⌃";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kOption
        ) {
          modsString += "⌥";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kShift
        ) {
          modsString += "⇧";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kCommand
        ) {
          modsString += "⌘";
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "aL") {
          modsString = "l⌥ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightOption
        ) {
          modsString = "r⌥ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "sL") {
          modsString = "l⇧ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightShift
        ) {
          modsString = "l⇧ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "cL") {
          modsString = "l⌘ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightCommand
        ) {
          modsString = "r⌘ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "rL") {
          modsString = "l⌃ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightControl
        ) {
          modsString = "r⌃ " + modsString;
          value = value.substring(3);
        }
        return (
          modsString + " " + getKeycodeStringForKeycode(parseInt(value, 10))
        );
      }
      function getActionNameForID(aid, trigger) {
        switch (aid) {
          case -1: {
            if (trigger.BTTTriggerType === 630) {
              return "Open Group";
            }
            if (trigger.BTTShortcutToSend) {
              return (
                "Execute Keyboard Shortcut: " +
                getShortcutString(trigger.BTTShortcutToSend)
              );
            }
            return "No Action";
          }

          default:
            return trigger.BTTPredefinedActionName;
        }
      }

      function BTTInitialize() {}

      function BTTWillCloseWindow() {}

      function BTTWillHideWindow() {}
    </script>
  </head>
  <body>
    <div
      id="triggerList"
      style="
        height: 100%;
        display: flex;
        flex-direction: column;
        align-items: left;
        justify-content: space-between;
      "
    ></div>
  </body>
</html>

If you get triggers via the get_triggers call they will also have a property BTTTriggerBelongsToPreset, so you could filter the list by preset. To filter by app you'd add the trigger_app_bundle_identifier filter ( Using Apple Script or JXA · GitBook (folivora.ai) )

It would also be easy to make the items clickable to trigger the action or you could make it always show the shortcuts for the active application.

Hi, @Andreas_Hegenberg. Thanks so much for the reply!

This looks very powerful and promising!

I suspect it does as did a quick test with the code you provided. (I'm using BTT v4.062 [Setapp].)

  1. In Preset: Default, All Apps I created a Keyboard Shortcut trigger with the action: Show Floating WebView: Show KM Hotkeys.

  2. In the HTML/Content>Instead of a URL Path you..., I pasted your code.

When using the Keyboard Shortcut to trigger, Safari moved to the foreground, but nothing appeared.

Maybe I'm missing something, or as you suspected, the latest alpha is required. Since I'm using the Setapp version, I don't think I have access to other versions. If I purchase a standalone license of BTT, is there anything I'd need to do to maintain my current BTT configuration?

Thanks again for the information, @Andreas_Hegenberg. I'm fairly adept with Keyboard Maestro, but very green with BTT. I do look forward to learning more!

Hi, @Frank1. This is what I've worked out so far.

Since this mostly pertains to Keyboard Maestro, when I get some extra time, I'll create a tutorial post at Keyboard Maestro Forum>Tips and Tutorials and then post a link to it in this thread.

Hi @jim-sauer :slightly_smiling_face:

What palette did you choose? These all seem to be macros for the KM editor, so app specific? Does the palette appear when you hold down a modifier on the right side (of the spacebar)? Do you use a key sequence in BTT to trigger the action "Show Macro Group / show palette" in KM?

While testing something else I improved the HTML posted above a little bit.

It's now functional (clicking one of the items triggers the action)

Could be a good starting point for others, but requires btt > 4.063, should be on Setapp soon.

  <head>
    <style>
      #triggerList {
        height: 100%;
        display: grid;
        grid-template-columns: 1fr 1fr 1fr;
        align-items: left;
        justify-content: space-between;
      }

      .trigger {
        border-radius: 4px;
        background: lightblue;
        padding: 4px;
        margin: 4px;
        border: 1 px solid gray;
        display: flex;
        flex-direction: column;
        justify-content: space-around;
        align-items: center;
        align-content: center;
      }

      .trigger:hover {
        cursor: hand;
        background: gray;
      }
    </style>
    <script>
          async function BTTWindowWillBecomeVisible() {
        console.log("loading triggers");
        const allNamedTriggersJSONString = await callBTT("get_triggers", {
          trigger_type: "BTTTriggerTypeKeyboardShortcut",
        });
        let allTriggersJSONArray = JSON.parse(allNamedTriggersJSONString);
        let allKeyboardShortcuts = "";
        for (const trigger of allTriggersJSONArray) {
          if (trigger.BTTShortcutModifierKeys) {
            const shortcut = `<div class="trigger" onclick="callBTT('execute_assigned_actions_for_trigger', {uuid: '${
              trigger.BTTUUID
            }'})"><span><b>${
              trigger?.BTTTriggerConfig?.BTTLeftRightModifierDifferentiation
                ? getModifiersForBitmask(
                    trigger.BTTAdditionalConfiguration,
                    true
                  )
                : getModifiersForBitmask(trigger.BTTShortcutModifierKeys, false)
            }${getKeycodeStringForKeycode(
              trigger.BTTShortcutKeyCode
            )}</b></span> <span>${getActionNameForID(
              trigger.BTTPredefinedActionType,
              trigger
            )}</span></div>`;
            allKeyboardShortcuts = allKeyboardShortcuts + shortcut;
          }
        }
        document.getElementById("triggerList").innerHTML = allKeyboardShortcuts;
      }
      
      function getKeycodeStringForKeycode(keycode) {
        switch (keycode) {
          case 0x00: {
            return "A";
          }
          case 0x01: {
            return "S";
          }
          case 0x02: {
            return "D";
          }
          case 0x03: {
            return "F";
          }
          case 0x04: {
            return "H";
          }
          case 0x05: {
            return "G";
          }
          case 0x06: {
            return "Z";
          }
          case 0x07: {
            return "X";
          }
          case 0x08: {
            return "C";
          }
          case 0x09: {
            return "V";
          }
          case 0x0b: {
            return "B";
          }
          case 0x0c: {
            return "Q";
          }
          case 0x0d: {
            return "W";
          }
          case 0x0e: {
            return "E";
          }
          case 0x0f: {
            return "R";
          }
          case 0x10: {
            return "Y";
          }
          case 0x11: {
            return "T";
          }
          case 0x12: {
            return "1";
          }
          case 0x13: {
            return "2";
          }
          case 0x14: {
            return "3";
          }
          case 0x15: {
            return "4";
          }
          case 0x16: {
            return "6";
          }
          case 0x17: {
            return "5";
          }
          case 0x18: {
            return "=";
          }
          case 0x19: {
            return "9";
          }
          case 0x1a: {
            return "7";
          }
          case 0x1b: {
            return "-";
          }
          case 0x1c: {
            return "8";
          }
          case 0x1d: {
            return "0";
          }
          case 0x1e: {
            return "]";
          }
          case 0x1f: {
            return "O";
          }
          case 0x20: {
            return "U";
          }
          case 0x21: {
            return "[";
          }
          case 0x22: {
            return "I";
          }
          case 0x23: {
            return "P";
          }
          case 0x25: {
            return "L";
          }
          case 0x26: {
            return "J";
          }
          case 0x27: {
            return '"';
          }
          case 0x28: {
            return "K";
          }
          case 0x29: {
            return ";";
          }
          case 0x2a: {
            return "\\";
          }
          case 0x2b: {
            return ",";
          }
          case 0x2c: {
            return "/";
          }
          case 0x2d: {
            return "N";
          }
          case 0x2e: {
            return "M";
          }
          case 0x2f: {
            return ".";
          }
          case 0x32: {
            return "?";
          }
          case 0x41: {
            return ".";
          }
          case 0x43: {
            return "*";
          }
          case 0x45: {
            return "+";
          }
          case 0x47: {
            return "?";
          }
          case 0x4b: {
            return "%";
          }
          case 0x4c: {
            return "return";
          }
          case 0x4e: {
            return "-";
          }
          case 0x51: {
            return "=";
          }
          case 0x52: {
            return "0";
          }
          case 0x53: {
            return "1";
          }
          case 0x54: {
            return "2";
          }
          case 0x55: {
            return "3";
          }
          case 0x56: {
            return "4";
          }
          case 0x57: {
            return "5";
          }
          case 0x58: {
            return "6";
          }
          case 0x59: {
            return "7";
          }
          case 0x5b: {
            return "8";
          }
          case 0x5c: {
            return "9";
          }
          case 0x24: {
            return "Return";
          }
          case 0x30: {
            return "Tab";
          }
          case 0x31: {
            return "Space";
          }
          case 0x33: {
            return "Delete";
          }
          case 0x35: {
            return "Escape";
          }
          case 0x37: {
            return "Command";
          }
          case 0x38: {
            return "Shift";
          }
          case 0x39: {
            return "CapsLock";
          }
          case 0x3a: {
            return "Option";
          }
          case 0x3b: {
            return "Control";
          }
          case 0x3c: {
            return "RightShift";
          }
          case 0x3d: {
            return "RightOption";
          }
          case 0x3e: {
            return "RightControl";
          }
          case 0x3f: {
            return "Function";
          }
          case 0x40: {
            return "F17";
          }
          case 0x48: {
            return "VolumeUp";
          }
          case 0x49: {
            return "VolumeDown";
          }
          case 0x4a: {
            return "Mute";
          }
          case 0x4f: {
            return "F18";
          }
          case 0x50: {
            return "F19";
          }
          case 0x5a: {
            return "F20";
          }
          case 0x60: {
            return "F5";
          }
          case 0x61: {
            return "F6";
          }
          case 0x62: {
            return "F7";
          }
          case 0x63: {
            return "F3";
          }
          case 0x64: {
            return "F8";
          }
          case 0x65: {
            return "F9";
          }
          case 0x67: {
            return "F11";
          }
          case 0x69: {
            return "F13";
          }
          case 0x6a: {
            return "F16";
          }
          case 0x6b: {
            return "F14";
          }
          case 0x6d: {
            return "F10";
          }
          case 0x6f: {
            return "F12";
          }
          case 0x71: {
            return "F15";
          }
          case 0x72: {
            return "Help";
          }
          case 0x73: {
            return "Home";
          }
          case 0x74: {
            return "PageUp";
          }
          case 0x75: {
            return "ForwardDelete";
          }
          case 0x76: {
            return "F4";
          }
          case 0x77: {
            return "End";
          }
          case 0x78: {
            return "F2";
          }
          case 0x79: {
            return "PageDown";
          }
          case 0x7a: {
            return "F1";
          }
          case 0x7b: {
            return "LeftArrow";
          }
          case 0x7c: {
            return "RightArrow";
          }
          case 0x7d: {
            return "DownArrow";
          }
          case 0x7e: {
            return "UpArrow";
          }
          default: {
            return "??";
          }
        }
      }

      function getModifiersForBitmask(bitMask, leftRight) {
        console.log("modifier", bitMask);
        if (bitMask === 0 || bitMask === -1) {
          return "";
        }
        var modifierString = "";

        if (leftRight && (bitMask & 0x00000001 || bitMask & 0x00002000)) {
          if (bitMask & 0x00000001) {
            modifierString += "⌃(L)";
          }
          if (bitMask & 0x00002000) {
            modifierString += "⌃(R)";
          }
        } else {
          if (bitMask & (1 << 18)) {
            modifierString += "⌃";
          }
        }

        if (leftRight && (bitMask & 0x00000002 || bitMask & 0x00000004)) {
          if (bitMask & 0x00000002) {
            modifierString += "⇧(L)";
          }
          if (bitMask & 0x00000004) {
            modifierString += "⇧(R)";
          }
        } else {
          if (bitMask & (1 << 17)) {
            modifierString += "⇧";
          }
        }

        if (leftRight && (bitMask & 0x00000010 || bitMask & 0x00000008)) {
          if (bitMask & 0x00000010) {
            modifierString += "⌘(R)";
          }
          if (bitMask & 0x00000008) {
            modifierString += "⌘(L)";
          }
        } else {
          if (bitMask & (1 << 20)) {
            modifierString += "⌘";
          }
        }

        if (leftRight && (bitMask & 0x00000020 || bitMask & 0x00000040)) {
          if (bitMask & 0x00000020) {
            modifierString += "⌥(L)";
          }
          if (bitMask & 0x00000040) {
            modifierString += "⌥(R)";
          }
        } else {
          if (bitMask & (1 << 19)) {
            modifierString += "⌥";
          }
        }

        if (bitMask & (1 << 23)) {
          modifierString += "fn";
        }

        console.log("modifierstring", modifierString);
        return modifierString;
      }

      function getShortcutString(value) {
        var kCommand = 0x37;
        var kShift = 0x38;
        var kOption = 0x3a;
        var kControl = 0x3b;
        var kRightCommand = 0x36;
        var kRightShift = 0x3c;
        var kRightOption = 0x3d;
        var kRightControl = 0x3e;
        var kFunction = 0x3f;
        var modsString = "";
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kFunction
        ) {
          modsString = "fn " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kControl
        ) {
          modsString += "⌃";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kOption
        ) {
          modsString += "⌥";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kShift
        ) {
          modsString += "⇧";
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kCommand
        ) {
          modsString += "⌘";
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "aL") {
          modsString = "l⌥ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightOption
        ) {
          modsString = "r⌥ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "sL") {
          modsString = "l⇧ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightShift
        ) {
          modsString = "l⇧ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "cL") {
          modsString = "l⌘ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightCommand
        ) {
          modsString = "r⌘ " + modsString;
          value = value.substring(3);
        }
        if (value.length > 2 && value.substring(0, 2) === "rL") {
          modsString = "l⌃ " + modsString;
          value = value.substring(3);
        }
        if (
          value.length > 2 &&
          parseInt(value.substring(0, 2), 10) === kRightControl
        ) {
          modsString = "r⌃ " + modsString;
          value = value.substring(3);
        }
        return (
          modsString + " " + getKeycodeStringForKeycode(parseInt(value, 10))
        );
      }
      function getActionNameForID(aid, trigger) {
        switch (aid) {
          case -1: {
            if (trigger.BTTTriggerType === 630) {
              return "Open Group";
            }
            if (trigger.BTTShortcutToSend) {
              return (
                "Execute Keyboard Shortcut: " +
                getShortcutString(trigger.BTTShortcutToSend)
              );
            }
            return "No Action";
          }

          default:
            return trigger.BTTPredefinedActionName;
        }
      }

    </script>
  </head>
  <body>
    <div id="triggerList"></div>
  </body>
</html>

1 Like

Great !

I have modified this one line...
over here I have a lot of disabled triggers and separator lines...otherwise
Thx,
Christian

for (const trigger of allTriggersJSONArray) {
if (trigger.BTTShortcutModifierKeys > 0) {

2 Likes

Yep in my version it just outputs all shortcuts. You can also only include triggers with trigger.BTTEnabled === 1 to exclude disabled ones.

1 Like

I may not understand anything about it, but what's the difference in doing the same thing with the Stream Deck Emulator, only much easier from my point of view.

What I posted will automatically retrieve your currently configured shortcuts. With the stream deck emulator you'd need to set every shortcut up manually.

With some additions you could e.g. make it always show the currently configured shortcuts for the active app.

1 Like

Ah, ok, it will find the BTT shortcuts that trigger the KM macros, speaking from @jim-sauer's point of view.

1 Like

Exactly. (Although to only show the KM shortcuts you'd need to adapt the get_triggers query like this:

   const allKeyboardShortcutsJSONString = await callBTT("get_triggers", {
          trigger_type: "BTTTriggerTypeKeyboardShortcut",
trigger_app_bundle_identifier: "com.stairways.keyboardmaestro.engine"
        });

(not sure about the bundle identifier!). Or if they are not assigned to keyboard maestro in BTT, you'd need to add some other identifier that you could use for filtering e.g. into the notes field.

1 Like

If this works, many KM users will be very happy ... if they also use BTT. But since I am mainly a BTT user, I am much more interested in the new floating menus.

2 Likes

Thx Andreas,
that is what I was looking for !

I have now some issue with long/short keyboard triggers, which are listed somehow multiple times.I want basically select only active keyboard triggers including description/notes like these:

here there are multiple times...

Christian