"Just act normal" in a macro

I want a key to have a special function when a flag is set and otherwise act normally.

I tried this:

// the N key works as "special function" if flag is set
const macro_t *macroSpecialN(uint8_t keyState) {

  // Key was just pressed and flag is set
  if (keyToggledOn(keyState) && flag == true) {
    return MACRO(special function here);
  }

  // Key was just pressed and flag is not set
  // then it's just an N
  if(keyToggledOn(keyState) && flag == false) {
    return MACRO(T(N));
  }

  // otherwise we do nothing
  return MACRO_NONE;
}

But this has a couple problems - it won’t let me type capital n or ctrl-n, and it won’t let me repeat n by holding it down.

How do i tell it to just act like a normal n, with whatever modifiers are already on?

const macro_t *macroSpecialN(uint8_t keyState) {
  if (flag) {
    return MACRO(special function here);
  }

  if (keyIsPressed(keyState)) {
    return MACRO(D(N));
  } else if (keyToggledOff(keyState)) {
    return MACRO(U(N));
  }
};

This might be easier to do as an event loop hook, though:

static Key specialKey(Key mappedKey, byte row, byte col, uint8_t keyState) {
  // If this key is not the one we want to treat specially, return early.
  if (mappedKey != NORMAL_KEY) { // Where NORMAL_KEY is Key_A or the like
    return mappedKey;
  }

  if (flag == true) {
    if (keyToggledOn(keyState)) {
      // Do the special thing! You can call Macro.play() from here, too!
    }
    // Return NoKey, to stop processing the key further.
    return Key_NoKey;
  }

  // If the flag is not set, return the original key, and the other handlers 
  // will deal with it.
  return mapped_key;
}

// In your setup() function:
Kaleidoscope.useEventLoopHook(specialKey);

Does your first example deal with the modifiers? I see that it would solve the key repeat problem but if T(n) doesn’t respect whatever modifiers are pressed, why would D(n) U(n)?

Whether it respects modifiers or not is… complicated. Depends on where the modifiers are on your keymap. The why of that is something I’d rather not explain right now, because it makes me sad.

So, uhh… yeah. Use the second method instead, that will respect modifiers. At least in the normal case. Whether it does so in the special case depends on what you put there. :slight_smile:

(We may end up figuring out how to make macros respect modifiers in a sane way, but we’re not there yet. Keyboards are complicated.)

So if I use the second example, what do I put in the keymap? Just the normal Key_n?

I’m not sure I understand how these event hooks work. Where do they go and where do I indicate which key it is? I’m doing this for several keys - do they get separate hooks?

Yep!

Event hooks are normal functions. They will be called for every key on the keymap, and the hooks decide if they want to do something with it or not. If not, they just return the same key they got. If yes, they can either return a different key (and in that case the hooks after it will receive that key instead of the original); or they can do pretty much anything (light up LEDs, play a macro, etc), and return Key_NoKey to signal the end of processing.

You can put the hook function anywhere in your sketch, just put it before the setup function.

The event hook itself can decide which keys it wants to act on. As such, you do not need separate hooks for different keys, just code the hook in a way that it does something for all keys you want it to.

For example:

static Key exampleHook(Key mapped_key, byte row, byte col, uint8_t key_state) {
  // We only want to act on K, B, D, I, and O. If we see anything else, return.
  if (mapped_key != Key_K && mapped_key != Key_B && mapped_key != Key_D &&
      mapped_key != Key_I && mapped_key != Key_O)
    return mapped_key;

  // For K, B, and D, we want to change them to the next char in the alphabet.
  // This is silly, but demonstrates how to return a different key.
  if (mapped_key == Key_K || mapped_key = Key_B || mapped_key == Key_D) {
    return (Key) { mapped_key.keyCode +1, mapped_key.flags };
  }

  // For I, we want to toggle the LED under it, and stop I from being input
  // We want to toggle the LED on key press
  if (mapped_key == Key_I) {
    static bool i_LED = false;

    if (keyToggledOn(key_state)) {
      if (i_LED) {
        LEDControl.setCrgbAt(row, col, CRGB(0, 0, 0));
      } else {
        LEDControl.setCrgbAt(row, col, CRGB(160, 0, 0));
      }
      i_LED = !i_LED;
    }

    return Key_NoKey;
  }

  // For O, we want to type "Keyboardio!", on key release.
  if (Key == Key_O) {
    if (keyToggledOff(key_state)) {
      Macro.type(PSTR("Keyboardio!"));
    }
    return Key_NoKey;
  }
}

And in the setup() method of the sketch: Kaleidoscope.useEventHandlerHook(exampleHook).

Hope this makes it clearer.

1 Like

Thanks!

The following is giving me a compile error within a hook:

else if (mappedKey == Key_H) {
    return MACRO(D(LAlt), T(LeftArrow));
}

Do I have to create a separate function for MACRO(D(LAlt), T(LeftArrow) and then .play that function? Or is there some way to do it inline within the hook?

Creating a separate macro and calling play doesn’t work either. I get the following error:

error: request for member 'play' in 'M_DLeftAlt', which is of non-class type '<anonymous enum>'

   M_DLeftAlt.play();

              ^

What is the original error message?

Just figured it out! It needs to be:

  Macros.play(MACRO(D(LeftAlt), T(LeftArrow)));

I still cannot get the alt key to stay pressed down when I press H, J, K, or L. Why is it being cleared?

This is for my macro key that pulls up the alt-tab switcher.

static Key specialHook(Key mappedKey, byte row, byte col, uint8_t keyState) {
  // If this key is not the one we want to treat specially, return early.
  if (mappedKey != Key_Enter && mappedKey != Key_Escape && mappedKey != Key_H &&
      mappedKey != Key_J && mappedKey != Key_K && mappedKey != Key_L)
  {
    return mappedKey;
  }
  if (appSwitchActive && keyToggledOn(keyState)) {
    if (mappedKey == Key_Enter) {
      appSwitchActive = false;
      return mappedKey;
    }

    else if (mappedKey == Key_Escape) {
      appSwitchActive = false;
      return mappedKey;
    }

    else if (mappedKey == Key_H) {
      Macros.play(MACRO(D(LeftAlt), T(LeftArrow)));
      return Key_NoKey;
    }

    else if (mappedKey == Key_J) {
      Macros.play(MACRO(D(LeftAlt), T(DownArrow)));
      return Key_NoKey;
    }

    else if (mappedKey == Key_K) {
      Macros.play(MACRO(D(LeftAlt), T(UpArrow)));
        return Key_NoKey;
    }

    else if (mappedKey == Key_L) {
      Macros.play(MACRO(D(LeftAlt), T(RightArrow)));
      return Key_NoKey;
    }
  }
  else return mappedKey;
}

const macro_t *macroAppSwitch(uint8_t keyState) {

  // Key was just pressed and we are in app switch mode
  if (keyToggledOn(keyState) && appSwitchActive == true) {
    appSwitchActive = false;
    return MACRO(U(LAlt));
  }

  // Key was just pressed and we are not in app switch mode
  if (keyToggledOn(keyState) && appSwitchActive == false) {
    appSwitchActive = true;
    return MACRO(D(LAlt), T(Tab));
  }

  // Key was not just released.
  // if appSwitchActive is true, we continue holding Alt.
  if (appSwitchActive) {
    return MACRO(D(LAlt));
  }

  // otherwise we do nothing
  return MACRO_NONE;
}

Never mind - sorry about that. I figured it out. Thanks for all your help!

I wasn’t accounting for the case where mappedKey was H, J, K, L but the key was not just toggled on.

So the working code is:

static Key specialHook(Key mappedKey, byte row, byte col, uint8_t keyState) {
  // If this key is not the one we want to treat specially, return early.
  if (mappedKey != Key_Enter && mappedKey != Key_Escape && mappedKey != Key_H &&
      mappedKey != Key_J && mappedKey != Key_K && mappedKey != Key_L)
  {
    return mappedKey;
  }
  if (appSwitchActive) {
    if (mappedKey == Key_Enter && keyToggledOn(keyState)) {
      appSwitchActive = false;
      return mappedKey;
    }

    else if (mappedKey == Key_Escape && keyToggledOn(keyState)) {
      appSwitchActive = false;
      return mappedKey;
    }

    else if (mappedKey == Key_H) {
      if (keyToggledOn(keyState)) {
        Macros.play(MACRO(D(LeftAlt), T(LeftArrow)));
      } else {
        Macros.play(MACRO(D(LeftAlt)));
        return Key_NoKey;
      }
    }

    else if (mappedKey == Key_J) {
      if (keyToggledOn(keyState)) {
        Macros.play(MACRO(D(LeftAlt), T(DownArrow)));
      } else {
        Macros.play(MACRO(D(LeftAlt)));
        return Key_NoKey;
      }
    }

    else if (mappedKey == Key_K) {
      if (keyToggledOn(keyState)) {
        Macros.play(MACRO(D(LeftAlt), T(UpArrow)));
      } else {
        Macros.play(MACRO(D(LeftAlt)));
        return Key_NoKey;
      }
    }

    else if (mappedKey == Key_L) {
      if (keyToggledOn(keyState)) {
        Macros.play(MACRO(D(LeftAlt), T(RightArrow)));
      } else {
        Macros.play(MACRO(D(LeftAlt)));
        return Key_NoKey;
      }
    }
  }
  else return mappedKey;
}

const macro_t *macroAppSwitch(uint8_t keyState) {

  // Key was just pressed and we are in app switch mode
  if (keyToggledOn(keyState) && appSwitchActive == true) {
    appSwitchActive = false;
    return MACRO(U(LAlt));
  }

  // Key was just pressed and we are not in app switch mode
  if (keyToggledOn(keyState) && appSwitchActive == false) {
    appSwitchActive = true;
    return MACRO(D(LAlt), T(Tab));
  }

  // Key was not just released.
  // if appSwitchActive is true, we continue holding Alt.
  if (appSwitchActive) {
    return MACRO(D(LAlt));
  }

  // otherwise we do nothing
  return MACRO_NONE;
}
1 Like