I've been working on this as well because I've wanted to use something like this for a while.
I noticed a few other requirements from the OP that appeal to me:
- Don't allow keystrokes to press through to the current application
• These could interfere with the current app
- Allow sequences larger than 2
• While it's true they only need '⌘⇧M > Key' for the macros mentioned – at some point there will be conflicts and a need to expand
- Input timeout after 3 seconds
• Allows the user to exit the input sequence
- Ability to use modifier keys
• More customization
Here is my solution: Initiator Keystroke Trigger.kmmacros (113.9 KB)
• You get to call commands via character sequences without disturbing the current app
• Lots of control and granularity
• Adding a new sequence is not quick
• It's easy to make a mistake when adding
- Trigger with whatever you choose (currently Ctrl-Option-Cmd+I)
- After the audible tone, type one of the demo input sequences: "ind", "pdf", "caps", or "⌥a⌥b".
- Terminate your sequence with 'Return'.
It's currently configured to launch 'Display Text' actions that say what would have been launched. To customize your sequences, take a look at the walkthrough to see what's going on in the macros.
Basically, you can use what's already there to build your own sequences.
I gotta say that this one is very bulky and requires some manual labor up front when adding new commands – but it does work almost exactly as desired and is pretty nice once up and running.
The main concept here is to assign a macro for every "level". For example, the "caps" command has 4 levels, "pdf" and "ind" have 3 and "⌥a⌥b" has 2. This walkthrough will show how each is encoded in the macro so you can make your own.
Level 1 Setup
The first major thing the macro does is save the current focused application. This is necessary so we can switch somewhere else while a key sequence is inputted – satisfying requirement #1 above:
Then we make the aforementioned switch to the Keyboard Maestro Engine app, which doesn't seem to care that we are focusing it or pressing buttons while focused:
From there you'll hear a sound signaling that you can enter a sequence now. I added this sound because at first I was entering commands too fast and needed some indicator of when to start pressing keys. It's being played asynchronously so we don't have to wait for the sound to end before we can start typing.
Listening for our first keys
After setup, the macro listens for expected keys pressed within a 3 second timeout – requirement #3:
If you click the gear on the 'Pause Until' action above, you'll notice the timeout is set to 3 seconds and both 'Timeout Aborts Macro' and 'Notify on Timeout' are disabled (since the timeout here is a feature and not a bug):
The next section consists of actions specific to each key at level 1.
Here is the start of the 'caps' chain:
The 'InitiatorString' variable is used to keep track of state. When the user eventually finishes entering a command, 'InitiatorString' will be checked.
Then the macro throws execution to the next level, the 'Initiator Trigger L2' macro.
Before we go there, note that there are other keys at this level to understand.
The one that sticks out is the '⌥a' in the '⌥a⌥b' sequence:
Here we want to be able to know if the key and modifier are pressed (requirement #4). This is why I had to use an 'If' action instead of a Switch. The Switch can't check for modifiers unfortunately.
The last key to look for at this level is the 'Escape' key:
No official actions here, but pressing 'Escape' allows the user to abort input without having to wait for a 3 second timeout.
Finally, at the end of the level 1 macro, and all of them, execution runs into this:
'ReturnToInitialApplication' is set to YES by default at the start of every sequence at level 1 (Initiator Trigger L1). Note that if the macro you're calling opens another app and continues there, then you'll want to be sure to set 'ReturnToInitialApplication' to NO right before executing that macro. More on this in the Level 4 section below.
The Next Level
Level 2 is more of the same – just continue adding the next link in whatever sequence you're building.
One difference worth noting is what must appear at every level starting at level 2: a special 'Pause Until' action.
This had to be added after first testing this macro because I noticed that sometimes keypresses were bleeding into the next level. I'd press a 'D' in level X and it would still be observed in level X+1 somehow. To stop it, this 'Pause Until' action will wait for there to be no keypresses.
Unfortunately, this meant hardcoding every key that'll be supported:
all the way to...
Specifically, supported keys are: a-z, 0-9, F1-F19, 'Return', & 'Escape'.
Of course you can add more. I will add symbols and Numpad support at some point, but I got tired of entering each one manually. Just add them at the end of the list. The good news is that you only have to specify them once and then you can copy/paste this 'Pause Until' action at the start of every macro at Level 2 or above.
Notice that you can't add modifier keys here in the 'Pause Until' action. Fortunately, when ⌥ is pressed with a key, let's say 'X', the 'X' registers as being pressed down, so this 'Pause Until' will still work even if modifiers are also being pressed at the moment.
If you want to check for a modifier key, you have to handle this below the 'Pause Until', just as was done in the above example where I showed the start of the ⌥a⌥b sequence.
Level 2 then continues on in the same way that Level 1 does recording the keys pressed and calling the next level.
The other Level 2 difference is that now we are appending onto the 'InitiatorString' variable instead of setting it:
This is the first level that includes a sequence termination and macro call. Ideally, we'd want sequences with 3 characters (like "ind" or "pdf") to terminate on level 3 – but we actually end up needing an extra character to terminate everything. That's why here at level 3 we're terminating a sequence with 2 characters: "⌥a⌥b".
The reason sequences are 1 keypress longer than their character count is because the macro needs to know when we're done sending input. A 'Return' key is then sent to terminate the whole thing.
Otherwise, all sequences would have to be the same length. You couldn't have a sequence of length 2 and a sequence of length 4 without signaling when to stop taking input. They'd all have to stop in the same level, and we want the flexibility of being able to create sequences at variable lengths (requirement #2).
Here's how the "⌥a⌥b" sequence terminates in Level 3:
We respond to the 'Return' keypress and check to see if the 'InitiatorString' matches the known terminating sequence at this level. If it does, we activate the initial application so that the macro will return from the focus on the KM Engine BEFORE executing. This is required if you want the macro to execute in the app you're currently in.
This section has 2 sequences that terminate – "ind" and "pdf":
Notice how "ind" uses 'Activate Initial Application' and "pdf" doesn't. Basically I made the assumption that a macro named 'Export to PDF' would open the Preview app and go there. It's an example of how to make sure the macro takes you to another application instead of the default – which is to return to it.
This is done by setting 'ReturnToInitialApplication' to NO right before calling the macro intended to run at the end of a sequence.
On the other hand, "ind" does return to the initial application first before executing the macro. This is so execution can continue in the intended app.
Finally, the last sequence is terminated in 'Initiator Trigger L5' ("caps"). Nothing special here other than it being the longest sequence. This macros will handle sequences of any length – but you'll have to add another level in the macro chain every time you add a command with a larger sequence. It currently supports sequences with 1-4 characters, which will get you a long way, but it helps to know you can get more.
..and that's it!
Go ahead and edit the macros to create whatever sequences you want!
New Sequence Quick Guide
Each new key needs to be added 2-3 times per each level:
a. In the collection of 'Pause Until key is down' specific to that level
b. In its own 'If key is down'
c. [L2+ only] In the first action entitled 'Pause Until None of the supported keys are down...'. Note that a-z, 0-9, F1-F19, 'Return', & 'Escape' are already added.
'If key is down' contents (before terminating)
a. 'InitiatorString' must be set in L1 with the first letter and then be appended to in L2+
b. Call the next level macro, Initiator Trigger L(X+1)
'If key is down' contents (while terminating)
a. A sequence ends in (character sequence count +1) with the 'Return' key
b. Call any macro from there based on the contents of 'InitiatorString'
c. If you want to return to the app you were in before inputting a sequence, then call the 'Activate Initial Application' macro before calling your sequence ending macro
d. If the macro you call switches applications, then set 'ReturnToInitialApplication' to 'NO' before calling that particular macro
Wish List / Future
• Some sort of visual component. I'd like to at least see what's been typed so far and if possible see what commands are still available in real-time while typing. Some kind of updating HUD.
• An audio beep after entering an unsupported sequence