Combining macros with modifiers

Hello all,

I am trying to map a macro for triggering 1Password to the Butterfly key in the QWERTY layout. If you are not familiar with 1Password shortcuts, in applications with native integrations the login box can be brought up with Cmd + \, while a more generic keyboard-emulating box can be brought up in any app with Cmd + Opt + \.

I accomplished triggering the first command with a MACRODOWN(D(LeftGui), T(Backslash)) and naively assumes that tapping Opt + the butterfly key would translate to the more generic shortcut described above. Unfortunately it seems that triggering the macro resets the down state of the Opt modifier. I looked at the keyboard events as reported by the OS (MacOS 10.13) and the sequence looks as following:

Opt down
Opt up
Cmd down
\ down
\ up
Cmd up
Opt down

The OS sees me holding down the Opt, but the triggering of the macro resets the key state until the macro is done, at which point the down state of the Opt is re-detected.

Question: Is there a way to combine macros with other modifiers in a way that preserves their down states?

1 Like

From looking at the source of the macros plugin, I think the issue is happening because it doesn’t mask the held modifiers, so when the macro sends the new keyboard report, it clears the previously-held keys (I think; my understanding of how key presses & reports work is not exactly 100%).

The easiest way I can think of to make this work is to replace the part of your config that has return MACRODOWN(D(LeftGui)...) with something like return kaleidoscope::hid::wasModifierKeyActive(LeftAlt) ? MACRODOWN(D(LeftGui), D(LeftAlt), ...) : MACRODOWN(D(LeftGui), T(Backslash))

1 Like

I’m betting this is (somewhat) dependent on key handling order. I think if you put the modifier (option) on a key that gets handled before the macro key (generally speaking, on a row closer to the top), you’d see the opt getting released after the first keypress event of the macro. It still wouldn’t work the way you’d like, though, and it’s debatable that the behaviour should be considered a bug (I would tend toward that opinion, though).

If I’m right, I can think of a way to make Kaleidoscope-Macros behave the way you were expecting — I might submit a PR in a couple of days that would make held keys stay held while a macro plays, without extra code in the macro functions.

@algernon: How do you feel about held keys (and OneShot modifiers) persisting in the keyboard report while Macros are playing? I think they should, and I’ve got a plan for how to do it, but I don’t want to bother doing the work on that unless you think it’s a good idea. If you do think it’s a good idea, I’ll write it up sometime next week and submit a PR to Kaleidoscope-Macros.

Macros should not be clearing the report, so held keys should persist. What we see here is more likely an ordering issue.

Other than that - yes, held keys, OneShots and everything else that was active when the macro triggered, should persist throughout the macro.

1 Like

Of course! I’m now wondering why I thought the report was cleared. I’ll submit a PR with a fix sometime this week unless you do it first.

1 Like

I’m working on a PR to fix this problem: https://github.com/keyboardio/Kaleidoscope-Macros/pull/20

Thanks for the help! In the mean time I’ve followed @james.nvc suggestion and augmented my macro to use wasModifierKeyActive, but unfortunately I am getting somewhat funky behavior:

case MACRO_1PASSWORD:
    if (kaleidoscope::hid::wasModifierKeyActive(Key_RightAlt)) {
      return MACRODOWN(D(RightGui), D(RightAlt), T(Backslash));
    }
    return MACRODOWN(D(RightGui), T(Backslash));
    break;
  }

Pressing just the butterfly key works as expected:

But when I execute Opt + Butterfly I get the following sequence:

It seems that the macro selects Cmd + \ + Opt, which is not idempotent with Cmd + Opt + . Is this another ordering issue and expected behavior?

I’m not sure why you’re seeing those keys out of order in this case. If I get some time, I’ll try it out tomorrow myself.

I suspect that your keymap contains a Key_LeftAlt (i.e. left option), but not a Key_RightAlt, which is what you would need for that macro to work the way you want it to. If that’s the case, you can change the test in MACRO_1PASSWORD to check for Key_LeftAlt, and then it should work.

@merlin You are right, the Alt key on the right side of the keyboard is actually a LeftAlt in the map. I corrected the macro and it now works as intended. Thank you so much!

2 Likes

Related question: is it possible for a macro to hold down a modifier key until the next keypress? My first usage scenario would be to make a macro that places a period, space, and holds down shift until the next letter (the start of the following sentence). I use OneShot for my shift keys, so it would be even better if I could just trigger my OSM(LeftShift).

I seem to remember another thread where someone did something similar, but I can’t seem to find it.

Here is the thread that mentioned this earlier.

For a macro, no. Macro keys can only act when it is their turn, they can’t watch other keys. To do what you described, one would need to code a bit more. Or, in your specific case, the macro can do something like this:

Macros.play(T(Period), T(Space));
OneShot.inject(OSM(LeftShift), IS_PRESSED);
kaleidoscope::hid::sendKeyboardReport();
OneShot.inject(OSM(LeftShift), WAS_PRESSED);
2 Likes

Sorry, coding amateur here. Can you tell me where I would insert that sequence? I tried this:

  case MACRO_SENTENCE:
  if (keyToggledOn(keyState)) {
      return Macros.play(T(Period), T(Space));
    OneShot.inject(OSM(LeftShift), IS_PRESSED);
    kaleidoscope::hid::sendKeyboardReport();
    OneShot.inject(OSM(LeftShift), WAS_PRESSED);
  }
    break;

What I get is an error, “No matching function for call to 'Macros_::play(MacroActionStepType,…” What am I doing wrong?

You don’t need the return before Macros.play. Returning will result in the code after not being run. It will also throw an error, because Macros.play() does not have a return value, while return Macros.play() in a function that does have a return value makes the compiler think it should have one. But there is no version of that function that does, hence the error.

Instead, replace the break with return MACRO_NONE after removing the return before Macros.play().

This is roughly what you should end up with:

 case MACRO_SENTENCE:
  if (keyToggledOn(keyState)) {
    Macros.play(T(Period), T(Space));
    OneShot.inject(OSM(LeftShift), IS_PRESSED);
    kaleidoscope::hid::sendKeyboardReport();
    OneShot.inject(OSM(LeftShift), WAS_PRESSED);
  }
  return MACRO_NONE;

Sorry for the delayed reply. Work has been piling up. I still get an error for “no matching function for call to Macros_::play…”

Any chance you see what I’m doing wrong? Here is my whole macroAction section of my firmware:

const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) {
  switch (macroIndex) {

  case MACRO_VERSION_INFO:
    versionInfoMacro(keyState);
    break;

  case MACRO_ANY:
    anyKeyMacro(keyState);
    break;

  case MACRO_SENTENCE:
    if (keyToggledOn(keyState)) {
      Macros.play(T(Period), T(Space));
      OneShot.inject(OSM(LeftShift), IS_PRESSED);
      kaleidoscope::hid::sendKeyboardReport();
      OneShot.inject(OSM(LeftShift), WAS_PRESSED);
  }
  return MACRO_NONE;

  }
  return MACRO_NONE;
}

I’m having problems getting this to work too. Getting similar errors. Any chance you got this to work in the meantime (or anyone else has an answer)?