On the use of INJECTED…

Hello. In the current code, almost (but not) all calls to handleKeyswitchEvent use INJECTED, and these are (obviously) the only locations where INJECTED is produced:

./Kaleidoscope/examples/AppSwitcher/Macros.cpp:    handleKeyswitchEvent(mod, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED);
./Kaleidoscope/src/Kaleidoscope.cpp:  handleKeyswitchEvent(Key_NoKey, 255, 255, 0);
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp:  handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED | INJECTED);
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp:  handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, WAS_PRESSED | INJECTED);
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp:  handleKeyswitchEvent(key, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED | INJECTED);
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp:  handleKeyswitchEvent(key, UNKNOWN_KEYSWITCH_LOCATION, WAS_PRESSED | INJECTED);
./Kaleidoscope-GhostInTheFirmware/src/Kaleidoscope/GhostInTheFirmware.cpp:      handleKeyswitchEvent(Key_NoKey, row, col, WAS_PRESSED);
./Kaleidoscope-GhostInTheFirmware/src/Kaleidoscope/GhostInTheFirmware.cpp:      handleKeyswitchEvent(Key_NoKey, row, col, IS_PRESSED);
./Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/hardware/ErgoDox.cpp:        handleKeyswitchEvent(Key_NoKey, row, col, keyState);
./Kaleidoscope-Hardware-Model01/src/Kaleidoscope-Hardware-Model01.cpp:        handleKeyswitchEvent(Key_NoKey, row, startPos - col, keyState);
./Kaleidoscope-Macros/src/Kaleidoscope-Macros.cpp:  handleKeyswitchEvent(key, UNKNOWN_KEYSWITCH_LOCATION, INJECTED | flags);
./Kaleidoscope-OneShot/src/Kaleidoscope/OneShot.cpp:  handleKeyswitchEvent(key, row, col, key_state | INJECTED);
./Kaleidoscope-Qukeys/src/Kaleidoscope/Qukeys.cpp:  handleKeyswitchEvent(keycode, row, col, IS_PRESSED);
./Kaleidoscope-Qukeys/src/Kaleidoscope/Qukeys.cpp:    handleKeyswitchEvent(keycode, row, col, IS_PRESSED | WAS_PRESSED);
./Kaleidoscope-SpaceCadet/src/Kaleidoscope/SpaceCadet.cpp:          handleKeyswitchEvent(map[i].input, row, col, IS_PRESSED | INJECTED);
./Kaleidoscope-SpaceCadet/src/Kaleidoscope/SpaceCadet.cpp:    handleKeyswitchEvent(alternate_key, row, col, IS_PRESSED | INJECTED);
./Kaleidoscope-Syster/README.md:    handleKeyswitchEvent (Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED | INJECTED);
./Kaleidoscope-Syster/README.md:    handleKeyswitchEvent (Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, WAS_PRESSED | INJECTED);
./Kaleidoscope-Syster/examples/Syster/Syster.ino:    handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED | INJECTED);
./Kaleidoscope-Syster/examples/Syster/Syster.ino:    handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, WAS_PRESSED | INJECTED);
./Kaleidoscope-Syster/src/Kaleidoscope/Syster.cpp:        handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, IS_PRESSED | INJECTED);
./Kaleidoscope-Syster/src/Kaleidoscope/Syster.cpp:        handleKeyswitchEvent(Key_Backspace, UNKNOWN_KEYSWITCH_LOCATION, WAS_PRESSED | INJECTED);
./Kaleidoscope-TapDance/src/Kaleidoscope/TapDance.cpp:    handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, IS_PRESSED | INJECTED);
./Kaleidoscope-TapDance/src/Kaleidoscope/TapDance.cpp:    handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, IS_PRESSED | WAS_PRESSED | INJECTED);
./Kaleidoscope-TapDance/src/Kaleidoscope/TapDance.cpp:    handleKeyswitchEvent(key, last_tap_dance_row_, last_tap_dance_col_, WAS_PRESSED | INJECTED);
./Kaleidoscope-TopsyTurvy/src/Kaleidoscope/TopsyTurvy.cpp:    handleKeyswitchEvent(new_key, row, col, key_state | TOPSYTURVY | INJECTED);
./Kaleidoscope-TopsyTurvy/src/Kaleidoscope/TopsyTurvy.cpp:    handleKeyswitchEvent(new_key, row, col, key_state | TOPSYTURVY | INJECTED);
./Kaleidoscope-Hardware-Virtual/src/Kaleidoscope-Hardware-Virtual.cpp:      handleKeyswitchEvent(Key_NoKey, row, col, keyState);
./Kaleidoscope-Hardware-Virtual/src/Kaleidoscope-Hardware-Virtual.cpp:        handleKeyswitchEvent(Key_NoKey, row, col, keyState);

… and the locations that INJECTED is consumed are:

./Kaleidoscope/src/key_events.cpp:  } else if (keyToggledOff(keyState) && (keyState & INJECTED)) {
./Kaleidoscope/src/key_events.cpp-    kaleidoscope::hid::releaseKey(mappedKey);
--
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp:  if (key_state & INJECTED)
./Kaleidoscope-Cycle/src/Kaleidoscope/Cycle.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-Heatmap/src/Kaleidoscope/Heatmap.cpp:  if (key_state & INJECTED)
./Kaleidoscope-Heatmap/src/Kaleidoscope/Heatmap.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-LED-AlphaSquare/src/Kaleidoscope/AlphaSquare-Effect.cpp:  if (keyState & INJECTED)
./Kaleidoscope-LED-AlphaSquare/src/Kaleidoscope/AlphaSquare-Effect.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-Leader/src/Kaleidoscope/Leader.cpp:  if (keyState & INJECTED)
./Kaleidoscope-Leader/src/Kaleidoscope/Leader.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-OneShot/src/Kaleidoscope/OneShot.cpp:  if (keyState & INJECTED)
./Kaleidoscope-OneShot/src/Kaleidoscope/OneShot.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-Qukeys/src/Kaleidoscope/Qukeys.cpp:  if (row >= ROWS || col >= COLS || (key_state & INJECTED) != 0)
./Kaleidoscope-Qukeys/src/Kaleidoscope/Qukeys.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-Syster/src/Kaleidoscope/Syster.cpp:  if (keyState & INJECTED)
./Kaleidoscope-Syster/src/Kaleidoscope/Syster.cpp-    return EventHandlerResult::OK;
--
./Kaleidoscope-TapDance/src/Kaleidoscope/TapDance.cpp:  if (keyState & INJECTED)
./Kaleidoscope-TapDance/src/Kaleidoscope/TapDance.cpp-    return EventHandlerResult::OK;

where mostly the result of being injected seems to be “getting ignored”.

Is this then mainly used to ignore injected keypresses when one seeks to handle only real ones? This would mean that for all practical purposes, calls to handleKeyswitchEvent should use INJECTED?

I’d also like to know about that one case in key_events.cpp where only the injected key is released when toggled off – why is this line needed?

The primary purpose of INJECTED is to prevent infinite recursion. When you inject a key from a plugin, or macro, or anywhere, via handleKeyswitchEvent, that runs the key event handlers for all plugins. Including the one that injected the key. In most cases, we’d like these to just go through - but not always. This is why we use a flag instead of using kaleidoscope::hid::pressKey() or similar directly. If we didn’t have the flag, there would be cases where you press a key, the firmware injects another, and a plugin triggers an action for both, injecting perhaps a third key, calling handleKeyswitchEvent again, and that new key triggering another key, and so on 'till the end of time. This is what INJECTED is meant to prevent.

It may not be the best interface, mind you. It’s been at the back of my mind to figure out something better, but didn’t get around to it yet.

Keys are normally “released” by not registering them in the next cycle (since we clear the report each cycle). With injected keys, we can have cases where we press and release the same key multiple times in the same cycle. For the release event to work, we release those keys. We can’t clear the report in these cases, so explicit release is the next best thing.

1 Like

The problem with INJECTED being used to prevent infinite loops is that it prevents one plugin from sending an event that a different plugin should handle (if both plugins ignore INJECTED events). Unlike most other plugins, Qukeys was designed to send events with arbitrary Key objects, which could be intended for other plugins (e.g. Macros) to handle. It couldn’t work if it set the INJECTED flag, so it uses a different method — it sets a static boolean to let itself know when it’s sending an event that Qukeys itself should ignore, but which other plugins should not, and clears it after the call to handleKeyswitchEvent() returns.

[Kaleidoglyph has an even better system for this, which doesn’t require plugins to manage that tracking themselves, and also partially solves the problem of plugin-order dependence, but that’s slightly off-topic.]

1 Like

So in the thread about Ctrl+wheel for zooming when you recommended a call to handleKeyswitchEvent but without INJECTED that was intentional, so that MouseKeys should handle it?

I mean, is it recommended to:

  1. normally use INJECTED in calls to handleKeyswitchEvent and omit it only in the case where one specifically wants other plugins to not ignore it as @merlin kindly explained for the Qukeys exception,

or

  1. you normally don’t need to bother with INJECTED and you need to use it only in the case when you specifically need it to be ignored?

And on the side of plugins, likewise should they normally contain an if check to ignore injected keys or only if there will be a problem if they handle them?

(I hope I’m being clear?)

Only plugins that call handleKeyswitchEvent() themselves (directly or indirectly) need to do something to prevent unbounded recursion (from those events). The simplest way to do so is to ignore events generated by the plugin, and the simplest way to accomplish that is to only generate events with the INJECTED flag set, and ignore any events with that flag set.

So, if a plugin never calls handleKeyswitchEvent() from its event handler, it should not be necessary to check for and ignore INJECTED events.

1 Like