diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index ae3963a7..49b21c38 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -7,6 +7,7 @@ If any of this does not make sense to you, or you have trouble updating your .in * [Upgrade notes](#upgrade-notes) + [New features](#new-features) + - [New event handler](#new-event-handler) - [Event-driven main loop](#event-driven-main-loop) - [Keyboard state array](#keyboard-state-array) - [New build system](#new-build-system) @@ -37,6 +38,10 @@ any API we've included in a release. Typically, this means that any code that us ## New features +### New event handler + +One more `KeyEvent` handler has been added: `afterReportingState(const KeyEvent &event)`. This handler gets called after HID reports are sent for an event, providing a point for plugins to act after an event has been fully processed by `Runtime.handleKeyEvent()`. + ### Event-driven main loop Kaleidoscope's main loop has been rewritten. It now responds to key toggle-on and toggle-off events, dealing with one event at a time (and possibly more than one in a given cycle). Instead of sending a keyboard HID report at the end of every scan cycle (and letting the HID module suppress duplicates), it now only sends HID reports in response to input events. diff --git a/docs/api-reference/event-handler-hooks.md b/docs/api-reference/event-handler-hooks.md index b86f0837..707c67ed 100644 --- a/docs/api-reference/event-handler-hooks.md +++ b/docs/api-reference/event-handler-hooks.md @@ -183,6 +183,14 @@ abortable. That is, if it returns a result other than `OK` it will stop the subsequent handlers from getting called, and if it returns `ABORT`, it will also stop the report from being sent.] +### `afterReportingState(const KeyEvent &event)` + +This gets called after the HID report is sent. This handler allows a plugin to +react to an event, but wait until after that event has been fully processed to +do so. For example, the OneShot plugin releases keys that are in the "one-shot" +state in response to key press events, but it does so after those triggering +press events take place. + ## Other events ### `onLayerChange()` diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 46234785..63b6b851 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -321,6 +321,11 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { return EventHandlerResult::OK; } +// ---------------------------------------------------------------------------- +EventHandlerResult OneShot::afterReportingState(const KeyEvent& event) { + return afterEachCycle(); +} + // ---------------------------------------------------------------------------- EventHandlerResult OneShot::afterEachCycle() { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 5ad18b1d..f626c4a7 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -222,6 +222,7 @@ class OneShot : public kaleidoscope::Plugin { EventHandlerResult onNameQuery(); EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult afterReportingState(const KeyEvent &event); EventHandlerResult afterEachCycle(); private: diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index c87df6e1..82b0df83 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -219,6 +219,11 @@ Runtime_::handleKeyEvent(KeyEvent event) { // Finally, send the new keyboard report sendKeyboardReport(event); + + // Now that the report has been sent, let plugins act on it after the fact. + // This is useful for plugins that need to react to an event, but must wait + // until after that event is processed to do so. + Hooks::afterReportingState(event); } // ---------------------------------------------------------------------------- diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index b3765601..11ca03e9 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -264,6 +264,19 @@ class SignatureCheckDummy {}; (const KeyEvent &event), __NL__ \ (event), ##__VA_ARGS__) __NL__ \ __NL__ \ + /* Called after reporting our state to the host. This is the last */ __NL__ \ + /* point at which a plugin can do something in response to an event */ __NL__ \ + /* before the next event is processed, if multiple events occur in */ __NL__ \ + /* and are processed in a single cycle (usually due to delayed */ __NL__ \ + /* events or generated events). */ __NL__ \ + OPERATION(afterReportingState, __NL__ \ + 1, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _NOT_ABORTABLE, __NL__ \ + (),(),(), /* non template */ __NL__ \ + (const KeyEvent &event), __NL__ \ + (event), ##__VA_ARGS__) __NL__ \ + __NL__ \ /* Called at the very end of a cycle, after everything's */ __NL__ \ /* said and done. */ __NL__ \ OPERATION(afterEachCycle, __NL__ \ @@ -351,6 +364,10 @@ class SignatureCheckDummy {}; OP(beforeReportingState, 2) __NL__ \ END(beforeReportingState, 1, 2) __NL__ \ __NL__ \ + START(afterReportingState, 1) __NL__ \ + OP(afterReportingState, 1) __NL__ \ + END(afterReportingState, 1) __NL__ \ + __NL__ \ START(afterEachCycle, 1) __NL__ \ OP(afterEachCycle, 1) __NL__ \ END(afterEachCycle, 1) __NL__ \ diff --git a/tests/issues/423/423.ino b/tests/issues/423/423.ino new file mode 100644 index 00000000..0cf1d584 --- /dev/null +++ b/tests/issues/423/423.ino @@ -0,0 +1,78 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2021 Keyboard.io, Inc. + * + * This program is free software: you can redistribute it and/or modify it under + * the terms of the GNU General Public License as published by the Free Software + * Foundation, version 3. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more + * details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see . + */ + +#include +#include +#include +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_Spacebar, Key_A, ___, ___, ___, ___, ___, + TD(0), OSM(LeftShift), ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +void tapDanceAction(uint8_t tap_dance_index, + KeyAddr key_addr, + uint8_t tap_count, + kaleidoscope::plugin::TapDance::ActionType tap_dance_action) { + switch (tap_dance_index) { + case 0: + return tapDanceActionKeys(tap_count, tap_dance_action, + Key_Period, M(0), LSHIFT(Key_1)); + default: + break; + } +} + +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (keyToggledOn(event.state)) { + switch (macro_id) { + case 0: + Macros.type(PSTR("abc")); + break; + default: + break; + } + } + return MACRO_NONE; +} + +KALEIDOSCOPE_INIT_PLUGINS(Macros, OneShot, TapDance); + +void setup() { + Kaleidoscope.setup(); + TapDance.time_out = 25; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/issues/423/sketch.json b/tests/issues/423/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/423/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/issues/423/test.ktest b/tests/issues/423/test.ktest new file mode 100644 index 00000000..fb5d1075 --- /dev/null +++ b/tests/issues/423/test.ktest @@ -0,0 +1,177 @@ +VERSION 1 + +KEYSWITCH SPACE 0 0 +KEYSWITCH A 0 1 +KEYSWITCH TD_0 1 0 +KEYSWITCH OS_SHIFT 1 1 + +# ============================================================================== +NAME Back and forth + +RUN 4 ms +PRESS TD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle + +RUN 4 ms +PRESS OS_SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_Period # report: { 37 } +EXPECT keyboard-report empty +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE OS_SHIFT +RUN 1 cycle + +RUN 4 ms +PRESS TD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle + +# ------------------------------------------------------------------------------ +# Next, we press `space`, triggering both the resolution of the TapDance key and +# the release of the OneShot key, in that order. +RUN 4 ms +PRESS SPACE +RUN 1 cycle +# First, the TapDance key is resolved, adding `.` to the report. This event also +# triggers the release of the OneShot key, which shouldn't happen until after +# the `.` press is processed. +EXPECT keyboard-report Key_LeftShift Key_Period # report: { 37 e1 } +# Now the OneShot key is released, removing `shift` from the report. +EXPECT keyboard-report Key_Period # report: { 37 } +# The TapDance `.` key has been released, so its release comes next. +EXPECT keyboard-report empty # report: { } +# Finally, we get the report for the press of the `space` key. +EXPECT keyboard-report Key_Spacebar # report: { 2c } + +RUN 4 ms +RELEASE SPACE +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 4 ms +PRESS OS_SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE OS_SHIFT +RUN 1 cycle + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 } +EXPECT keyboard-report Key_A # report: { 4 } + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME Single rollover + +RUN 4 ms +PRESS TD_0 +RUN 1 cycle + +RUN 4 ms +PRESS SPACE +RUN 1 cycle +EXPECT keyboard-report Key_Period # report: { 37 } +EXPECT keyboard-report Key_Period Key_Spacebar # report: { 37 2c } + +RUN 4 ms +PRESS OS_SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_Period Key_Spacebar Key_LeftShift # report: { 2c 37 e1 } + +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle +EXPECT keyboard-report Key_Spacebar Key_LeftShift # report: { 2c e1 } + +RUN 4 ms +RELEASE SPACE +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 } + +RUN 4 ms +RELEASE OS_SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_A # report: { 4 } + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME OSM applies to whole Macro + +RUN 4 ms +PRESS OS_SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE OS_SHIFT +RUN 1 cycle + +RUN 4 ms +PRESS TD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle + +RUN 4 ms +PRESS TD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle + +# ------------------------------------------------------------------------------ +# Next, we press `space`, triggering both the resolution of the TapDance key and +# the release of the OneShot key, in that order. +RUN 4 ms +PRESS SPACE +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # report: { 4 e1 } +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_B # report: { 5 e1 } +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_C # report: { 6 e1 } +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report empty # report: { } +EXPECT keyboard-report Key_Spacebar # report: { 2c } + +RUN 4 ms +RELEASE SPACE +RUN 1 cycle +EXPECT keyboard-report empty + + + +RUN 5 ms