From b213fb7677b35244505ef8af9805c66af0204a39 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 23 Nov 2020 14:51:09 -0600 Subject: [PATCH 001/108] Add to basic TapDance testcases This adds a testcase for rollover from a TapDance key to the key that interrupts the sequence, and a testcase for a TapDance key that times out while held. I also adjusted the timing of the existing testcases to match the new version of TapDance. Signed-off-by: Michael Richters --- tests/plugins/TapDance/basic/test.ktest | 80 ++++++++++++------------- 1 file changed, 39 insertions(+), 41 deletions(-) diff --git a/tests/plugins/TapDance/basic/test.ktest b/tests/plugins/TapDance/basic/test.ktest index 64d90c94..24d41faa 100644 --- a/tests/plugins/TapDance/basic/test.ktest +++ b/tests/plugins/TapDance/basic/test.ktest @@ -20,8 +20,8 @@ RUN 5 ms PRESS X RUN 1 cycle EXPECT keyboard-report Key_B # The report should contain `B` -EXPECT keyboard-report empty # Report should be empty RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty EXPECT keyboard-report Key_X # Report should contain `X` RUN 5 ms @@ -47,59 +47,57 @@ RUN 20 ms # Timeout = 25ms RUN 2 ms # Extra 2 cycles for some reason EXPECT keyboard-report Key_B # The report should contain `B` +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty # ============================================================================== -# The testcases below are commented out because they are currently failing. +NAME Tapdance interrupt with rollover -# # ============================================================================== -# NAME Tapdance interrupt with rollover - -# RUN 5 ms -# PRESS TD_0 -# RUN 5 ms -# RELEASE TD_0 +RUN 5 ms +PRESS TD_0 +RUN 5 ms +RELEASE TD_0 -# RUN 10 ms -# PRESS TD_0 +RUN 10 ms +PRESS TD_0 -# RUN 5 ms -# PRESS X -# RUN 1 cycle -# EXPECT keyboard-report Key_B # The report should contain `B` -# RUN 1 cycle -# EXPECT keyboard-report Key_B Key_X # Report should contain `B` & `X` +RUN 5 ms +PRESS X +RUN 1 cycle +EXPECT keyboard-report Key_B # The report should contain `B` +RUN 1 cycle +EXPECT keyboard-report Key_B Key_X # Report should contain `B` & `X` -# RUN 5 ms -# RELEASE TD_0 -# RUN 1 cycle -# EXPECT keyboard-report Key_X # Report should contain `X` +RUN 5 ms +RELEASE TD_0 +RUN 1 cycle +EXPECT keyboard-report Key_X # Report should contain `X` -# RUN 5 ms -# RELEASE X -# RUN 1 cycle -# EXPECT keyboard-report empty # Report should be empty +RUN 5 ms +RELEASE X +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty -# # ============================================================================== -# NAME Tapdance timeout while held +# ============================================================================== +NAME Tapdance timeout while held -# RUN 5 ms +RUN 5 ms -# PRESS TD_0 -# RUN 5 ms -# RELEASE TD_0 -# RUN 10 ms +PRESS TD_0 +RUN 5 ms +RELEASE TD_0 +RUN 10 ms -# PRESS TD_0 -# RUN 1 cycle -# RUN 25 ms +PRESS TD_0 +RUN 1 cycle +RUN 25 ms -# RUN 2 cycles -# EXPECT keyboard-report Key_B # The report should contain `B` +RUN 2 cycles +EXPECT keyboard-report Key_B # The report should contain `B` -# RUN 10 ms -# RELEASE TD_0 -# RUN 1 cycle +RUN 10 ms +RELEASE TD_0 +RUN 1 cycle -# EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report empty # Report should be empty From a15f4e6d6acee06fcee4db4ad6f64e127235dd9c Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 18 Nov 2020 20:35:24 -0600 Subject: [PATCH 002/108] Add testcase for TapDance issue #806 Signed-off-by: Michael Richters --- tests/issues/806/806.ino | 85 ++++++++++++++++++++++++++++++++++++ tests/issues/806/common.h | 27 ++++++++++++ tests/issues/806/sketch.json | 6 +++ tests/issues/806/test.ktest | 24 ++++++++++ 4 files changed, 142 insertions(+) create mode 100644 tests/issues/806/806.ino create mode 100644 tests/issues/806/common.h create mode 100644 tests/issues/806/sketch.json create mode 100644 tests/issues/806/test.ktest diff --git a/tests/issues/806/806.ino b/tests/issues/806/806.ino new file mode 100644 index 00000000..2d51e094 --- /dev/null +++ b/tests/issues/806/806.ino @@ -0,0 +1,85 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +#undef min +#undef max +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_X, ___, ___, ___, ___, ___, ___, + TD(0), ___, ___, ___, ___, ___, ___, + LockLayer(1), ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), + [1] = KEYMAP_STACKED + ( + Key_Y, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *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_A, LockLayer(1)); + default: + break; + } +} + +KALEIDOSCOPE_INIT_PLUGINS(TapDance); + +void setup() { + Kaleidoscope.setup(); + TapDance.time_out = 25; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/issues/806/common.h b/tests/issues/806/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/issues/806/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/issues/806/sketch.json b/tests/issues/806/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/806/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/issues/806/test.ktest b/tests/issues/806/test.ktest new file mode 100644 index 00000000..8beda100 --- /dev/null +++ b/tests/issues/806/test.ktest @@ -0,0 +1,24 @@ +VERSION 1 + +KEYSWITCH X 0 0 +KEYSWITCH TD_0 1 0 +KEYSWITCH LL_1 2 0 + +# ============================================================================== +NAME TapDance hold past timeout + +RUN 5 ms + +PRESS TD_0 +RUN 1 cycle +RUN 25 ms # timeout is 25 ms + +RUN 2 cycles +EXPECT keyboard-report Key_A # The report should contain only `A` + +RUN 20 ms + +RELEASE TD_0 +RUN 1 cycle + +EXPECT keyboard-report empty # The report should be empty From dbf46d83e0157ee30e9c0836d93e4ffe913fdf17 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 18 Nov 2020 23:00:46 -0600 Subject: [PATCH 003/108] Add testcase for TapDance issue #922 Signed-off-by: Michael Richters --- tests/issues/922/922.ino | 72 +++++++++++++++++++++ tests/issues/922/common.h | 27 ++++++++ tests/issues/922/sketch.json | 6 ++ tests/issues/922/test.ktest | 121 +++++++++++++++++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 tests/issues/922/922.ino create mode 100644 tests/issues/922/common.h create mode 100644 tests/issues/922/sketch.json create mode 100644 tests/issues/922/test.ktest diff --git a/tests/issues/922/922.ino b/tests/issues/922/922.ino new file mode 100644 index 00000000..bb3eaaa4 --- /dev/null +++ b/tests/issues/922/922.ino @@ -0,0 +1,72 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +#undef min +#undef max +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + TD(0), TD(1), ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *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_A, Key_X); + case 1: + return tapDanceActionKeys(tap_count, tap_dance_action, + Key_B, Key_Y); + default: + break; + } +} + +KALEIDOSCOPE_INIT_PLUGINS(TapDance); + +void setup() { + Kaleidoscope.setup(); + TapDance.time_out = 25; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/issues/922/common.h b/tests/issues/922/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/issues/922/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/issues/922/sketch.json b/tests/issues/922/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/922/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/issues/922/test.ktest b/tests/issues/922/test.ktest new file mode 100644 index 00000000..ce7c683c --- /dev/null +++ b/tests/issues/922/test.ktest @@ -0,0 +1,121 @@ +VERSION 1 + +KEYSWITCH TD_0 0 0 +KEYSWITCH TD_1 0 1 + +# ============================================================================== +NAME TapDance to TapDance rollover left to right + +RUN 5 ms +PRESS TD_0 +RUN 5 ms +PRESS TD_1 +RUN 1 cycle +EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_0 release +RUN 4 ms +RELEASE TD_1 +RUN 18 ms +EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 11 ms + +RUN 5 ms +PRESS TD_0 +RUN 5 ms +PRESS TD_1 +RUN 1 cycle +EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_0 release +RUN 4 ms +RELEASE TD_1 +RUN 18 ms +EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 11 ms + +# ============================================================================== +NAME TapDance to TapDance rollover right to left + +RUN 5 ms +PRESS TD_1 +RUN 5 ms +PRESS TD_0 +RUN 1 cycle +EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` +RUN 4 ms +RELEASE TD_1 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_1 release +RUN 4 ms +RELEASE TD_0 +RUN 18 ms +EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_0 timeout +RUN 11 ms + +RUN 5 ms +PRESS TD_1 +RUN 5 ms +PRESS TD_0 +RUN 1 cycle +EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` +RUN 4 ms +RELEASE TD_1 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_1 release +RUN 4 ms +RELEASE TD_0 +RUN 18 ms +EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_0 timeout +RUN 11 ms + +# ============================================================================== +NAME TapDance to TapDance rollover back and forth + +RUN 5 ms +PRESS TD_0 +RUN 5 ms +PRESS TD_1 +RUN 1 cycle +EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` +RUN 4 ms +RELEASE TD_0 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_0 release +RUN 4 ms +RELEASE TD_1 +RUN 18 ms +EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 11 ms + +RUN 5 ms +PRESS TD_1 +RUN 5 ms +PRESS TD_0 +RUN 1 cycle +EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` +RUN 4 ms +RELEASE TD_1 +RUN 1 cycle +EXPECT keyboard-report empty # Empty report on TD_1 release +RUN 4 ms +RELEASE TD_0 +RUN 18 ms +EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 1 cycle +EXPECT keyboard-report empty # Empty report after TD_0 timeout +RUN 11 ms From 8b01f51c247825c22b60ca1a4178490967da5008 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 17 Nov 2020 17:50:23 -0600 Subject: [PATCH 004/108] Add testcase for TapDance issue #980 Signed-off-by: Michael Richters --- tests/issues/980/980.ino | 85 ++++++++++++++++++++++++++++++++++++ tests/issues/980/common.h | 27 ++++++++++++ tests/issues/980/sketch.json | 6 +++ tests/issues/980/test.ktest | 69 +++++++++++++++++++++++++++++ 4 files changed, 187 insertions(+) create mode 100644 tests/issues/980/980.ino create mode 100644 tests/issues/980/common.h create mode 100644 tests/issues/980/sketch.json create mode 100644 tests/issues/980/test.ktest diff --git a/tests/issues/980/980.ino b/tests/issues/980/980.ino new file mode 100644 index 00000000..2d51e094 --- /dev/null +++ b/tests/issues/980/980.ino @@ -0,0 +1,85 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +#undef min +#undef max +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_X, ___, ___, ___, ___, ___, ___, + TD(0), ___, ___, ___, ___, ___, ___, + LockLayer(1), ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), + [1] = KEYMAP_STACKED + ( + Key_Y, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *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_A, LockLayer(1)); + default: + break; + } +} + +KALEIDOSCOPE_INIT_PLUGINS(TapDance); + +void setup() { + Kaleidoscope.setup(); + TapDance.time_out = 25; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/issues/980/common.h b/tests/issues/980/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/issues/980/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/issues/980/sketch.json b/tests/issues/980/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/980/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/issues/980/test.ktest b/tests/issues/980/test.ktest new file mode 100644 index 00000000..d695aa5a --- /dev/null +++ b/tests/issues/980/test.ktest @@ -0,0 +1,69 @@ +VERSION 1 + +KEYSWITCH X 0 0 +KEYSWITCH TD_0 1 0 +KEYSWITCH LL_1 2 0 + +# ============================================================================== +NAME TapDance issue 980 no overlap + +RUN 5 ms + +PRESS TD_0 +RUN 5 ms +RELEASE TD_0 +RUN 10 ms + +PRESS TD_0 +RUN 5 ms + +RELEASE TD_0 +RUN 5 ms + +PRESS X +RUN 2 cycles +EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X) +RUN 5 ms + +RELEASE X +RUN 1 cycle +EXPECT keyboard-report empty # The report should be empty +RUN 5 ms + +PRESS LL_1 +RUN 1 cycle + +RELEASE LL_1 +RUN 1 cycle + +# ============================================================================== +NAME TapDance issue 980 rollover + +RUN 5 ms + +PRESS TD_0 +RUN 5 ms +RELEASE TD_0 +RUN 10 ms + +PRESS TD_0 +RUN 5 ms + +PRESS X +RUN 2 cycles +EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X) +RUN 5 ms + +RELEASE TD_0 +RUN 5 ms + +RELEASE X +RUN 1 cycle +EXPECT keyboard-report empty # The report should be empty +RUN 5 ms + +PRESS LL_1 +RUN 1 cycle + +RELEASE LL_1 +RUN 1 cycle From ffeb137082aebcc62ff7e0f3caca4e4f86733225 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 14 Nov 2020 10:16:24 -0600 Subject: [PATCH 005/108] Add testcase for the keyboard state array This testcase checks to make sure that the keyboard state array gets cleared by `handleKeyswitchEvent()` when a plugin returns `EVENT_CONSUMED`. Signed-off-by: Michael Richters --- .../keyboard-state/release-cleared/common.h | 27 +++++ .../release-cleared/release-cleared.ino | 104 ++++++++++++++++++ .../release-cleared/sketch.json | 6 + .../keyboard-state/release-cleared/test.ktest | 59 ++++++++++ 4 files changed, 196 insertions(+) create mode 100644 tests/features/events/keyboard-state/release-cleared/common.h create mode 100644 tests/features/events/keyboard-state/release-cleared/release-cleared.ino create mode 100644 tests/features/events/keyboard-state/release-cleared/sketch.json create mode 100644 tests/features/events/keyboard-state/release-cleared/test.ktest diff --git a/tests/features/events/keyboard-state/release-cleared/common.h b/tests/features/events/keyboard-state/release-cleared/common.h new file mode 100644 index 00000000..3ab385ca --- /dev/null +++ b/tests/features/events/keyboard-state/release-cleared/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/features/events/keyboard-state/release-cleared/release-cleared.ino b/tests/features/events/keyboard-state/release-cleared/release-cleared.ino new file mode 100644 index 00000000..b3d4c0c5 --- /dev/null +++ b/tests/features/events/keyboard-state/release-cleared/release-cleared.ino @@ -0,0 +1,104 @@ +/* -*- 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 "./common.h" + +#undef min +#undef max +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_A, ___, ___, ___, ___, ___, ___, + ShiftToLayer(1), ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), + [1] = KEYMAP_STACKED + ( + Key_X, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +namespace kaleidoscope { +namespace plugin { + +class ConvertXtoY : public kaleidoscope::Plugin { + public: + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) { + if (keyToggledOn(key_state)) { + if (key == Key_X) + key = Key_Y; + } + // It should be impossible to get a `Key_X` at this point, because the + // previous block changes any `Key_X` to `Key_Y`, which results in the + // active keys cache storing that value. On subsequent cycles (while the key + // is still pressed), the `key` value should be `Key_Y`. + if (keyIsPressed(key_state) && key == Key_X) { + std::cerr << "t=" << Runtime.millisAtCycleStart() << ": " + << "Error: we shouldn't see a key with value `X`" << std::endl; + } + // When `Key_Y` toggles off, return `EVENT_CONSUMED`. Despite this, the + // active keys cache entry should be cleared. If it's not, then a subsequent + // press of the same key without the layer shift in effect will still result + // in `Key_Y` instead of `Key_A`. + if (keyToggledOff(key_state) && (key == Key_Y)) { + return EventHandlerResult::EVENT_CONSUMED; + } + return EventHandlerResult::OK; + } +}; + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::ConvertXtoY ConvertXtoY; + +KALEIDOSCOPE_INIT_PLUGINS(ConvertXtoY); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/features/events/keyboard-state/release-cleared/sketch.json b/tests/features/events/keyboard-state/release-cleared/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/features/events/keyboard-state/release-cleared/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/features/events/keyboard-state/release-cleared/test.ktest b/tests/features/events/keyboard-state/release-cleared/test.ktest new file mode 100644 index 00000000..d44f6d4c --- /dev/null +++ b/tests/features/events/keyboard-state/release-cleared/test.ktest @@ -0,0 +1,59 @@ +VERSION 1 + +KEYSWITCH A 0 0 +KEYSWITCH LAYER_SHIFT 1 0 + +# ============================================================================== +# Keyboard state array +NAME Keyboard state cleared + +RUN 10 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` + +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# Press and hold `ShiftToLayer(1)`, changing the `A` key to `X` +PRESS LAYER_SHIFT +RUN 5 ms + +# Press `X`, which gets converted to `Y` by the `ConvertXtoY` plugin defined in +# the sketch. The plugin simply changes the value of the key, which causes it to +# get set to `Key_Y` in the keyboard state array. +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_Y # Report should contain only `Y` + +RUN 5 ms + +# Release the `X` key (on Layer 1), which has become a `Y` key in the keyboard +# state array. This should clear its entry. +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# Release `ShiftToLayer(1)`. This should restore the `A` key to its base layer +# value on subsequent presses, unless the Macros key gets "stuck" in the +# keyboard state array because it returns `EVENT_CONSUMED`. +RELEASE LAYER_SHIFT +RUN 5 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` + +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty From e5e05445406e0daa3a62662619ab6bd979d59cf1 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 14 Nov 2020 17:25:53 -0600 Subject: [PATCH 006/108] Add testcase for keyboard state array using Macros Signed-off-by: Michael Richters --- .../events/keyboard-state/macros/common.h | 27 ++++++ .../events/keyboard-state/macros/macros.ino | 82 +++++++++++++++++++ .../events/keyboard-state/macros/sketch.json | 6 ++ .../events/keyboard-state/macros/test.ktest | 51 ++++++++++++ 4 files changed, 166 insertions(+) create mode 100644 tests/features/events/keyboard-state/macros/common.h create mode 100644 tests/features/events/keyboard-state/macros/macros.ino create mode 100644 tests/features/events/keyboard-state/macros/sketch.json create mode 100644 tests/features/events/keyboard-state/macros/test.ktest diff --git a/tests/features/events/keyboard-state/macros/common.h b/tests/features/events/keyboard-state/macros/common.h new file mode 100644 index 00000000..3ab385ca --- /dev/null +++ b/tests/features/events/keyboard-state/macros/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/features/events/keyboard-state/macros/macros.ino b/tests/features/events/keyboard-state/macros/macros.ino new file mode 100644 index 00000000..5f98bf6b --- /dev/null +++ b/tests/features/events/keyboard-state/macros/macros.ino @@ -0,0 +1,82 @@ +/* -*- 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 "./common.h" + +#undef min +#undef max +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_A, ___, ___, ___, ___, ___, ___, + ShiftToLayer(1), ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), + [1] = KEYMAP_STACKED + ( + M(0), ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +const macro_t *macroAction(uint8_t index, uint8_t key_state) { + if (keyToggledOn(key_state)) { + switch (index) { + case 0: + Kaleidoscope.hid().keyboard().pressKey(Key_Y); + break; + } + } + return MACRO_NONE; +} + +KALEIDOSCOPE_INIT_PLUGINS(Macros); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/features/events/keyboard-state/macros/sketch.json b/tests/features/events/keyboard-state/macros/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/features/events/keyboard-state/macros/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/features/events/keyboard-state/macros/test.ktest b/tests/features/events/keyboard-state/macros/test.ktest new file mode 100644 index 00000000..2a8754e7 --- /dev/null +++ b/tests/features/events/keyboard-state/macros/test.ktest @@ -0,0 +1,51 @@ +VERSION 1 + +KEYSWITCH A 0 0 +KEYSWITCH LAYER_SHIFT 1 0 + +# ============================================================================== +# Keyboard state array +NAME Keyboard state array cleared + +RUN 10 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` + +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# Press and hold `ShiftToLayer(1)`, changing the `A` key to `X` +PRESS LAYER_SHIFT +RUN 5 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_Y # Report should contain only `Y` +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +RELEASE A + +RUN 5 ms + +RELEASE LAYER_SHIFT +RUN 5 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` + +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty From 9dd9c9557cdf26fe2a5e52322dafcc1f65aca45b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 13 Nov 2020 15:13:05 -0600 Subject: [PATCH 007/108] Abort hook functions on any result other than `OK` Instead of only aborting hook functions if a handler returns `EVENT_CONSUMED`, only continue abortable hooks if a handler returns `OK`. For existing core plugins, this shouldn't make any difference because none of them use the `ERROR` return value. Also rename `shouldAbortOnConsumedEvent` to better match the new conditional. Signed-off-by: Michael Richters --- src/kaleidoscope/event_handlers.h | 6 +++--- src/kaleidoscope/hooks.cpp | 2 +- src/kaleidoscope/hooks.h | 2 +- src/kaleidoscope_internal/event_dispatch.h | 10 +++++----- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index 29b73d29..535d4e61 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -27,7 +27,7 @@ // OPERATION once for each event handler defined herein with the following // parameters: // -// HOOK_NAME, SHOULD_ABORT_ON_CONSUMED_EVENT, SIGNATURE, ARGS_LIST, ... +// HOOK_NAME, SHOULD_EXIT_IF_RESULT_NOT_OK, SIGNATURE, ARGS_LIST, ... // // Any additional parameters that are added to an invokation // of _FOR_EACH_EVENT_HANDLER are passed through to OP. @@ -72,8 +72,8 @@ // Plugins' event handlers are called in the same order as the plugins // are passed to the KALEIDOSCOPE_INIT_PLUGINS(...) macro within the // sketch. For some handlers it is desirable to not call subsequent -// plugins' handlers once a plugin's handler returned the value -// kaleidoscope::EventHandlerResult::EVENT_CONSUMED. To enable this +// plugins' handlers once a plugin's handler returned a value other +// than kaleidoscope::EventHandlerResult::OK. To enable this, // pass the abortable flag value _ABORTABLE, _NOT_ABORTABLE otherwise. // // template parameter type list: diff --git a/src/kaleidoscope/hooks.cpp b/src/kaleidoscope/hooks.cpp index 015837f5..c99a0032 100644 --- a/src/kaleidoscope/hooks.cpp +++ b/src/kaleidoscope/hooks.cpp @@ -32,7 +32,7 @@ namespace kaleidoscope { #define INSTANTIATE_WEAK_HOOK_FUNCTION( \ HOOK_NAME, HOOK_VERSION, DEPRECATION_TAG, \ - SHOULD_ABORT_ON_CONSUMED_EVENT, \ + SHOULD_EXIT_IF_RESULT_NOT_OK, \ TMPL_PARAM_TYPE_LIST, TMPL_PARAM_LIST, TMPL_DUMMY_ARGS_LIST, \ SIGNATURE, ARGS_LIST) __NL__ \ __NL__ \ diff --git a/src/kaleidoscope/hooks.h b/src/kaleidoscope/hooks.h index abd6f682..d59ffccf 100644 --- a/src/kaleidoscope/hooks.h +++ b/src/kaleidoscope/hooks.h @@ -78,7 +78,7 @@ class Hooks { #define DEFINE_WEAK_HOOK_FUNCTION( \ HOOK_NAME, HOOK_VERSION, DEPRECATION_TAG, \ - SHOULD_ABORT_ON_CONSUMED_EVENT, \ + SHOULD_EXIT_IF_RESULT_NOT_OK, \ TMPL_PARAM_TYPE_LIST, TMPL_PARAM_LIST, TMPL_DUMMY_ARGS_LIST, \ SIGNATURE, ARGS_LIST) __NL__ \ __NL__ \ diff --git a/src/kaleidoscope_internal/event_dispatch.h b/src/kaleidoscope_internal/event_dispatch.h index 5211e86d..747dc270 100644 --- a/src/kaleidoscope_internal/event_dispatch.h +++ b/src/kaleidoscope_internal/event_dispatch.h @@ -80,7 +80,7 @@ #define _REGISTER_EVENT_HANDLER( \ HOOK_NAME, HOOK_VERSION, DEPRECATION_TAG, \ - SHOULD_ABORT_ON_CONSUMED_EVENT, \ + SHOULD_EXIT_IF_RESULT_NOT_OK, \ TMPL_PARAM_TYPE_LIST, TMPL_PARAM_LIST, TMPL_DUMMY_ARGS_LIST, \ SIGNATURE, ARGS_LIST) __NL__ \ __NL__ \ @@ -116,8 +116,8 @@ MAKE_TEMPLATE_SIGNATURE(UNWRAP TMPL_PARAM_TYPE_LIST) __NL__ \ struct _NAME4(EventHandler_, HOOK_NAME, _v, HOOK_VERSION) { __NL__ \ __NL__ \ - static bool shouldAbortOnConsumedEvent() { __NL__ \ - return SHOULD_ABORT_ON_CONSUMED_EVENT; __NL__ \ + static bool shouldExitIfResultNotOk() { __NL__ \ + return SHOULD_EXIT_IF_RESULT_NOT_OK; __NL__ \ } __NL__ \ __NL__ \ template Date: Fri, 13 Nov 2020 16:15:47 -0600 Subject: [PATCH 008/108] Add `EventHandlerResult::ABORT` This will allow plugin handlers to send one of three different signals to the calling hook functions, with three different interpretations: `OK`: Continue calling the next handler. `EVENT_CONSUMED`: Don't proceed to the next handler, but signal to the hook function's caller that an event was handled successfully. `ABORT`: Stop processing, and signal to the hook function's caller that the event should be ignored. Signed-off-by: Michael Richters --- src/kaleidoscope/event_handler_result.h | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/kaleidoscope/event_handler_result.h b/src/kaleidoscope/event_handler_result.h index 9b5d6caf..f22bffee 100644 --- a/src/kaleidoscope/event_handler_result.h +++ b/src/kaleidoscope/event_handler_result.h @@ -18,9 +18,33 @@ namespace kaleidoscope { +// This is the set of return values for event handlers. Event handlers for +// plugins are called in sequence by the corresponding hook function, in plugin +// initialization order. The interpretation of these return values can vary +// based on the needs of the hook function, but should be as follows: +// +// - OK: Continue processing the event. The calling hook function should +// continue calling next event handler in the sequence. If all event +// handlers return `OK`, finish processing the event. +// +// - EVENT_CONSUMED: Stop processing event handlers. The calling hook function +// should not call any further handlers, but may continue to take some +// actions to finish processing the event. This should be used to indicate +// that the event has been successfully handled. +// +// - ABORT: Ignore the event. The calling hook function should not call any +// further handlers, and should treat the event as if it didn't +// happen. This should be used by plugin handlers that need to either +// suppress an event or queue the event in order to delay it. +// +// - ERROR: Undefined error. The calling hook function should not call any +// further handlers. There is currently no specification for what should +// happen if this is returned. + enum class EventHandlerResult { OK, EVENT_CONSUMED, + ABORT, ERROR, }; From 3c2175b062e6ac4e62c80637278542af4d4e6bba Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 25 May 2021 14:19:00 -0500 Subject: [PATCH 009/108] Add aliases `Key_Unbound` & `Key_Masked` Signed-off-by: Michael Richters --- src/kaleidoscope/key_defs.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/kaleidoscope/key_defs.h b/src/kaleidoscope/key_defs.h index 5c7c6893..b8ba7363 100644 --- a/src/kaleidoscope/key_defs.h +++ b/src/kaleidoscope/key_defs.h @@ -285,6 +285,11 @@ typedef kaleidoscope::Key Key_; #define ___ Key_Transparent #define XXX Key_NoKey +// For entries in the `live_keys[]` array for inactive keys and masked keys, +// respectively: +#define Key_Inactive Key_Transparent +#define Key_Masked Key_NoKey + #define KEY_BACKLIGHT_DOWN 0xf1 #define KEY_BACKLIGHT_UP 0xf2 #define Key_BacklightDown Key(KEY_BACKLIGHT_DOWN, KEY_FLAGS) From 4a63fe144021437ccc0868276581ef781307a66a Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 00:06:54 -0500 Subject: [PATCH 010/108] Replace the keymap cache with a keyboard state array This replaces the `Layer.live_composite_keymap_[]` cache with a representation of the keyboard's current state as an array of `Key` objects, one per key on the keyboard. In this new array, an idle key will have the value `Key_Transparent`, and a pressed key will have the value of whatever key it's currently mapped to in the keymap (or whatever value the active set of plugins has assigned to it). A value of `Key_NoKey` will mask that key until it is released. If a plugin returns `ABORT` from its `onKeyswitchEvent()` handler, that means that the keymap cache should not be updated. It's especially important to have this occur after plugins like OneShot and Qukeys, where the key can stay active (or become active) after the physical keyswitch has been released. Signed-off-by: Michael Richters --- src/kaleidoscope/KeyAddrMap.h | 98 +++++++++++++++++++++ src/kaleidoscope/KeyMap.h | 25 ++++++ src/kaleidoscope/LiveKeys.cpp | 23 +++++ src/kaleidoscope/LiveKeys.h | 105 +++++++++++++++++++++++ src/kaleidoscope/Runtime.cpp | 4 + src/kaleidoscope/key_defs.h | 52 +++++------ src/kaleidoscope/key_events.cpp | 48 +++++++---- src/kaleidoscope/layers.cpp | 34 ++++---- src/kaleidoscope/layers.h | 16 +++- src/kaleidoscope_internal/deprecations.h | 7 ++ 10 files changed, 348 insertions(+), 64 deletions(-) create mode 100644 src/kaleidoscope/KeyAddrMap.h create mode 100644 src/kaleidoscope/KeyMap.h create mode 100644 src/kaleidoscope/LiveKeys.cpp create mode 100644 src/kaleidoscope/LiveKeys.h diff --git a/src/kaleidoscope/KeyAddrMap.h b/src/kaleidoscope/KeyAddrMap.h new file mode 100644 index 00000000..6ef7c0c3 --- /dev/null +++ b/src/kaleidoscope/KeyAddrMap.h @@ -0,0 +1,98 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-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 . + */ + +#pragma once + +#include "kaleidoscope_internal/device.h" +#include "kaleidoscope/KeyAddr.h" +#include "kaleidoscope/key_defs.h" + +namespace kaleidoscope { + +// A `KeyAddrMap` is a collection of objects, indexed by `KeyAddr`, with one +// entry per key on the keyboard. + +template +class KeyAddrMap { + + private: + _ContentType values_[_size]; // NOLINT(runtime/arrays) + + public: + typedef KeyAddrMap<_ContentType, _size> ThisType; + + // Return the number of `Key` entries in the array + static constexpr uint8_t size() { + return _size; + } + + // To set the value of an entry: + // key_array[key_addr] = Key_X; + _ContentType& operator[](KeyAddr key_addr) { + return values_[key_addr.toInt()]; + } + + // To get the value of an entry: + // Key key = key_array[key_addr]; + const _ContentType& operator[](KeyAddr key_addr) const { + return values_[key_addr.toInt()]; + } + + // --------------------------------------------------------------------------- + // The following code defines an iterator class for a class `KeyMap`, such + // that we can write the following code to get each entry in the array: + // + // typedef KeyAddrMap KeyMap; + // + // for (Key key : key_map) {...} + // + // Or, if we need write access to the entries in the array: + // + // for (Key &key : key_map) {...} + private: + class Iterator; + friend class ThisType::Iterator; + + public: + Iterator begin() { + return {*this, KeyAddr(uint8_t(0))}; + } + Iterator end() { + return {*this, KeyAddr(_size)}; + } + + private: + class Iterator { + public: + Iterator(ThisType &map, KeyAddr key_addr) + : map_(map), key_addr_(key_addr) {} + bool operator!=(const Iterator &other) const { + return key_addr_ != other.key_addr_; + } + _ContentType& operator*() const { + return map_[key_addr_]; + } + Iterator& operator++() { + ++key_addr_; + return *this; + } + private: + ThisType &map_; + KeyAddr key_addr_; + }; +}; + +} // namespace kaleidoscope diff --git a/src/kaleidoscope/KeyMap.h b/src/kaleidoscope/KeyMap.h new file mode 100644 index 00000000..bee7696d --- /dev/null +++ b/src/kaleidoscope/KeyMap.h @@ -0,0 +1,25 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-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 . + */ + +#pragma once + +#include "kaleidoscope/KeyAddrMap.h" + +namespace kaleidoscope { + +typedef KeyAddrMap KeyMap; + +} // namespace kaleidoscope diff --git a/src/kaleidoscope/LiveKeys.cpp b/src/kaleidoscope/LiveKeys.cpp new file mode 100644 index 00000000..1c66d1b9 --- /dev/null +++ b/src/kaleidoscope/LiveKeys.cpp @@ -0,0 +1,23 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-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 "kaleidoscope/LiveKeys.h" + +namespace kaleidoscope { + +LiveKeys live_keys; + +} // namespace kaleidoscope diff --git a/src/kaleidoscope/LiveKeys.h b/src/kaleidoscope/LiveKeys.h new file mode 100644 index 00000000..bccc549c --- /dev/null +++ b/src/kaleidoscope/LiveKeys.h @@ -0,0 +1,105 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2013-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 . + */ + +#pragma once + +#include "kaleidoscope/key_defs.h" // for Key, Key_NoKey, Key_Transparent +#include "kaleidoscope/KeyAddr.h" // for KeyAddr +#include "kaleidoscope/KeyMap.h" // for KeyMap + +namespace kaleidoscope { + +/// A representation of the "live" state of the keys on the keyboard +/// +/// This is structure of `Key` values, indexed by `KeyAddr` values, with one +/// entry per key on the keyboard. These entries are meant to represent the +/// current state of the keys, with two special values: +/// +/// - `Key_Inactive` indicates that the given key is not active. Usually this +/// will correspond to the keyswitch being off (not pressed). +/// +/// - `Key_Masked` indicates that the given key has been masked. When a key +/// release event is processed for that key, it will be reset to +/// `Key_Inactive`. +/// +/// Any other value indicates that the key is active (i.e. pressed, though +/// plugins can set entries to active even when the physical keyswitches are not +/// engaged), and the `Key` value is what the that key is "sending" at the +/// time. At the end of its processing of a `KeyEvent`, Kaleidoscope will use +/// the contents of this array to populate the Keyboard HID reports. + +class LiveKeys { + public: + // For array-style subscript addressing of entries in a read-only context: + const Key& operator[](KeyAddr key_addr) const { + if (key_addr.isValid()) { + return key_map_[key_addr]; + } + dummy_ = Key_Masked; + return dummy_; + } + + // For array-style subscript addressing of entries by reference. The client + // code can alter values in the array this way. + Key& operator[](KeyAddr key_addr) { + if (key_addr.isValid()) { + return key_map_[key_addr]; + } + dummy_ = Key_Masked; + return dummy_; + } + + /// Set an entry to "active" with a specified `Key` value. + void activate(KeyAddr key_addr, Key key) { + if (key_addr.isValid()) + key_map_[key_addr] = key; + } + + /// Deactivate an entry by setting its value to `Key_Inactive`. + void clear(KeyAddr key_addr) { + if (key_addr.isValid()) + key_map_[key_addr] = Key_Inactive; + } + + /// Mask a key by setting its entry to `Key_Masked`. The key will become + /// unmasked by Kaleidoscope on release (but not on a key press event). + void mask(KeyAddr key_addr) { + if (key_addr.isValid()) + key_map_[key_addr] = Key_Masked; + } + + /// Clear the entire array by setting all values to `Key_Inactive`. + void clear() { + for (Key &key : key_map_) { + key = Key_Inactive; + } + } + + /// Returns an iterator for use in range-based for loops: + /// + /// for (Key key : live_keys.all()) {...} + KeyMap& all() { + return key_map_; + } + + private: + KeyMap key_map_; + mutable Key dummy_{0, 0}; +}; + +extern LiveKeys live_keys; + +} // namespace kaleidoscope diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index 380ec63a..ec353aef 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -15,6 +15,7 @@ */ #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/layers.h" #include "kaleidoscope/keyswitch_state.h" @@ -42,6 +43,9 @@ Runtime_::setup(void) { device().setup(); + // Clear the keyboard state array (all keys idle at start) + live_keys.clear(); + Layer.setup(); } diff --git a/src/kaleidoscope/key_defs.h b/src/kaleidoscope/key_defs.h index b8ba7363..c0a0f06c 100644 --- a/src/kaleidoscope/key_defs.h +++ b/src/kaleidoscope/key_defs.h @@ -16,13 +16,15 @@ #pragma once +#include // for uint8_t, uint16_t -#include "kaleidoscope/HIDTables.h" +#include // for pgm_read_byte, INPUT +#include "kaleidoscope/HIDTables.h" // for HID_KEYBOARD_LEFT_CONTROL #include "kaleidoscope/key_defs/keyboard.h" #include "kaleidoscope/key_defs/sysctl.h" #include "kaleidoscope/key_defs/consumerctl.h" -#include "kaleidoscope/key_defs/keymaps.h" +#include "kaleidoscope/key_defs/keymaps.h" // for LAYER_SHIFT_OFFSET #include "kaleidoscope/key_defs/aliases.h" // ----------------------------------------------------------------------------- @@ -34,21 +36,21 @@ // ----------------------------------------------------------------------------- // Constant flags values -#define KEY_FLAGS B00000000 -#define CTRL_HELD B00000001 -#define LALT_HELD B00000010 -#define RALT_HELD B00000100 -#define SHIFT_HELD B00001000 -#define GUI_HELD B00010000 +#define KEY_FLAGS 0b00000000 +#define CTRL_HELD 0b00000001 +#define LALT_HELD 0b00000010 +#define RALT_HELD 0b00000100 +#define SHIFT_HELD 0b00001000 +#define GUI_HELD 0b00010000 -#define SYNTHETIC B01000000 -#define RESERVED B10000000 +#define SYNTHETIC 0b01000000 +#define RESERVED 0b10000000 // we assert that synthetic keys can never have keys held, so we reuse the _HELD bits -#define IS_SYSCTL B00000001 -#define IS_INTERNAL B00000010 -#define SWITCH_TO_KEYMAP B00000100 -#define IS_CONSUMER B00001000 +#define IS_SYSCTL 0b00000001 +#define IS_INTERNAL 0b00000010 +#define SWITCH_TO_KEYMAP 0b00000100 +#define IS_CONSUMER 0b00001000 // HID Usage Types: Because these constants, like the ones above, are // used in the flags byte of the Key class, they can't overlap any of @@ -56,18 +58,18 @@ // the HID usage type of a keycode, which leaves us with only two // bits. Since we don't currently do anything different based on HID // usage type, these are currently all set to zeroes. -#define HID_TYPE_CA B00000000 -#define HID_TYPE_CL B00000000 -#define HID_TYPE_LC B00000000 -#define HID_TYPE_MC B00000000 -#define HID_TYPE_NARY B00000000 -#define HID_TYPE_OOC B00000000 -#define HID_TYPE_OSC B00000000 -#define HID_TYPE_RTC B00000000 -#define HID_TYPE_SEL B00000000 -#define HID_TYPE_SV B00000000 +#define HID_TYPE_CA 0b00000000 +#define HID_TYPE_CL 0b00000000 +#define HID_TYPE_LC 0b00000000 +#define HID_TYPE_MC 0b00000000 +#define HID_TYPE_NARY 0b00000000 +#define HID_TYPE_OOC 0b00000000 +#define HID_TYPE_OSC 0b00000000 +#define HID_TYPE_RTC 0b00000000 +#define HID_TYPE_SEL 0b00000000 +#define HID_TYPE_SV 0b00000000 // Mask defining the allowed usage type flag bits: -#define HID_TYPE_MASK B00110000 +#define HID_TYPE_MASK 0b00110000 // ============================================================================= diff --git a/src/kaleidoscope/key_events.cpp b/src/kaleidoscope/key_events.cpp index 9f80195a..6d2aaf46 100644 --- a/src/kaleidoscope/key_events.cpp +++ b/src/kaleidoscope/key_events.cpp @@ -15,6 +15,7 @@ */ #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/hooks.h" #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/layers.h" @@ -85,35 +86,44 @@ void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { * them. */ if (key_addr.isValid()) { - - /* If a key had an on event, we update the live composite keymap. See - * layers.h for an explanation about the different caches we have. */ - if (keyToggledOn(keyState)) { - if (mappedKey == Key_NoKey || keyState & EPHEMERAL) { - Layer.updateLiveCompositeKeymap(key_addr); - } else { - Layer.updateLiveCompositeKeymap(key_addr, mappedKey); - } - } - - - /* Convert key_addr to the correct mappedKey - * The condition here means that if mappedKey and key_addr are both valid, - * the mappedKey wins - we don't re-look-up the mappedKey - */ + // If the caller did not supply a `Key` value, get it from the keymap + // cache. If that value is transparent, look it up from the active layer for + // that key address. if (mappedKey == Key_NoKey) { + // Note: If the next line returns `Key_NoKey`, that will effectively mask + // the key. mappedKey = Layer.lookup(key_addr); } } // key_addr valid // Keypresses with out-of-bounds key_addr start here in the processing chain + auto result = kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState); + + // If any plugin returns `ABORT`, stop here and don't update the active keys + // cache entry. + if (result == kaleidoscope::EventHandlerResult::ABORT) + return; - if (kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState) != kaleidoscope::EventHandlerResult::OK) + // Update the keyboard state array + if (key_addr.isValid()) { + if (keyToggledOn(keyState)) { + kaleidoscope::live_keys.activate(key_addr, mappedKey); + } else if (keyToggledOff(keyState)) { + kaleidoscope::live_keys.clear(key_addr); + } + } + + // Only continue if all plugin handlers returned `OK`. + if (result != kaleidoscope::EventHandlerResult::OK) return; - mappedKey = Layer.eventHandler(mappedKey, key_addr, keyState); - if (mappedKey == Key_NoKey) + // If the key has been masked (i.e. `Key_NoKey`), or it's a plugin-specific + // key (`RESERVED`), don't bother continuing. + if (mappedKey == Key_NoKey || (mappedKey.getFlags() & RESERVED) != 0) return; + + // Handle all built-in key types. + Layer.eventHandler(mappedKey, key_addr, keyState); handleKeyswitchEventDefault(mappedKey, key_addr, keyState); } diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index 67e30768..12c4ffc7 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -1,5 +1,5 @@ /* Kaleidoscope - Firmware for computer input devices - * Copyright (C) 2013-2018 Keyboard.io, Inc. + * Copyright (C) 2013-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 @@ -14,7 +14,8 @@ * this program. If not, see . */ -#include "kaleidoscope/Runtime.h" +#include "kaleidoscope_internal/device.h" +#include "kaleidoscope/hooks.h" #include "kaleidoscope/layers.h" #include "kaleidoscope/keyswitch_state.h" @@ -42,19 +43,15 @@ uint32_t Layer_::layer_state_; uint8_t Layer_::active_layer_count_ = 1; int8_t Layer_::active_layers_[31]; -Key Layer_::live_composite_keymap_[Runtime.device().numKeys()]; -uint8_t Layer_::active_layer_keymap_[Runtime.device().numKeys()]; +uint8_t Layer_::active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM; void Layer_::setup() { // Explicitly set layer 0's state to 1 bitSet(layer_state_, 0); - // Update the keymap cache, so we start with a non-empty state. + // Update the active layer cache (every entry will be `0` to start) Layer.updateActiveLayers(); - for (auto key_addr : KeyAddr::all()) { - Layer.updateLiveCompositeKeymap(key_addr); - } } void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { @@ -120,26 +117,31 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { } Key Layer_::eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { - if (mappedKey.getFlags() != (SYNTHETIC | SWITCH_TO_KEYMAP)) - return mappedKey; - - handleKeymapKeyswitchEvent(mappedKey, keyState); - return Key_NoKey; + if (mappedKey.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP)) + handleKeymapKeyswitchEvent(mappedKey, keyState); + return mappedKey; } Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) { return keyFromKeymap(layer, key_addr); } +// Deprecated void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) { - int8_t layer = active_layer_keymap_[key_addr.toInt()]; - live_composite_keymap_[key_addr.toInt()] = (*getKey)(layer, key_addr); + // We could update the active keys cache here (as commented below), but I + // think that's unlikely to serve whatever purpose the caller had in + // mind. `Layer.lookup()` will still give the correct result, and without a + // `Key` value is specified, this function no longer serves a purpose. + + // #include "kaleidoscope/LiveKeys.h" + // int8_t layer = active_layer_keymap_[key_addr.toInt()]; + // live_keys.activate(key_addr, (*getKey)(layer, key_addr)); } void Layer_::updateActiveLayers(void) { // First, set every entry in the active layer keymap to point to the default // layer (layer 0). - memset(active_layer_keymap_, 0, Runtime.device().numKeys()); + memset(active_layer_keymap_, 0, kaleidoscope_internal::device.numKeys()); // For each key address, set its entry in the active layer keymap to the value // of the top active layer that has a non-transparent entry for that address. diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index af8c4563..101d499b 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -1,5 +1,5 @@ /* Kaleidoscope - Firmware for computer input devices - * Copyright (C) 2013-2018 Keyboard.io, Inc. + * Copyright (C) 2013-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 @@ -20,6 +20,7 @@ #include "kaleidoscope/key_defs.h" #include "kaleidoscope/keymaps.h" #include "kaleidoscope/device/device.h" +#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope_internal/device.h" #include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h" #include "kaleidoscope_internal/shortname.h" @@ -83,7 +84,13 @@ class Layer_ { * `lookupOnActiveLayer`. */ static Key lookup(KeyAddr key_addr) { - return live_composite_keymap_[key_addr.toInt()]; + // First check the keyboard state array + Key key = live_keys[key_addr]; + // If that entry is clear, look up the entry from the active keymap layers + if (key == Key_Transparent) { + key = lookupOnActiveLayer(key_addr); + } + return key; } static Key lookupOnActiveLayer(KeyAddr key_addr) { uint8_t layer = active_layer_keymap_[key_addr.toInt()]; @@ -111,9 +118,11 @@ class Layer_ { static Key getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr); + DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP) static void updateLiveCompositeKeymap(KeyAddr key_addr, Key mappedKey) { - live_composite_keymap_[key_addr.toInt()] = mappedKey; + live_keys.activate(key_addr, mappedKey); } + DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP) static void updateLiveCompositeKeymap(KeyAddr key_addr); static void updateActiveLayers(void); @@ -127,7 +136,6 @@ class Layer_ { static uint32_t layer_state_; static uint8_t active_layer_count_; static int8_t active_layers_[31]; - static Key live_composite_keymap_[kaleidoscope_internal::device.numKeys()]; static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index 21272005..7776fc2f 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -27,3 +27,10 @@ /* Messages */ +#define _DEPRECATED_MESSAGE_LAYER_UPDATELIVECOMPOSITEKEYMAP __NL__ \ + "`Layer.updateLiveCompositeKeymap()` is deprecated.\n" __NL__ \ + "The 'live composite keymap' cache has been replaced with the\n" __NL__ \ + "'active keys' cache, which now represents the state of the active\n" __NL__ \ + "keys at any given time. It is probably not necessary to directly\n" __NL__ \ + "update that cache from a plugin, but if you need to, please use\n" __NL__ \ + "the `live_keys.activate(key_addr, key)` function instead." From 65f54d63d765f5d24e60e24b18da7934350b92fc Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 13 Nov 2020 23:18:30 -0600 Subject: [PATCH 011/108] Deprecate `Layer.eventHandler()` Signed-off-by: Michael Richters --- src/kaleidoscope/key_events.cpp | 3 +-- src/kaleidoscope/layers.h | 5 +++-- src/kaleidoscope_internal/deprecations.h | 4 ++++ 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/kaleidoscope/key_events.cpp b/src/kaleidoscope/key_events.cpp index 6d2aaf46..55987049 100644 --- a/src/kaleidoscope/key_events.cpp +++ b/src/kaleidoscope/key_events.cpp @@ -41,7 +41,7 @@ static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) { } else if (mappedKey.getFlags() & IS_INTERNAL) { return false; } else if (mappedKey.getFlags() & SWITCH_TO_KEYMAP) { - // Should not happen, handled elsewhere. + Layer.handleKeymapKeyswitchEvent(mappedKey, keyState); } return true; @@ -124,6 +124,5 @@ void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { return; // Handle all built-in key types. - Layer.eventHandler(mappedKey, key_addr, keyState); handleKeyswitchEventDefault(mappedKey, key_addr, keyState); } diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index 101d499b..d12fbdf1 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -111,6 +111,9 @@ class Layer_ { } static boolean isActive(uint8_t layer); + static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); + + DEPRECATED(LAYER_EVENTHANDLER) static Key eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState); typedef Key(*GetKeyFunction)(uint8_t layer, KeyAddr key_addr); @@ -137,8 +140,6 @@ class Layer_ { static uint8_t active_layer_count_; static int8_t active_layers_[31]; static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()]; - - static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); }; } diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index 7776fc2f..5cae97ee 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -34,3 +34,7 @@ "keys at any given time. It is probably not necessary to directly\n" __NL__ \ "update that cache from a plugin, but if you need to, please use\n" __NL__ \ "the `live_keys.activate(key_addr, key)` function instead." + +#define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \ + "`Layer.eventHandler()` is deprecated.\n" __NL__ \ + "Please use `Layer.handleKeymapKeyswitchEvent()` instead." From e984bc8a3a7d741ebe530dd07be51f05e32fe70f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 9 Sep 2020 00:38:28 -0500 Subject: [PATCH 012/108] Remove unused `EPHEMERAL` keyswitch state bit Signed-off-by: Michael Richters --- src/kaleidoscope/keyswitch_state.h | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kaleidoscope/keyswitch_state.h b/src/kaleidoscope/keyswitch_state.h index dd3a24f2..1c87b026 100644 --- a/src/kaleidoscope/keyswitch_state.h +++ b/src/kaleidoscope/keyswitch_state.h @@ -20,7 +20,6 @@ #include #define INJECTED B10000000 -#define EPHEMERAL B01000000 #define IS_PRESSED B00000010 #define WAS_PRESSED B00000001 From 9d06843bd23cb2b2c51dafdcc8a100cb83d8ab5f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 13 Nov 2020 22:47:21 -0600 Subject: [PATCH 013/108] Update documentation for keyboard state array changes Signed-off-by: Michael Richters --- docs/UPGRADING.md | 71 +++++++++++++++++++++++++++++++++++++++ docs/codebase/glossary.md | 9 +++-- 2 files changed, 75 insertions(+), 5 deletions(-) diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index e4bab2a4..87714f0c 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -34,6 +34,69 @@ any API we've included in a release. Typically, this means that any code that us ## New features +### Keyboard State array + +The keymap cache (`Layer_::live_composite_keymap_[]`) has been replaced by a keyboard state array (`kaleidoscope::live_keys[]`). The top-level functions that handle keyswitch events have been updated to treat this new array as a representation of the current state of the keyboard, with corresponding `Key` values for any keys that are active (physically held or activated by a plugin). + +#### For end-users + +There should be no user-visible changes for anyone who simply uses core plugins. A few functions have been deprecated (`Layer.eventHandler()` & `Layer.updateLiveCompositeKeymap()`), but there are straightforward replacements for both. + +#### For developers + +The major changes are to the `handleKeyswitchEvent()` function, which has been reorganized in order to update the new keyboard state array with correct values at the appropriate times. In addition to that, two new facilities are available: + +##### `EventHandlerResult::ABORT` + +This is a new return value available to plugin event handlers, which is similar to `EVENT_CONSUMED` in that it causes the calling hook function to return early (stopping any subsequent handlers from seeing the event), but is treated differently by `handleKeyswitchEvent()`. If a handler returns `EVENT_CONSUMED`, the keyboard state array will still be updated by `handleKeyswitchEvent()`, but if it returns `ABORT`, it will not. In both cases, no further event processing will be done by the built-in event handler. + +##### `live_keys[key_addr]` + +This is the new facility for checking the value of an entry in the keyboard state array. It is indexed directly by `KeyAddr` values, without the need to convert them to integers first. For example, it could be used in a range-based `for` loop to check for values of interest: + +```c++ +for (KeyAddr key_addr : KeyAddr::all()) { + Key key = live_keys[key_addr]; + if (key == Key_LeftShift || key == Key_RightShift) { + // do something special... + } +} +``` + +Additionally, if the `KeyAddr` values are not needed, one can use the iterator from the new `KeyMap` class like so: + +```c++ +for (Key key : live_keys.all()) { + if (key == Key_X) { + // do something special... + } +} +``` + +The `live_keys` object's subscript operator can also be used to set values in the keyboard state array: + +```c++ +live_keys[key_addr] = Key_X; +``` + +It also comes with several convenience functions which can be used to make the intention of the code clear: + +```c++ +// Set a value in the keyboard state array to a specified Key value: +live_keys.activate(key_addr, Key_X); + +// Set a value to Key_Unbound, deactivating the key: +live_keys.clear(key_addr); + +// Set all values in the array to Key_Unbound: +live_keys.clear();) + +// Set a value to Key_Masked, masking the key until its next release event: +live_keys.mask(key_addr); +``` + +In most cases, it won't be necessary for plugins or user sketches to call any of these functions directly, as the built-in event handler functions will manage the keyboard state array automatically. + ### New build system In this release, we replace kaleidoscope-builder with a new Makefile based build system that uses `arduino-cli` instead of of the full Arduino IDE. This means that you can now check out development copies of Kaliedoscope into any directory, using the `KALEIDOSCOPE_DIR` environment variable to point to your installation. @@ -584,6 +647,14 @@ The following headers and names have changed: - [Syster](plugins/Kaleidoscope-Syster.md) had the `kaleidoscope::Syster::action_t` type replaced by `kaleidoscope::plugin::Syster::action_t`. - [TapDance](plugins/Kaleidoscope-TapDance.md) had the `kaleidoscope::TapDance::ActionType` type replaced by `kaleidoscope::plugin::TapDance::ActionType`. +### Live Composite Keymap Cache + +The live composite keymap, which contained a lazily-updated version of the current keymap, has been replaced. The `Layer.updateLiveCompositeKeymap()` functions have been deprecated, and depending on the purpose of the caller, it might be appropriate to use `Runtime.updateActiveKey()` instead. + +When `handleKeyswitchEvent()` is looking up a `Key` value for an event, it first checks the value in the active keys cache before calling `Layer.lookup()` to get the value from the keymap. In the vast majority of cases, it won't be necessary to call `Runtime.updateActiveKey()` manually, however, because simply changing the value of the `Key` parameter of an `onKeyswitchEvent()` handler will have the same effect. + +Second, the `Layer.eventHandler()` function has been deprecated. There wasn't much need for this to be available to plugins, and it's possible to call `Layer.handleKeymapKeyswitchEvent()` directly instead. + # Removed APIs ### Removed on 2020-10-10 diff --git a/docs/codebase/glossary.md b/docs/codebase/glossary.md index dcc749eb..317d63e4 100644 --- a/docs/codebase/glossary.md +++ b/docs/codebase/glossary.md @@ -17,7 +17,7 @@ A single physical input, such as a keyswitch or other input like a knob or a sli ### Key number -An integer representing a Keyswitch’s position in the “Physical Layout” +An integer representing a Keyswitch’s position in the “Physical Layout”. Represented in the code by the `KeyAddr` type. ### Physical Layout @@ -33,7 +33,7 @@ A representation of a specific behavior. Most often a representation of a specif ### Keymap -A list of key bindings for all keyswitchess on the Physical Layout +A list of key bindings for all keyswitchess on the Physical Layout. Represented in the code by the `KeyMap` type. ### Keymaps @@ -47,10 +47,9 @@ An entry in that ordered list of keymaps. Each layer has a unique id number that An ordered list of all the currently-active layers, in the order they should be evaluated when figuring out what a key does. -### Override Layer - -A special layer that’s always active and evaluated before checking keys in the “Active layer stack” +### Live keys +A representation of the current state of the keyboard's keys, where non-transparent entries indicate keys that are active (logically—usually, but not necessarily, physically held). Represented in the code by the `LiveKeys` type (and the `live_keys` object). ## Keyswitch state From 081ca52c91299b76c4d48786afbce0e2dbdb1893 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 13 Nov 2020 18:35:37 -0600 Subject: [PATCH 014/108] Adapt Qukeys to keyboard state array When Qukeys stops event processing from its `onKeyswitchEvent()` handler, it's because the event should be treated as non-existent (in most cases, it's merely delayed). This keeps the keyboard state array from getting updated, as well as completely stopping event processing. Signed-off-by: Michael Richters --- .../Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp index d3bf777e..14ce696d 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp @@ -72,7 +72,7 @@ EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_sta } // Any event that gets added to the queue gets re-processed later, so we // need to abort processing now. - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; } // The key is being held. We need to determine if we should block it because @@ -89,7 +89,7 @@ EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_sta } // Otherwise, the first matching event was a key press, so we need to // suppress it for now. - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; } } From a956c2d77db32be99577a73969589645072173cd Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:17:17 -0500 Subject: [PATCH 015/108] Adapt TapDance to keyboard state array This is a major rewrite of TapDance, taking advantage of the keyboard state array and the `KeyAddrEventQueue` class originally written for Qukeys. fixes #806 fixes #922 fixes #908 fixes #985 Signed-off-by: Michael Richters --- .../plugin/Qukeys/KeyAddrEventQueue.h | 13 + .../src/kaleidoscope/plugin/TapDance.cpp | 230 ++++++++---------- .../src/kaleidoscope/plugin/TapDance.h | 32 ++- 3 files changed, 128 insertions(+), 147 deletions(-) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h index ce8114d4..60f6d586 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h @@ -105,6 +105,19 @@ class KeyAddrEventQueue { release_event_bits_ >>= 1; } + void shift(uint8_t n) { + if (n >= length_) { + clear(); + return; + } + length_ -= n; + for (uint8_t i{0}; i < length_; ++i) { + addrs_[i] = addrs_[i + n]; + timestamps_[i] = timestamps_[i + n]; + } + release_event_bits_ >>= n; + } + // Empty the queue entirely. void clear() { length_ = 0; diff --git a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp index 6ebc67db..34d359c8 100644 --- a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp +++ b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp @@ -18,70 +18,21 @@ #include #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { namespace plugin { -// --- state --- -uint16_t TapDance::start_time_; -uint16_t TapDance::time_out = 200; -TapDance::TapDanceState TapDance::state_[TapDance::TAPDANCE_KEY_COUNT]; -Key TapDance::last_tap_dance_key_ = Key_NoKey; -KeyAddr TapDance::last_tap_dance_addr_; - -// --- actions --- - -void TapDance::interrupt(KeyAddr key_addr) { - uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST; - - tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Interrupt); - state_[idx].triggered = true; - - last_tap_dance_key_ = Key_NoKey; - - Runtime.hid().keyboard().sendReport(); - Runtime.hid().keyboard().releaseAllKeys(); - - if (state_[idx].pressed) - return; - - release(idx); -} - -void TapDance::timeout(void) { - uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST; - - tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Timeout); - state_[idx].triggered = true; - - if (state_[idx].pressed) - return; - - last_tap_dance_key_ = Key_NoKey; - - release(idx); -} - -void TapDance::release(uint8_t tap_dance_index) { - last_tap_dance_key_ = Key_NoKey; - - state_[tap_dance_index].pressed = false; - state_[tap_dance_index].triggered = false; - state_[tap_dance_index].release_next = true; -} +// --- config --- -void TapDance::tap(void) { - uint8_t idx = last_tap_dance_key_.getRaw() - ranges::TD_FIRST; - - state_[idx].count++; - start_time_ = Runtime.millisAtCycleStart(); - - tapDanceAction(idx, last_tap_dance_addr_, state_[idx].count, Tap); -} +uint16_t TapDance::time_out = 200; +KeyAddr TapDance::release_addr_ = KeyAddr{KeyAddr::invalid_state}; // --- api --- - -void TapDance::actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_t max_keys, const Key tap_keys[]) { +void TapDance::actionKeys(uint8_t tap_count, + ActionType tap_dance_action, + uint8_t max_keys, + const Key tap_keys[]) { if (tap_count > max_keys) tap_count = max_keys; @@ -92,112 +43,131 @@ void TapDance::actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_ break; case Interrupt: case Timeout: - handleKeyswitchEvent(key, last_tap_dance_addr_, IS_PRESSED | INJECTED); + if (event_queue_.isEmpty()) + break; + { + KeyAddr td_addr = event_queue_.addr(0); + bool key_released = (live_keys[td_addr] == Key_Transparent); + handleKeyswitchEvent(key, td_addr, IS_PRESSED | INJECTED); + if (key_released) + release_addr_ = td_addr; + } break; case Hold: - handleKeyswitchEvent(key, last_tap_dance_addr_, IS_PRESSED | WAS_PRESSED | INJECTED); - break; case Release: - kaleidoscope::Runtime.hid().keyboard().sendReport(); - handleKeyswitchEvent(key, last_tap_dance_addr_, WAS_PRESSED | INJECTED); break; } } + // --- hooks --- EventHandlerResult TapDance::onNameQuery() { return ::Focus.sendName(F("TapDance")); } -EventHandlerResult TapDance::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - if (keyState & INJECTED) +EventHandlerResult TapDance::onKeyswitchEvent(Key &key, + KeyAddr key_addr, + uint8_t key_state) { + if (key_state & INJECTED) return EventHandlerResult::OK; - if (mapped_key.getRaw() < ranges::TD_FIRST || mapped_key.getRaw() > ranges::TD_LAST) { - if (last_tap_dance_key_ == Key_NoKey) - return EventHandlerResult::OK; - - if (keyToggledOn(keyState)) { - interrupt(key_addr); - mapped_key = Key_NoKey; + if (event_queue_.isEmpty()) { + if (keyToggledOn(key_state) && isTapDanceKey(key)) { + // Begin a new TapDance sequence: + uint8_t td_id = key.getRaw() - ranges::TD_FIRST; + tapDanceAction(td_id, key_addr, 1, Tap); + event_queue_.append(key_addr, key_state); + return EventHandlerResult::EVENT_CONSUMED; } - return EventHandlerResult::OK; } - uint8_t tap_dance_index = mapped_key.getRaw() - ranges::TD_FIRST; - - if (keyToggledOff(keyState)) - state_[tap_dance_index].pressed = false; - - if (last_tap_dance_key_ != mapped_key) { - if (last_tap_dance_key_ == Key_NoKey) { - if (state_[tap_dance_index].triggered) { - if (keyToggledOff(keyState)) { - release(tap_dance_index); - } - - return EventHandlerResult::EVENT_CONSUMED; - } - - last_tap_dance_key_ = mapped_key; - last_tap_dance_addr_ = key_addr; - - tap(); + uint8_t td_count = event_queue_.length(); + KeyAddr td_addr = event_queue_.addr(0); + Key td_key = Layer.lookup(td_addr); + uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST; - return EventHandlerResult::EVENT_CONSUMED; + if (keyToggledOn(key_state)) { + if (key_addr == td_addr) { + // The same TapDance key was pressed again; continue the sequence: + tapDanceAction(td_id, td_addr, ++td_count, Tap); } else { - if (keyToggledOff(keyState) && state_[tap_dance_index].count) { - release(tap_dance_index); - return EventHandlerResult::EVENT_CONSUMED; + // A different key was pressed; interrupt the sequeunce: + tapDanceAction(td_id, td_addr, td_count, Interrupt); + event_queue_.clear(); + // If the sequence was interrupted by another TapDance key, start the new + // sequence: + if (isTapDanceKey(Layer.lookup(key_addr))) { + td_id = key.getRaw() - ranges::TD_FIRST; + tapDanceAction(td_id, key_addr, 1, Tap); } - - if (!keyToggledOn(keyState)) { - return EventHandlerResult::EVENT_CONSUMED; + } + // Any key that toggles on while a TapDance sequence is live gets added to + // the queue. If it interrupted the queue, we need to hold off on processing + // that event until the next cycle to guarantee that the events appear in + // order on the host. + event_queue_.append(key_addr, key_state); + if (isTapDanceKey(key)) + return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; + } else if (keyIsPressed(key_state)) { + // Until a key press event has been released from the queue, its "hold + // event" must be suppressed every cycle. + for (uint8_t i{0}; i < event_queue_.length(); ++i) { + if (event_queue_.addr(i) == key_addr) { + return EventHandlerResult::ABORT; } - - interrupt(key_addr); } } - - // in sequence - - if (keyToggledOff(keyState)) { + // We always indicate that other plugins don't need to handle TapDance keys, + // but we do allow them to show up as active keys when they're held. This way, + // when one times out, if it's not being held any longer, we can send the + // release event. + if (isTapDanceKey(key)) return EventHandlerResult::EVENT_CONSUMED; - } - - last_tap_dance_key_ = mapped_key; - last_tap_dance_addr_ = key_addr; - state_[tap_dance_index].pressed = true; - - if (keyToggledOn(keyState)) { - tap(); - return EventHandlerResult::EVENT_CONSUMED; - } - - if (state_[tap_dance_index].triggered) - tapDanceAction(tap_dance_index, key_addr, state_[tap_dance_index].count, Hold); - - return EventHandlerResult::EVENT_CONSUMED; + // This key is being held, but is not in the queue, or it toggled off, but is + // not (currently) a TapDance key. + return EventHandlerResult::OK; } EventHandlerResult TapDance::afterEachCycle() { - for (uint8_t i = 0; i < TAPDANCE_KEY_COUNT; i++) { - if (!state_[i].release_next) - continue; - - tapDanceAction(i, last_tap_dance_addr_, state_[i].count, Release); - state_[i].count = 0; - state_[i].release_next = false; + if (release_addr_.isValid()) { + handleKeyswitchEvent(Key_NoKey, release_addr_, WAS_PRESSED | INJECTED); + release_addr_ = KeyAddr{KeyAddr::invalid_state}; + } + Key event_key; + // Purge any non-TapDance key events from the front of the queue. + while (! event_queue_.isEmpty()) { + KeyAddr event_addr = event_queue_.addr(0); + event_key = Layer.lookup(event_addr); + if (isTapDanceKey(event_key)) { + break; + } + handleKeyswitchEvent(event_key, event_addr, IS_PRESSED | INJECTED); + event_queue_.shift(); } - if (last_tap_dance_key_ == Key_NoKey) + if (event_queue_.isEmpty()) return EventHandlerResult::OK; - if (Runtime.hasTimeExpired(start_time_, time_out)) - timeout(); - + // The first event in the queue is now guaranteed to be a TapDance key. + uint8_t td_id = event_key.getRaw() - ranges::TD_FIRST; + KeyAddr td_addr = event_queue_.addr(0); + + // Check for timeout + uint8_t td_count = event_queue_.length(); + uint16_t start_time = event_queue_.timestamp(td_count - 1); + if (Runtime.hasTimeExpired(start_time, time_out)) { + tapDanceAction(td_id, td_addr, td_count, Timeout); + event_queue_.clear(); + // There's still a race condition here, but it's a minor one. If a TapDance + // sequence times out in the `afterEachCycle()` handler, then another key + // toggles on in the following scan cycle, and that key is handled first, + // the two events could show up out of order on the host. The probability of + // this happening is low, and event-driven Kaleidoscope will fix it + // completely, so I'm willing to accept the risk for now. + } return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h index 605f149b..baea0356 100644 --- a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h +++ b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h @@ -18,7 +18,9 @@ #pragma once #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/LiveKeys.h" #include +#include "kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h" #define TD(n) Key(kaleidoscope::ranges::TD_FIRST + n) @@ -50,24 +52,20 @@ class TapDance : public kaleidoscope::Plugin { EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); EventHandlerResult afterEachCycle(); + static constexpr bool isTapDanceKey(Key key) { + return (key.getRaw() >= ranges::TD_FIRST && + key.getRaw() <= ranges::TD_LAST); + } + private: - static constexpr uint8_t TAPDANCE_KEY_COUNT = 16; - struct TapDanceState { - bool pressed: 1; - bool triggered: 1; - bool release_next: 1; - uint8_t count; - }; - static TapDanceState state_[TAPDANCE_KEY_COUNT]; - - static uint16_t start_time_; - static Key last_tap_dance_key_; - static KeyAddr last_tap_dance_addr_; - - static void tap(void); - static void interrupt(KeyAddr key_addr); - static void timeout(void); - static void release(uint8_t tap_dance_index); + // The maximum number of events in the queue at a time. + static constexpr uint8_t queue_capacity_{8}; + + // The event queue stores a series of press and release events. + KeyAddrEventQueue event_queue_; + + static KeyAddr release_addr_; + }; } From 71bf24a503fbe88d44b58f6beb1589478d1641fa Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 18 Nov 2020 10:00:50 -0600 Subject: [PATCH 016/108] Adapt TopsyTurvy to keyboard state array This is an extensive rewrite, but I think it simplifies the logic and makes the plugin's code easier to follow. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/TopsyTurvy.cpp | 80 ++++++++++--------- .../src/kaleidoscope/plugin/TopsyTurvy.h | 14 +++- 2 files changed, 54 insertions(+), 40 deletions(-) diff --git a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp index 774ae7f2..e587c7d2 100644 --- a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp +++ b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp @@ -21,53 +21,61 @@ namespace kaleidoscope { namespace plugin { -uint8_t TopsyTurvy::last_pressed_position_; -bool TopsyTurvy::is_shifted_; -bool TopsyTurvy::is_active_; +KeyAddr TopsyTurvy::tt_addr_ = KeyAddr::none(); +bool TopsyTurvy::shift_detected_ = false; -EventHandlerResult TopsyTurvy::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - - if (mapped_key == Key_LeftShift || - mapped_key == Key_RightShift) { - is_shifted_ = keyIsPressed(key_state); - if (is_active_) - return EventHandlerResult::EVENT_CONSUMED; - } - - if (mapped_key < ranges::TT_FIRST || mapped_key > ranges::TT_LAST) { - if (keyToggledOn(key_state) && (mapped_key < Key_LeftControl || mapped_key > Key_RightGui)) { - last_pressed_position_ = key_addr.toInt(); - } +EventHandlerResult TopsyTurvy::beforeEachCycle() { + // Clear the shift detection state before each scan cycle. + shift_detected_ = false; + return EventHandlerResult::OK; +} +EventHandlerResult TopsyTurvy::beforeReportingState() { + // If no TopsyTurvy key is active, there's nothing to do. + if (! tt_addr_.isValid()) return EventHandlerResult::OK; - } - is_active_ = keyIsPressed(key_state); - - if (keyToggledOn(key_state)) { - last_pressed_position_ = key_addr.toInt(); + // A TopsyTurvy key is active. That means we need to reverse the shift state, + // whether it was on or off. + if (shift_detected_) { + kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_LeftShift); + kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_RightShift); } else { - if (last_pressed_position_ != key_addr.toInt()) { - return EventHandlerResult::EVENT_CONSUMED; - } + kaleidoscope::Runtime.hid().keyboard().pressKey(Key_LeftShift); } + return EventHandlerResult::OK; +} - mapped_key.setRaw(mapped_key.getRaw() - ranges::TT_FIRST); - - // invert the shift state - if (!is_shifted_) { - mapped_key.setFlags(mapped_key.getFlags() | SHIFT_HELD); +EventHandlerResult TopsyTurvy::onKeyswitchEvent(Key &key, + KeyAddr key_addr, + uint8_t key_state) { + // If a modifer key (including combo modifiers, but not non-modifier keys with + // mod flags) is active, and it includes `shift` (either from its keycode or a + // mod flag), record that we detected an "intentional shift". + if (key.isKeyboardShift() && keyIsPressed(key_state)) + shift_detected_ = true; + + // If the active TopsyTurvy key toggles off, clear the stored address to + // record that. + if (keyToggledOff(key_state)) { + if (key_addr == tt_addr_) { + tt_addr_.clear(); + } return EventHandlerResult::OK; } - if (keyIsPressed(key_state)) { - kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_LeftShift); - kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_RightShift); - - return EventHandlerResult::OK; + if (keyToggledOn(key_state)) { + if (isTopsyTurvyKey(key)) { + // If a TopsyTurvy key toggles on, store its address to indicate that it's + // active, and decode its key value to store in the active keys cache. + tt_addr_ = key_addr; + key = Key(key.getRaw() - ranges::TT_FIRST); + } else { + // If any other key toggles on, clear the active TopsyTurvy address. + tt_addr_.clear(); + } } - - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::OK; } } diff --git a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h index bfd77c24..a1903615 100644 --- a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h +++ b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h @@ -29,12 +29,18 @@ class TopsyTurvy: public kaleidoscope::Plugin { public: TopsyTurvy(void) {} - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult beforeEachCycle(); + EventHandlerResult beforeReportingState(); + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + + static bool isTopsyTurvyKey(Key key) { + return (key >= ranges::TT_FIRST && + key <= ranges::TT_LAST); + } private: - static uint8_t last_pressed_position_; - static bool is_shifted_; - static bool is_active_; + static KeyAddr tt_addr_; + static bool shift_detected_; }; } } From d4f80b101ccde794121078f73defffc8e97e7ece Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 17 Nov 2020 15:31:36 -0600 Subject: [PATCH 017/108] Adapt ShapeShifter to keyboard state array Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/ShapeShifter.cpp | 25 +++++++++++-------- .../src/kaleidoscope/plugin/ShapeShifter.h | 4 --- 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp index c1fed6e9..3692c8c3 100644 --- a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp +++ b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp @@ -16,25 +16,21 @@ */ #include +#include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/LiveKeys.h" namespace kaleidoscope { namespace plugin { const ShapeShifter::dictionary_t *ShapeShifter::dictionary = NULL; -bool ShapeShifter::mod_active_; - -EventHandlerResult ShapeShifter::beforeReportingState() { - mod_active_ = kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_LeftShift) || - kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_RightShift); - return EventHandlerResult::OK; -} EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (!dictionary) + // Only act on keys that toggle on to prevent cycles (if the dictionary has + // two keys mapped to each other). + if (!keyToggledOn(key_state)) return EventHandlerResult::OK; - // If Shift is not active, bail out early. - if (!mod_active_) + if (!dictionary) return EventHandlerResult::OK; Key orig, repl; @@ -52,6 +48,15 @@ EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_a if (orig == Key_NoKey) return EventHandlerResult::OK; + bool shift_detected = false; + + for (KeyAddr k : KeyAddr::all()) { + if (live_keys[k].isKeyboardShift()) + shift_detected = true; + } + if (! shift_detected) + return EventHandlerResult::OK; + repl = dictionary[i].replacement.readFromProgmem(); // If found, handle the alternate key instead diff --git a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h index cdb2dc72..3b8ad7da 100644 --- a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h +++ b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h @@ -33,10 +33,6 @@ class ShapeShifter : public kaleidoscope::Plugin { static const dictionary_t *dictionary; EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); - EventHandlerResult beforeReportingState(); - - private: - static bool mod_active_; }; } From a1bbe40967c887e969fb9919832e021afcc21101 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 17 Nov 2020 15:32:17 -0600 Subject: [PATCH 018/108] Adapt Escape-OneShot to keyboard state array Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Escape-OneShot.cpp | 8 +++----- .../src/kaleidoscope/plugin/Escape-OneShot.h | 2 -- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp index 47078c40..3caeb077 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp @@ -23,21 +23,19 @@ namespace kaleidoscope { namespace plugin { -bool EscapeOneShot::did_escape_; - EventHandlerResult EscapeOneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { if (mapped_key != Key_Escape || (keyState & INJECTED)) return EventHandlerResult::OK; - if (did_escape_) - mapped_key = Key_NoKey; - did_escape_ = !keyToggledOff(keyState); + if (!keyToggledOn(keyState)) + return EventHandlerResult::OK; if ((!::OneShot.isActive() || ::OneShot.isPressed()) && !::OneShot.isSticky()) { return EventHandlerResult::OK; } ::OneShot.cancel(true); + mapped_key = Key_NoKey; return EventHandlerResult::EVENT_CONSUMED; } diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h index 027dabf6..af214228 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h @@ -27,8 +27,6 @@ class EscapeOneShot : public kaleidoscope::Plugin { EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); - private: - static bool did_escape_; }; } } From c00bd1a0d66e1737a2e92791655fa977b04f58d3 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 16 Nov 2020 17:02:27 -0600 Subject: [PATCH 019/108] Adapt Redial to keyboard state array Because the active key for redial was getting cached as the key being pressed, Redial would only ever see a key toggled on event for `Key_Redial`. It would then set `redial_held_` to `true`, but it would never get set to `false` on the key's release. This change both fixes it and simplifies the plugin as it is adapted to the keyboard state array by doing away with unnecessary state variables, including `redial_held_`. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Redial.cpp | 23 +++++-------------- .../src/kaleidoscope/plugin/Redial.h | 2 -- 2 files changed, 6 insertions(+), 19 deletions(-) diff --git a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp index 1a506a48..d81a0534 100644 --- a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp +++ b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp @@ -22,31 +22,20 @@ namespace kaleidoscope { namespace plugin { -Key Redial::key_to_redial_; Key Redial::last_key_; -bool Redial::redial_held_ = false; EventHandlerResult Redial::onNameQuery() { return ::Focus.sendName(F("Redial")); } EventHandlerResult Redial::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (mapped_key == Key_Redial) { - if (keyToggledOff(key_state)) - key_to_redial_ = last_key_; - - mapped_key = key_to_redial_; - redial_held_ = keyIsPressed(key_state); - - return EventHandlerResult::OK; + if (keyToggledOn(key_state)) { + if (mapped_key == Key_Redial) { + mapped_key = last_key_; + } else if (shouldRemember(mapped_key)) { + last_key_ = mapped_key; + } } - - if (keyToggledOn(key_state) && shouldRemember(mapped_key)) { - last_key_ = mapped_key; - if (!redial_held_) - key_to_redial_ = mapped_key; - } - return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h index d2052c32..701796e5 100644 --- a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h +++ b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h @@ -35,9 +35,7 @@ class Redial : public kaleidoscope::Plugin { EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); private: - static Key key_to_redial_; static Key last_key_; - static bool redial_held_; }; } From 4fe4b2ef30d30051f8c9abaae799710010afb11f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 18 Nov 2020 13:23:06 -0600 Subject: [PATCH 020/108] Adapt WinKeyToggle to keyboard state array Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/WinKeyToggle.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp index 89ce0a26..2cd3a328 100644 --- a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp +++ b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp @@ -27,8 +27,10 @@ EventHandlerResult WinKeyToggle::onKeyswitchEvent(Key &key, KeyAddr key_addr, ui if (!enabled_) return EventHandlerResult::OK; - if (key == Key_LeftGui || key == Key_RightGui) + if (key == Key_LeftGui || key == Key_RightGui) { + key = Key_NoKey; return EventHandlerResult::EVENT_CONSUMED; + } return EventHandlerResult::OK; } From add3ab4e830437b85e90864bd8f9312314265856 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 4 Jan 2021 14:07:28 -0600 Subject: [PATCH 021/108] Move KeyAddrEventQueue class from Qukeys to Kaleidoscope core It's now being used by more than one plugin, and is likely to get used by at least a third, if not more. Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h | 2 +- .../Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h | 2 +- .../plugin/Qukeys => src/kaleidoscope}/KeyAddrEventQueue.h | 2 -- 3 files changed, 2 insertions(+), 4 deletions(-) rename {plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys => src/kaleidoscope}/KeyAddrEventQueue.h (98%) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h index cdcf0081..80a89f4a 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h @@ -20,7 +20,7 @@ #include "kaleidoscope/Runtime.h" #include -#include "kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h" +#include "kaleidoscope/KeyAddrEventQueue.h" // DualUse Key definitions for Qukeys in the keymap #define MT(mod, key) Key( \ diff --git a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h index baea0356..9eef4c25 100644 --- a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h +++ b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h @@ -20,7 +20,7 @@ #include "kaleidoscope/Runtime.h" #include "kaleidoscope/LiveKeys.h" #include -#include "kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h" +#include "kaleidoscope/KeyAddrEventQueue.h" #define TD(n) Key(kaleidoscope::ranges::TD_FIRST + n) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h b/src/kaleidoscope/KeyAddrEventQueue.h similarity index 98% rename from plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h rename to src/kaleidoscope/KeyAddrEventQueue.h index 60f6d586..8a646394 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys/KeyAddrEventQueue.h +++ b/src/kaleidoscope/KeyAddrEventQueue.h @@ -25,7 +25,6 @@ #include "kaleidoscope/keyswitch_state.h" namespace kaleidoscope { -namespace plugin { // This class defines a keyswitch event queue that stores both press and release // events, recording the key address, a timestamp, and the keyswitch state @@ -125,5 +124,4 @@ class KeyAddrEventQueue { } }; -} // namespace plugin } // namespace kaleidoscope From f1e727dba71830b5d1937ac51fa999b827e3fbc5 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Thu, 19 Nov 2020 18:24:59 -0600 Subject: [PATCH 022/108] Add testcase for OneShot issue #896 Signed-off-by: Michael Richters --- tests/issues/896/896.ino | 70 +++++++++++++++++++++++++++++++++++++ tests/issues/896/common.h | 27 ++++++++++++++ tests/issues/896/test.ktest | 61 ++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+) create mode 100644 tests/issues/896/896.ino create mode 100644 tests/issues/896/common.h create mode 100644 tests/issues/896/test.ktest diff --git a/tests/issues/896/896.ino b/tests/issues/896/896.ino new file mode 100644 index 00000000..36e1bc19 --- /dev/null +++ b/tests/issues/896/896.ino @@ -0,0 +1,70 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + OSL(1), ___, ___, ___, ___, ___, ___, + Key_A, Key_B, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), + [1] = KEYMAP_STACKED + ( + Key_X, ___, ___, ___, ___, ___, ___, + Key_C, Key_D, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(OneShot); + +void setup() { + Kaleidoscope.setup(); + OneShot.time_out = 50; + OneShot.hold_time_out = 20; + OneShot.double_tap_time_out = 20; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/issues/896/common.h b/tests/issues/896/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/issues/896/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/issues/896/test.ktest b/tests/issues/896/test.ktest new file mode 100644 index 00000000..f2396f46 --- /dev/null +++ b/tests/issues/896/test.ktest @@ -0,0 +1,61 @@ +VERSION 1 + +KEYSWITCH OSL_1 0 0 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 + +# ============================================================================== +NAME OneShot next key + +RUN 5 ms + +PRESS OSL_1 +RUN 5 ms +RELEASE OSL_1 +RUN 10 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_C # There should be only `C` +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME OneShot double tap layer 896 + +RUN 5 ms +PRESS OSL_1 +RUN 5 ms +RELEASE OSL_1 + +RUN 5 ms +PRESS OSL_1 +RUN 5 ms +RELEASE OSL_1 + +RUN 50 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_C # There should be only `C` +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 10 ms + +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_C # There should be only `C` +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms +PRESS OSL_1 +RUN 5 ms +RELEASE OSL_1 +RUN 1 cycle From dc21cc28953b4367de614b511b1e621cc5803beb Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:23:37 -0500 Subject: [PATCH 023/108] Rewrite OneShot plugin This is a complete rewrite of OneShot, based on the keymap cache redesign. This allows OneShot to abort the release of a key, causing its cache entry to stay valid if it's in an active state after the key is released, allowing us to fix #896 (double-tapping a layer shift key doesn't make it sticky). Instead of tracking `Key` values, OneShot now uses two bitfields of the keyboard in order to track the OneShot state of every valid `KeyAddr` independently. This could enable the creation of a OneShot "meta" key, which could be used as a way to make any key on the keyboard exhibit OneShot behaviour. The new OneShot plugin immediately replaces the OneShot `Key` value with its corresponding "normal" key, and activates its OneShot status by setting one bit in one of the bitfields. Also included: * A rewrite of LED-ActiveModColor that makes it compatible with the new OneShot, and add support for Qukeys * Updates to Escape-OneShot for compatibility and efficiency * Minor updates to Qukeys * The new KeyAddrBitfield class KeyAddrBitfield: This class can be used to represent a binary state of the physical key addresses on the keyboard. For example, ActiveModColor can use to to mark all the keys which should be highlighted at any given time. It includes a very efficient iterator, which returns only `KeyAddr` values corresponding to bits that are set in the bitfield. It checks a whole byte at a time before examining individual bits, so if most bits are unset most of the time, it's very fast, and suitable for use in hooks that get called every cycle. ActiveModColor: This makes LED-ActiveModColor compatible with Qukeys, and removes its 16-modifier limit, while simultaneously reducing it's footprint in RAM and eliminating a potential buffer overrun bug where it could have written past the end of its state array. Fixes #882 Fixes #894 Fixes #896 Signed-off-by: Michael Richters --- .../kaleidoscope/plugin/Escape-OneShot.cpp | 33 +- .../src/kaleidoscope/plugin/Escape-OneShot.h | 5 +- .../plugin/LED-ActiveModColor.cpp | 90 ++-- .../kaleidoscope/plugin/LED-ActiveModColor.h | 14 +- .../src/kaleidoscope/plugin/OneShot.cpp | 481 +++++++++++------- .../src/kaleidoscope/plugin/OneShot.h | 125 +++-- .../src/kaleidoscope/plugin/Qukeys.cpp | 9 +- src/kaleidoscope/KeyAddrBitfield.h | 184 +++++++ 8 files changed, 628 insertions(+), 313 deletions(-) create mode 100644 src/kaleidoscope/KeyAddrBitfield.h diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp index 3caeb077..26367a94 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Escape-OneShot -- Turn ESC into a key that cancels OneShots, if active. - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -19,24 +19,31 @@ #include #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { namespace plugin { -EventHandlerResult EscapeOneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - if (mapped_key != Key_Escape || (keyState & INJECTED)) - return EventHandlerResult::OK; - - if (!keyToggledOn(keyState)) - return EventHandlerResult::OK; - - if ((!::OneShot.isActive() || ::OneShot.isPressed()) && !::OneShot.isSticky()) { - return EventHandlerResult::OK; +EventHandlerResult EscapeOneShot::onKeyswitchEvent( + Key &key, KeyAddr key_addr, uint8_t key_state) { + // We only act on an escape key that has just been pressed, and not + // generated by some other plugin. Also, only if at least one + // OneShot key is active and/or sticky. Last, only if there are no + // OneShot keys currently being held. + if (key == Key_Escape && + keyToggledOn(key_state) && + !(key_state & INJECTED) && + ::OneShot.isActive()) { + // Cancel all OneShot keys + ::OneShot.cancel(true); + // Change the escape key to a blank key, and signal that event processing is + // complete. + key = Key_NoKey; + return EventHandlerResult::EVENT_CONSUMED; } - ::OneShot.cancel(true); - mapped_key = Key_NoKey; - return EventHandlerResult::EVENT_CONSUMED; + // Otherwise, do nothing + return EventHandlerResult::OK; } } diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h index af214228..bc52a8ad 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Escape-OneShot -- Turn ESC into a key that cancels OneShots, if active. - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -25,8 +25,7 @@ class EscapeOneShot : public kaleidoscope::Plugin { public: EscapeOneShot(void) {} - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); - + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); }; } } diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 5e4004e5..1b293cdc 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-LED-ActiveModColor -- Light up the LEDs under the active modifiers - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -18,34 +18,44 @@ #include #include #include "kaleidoscope/layers.h" +#include "kaleidoscope/keyswitch_state.h" namespace kaleidoscope { namespace plugin { -KeyAddr ActiveModColorEffect::mod_keys_[MAX_MODS_PER_LAYER]; -uint8_t ActiveModColorEffect::mod_key_count_; +KeyAddrBitfield ActiveModColorEffect::mod_key_bits_; bool ActiveModColorEffect::highlight_normal_modifiers_ = true; -cRGB ActiveModColorEffect::highlight_color = (cRGB) { - 160, 160, 160 -}; - +cRGB ActiveModColorEffect::highlight_color = CRGB(160, 160, 160); +cRGB ActiveModColorEffect::oneshot_color = CRGB(160, 160, 0); cRGB ActiveModColorEffect::sticky_color = CRGB(160, 0, 0); -EventHandlerResult ActiveModColorEffect::onLayerChange() { - if (!Runtime.has_leds) - return EventHandlerResult::OK; - - mod_key_count_ = 0; +EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( + Key &key, + KeyAddr key_addr, + uint8_t key_state) { - for (auto key_addr : KeyAddr::all()) { - Key k = Layer.lookupOnActiveLayer(key_addr); + // If `key_addr` is not a physical key address, ignore it: + if (! key_addr.isValid()) { + return EventHandlerResult::OK; + } - if (::OneShot.isOneShotKey(k) || - (highlight_normal_modifiers_ && ( - (k >= Key_LeftControl && k <= Key_RightGui) || - (k.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))))) { - mod_keys_[mod_key_count_++] = key_addr; + if (keyToggledOn(key_state)) { + // If a key toggles on, we check its value. If it's a OneShot key, + // it will get highlighted. Conditionally (if + // `highlight_normal_modifiers_` is set), we also highlight + // modifier and layer-shift keys. + if ((key >= Key_LeftControl && key <= Key_RightGui) || + (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))) { + mod_key_bits_.set(key_addr); + } + } else if (keyToggledOff(key_state)) { + // Things get a bit ugly here because this plugin might come + // before OneShot in the order, so we can't just count on OneShot + // stopping the suppressed release event before we see it here. + if (mod_key_bits_.read(key_addr) && !::OneShot.isActive(key_addr)) { + mod_key_bits_.clear(key_addr); + ::LEDControl.refreshAt(key_addr); } } @@ -53,36 +63,20 @@ EventHandlerResult ActiveModColorEffect::onLayerChange() { } EventHandlerResult ActiveModColorEffect::beforeReportingState() { - if (mod_key_count_ == 0) { - onLayerChange(); - } - - for (uint8_t i = 0; i < mod_key_count_; i++) { - const KeyAddr &key_addr = mod_keys_[i]; - - Key k = Layer.lookupOnActiveLayer(key_addr); - - if (::OneShot.isOneShotKey(k)) { - if (::OneShot.isSticky(k)) - ::LEDControl.setCrgbAt(key_addr, sticky_color); - else if (::OneShot.isActive(k)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); - } else if (k >= Key_LeftControl && k <= Key_RightGui) { - if (kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(k)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); - } else if (k.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP)) { - uint8_t layer = k.getKeyCode(); - if (layer >= LAYER_SHIFT_OFFSET) - layer -= LAYER_SHIFT_OFFSET; - if (Layer.isActive(layer)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); + // This loop iterates through only the `key_addr`s that have their + // bits in the `mod_key_bits_` bitfield set. + for (KeyAddr key_addr : mod_key_bits_) { + + if (::OneShot.isTemporary(key_addr)) { + // Temporary OneShot keys get one color: + ::LEDControl.setCrgbAt(key_addr, oneshot_color); + } else if (::OneShot.isSticky(key_addr)) { + // Sticky OneShot keys get another color: + ::LEDControl.setCrgbAt(key_addr, sticky_color); + } else if (highlight_normal_modifiers_) { + // Normal modifiers get a third color: + ::LEDControl.setCrgbAt(key_addr, highlight_color); } } diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h index 7b554f42..bdbc3bdd 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-LED-ActiveModColor -- Light up the LEDs under the active modifiers - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -18,6 +18,7 @@ #pragma once #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyAddrBitfield.h" #include #define MAX_MODS_PER_LAYER 16 @@ -29,22 +30,21 @@ class ActiveModColorEffect : public kaleidoscope::Plugin { ActiveModColorEffect(void) {} static cRGB highlight_color; + static cRGB oneshot_color; static cRGB sticky_color; static void highlightNormalModifiers(bool value) { highlight_normal_modifiers_ = value; } + EventHandlerResult onKeyswitchEvent(Key &key, + KeyAddr key_addr, + uint8_t key_state); EventHandlerResult beforeReportingState(); - EventHandlerResult onLayerChange(); - EventHandlerResult onSetup() { - return onLayerChange(); - } private: static bool highlight_normal_modifiers_; - static KeyAddr mod_keys_[MAX_MODS_PER_LAYER]; - static uint8_t mod_key_count_; + static KeyAddrBitfield mod_key_bits_; }; } } diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index c7a07aa0..99a78850 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -19,274 +19,375 @@ #include #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { namespace plugin { -// ---- state --------- +// ---------------------------------------------------------------------------- +// Configuration variables -uint16_t OneShot::start_time_ = 0; uint16_t OneShot::time_out = 2500; uint16_t OneShot::hold_time_out = 250; int16_t OneShot::double_tap_time_out = -1; -OneShot::key_state_t OneShot::state_[OneShot::ONESHOT_KEY_COUNT]; -Key OneShot::prev_key_; -bool OneShot::should_cancel_ = false; -bool OneShot::should_cancel_stickies_ = false; - -bool OneShot::isPressed() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].pressed) - return true; + +// ---------------------------------------------------------------------------- +// State variables + +uint16_t OneShot::stickable_keys_ = -1; + +KeyAddrBitfield OneShot::temp_addrs_; +KeyAddrBitfield OneShot::glue_addrs_; + +uint16_t OneShot::start_time_ = 0; +KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr; +uint8_t OneShot::release_countdown_ = 0; + + +// ============================================================================ +// Public interface + +// ---------------------------------------------------------------------------- +// Configuration functions + +void OneShot::enableStickability(Key key) { + uint8_t n = getKeyIndex(key); + stickable_keys_ |= (1 << n); +} + +void OneShot::disableStickability(Key key) { + uint8_t n = getKeyIndex(key); + stickable_keys_ &= ~(1 << n); +} + +void OneShot::enableStickabilityForModifiers() { + stickable_keys_ |= stickable_modifiers_mask; +} + +void OneShot::disableStickabilityForModifiers() { + stickable_keys_ &= ~stickable_modifiers_mask; +} + +void OneShot::enableStickabilityForLayers() { + stickable_keys_ |= stickable_layers_mask; +} + +void OneShot::disableStickabilityForLayers() { + stickable_keys_ &= ~stickable_layers_mask; +} + +// ---------------------------------------------------------------------------- +// Global tests for any OneShot key + +bool OneShot::isActive() { + for (KeyAddr key_addr __attribute__((unused)) : temp_addrs_) { + return true; + } + for (KeyAddr key_addr __attribute__((unused)) : glue_addrs_) { + return true; } return false; } bool OneShot::isSticky() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].sticky) + for (KeyAddr key_addr : glue_addrs_) { + if (! temp_addrs_.read(key_addr)) { return true; + } } return false; } -bool OneShot::isStickable(Key key) { - return state_[key.getRaw() - ranges::OS_FIRST].stickable; -} +// ---------------------------------------------------------------------------- +// Key-specific OneShot key tests -// ---- OneShot stuff ---- -void OneShot::injectNormalKey(uint8_t idx, uint8_t key_state) { - Key key; +// These functions are particularly useful for ActiveModColor, which +// could potentially use three different color values for the three +// states (sticky | active && !sticky | pressed && !active). - if (idx < 8) { - key = Key(Key_LeftControl.getKeyCode() + idx, - Key_LeftControl.getFlags()); - } else { - key = Key(LAYER_SHIFT_OFFSET + idx - 8, - KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP); +bool OneShot::isModifier(Key key) { + // Returns `true` if `key` is a modifier key, including modifiers + // with extra mod flags applied (e.g. `Key_Meh`). + if ((key.getFlags() & (SYNTHETIC | RESERVED)) != 0) { + return false; } + return (key.getKeyCode() >= Key_LeftControl.getKeyCode() && + key.getKeyCode() <= Key_RightGui.getKeyCode()); +} - handleKeyswitchEvent(key, UnknownKeyswitchLocation, key_state | INJECTED); +bool OneShot::isLayerShift(Key key) { + // Returns `true` if `key` is a layer-shift key. + return (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP) && + key.getKeyCode() >= LAYER_SHIFT_OFFSET && + key.getKeyCode() < LAYER_MOVE_OFFSET); } -void OneShot::activateOneShot(uint8_t idx) { - injectNormalKey(idx, IS_PRESSED); +bool OneShot::isStickable(Key key) { + int8_t n; + if (isModifier(key)) { + n = key.getKeyCode() - Key_LeftControl.getKeyCode(); + return bitRead(stickable_keys_, n); + } else if (isLayerShift(key)) { + n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; + if (n < oneshot_key_count) { + return bitRead(stickable_keys_, n); + } + } + return false; } -void OneShot::cancelOneShot(uint8_t idx) { - state_[idx].active = false; - injectNormalKey(idx, WAS_PRESSED); +bool OneShot::isTemporary(KeyAddr key_addr) { + return temp_addrs_.read(key_addr); } -EventHandlerResult OneShot::onNameQuery() { - return ::Focus.sendName(F("OneShot")); +bool OneShot::isSticky(KeyAddr key_addr) { + return (glue_addrs_.read(key_addr) && !temp_addrs_.read(key_addr)); } -EventHandlerResult OneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - uint8_t idx = mapped_key.getRaw() - ranges::OS_FIRST; +bool OneShot::isActive(KeyAddr key_addr) { + return (isTemporary(key_addr) || glue_addrs_.read(key_addr)); +} - if (keyState & INJECTED) - return EventHandlerResult::OK; +// ---------------------------------------------------------------------------- +// Other functions - if (!isActive()) { - if (!isOneShotKey_(mapped_key)) { - return EventHandlerResult::OK; +// Cancel all active OneShot keys (if `cancel_stickies` is true) or +// just non-sticky active OneShot keys. This function is called by +// Escape-OneShot to release active OneShot keys. +void OneShot::cancel(bool cancel_stickies) { + if (cancel_stickies) { + for (KeyAddr key_addr : glue_addrs_) { + releaseKey(key_addr); } - - if (keyToggledOff(keyState)) { - state_[idx].pressed = false; - } else if (keyToggledOn(keyState)) { - start_time_ = Runtime.millisAtCycleStart(); - state_[idx].position = key_addr.toInt(); - state_[idx].pressed = true; - state_[idx].active = true; - prev_key_ = mapped_key; - - activateOneShot(idx); + } + for (KeyAddr key_addr : temp_addrs_) { + if (glue_addrs_.read(key_addr)) { + releaseKey(key_addr); + } else { + temp_addrs_.clear(key_addr); } - - return EventHandlerResult::EVENT_CONSUMED; } +} - if (isOneShotKey_(mapped_key)) { - if (state_[idx].sticky) { - if (keyToggledOn(keyState)) { // maybe on _off instead? - prev_key_ = mapped_key; - state_[idx].sticky = false; - cancelOneShot(idx); - should_cancel_ = false; - } - } else { - if (keyToggledOff(keyState)) { - state_[idx].pressed = false; - if (Runtime.hasTimeExpired(start_time_, hold_time_out)) { - cancelOneShot(idx); - should_cancel_ = false; - } - } +// ---------------------------------------------------------------------------- +// Plugin hook functions - if (keyToggledOn(keyState)) { - state_[idx].pressed = true; +EventHandlerResult OneShot::onNameQuery() { + return ::Focus.sendName(F("OneShot")); +} - if (prev_key_ == mapped_key && isStickable(mapped_key)) { - uint16_t dtto = (double_tap_time_out == -1) ? time_out : double_tap_time_out; - if (!Runtime.hasTimeExpired(start_time_, dtto)) { - state_[idx].sticky = true; - prev_key_ = mapped_key; - } - } else { - start_time_ = Runtime.millisAtCycleStart(); +EventHandlerResult OneShot::onKeyswitchEvent( + Key &key, KeyAddr key_addr, uint8_t key_state) { - state_[idx].position = key_addr.toInt(); - state_[idx].active = true; - prev_key_ = mapped_key; + // Ignore injected key events. This prevents re-processing events + // that the hook functions generate (by calling `injectNormalKey()` + // via one of the `*OneShot()` functions). There are more robust + // ways to do this, but since OneShot is intended to react to only + // physical keypresses, this is adequate. + if (key_state & INJECTED) + return EventHandlerResult::OK; - activateOneShot(idx); + bool temp = temp_addrs_.read(key_addr); + bool glue = glue_addrs_.read(key_addr); + + if (keyToggledOn(key_state)) { + + if (!temp && !glue) { + // This key_addr is not in a OneShot state. + if (isOneShotKey(key)) { + // Replace the OneShot key with its corresponding normal key. + pressKey(key_addr, key); + return EventHandlerResult::ABORT; + } else if (!isModifier(key) && !isLayerShift(key)) { + // Only trigger release of temporary OneShot keys if the + // pressed key is neither a modifier nor a layer shift. + release_countdown_ = (1 << 1); + } + // return EventHandlerResult::OK; + + } else if (temp && glue) { + // This key_addr is in the temporary OneShot state. + if (key_addr == prev_key_addr_) { + // The same OneShot key has been pressed twice in a row. It + // will either become sticky (if it has been double-tapped), + // or it will be become a normal key. Either way, its `temp` + // state will be cleared. + temp_addrs_.clear(key_addr); + + // Derive the true double-tap timeout value if we're using the default. + uint16_t dtto = (double_tap_time_out < 0) ? time_out : double_tap_time_out; + + // If the key is not stickable, or the double-tap timeout has + // expired, clear the `glue` state, as well; this OneShot key + // has been cancelled, and will become a normal key. + if (!isStickable(key) || hasTimedOut(dtto)) { + glue_addrs_.clear(key_addr); + } else { + return EventHandlerResult::ABORT; } + } else { + // This is a temporary OneShot key, but has not been pressed + // twice in a row, so we need to clear its state. + temp_addrs_.clear(key_addr); + glue_addrs_.clear(key_addr); } - } - return EventHandlerResult::EVENT_CONSUMED; - } + } else if (!temp && glue) { + // This is a sticky OneShot key that has been pressed. Clear + // state now, so it will become a normal key. + glue_addrs_.clear(key_addr); + + } else { // (temp && !glue) + // A key has been pressed that is in the "pending" OneShot + // state. Since this key should have entered the "temporary" + // OneShot state as soon as it was released (from its first + // press), it should only be possible to release a key that's in + // this state. + } + // Always record the address of a keypress. It might be useful for + // other plugins, so this could perhaps be tracked in the + // Kaleidoscope core. + prev_key_addr_ = key_addr; + + } else if (keyToggledOff(key_state)) { + + if (temp || glue) { + // Any key in the "pending" OneShot state needs its `glue` state + // bit set to make it "temporary". If it's in the "sticky" + // OneShot state, this is redundant, but we're trading time + // efficiency to get smaller binary size. + glue_addrs_.set(key_addr); + // This is an active OneShot key that has just been released. We + // need to stop that event from sending a report, and instead + // send a "hold" event. This is handled in the + // `beforeReportingState()` hook below. + //Layer.updateLiveCompositeKeymap(key_addr, key); + return EventHandlerResult::ABORT; + } - // ordinary key here, with some event + } else { + // This key is being held. + if (temp && !glue) { + // This key is in the "pending" OneShot state. We need to check + // its hold timeout, and turn it back into a normal key if it + // has timed out. + if (hasTimedOut(hold_time_out)) { + temp_addrs_.clear(key_addr); + } + } - if (keyIsPressed(keyState)) { - prev_key_ = mapped_key; - if (!(mapped_key >= Key_LeftControl && mapped_key <= Key_RightGui) && - !(mapped_key.getFlags() == (KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP))) { - should_cancel_ = true; + if (isOneShotKey(key)) { + // whoops! someone cancelled a oneshot key while it was being + // held; reactivate it, but set it as a normal modifier + // instead. Or better yet, mask it, in case of a layer change. } } return EventHandlerResult::OK; } + +// For any active OneShot modifier keys, keep those modifiers active +// in the keyboard HID report. EventHandlerResult OneShot::beforeReportingState() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - if (state_[i].active) { - activateOneShot(i); - } + for (KeyAddr key_addr : glue_addrs_) { + holdKey(key_addr); } - return EventHandlerResult::OK; } + EventHandlerResult OneShot::afterEachCycle() { - bool oneshot_active = false; - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].active) { - oneshot_active = true; - break; - } - } - if (oneshot_active && hasTimedOut()) - cancel(); - - bool is_cancelled = false; - - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (should_cancel_) { - if (state_[i].sticky) { - if (should_cancel_stickies_) { - is_cancelled = true; - state_[i].sticky = false; - cancelOneShot(i); - state_[i].pressed = false; - } - } else if (state_[i].active && !state_[i].pressed) { - is_cancelled = true; - cancelOneShot(i); + // If a normal, non-modifier key has been pressed, or if active, + // non-sticky OneShot keys have timed out, this is where they get + // released. Release is triggered when `release_countdown_` gets to + // 1, not 0, because most of the time it will be 0 (see below). It + // gets set to 2 on the press of a normal key when there are any + // active OneShot keys; that way, the OneShot keys will stay active + // long enough to apply to the newly-pressed key. + if ((release_countdown_ == 1) || hasTimedOut(time_out)) { + for (KeyAddr key_addr : temp_addrs_) { + if (glue_addrs_.read(key_addr)) { + releaseKey(key_addr); } + temp_addrs_.clear(key_addr); } } - - if (is_cancelled) { - should_cancel_ = false; - should_cancel_stickies_ = false; - } + // Also, advance the counter for OneShot keys that have been + // cancelled by the press of a non-OneShot, non-modifier key. An + // unconditional bit shift should be more efficient than checking + // for zero to avoid underflow. + release_countdown_ >>= 1; return EventHandlerResult::OK; } -void OneShot::inject(Key mapped_key, uint8_t key_state) { - onKeyswitchEvent(mapped_key, UnknownKeyswitchLocation, key_state); -} +// ============================================================================ +// Private functions, not exposed to other plugins -// --- glue code --- +// ---------------------------------------------------------------------------- +// Helper functions for acting on OneShot key events -bool OneShot::isActive(void) { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if ((state_[i].active && !hasTimedOut()) || - state_[i].pressed || - state_[i].sticky) - return true; - } - return false; +uint8_t OneShot::getOneShotKeyIndex(Key oneshot_key) { + // The calling function is responsible for verifying that + // `oneshot_key` is an actual OneShot key (i.e. call + // `isOneShotKey(oneshot_key)` first). + uint8_t index = oneshot_key.getRaw() - ranges::OS_FIRST; + return index; } -bool OneShot::isActive(Key key) { - uint8_t idx = key.getRaw() - ranges::OS_FIRST; - - return (state_[idx].active && !hasTimedOut()) || - state_[idx].pressed || - state_[idx].sticky; -} - -bool OneShot::isSticky(Key key) { - uint8_t idx = key.getRaw() - ranges::OS_FIRST; - - return state_[idx].sticky; -} - -bool OneShot::isModifierActive(Key key) { - if (key < Key_LeftControl || key > Key_RightGui) - return false; - - uint8_t idx = key.getKeyCode() - Key_LeftControl.getKeyCode(); - return state_[idx].active; -} - -void OneShot::cancel(bool with_stickies) { - should_cancel_ = true; - should_cancel_stickies_ = with_stickies; -} - -void OneShot::enableStickability(Key key) { - if (key >= ranges::OS_FIRST && key <= ranges::OS_LAST) - state_[key.getRaw() - ranges::OS_FIRST].stickable = true; -} - -void OneShot::disableStickability(Key key) { - if (key >= ranges::OS_FIRST && key <= ranges::OS_LAST) - state_[key.getRaw() - ranges::OS_FIRST].stickable = false; -} - -void OneShot::enableStickabilityForModifiers() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - state_[i].stickable = true; +uint8_t OneShot::getKeyIndex(Key key) { + // Default to returning a value that's out of range. This should be + // harmless because we only use the returned index to reference a + // bit in a bitfield, not as a memory address. + uint8_t n{oneshot_key_count}; + + if (isOneShotKey(key)) { + n = getOneShotKeyIndex(key); + } else if (isModifier(key)) { + n = key.getKeyCode() - Key_LeftControl.getKeyCode(); + } else if (isLayerShift(key)) { + n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; } + return n; } -void OneShot::enableStickabilityForLayers() { - for (uint8_t i = ONESHOT_KEY_COUNT / 2; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = true; +Key OneShot::decodeOneShotKey(Key oneshot_key) { + // The calling function is responsible for verifying that + // `oneshot_key` is an actual OneShot key (i.e. call + // `isOneShotKey(oneshot_key)` first). + uint8_t n = getOneShotKeyIndex(oneshot_key); + if (n < oneshot_mod_count) { + return Key(Key_LeftControl.getKeyCode() + n, + Key_LeftControl.getFlags()); + } else { + return Key(LAYER_SHIFT_OFFSET + n - oneshot_mod_count, + KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP); } } -void OneShot::disableStickabilityForModifiers() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - state_[i].stickable = false; - } -} +// ------------------------------------------------------------------------------ +// Helper functions for sending key events for keys in OneShot states -void OneShot::disableStickabilityForLayers() { - for (uint8_t i = ONESHOT_KEY_COUNT / 2; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = false; - } +void OneShot::pressKey(KeyAddr key_addr, Key oneshot_key) { + Key key = decodeOneShotKey(oneshot_key); + prev_key_addr_ = key_addr; + start_time_ = Runtime.millisAtCycleStart(); + temp_addrs_.set(key_addr); + handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED); } +void OneShot::holdKey(KeyAddr key_addr) { + handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | IS_PRESSED | INJECTED); } + +void OneShot::releaseKey(KeyAddr key_addr) { + glue_addrs_.clear(key_addr); + temp_addrs_.clear(key_addr); + handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); } +} // namespace plugin +} // namespace kaleidoscope + kaleidoscope::plugin::OneShot OneShot; diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 30c14167..033c5e87 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -19,6 +19,11 @@ #include "kaleidoscope/Runtime.h" #include +#include "kaleidoscope/key_events.h" +#include "kaleidoscope/KeyAddrBitfield.h" + +// ---------------------------------------------------------------------------- +// Keymap macros #define OSM(kc) Key(kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).getKeyCode() - Key_LeftControl.getKeyCode()) #define OSL(n) Key(kaleidoscope::ranges::OSL_FIRST + n) @@ -28,25 +33,11 @@ namespace plugin { class OneShot : public kaleidoscope::Plugin { public: - OneShot(void) { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = true; - } - } - - static bool isOneShotKey(Key key) { - return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && key.getRaw() <= kaleidoscope::ranges::OS_LAST); - } - static bool isActive(void); - static bool isActive(Key key); - static bool isPressed(); - static bool isSticky(); - static bool isSticky(Key key); - static void cancel(bool with_stickies = false); + // Constructor + OneShot() {} - static uint16_t time_out; - static int16_t double_tap_time_out; - static uint16_t hold_time_out; + // -------------------------------------------------------------------------- + // Configuration functions static inline void enableStickablity() {} static void enableStickability(Key key); @@ -68,46 +59,86 @@ class OneShot : public kaleidoscope::Plugin { static void disableStickabilityForModifiers(); static void disableStickabilityForLayers(); - static bool isStickable(Key key); + // -------------------------------------------------------------------------- + // Global test functions + + static bool isActive(); + static bool isSticky(); + + // -------------------------------------------------------------------------- + // Single-key test functions + static bool isOneShotKey(Key key) { + return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && + key.getRaw() <= kaleidoscope::ranges::OS_LAST); + } + static bool isStickable(Key key); // inline? + + static bool isTemporary(KeyAddr key_addr); // inline? + static bool isSticky(KeyAddr key_addr); // inline? + static bool isActive(KeyAddr key_addr); // inline? - static bool isModifierActive(Key key); + // -------------------------------------------------------------------------- + // Utility function for other plugins to cancel OneShot keys + static void cancel(bool with_stickies = false); + + // -------------------------------------------------------------------------- + // Vestigial functions? + void inject(Key key, uint8_t key_state) {} + static bool isModifierActive(Key key) { + return false; + } + + // -------------------------------------------------------------------------- + // Configuration variables (should probably be private) + static uint16_t time_out; + static uint16_t hold_time_out; + static int16_t double_tap_time_out; + + // -------------------------------------------------------------------------- + // Plugin hook functions EventHandlerResult onNameQuery(); + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); EventHandlerResult beforeReportingState(); EventHandlerResult afterEachCycle(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); - - void inject(Key mapped_key, uint8_t key_state); private: - static constexpr uint8_t ONESHOT_KEY_COUNT = 16; - typedef struct { - bool active: 1; - bool pressed: 1; - bool stickable: 1; - bool sticky: 1; - uint8_t __reserved: 4; - uint8_t position; - } key_state_t; - static key_state_t state_[ONESHOT_KEY_COUNT]; - static uint16_t start_time_; - static Key prev_key_; - static bool should_cancel_; - static bool should_cancel_stickies_; + // -------------------------------------------------------------------------- + // Constants + static constexpr uint8_t oneshot_key_count = 16; + static constexpr uint8_t oneshot_mod_count = 8; + static constexpr uint8_t oneshot_layer_count = oneshot_key_count - oneshot_mod_count; + static constexpr uint16_t stickable_modifiers_mask = uint16_t(uint16_t(-1) >> oneshot_layer_count); + static constexpr uint16_t stickable_layers_mask = uint16_t(uint16_t(-1) << oneshot_mod_count); + static constexpr KeyAddr invalid_key_addr = KeyAddr(KeyAddr::invalid_state); - static void injectNormalKey(uint8_t idx, uint8_t key_state); - static void activateOneShot(uint8_t idx); - static void cancelOneShot(uint8_t idx); + // -------------------------------------------------------------------------- + // State variables + static uint16_t stickable_keys_; - static bool isOneShotKey_(Key key) { - return key.getRaw() >= ranges::OS_FIRST && key.getRaw() <= ranges::OS_LAST; - } - static bool hasTimedOut() { - return Runtime.hasTimeExpired(start_time_, time_out); + static KeyAddrBitfield temp_addrs_; + static KeyAddrBitfield glue_addrs_; + + static uint16_t start_time_; + static KeyAddr prev_key_addr_; + static uint8_t release_countdown_; + + // -------------------------------------------------------------------------- + // Internal utility functions + static bool hasTimedOut(uint16_t ttl) { + return Runtime.hasTimeExpired(start_time_, ttl); } + static uint8_t getOneShotKeyIndex(Key oneshot_key); + static uint8_t getKeyIndex(Key key); + static Key decodeOneShotKey(Key oneshot_key); + + static void pressKey(KeyAddr key_addr, Key oneshot_key); + static void holdKey(KeyAddr key_addr); + static void releaseKey(KeyAddr key_addr); }; -} -} + +} // namespace plugin +} // namespace kaleidoscope extern kaleidoscope::plugin::OneShot OneShot; diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp index 14ce696d..732c461f 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp @@ -327,11 +327,10 @@ void Qukeys::flushEvent(Key event_key) { // that qukey's primary and alternate `Key` values for use later. We do this // because it's much more efficient than doing that as a separate step. bool Qukeys::isQukey(KeyAddr k) { - // First, look up the value from the keymap. We need to do a full lookup, not - // just looking up the cached value (i.e. `Layer.lookup(k)`), because the - // cached value will be out of date if a layer change happened since the - // keyswitch toggled on. - Key key = Layer.lookupOnActiveLayer(k); + // First, look up the value from the keymap. This value should be + // correct in the cache, even if there's been a layer change since + // the key was pressed. + Key key = Layer.lookup(k); // Next, we check to see if this is a DualUse-type qukey (defined in the keymap) if (isDualUseKey(key)) { diff --git a/src/kaleidoscope/KeyAddrBitfield.h b/src/kaleidoscope/KeyAddrBitfield.h new file mode 100644 index 00000000..cf4cc66c --- /dev/null +++ b/src/kaleidoscope/KeyAddrBitfield.h @@ -0,0 +1,184 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +#include "kaleidoscope/KeyAddr.h" + + +namespace kaleidoscope { + +// Return the number of `UnitType` units required to store `n` bits. Both `UnitType` & +// `WidthType` should be integer types. `WidthType` is whatever type the parameter `n` is +// stored as, and can be deduced by the compiler, so it's not necessary to declare it +// when calling this function (e.g. `bitfieldSize(n)`). The default `UnitType` +// is `byte` (i.e. `uint8_t`, which is almost always what we want, so most of the time we +// can also drop that template parameter (e.g. `bitfieldSize(n)`). +template +constexpr _WidthType bitfieldSize(_WidthType n) { + return ((n - 1) / (8 * sizeof(_UnitType))) + 1; +} + +// ================================================================================ +// Generic Bitfield class, useful for defining KeyAddrBitfield, and others. +class KeyAddrBitfield { + + public: + + static constexpr uint8_t size = KeyAddr::upper_limit; + static constexpr uint8_t block_size = 8 * sizeof(uint8_t); + static constexpr uint8_t total_blocks = bitfieldSize(size); + + static constexpr uint8_t blockIndex(KeyAddr k) { + return k.toInt() / block_size; + } + static constexpr uint8_t bitIndex(KeyAddr k) { + return k.toInt() % block_size; + } + static constexpr KeyAddr index(uint8_t block_index, uint8_t bit_index) { + uint8_t offset = (block_index * block_size) + bit_index; + return KeyAddr(offset); + } + bool read(KeyAddr k) const { + // assert(k.toInt() < size); + return bitRead(data_[blockIndex(k)], bitIndex(k)); + } + void set(KeyAddr k) { + // assert(k.toInt() < size); + bitSet(data_[blockIndex(k)], bitIndex(k)); + } + void clear(KeyAddr k) { + // assert(k.toInt() < size); + bitClear(data_[blockIndex(k)], bitIndex(k)); + } + void write(KeyAddr k, bool value) { + // assert(k.toInt() < size); + bitWrite(data_[blockIndex(k)], bitIndex(k), value); + } + + // This function returns the number of set bits in the bitfield up to and + // including the bit at index `k`. Two important things to note: it doesn't + // verify that the bit for index `k` is set (the caller must do so first, + // using `read()`), and what is returned is 1-indexed, so the caller will need + // to subtract 1 before using it as an array index (e.g. when doing a `Key` + // lookup for a sparse keymap layer). + uint8_t ordinal(KeyAddr k) const { + // assert(k.toInt() < size); + uint8_t block_index = blockIndex(k); + uint8_t count{0}; + for (uint8_t b{0}; b < block_index; ++b) { + count += __builtin_popcount(data_[b]); + } + uint8_t last_data_unit = data_[block_index]; + last_data_unit &= ~(0xFF << bitIndex(k)); + count += __builtin_popcount(last_data_unit); + return count; + } + + uint8_t &block(uint8_t block_index) { + // assert(block_index < total_blocks); + return data_[block_index]; + } + + private: + + uint8_t data_[total_blocks] = {}; + + + // ---------------------------------------------------------------------------- + // Iterator! + public: + class Iterator; + friend class KeyAddrBitfield::Iterator; + + Iterator begin() { + return Iterator{*this, 0}; + } + Iterator end() { + return Iterator{*this, total_blocks}; + } + + class Iterator { + public: + Iterator(KeyAddrBitfield &bitfield, uint8_t x) + : bitfield_(bitfield), block_index_(x) {} + + bool operator!=(const Iterator &other) { + // First, the test for the end condition (return false when all the blocks have been + // tested): + while (block_index_ < other.block_index_) { + // Get the data for the block at `block_index_` from the bitfield, then shift it + // by the number of bits we've already checked (`bit_index_`): + block_ = bitfield_.data_[block_index_]; + block_ >>= bit_index_; + + // Now we iterate through that block until we either find a bit that is set, or we + // find that there are no more bits set. If (as expected most of the time) no bits + // are set, we do nothing: + while (block_ != 0) { + // If the low (remaining) bit is set, generate an `KeyAddr` object from the + // bitfield coordinates and store it for the dereference operator to return: + if (block_ & 1) { + index_ = KeyAddrBitfield::index(block_index_, bit_index_); + return true; + } + // The low bit wasn't set, so we shift the data block by one and track that + // shift with the bit coordinate (`bit_index_`): + block_ >>= 1; + bit_index_ += 1; + } + + // When we're done checking a block, move on to the next one: + block_index_ += 1; + bit_index_ = 0; + } + return false; + } + + KeyAddr operator*() { + // assert(index_ < size); + return index_; + } + + void operator++() { + ++bit_index_; + } + + private: + KeyAddrBitfield &bitfield_; + uint8_t block_index_; // index of the block + uint8_t bit_index_{0}; // bit index in the block + uint8_t block_; + KeyAddr index_; + + }; // class Iterator { + +} __attribute__((packed)); // class KeyAddrBitfield { + +} // namespace kaleidoscope { + + +// ================================================================================ +// How to use the iterator above: +#if 0 +// To use the KeyAddrBitfield::Iterator, write a loop like the following: +KeyAddrBitfield bitfield; +for (KeyAddr k : bitfield) { + // Here, you'll get a `KeyAddr` object for each bit that is set in `bitfield`. +} +#endif From 6dd77b96c432e1e072fd25d4e04d074b6df5aaa2 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 18 Sep 2020 15:55:03 -0500 Subject: [PATCH 024/108] Allow user to customize which key will cancel one-shot keys Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Escape-OneShot/README.md | 12 +++++++++++- .../src/kaleidoscope/plugin/Escape-OneShot.cpp | 16 +++++++++------- .../src/kaleidoscope/plugin/Escape-OneShot.h | 9 +++++++++ .../src/Kaleidoscope-Ranges.h | 1 + 4 files changed, 30 insertions(+), 8 deletions(-) diff --git a/plugins/Kaleidoscope-Escape-OneShot/README.md b/plugins/Kaleidoscope-Escape-OneShot/README.md index 9db9ae65..8ce762f0 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/README.md +++ b/plugins/Kaleidoscope-Escape-OneShot/README.md @@ -27,7 +27,17 @@ The plugin only makes sense when using one-shot keys. ## Plugin methods -The plugin provides the `EscapeOneShot` object, which has no public methods. +The plugin provides the `EscapeOneShot` object, which has one public +configuration method: + +### `.setCancelKey(key)` + +> Changes the `Key` value that will trigger deactivation of one-shot +> (including sticky) keys. The default is to use `Key_Escape` (the +> normal `Esc` key), but if you would rather have a dedicated key (so +> that you can use `Key_Escape` in combination with one-shot +> modifiers), there is the special `OneShotCancelKey`, which will not +> have any side effects. ## Dependencies diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp index 26367a94..2f60bf5d 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp @@ -24,20 +24,22 @@ namespace kaleidoscope { namespace plugin { +Key EscapeOneShot::cancel_oneshot_key_{Key_Escape}; + EventHandlerResult EscapeOneShot::onKeyswitchEvent( Key &key, KeyAddr key_addr, uint8_t key_state) { - // We only act on an escape key that has just been pressed, and not - // generated by some other plugin. Also, only if at least one - // OneShot key is active and/or sticky. Last, only if there are no - // OneShot keys currently being held. - if (key == Key_Escape && + // We only act on an escape key (or `cancel_oneshot_key_`, if that has been + // set) that has just been pressed, and not generated by some other + // plugin. Also, only if at least one OneShot key is active and/or + // sticky. Last, only if there are no OneShot keys currently being held. + if (key == cancel_oneshot_key_ && keyToggledOn(key_state) && !(key_state & INJECTED) && ::OneShot.isActive()) { // Cancel all OneShot keys ::OneShot.cancel(true); - // Change the escape key to a blank key, and signal that event processing is - // complete. + // Change the cancellation key to a blank key, and signal that event + // processing is complete. key = Key_NoKey; return EventHandlerResult::EVENT_CONSUMED; } diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h index bc52a8ad..4e0393cd 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h @@ -19,6 +19,8 @@ #include "kaleidoscope/Runtime.h" +constexpr Key OneShotCancelKey {kaleidoscope::ranges::OS_CANCEL}; + namespace kaleidoscope { namespace plugin { class EscapeOneShot : public kaleidoscope::Plugin { @@ -26,6 +28,13 @@ class EscapeOneShot : public kaleidoscope::Plugin { EscapeOneShot(void) {} EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + + void setCancelKey(Key cancel_key) { + cancel_oneshot_key_ = cancel_key; + } + + private: + static Key cancel_oneshot_key_; }; } } diff --git a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h index 75a87388..cd6ef682 100644 --- a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h +++ b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h @@ -56,6 +56,7 @@ enum : uint16_t { OSL_FIRST, OSL_LAST = OSL_FIRST + 7, OS_LAST = OSL_LAST, + OS_CANCEL, DU_FIRST, DUM_FIRST = DU_FIRST, DUM_LAST = DUM_FIRST + (8 << 8), From 341388995a5e192521c35897796ee20cc910c246 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 14 Sep 2020 13:17:09 -0500 Subject: [PATCH 025/108] Deprecate old OneShot functions and public member variables Deprecates OneShot direct-access configuration variables, and replaces them with setter functions: - `time_out` => `setTimeout()` - `hold_time_out` => `setHoldTimeout()` - `double_tap_time_out` => `setDoubleTapTimeout()` Deprecating public member variables is tricky, but possible. I've created new, private member variables, and added code to keep them in sync with the deprecated public ones for now. Also of note: The old `OneShot.inject()` function should now be unnecessary for most purposes. It still works, but has a potential undesirable side effect. It now needs to pick a physical keyswitch address to use for the injected OneShot key, and that key will not be usable for its normal value until that OneShot key is deactivated. Because of this, use of `inject()` is not strongly discouraged. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/OneShot.cpp | 82 +++++++++++++++++- .../src/kaleidoscope/plugin/OneShot.h | 83 ++++++++++++++++++- 2 files changed, 159 insertions(+), 6 deletions(-) diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 99a78850..59c9e86b 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -27,6 +27,11 @@ namespace plugin { // ---------------------------------------------------------------------------- // Configuration variables +uint16_t OneShot::timeout_ = 2500; +uint16_t OneShot::hold_timeout_ = 250; +int16_t OneShot::double_tap_timeout_ = -1; + +// Deprecated uint16_t OneShot::time_out = 2500; uint16_t OneShot::hold_time_out = 250; int16_t OneShot::double_tap_time_out = -1; @@ -215,7 +220,7 @@ EventHandlerResult OneShot::onKeyswitchEvent( temp_addrs_.clear(key_addr); // Derive the true double-tap timeout value if we're using the default. - uint16_t dtto = (double_tap_time_out < 0) ? time_out : double_tap_time_out; + uint16_t dtto = (double_tap_timeout_ < 0) ? timeout_ : double_tap_timeout_; // If the key is not stickable, or the double-tap timeout has // expired, clear the `glue` state, as well; this OneShot key @@ -271,7 +276,7 @@ EventHandlerResult OneShot::onKeyswitchEvent( // This key is in the "pending" OneShot state. We need to check // its hold timeout, and turn it back into a normal key if it // has timed out. - if (hasTimedOut(hold_time_out)) { + if (hasTimedOut(hold_timeout_)) { temp_addrs_.clear(key_addr); } } @@ -305,7 +310,7 @@ EventHandlerResult OneShot::afterEachCycle() { // gets set to 2 on the press of a normal key when there are any // active OneShot keys; that way, the OneShot keys will stay active // long enough to apply to the newly-pressed key. - if ((release_countdown_ == 1) || hasTimedOut(time_out)) { + if ((release_countdown_ == 1) || hasTimedOut(timeout_)) { for (KeyAddr key_addr : temp_addrs_) { if (glue_addrs_.read(key_addr)) { releaseKey(key_addr); @@ -319,6 +324,14 @@ EventHandlerResult OneShot::afterEachCycle() { // for zero to avoid underflow. release_countdown_ >>= 1; + // Temporary fix for deprecated variables +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + timeout_ = time_out; + hold_timeout_ = hold_time_out; + double_tap_timeout_ = double_tap_time_out; +#pragma GCC diagnostic pop + return EventHandlerResult::OK; } @@ -387,6 +400,69 @@ void OneShot::releaseKey(KeyAddr key_addr) { handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); } +// ------------------------------------------------------------------------------ +// Deprecated functions + +void OneShot::inject(Key key, uint8_t key_state) { + if (! isOneShotKey(key)) { + return; + } + // Find an idle keyswitch to use for the injected OneShot key and activate + // it. This is an ugly hack, but it will work. It does mean that whatever key + // is used will be unavailable for its normal function until the injected + // OneShot key is deactivated, so use of `inject()` is strongly discouraged. + for (KeyAddr key_addr : KeyAddr::all()) { + if (live_keys[key_addr] == Key_Transparent) { + pressKey(key_addr, key); + glue_addrs_.set(key_addr); + break; + } + } +} + +bool OneShot::isModifierActive(Key key) { + // This actually works for any `Key` value, not just modifiers. Because we're + // just searching the keymap cache, it's also possible to return a false + // positive (a plugin might have altered the cache for an idle `KeyAddr`), or + // a false negative (a plugin might be inserting a modifier without a valid + // `KeyAddr`), but as this is a deprecated function, I think this is good + // enough. + for (KeyAddr key_addr : KeyAddr::all()) { + if (live_keys[key_addr] == key) { + return true; + } + } + return false; +} + +bool OneShot::isActive(Key oneshot_key) { + if (! isOneShotKey(oneshot_key)) { + return false; + } + Key key = decodeOneShotKey(oneshot_key); + for (KeyAddr key_addr : glue_addrs_) { + if (live_keys[key_addr] == key) { + return true; + } + } + return false; +} + +bool OneShot::isSticky(Key oneshot_key) { + if (! isOneShotKey(oneshot_key)) { + return false; + } + Key key = decodeOneShotKey(oneshot_key); + for (KeyAddr key_addr : glue_addrs_) { + if (live_keys[key_addr] == key && + !temp_addrs_.read(key_addr)) { + return true; + } + } + return false; +} + + } // namespace plugin } // namespace kaleidoscope diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 033c5e87..d8dd9d77 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -22,6 +22,38 @@ #include "kaleidoscope/key_events.h" #include "kaleidoscope/KeyAddrBitfield.h" +// ---------------------------------------------------------------------------- +// Deprecation warning messages +#define _DEPRECATED_MESSAGE_ONESHOT_TIMEOUT \ + "The `OneShot.time_out` variable is deprecated. Please use the\n" \ + "`OneShot.setTimeout()` function instead." + +#define _DEPRECATED_MESSAGE_ONESHOT_HOLD_TIMEOUT \ + "The `OneShot.hold_time_out` variable is deprecated. Please use the\n" \ + "`OneShot.setHoldTimeout()` function instead." + +#define _DEPRECATED_MESSAGE_ONESHOT_DOUBLE_TAP_TIMEOUT \ + "The `OneShot.double_tap_time_out` variable is deprecated. Please use the\n" \ + "`OneShot.setDoubleTapTimeout()` function instead." + +#define _DEPRECATED_MESSAGE_ONESHOT_INJECT \ + "The `OneShot.inject(key, key_state)` function has been deprecated." + +#define _DEPRECATED_MESSAGE_ONESHOT_ISACTIVE_KEY \ + "The `OneShot.isActive(key)` function is deprecated. Please use\n" \ + "`OneShot.isActive(key_addr)` instead, if possible." + +#define _DEPRECATED_MESSAGE_ONESHOT_ISSTICKY_KEY \ + "The `OneShot.isSticky(key)` function is deprecated. Please use\n" \ + "`OneShot.isSticky(key_addr)` instead, if possible." + +#define _DEPRECATED_MESSAGE_ONESHOT_ISPRESSED \ + "The `OneShot.isPressed()` function is deprecated. This function now\n" \ + "always returns false." + +#define _DEPRECATED_MESSAGE_ONESHOT_ISMODIFIERACTIVE \ + "The `OneShot.isModifierActive()` function is deprecated." + // ---------------------------------------------------------------------------- // Keymap macros @@ -82,16 +114,55 @@ class OneShot : public kaleidoscope::Plugin { static void cancel(bool with_stickies = false); // -------------------------------------------------------------------------- - // Vestigial functions? - void inject(Key key, uint8_t key_state) {} - static bool isModifierActive(Key key) { + // Deprecated functions + DEPRECATED(ONESHOT_INJECT) + void inject(Key key, uint8_t key_state); + + DEPRECATED(ONESHOT_ISMODIFIERACTIVE) + static bool isModifierActive(Key key); + + DEPRECATED(ONESHOT_ISACTIVE_KEY) + static bool isActive(Key oneshot_key); + + DEPRECATED(ONESHOT_ISSTICKY_KEY) + static bool isSticky(Key oneshot_key); + + DEPRECATED(ONESHOT_ISPRESSED) + static bool isPressed() { return false; } + // -------------------------------------------------------------------------- + // Timeout onfiguration functions + static void setTimeout(uint16_t ttl) { + timeout_ = ttl; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + time_out = ttl; +#pragma GCC diagnostic pop + } + static void setHoldTimeout(uint16_t ttl) { + hold_timeout_ = ttl; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + hold_time_out = ttl; +#pragma GCC diagnostic pop + } + static void setDoubleTapTimeout(int16_t ttl) { + double_tap_timeout_ = ttl; +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + double_tap_time_out = ttl; +#pragma GCC diagnostic pop + } + // -------------------------------------------------------------------------- // Configuration variables (should probably be private) + DEPRECATED(ONESHOT_TIMEOUT) static uint16_t time_out; + DEPRECATED(ONESHOT_HOLD_TIMEOUT) static uint16_t hold_time_out; + DEPRECATED(ONESHOT_DOUBLE_TAP_TIMEOUT) static int16_t double_tap_time_out; // -------------------------------------------------------------------------- @@ -113,6 +184,12 @@ class OneShot : public kaleidoscope::Plugin { static constexpr uint16_t stickable_layers_mask = uint16_t(uint16_t(-1) << oneshot_mod_count); static constexpr KeyAddr invalid_key_addr = KeyAddr(KeyAddr::invalid_state); + // -------------------------------------------------------------------------- + // Configuration variables + static uint16_t timeout_; + static uint16_t hold_timeout_; + static int16_t double_tap_timeout_; + // -------------------------------------------------------------------------- // State variables static uint16_t stickable_keys_; From bac8e7569895033d5ec7b2d5e586db974d44538e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 16 Sep 2020 13:29:55 -0500 Subject: [PATCH 026/108] Add automatic OneShot modifier/layer-shift feature This adds a new feature to OneShot: it can now (optionally) treat modifiers and layer-shift keys as automatic OneShot keys, with functions to enable and disable this feature for modifiers and layer-shifts independently. Signed-off-by: Michael Richters --- .../plugin/LED-ActiveModColor.cpp | 3 +- .../src/kaleidoscope/plugin/OneShot.cpp | 31 ++++++++------ .../src/kaleidoscope/plugin/OneShot.h | 41 +++++++++++++++++++ 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 1b293cdc..30f71222 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -45,8 +45,7 @@ EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( // it will get highlighted. Conditionally (if // `highlight_normal_modifiers_` is set), we also highlight // modifier and layer-shift keys. - if ((key >= Key_LeftControl && key <= Key_RightGui) || - (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))) { + if (::OneShot.isModifier() || ::OneShot.isLayerShift()) { mod_key_bits_.set(key_addr); } } else if (keyToggledOff(key_state)) { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 59c9e86b..6ba00454 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -41,6 +41,9 @@ int16_t OneShot::double_tap_time_out = -1; uint16_t OneShot::stickable_keys_ = -1; +bool OneShot::auto_modifiers_ = false; +bool OneShot::auto_layers_ = false; + KeyAddrBitfield OneShot::temp_addrs_; KeyAddrBitfield OneShot::glue_addrs_; @@ -199,7 +202,9 @@ EventHandlerResult OneShot::onKeyswitchEvent( if (!temp && !glue) { // This key_addr is not in a OneShot state. - if (isOneShotKey(key)) { + if (isOneShotKey(key) || + (auto_modifiers_ && isModifier(key)) || + (auto_layers_ && isLayerShift(key))) { // Replace the OneShot key with its corresponding normal key. pressKey(key_addr, key); return EventHandlerResult::ABORT; @@ -382,8 +387,10 @@ Key OneShot::decodeOneShotKey(Key oneshot_key) { // ------------------------------------------------------------------------------ // Helper functions for sending key events for keys in OneShot states -void OneShot::pressKey(KeyAddr key_addr, Key oneshot_key) { - Key key = decodeOneShotKey(oneshot_key); +void OneShot::pressKey(KeyAddr key_addr, Key key) { + if (isOneShotKey(key)) { + key = decodeOneShotKey(key); + } prev_key_addr_ = key_addr; start_time_ = Runtime.millisAtCycleStart(); temp_addrs_.set(key_addr); @@ -404,8 +411,8 @@ void OneShot::releaseKey(KeyAddr key_addr) { // Deprecated functions void OneShot::inject(Key key, uint8_t key_state) { - if (! isOneShotKey(key)) { - return; + if (isOneShotKey(key)) { + key = decodeOneShotKey(key); } // Find an idle keyswitch to use for the injected OneShot key and activate // it. This is an ugly hack, but it will work. It does mean that whatever key @@ -435,11 +442,10 @@ bool OneShot::isModifierActive(Key key) { return false; } -bool OneShot::isActive(Key oneshot_key) { - if (! isOneShotKey(oneshot_key)) { - return false; +bool OneShot::isActive(Key key) { + if (isOneShotKey(key)) { + key = decodeOneShotKey(key); } - Key key = decodeOneShotKey(oneshot_key); for (KeyAddr key_addr : glue_addrs_) { if (live_keys[key_addr] == key) { return true; @@ -448,11 +454,10 @@ bool OneShot::isActive(Key oneshot_key) { return false; } -bool OneShot::isSticky(Key oneshot_key) { - if (! isOneShotKey(oneshot_key)) { - return false; +bool OneShot::isSticky(Key key) { + if (isOneShotKey(key)) { + key = decodeOneShotKey(key); } - Key key = decodeOneShotKey(oneshot_key); for (KeyAddr key_addr : glue_addrs_) { if (live_keys[key_addr] == key && !temp_addrs_.read(key_addr)) { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index d8dd9d77..f900f587 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -91,6 +91,42 @@ class OneShot : public kaleidoscope::Plugin { static void disableStickabilityForModifiers(); static void disableStickabilityForLayers(); + static void enableAutoModifiers() { + auto_modifiers_ = true; + } + static void enableAutoLayers() { + auto_layers_ = true; + } + static void enableAutoOneShot() { + enableAutoModifiers(); + enableAutoLayers(); + } + + static void disableAutoModifiers() { + auto_modifiers_ = false; + } + static void disableAutoLayers() { + auto_layers_ = false; + } + static void disableAutoOneShot() { + disableAutoModifiers(); + disableAutoLayers(); + } + + static void toggleAutoModifiers() { + auto_modifiers_ = ! auto_modifiers_; + } + static void toggleAutoLayers() { + auto_layers_ = ! auto_layers_; + } + static void toggleAutoOneShot() { + if (auto_modifiers_ || auto_layers_) { + disableAutoOneShot(); + } else { + enableAutoOneShot(); + } + } + // -------------------------------------------------------------------------- // Global test functions @@ -103,6 +139,9 @@ class OneShot : public kaleidoscope::Plugin { return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && key.getRaw() <= kaleidoscope::ranges::OS_LAST); } + static bool isModifier(Key key); + static bool isLayerShift(Key key); + static bool isStickable(Key key); // inline? static bool isTemporary(KeyAddr key_addr); // inline? @@ -193,6 +232,8 @@ class OneShot : public kaleidoscope::Plugin { // -------------------------------------------------------------------------- // State variables static uint16_t stickable_keys_; + static bool auto_modifiers_; + static bool auto_layers_; static KeyAddrBitfield temp_addrs_; static KeyAddrBitfield glue_addrs_; From af19a43146371a0ae5e04d676d665db36ccdbecc Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 16 Sep 2020 23:29:20 -0500 Subject: [PATCH 027/108] Add `OneShot_MetaStickyKey` This is a special OneShot key that makes any subsequently-pressed key sticky, regardless of its value. Signed-off-by: Michael Richters --- .../plugin/LED-ActiveModColor.cpp | 4 +- .../src/kaleidoscope/plugin/OneShot.cpp | 47 +++++++++++++++++-- .../src/kaleidoscope/plugin/OneShot.h | 5 ++ .../src/Kaleidoscope-Ranges.h | 3 +- 4 files changed, 54 insertions(+), 5 deletions(-) diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 30f71222..30ed1c07 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -45,7 +45,9 @@ EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( // it will get highlighted. Conditionally (if // `highlight_normal_modifiers_` is set), we also highlight // modifier and layer-shift keys. - if (::OneShot.isModifier() || ::OneShot.isLayerShift()) { + if (::OneShot.isModifier(key) || + ::OneShot.isLayerShift(key) || + ::OneShot.isActive(key_addr)) { mod_key_bits_.set(key_addr); } } else if (keyToggledOff(key_state)) { diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 6ba00454..27550cee 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -44,6 +44,8 @@ uint16_t OneShot::stickable_keys_ = -1; bool OneShot::auto_modifiers_ = false; bool OneShot::auto_layers_ = false; +KeyAddr OneShot::meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + KeyAddrBitfield OneShot::temp_addrs_; KeyAddrBitfield OneShot::glue_addrs_; @@ -140,6 +142,8 @@ bool OneShot::isStickable(Key key) { if (n < oneshot_key_count) { return bitRead(stickable_keys_, n); } + } else if (key == OneShot_MetaStickyKey) { + return true; } return false; } @@ -202,13 +206,40 @@ EventHandlerResult OneShot::onKeyswitchEvent( if (!temp && !glue) { // This key_addr is not in a OneShot state. - if (isOneShotKey(key) || - (auto_modifiers_ && isModifier(key)) || - (auto_layers_ && isLayerShift(key))) { + if (meta_sticky_key_addr_.isValid()) { + // If the meta key isn't sticky, release it + bool ms_temp = temp_addrs_.read(meta_sticky_key_addr_); + bool ms_glue = glue_addrs_.read(meta_sticky_key_addr_); + if (ms_temp) { + if (ms_glue) { + // ms key is temp one-shot + releaseKey(meta_sticky_key_addr_); + meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + } else { + // ms key is held + temp_addrs_.clear(meta_sticky_key_addr_); + } + } else { + // ms key is sticky + } + glue_addrs_.set(key_addr); + //prev_key_addr_ = key_addr; + start_time_ = Runtime.millisAtCycleStart(); + //return EventHandlerResult::OK; + + } else if (key == OneShot_MetaStickyKey) { + meta_sticky_key_addr_ = key_addr; + temp_addrs_.set(key_addr); + start_time_ = Runtime.millisAtCycleStart(); + + } else if (isOneShotKey(key) || + (auto_modifiers_ && isModifier(key)) || + (auto_layers_ && isLayerShift(key))) { // Replace the OneShot key with its corresponding normal key. pressKey(key_addr, key); return EventHandlerResult::ABORT; } else if (!isModifier(key) && !isLayerShift(key)) { + // Only trigger release of temporary OneShot keys if the // pressed key is neither a modifier nor a layer shift. release_countdown_ = (1 << 1); @@ -246,6 +277,9 @@ EventHandlerResult OneShot::onKeyswitchEvent( // This is a sticky OneShot key that has been pressed. Clear // state now, so it will become a normal key. glue_addrs_.clear(key_addr); + // Then replace the key toggled on event with a key held event. + holdKey(key_addr); + return EventHandlerResult::EVENT_CONSUMED; } else { // (temp && !glue) // A key has been pressed that is in the "pending" OneShot @@ -273,6 +307,9 @@ EventHandlerResult OneShot::onKeyswitchEvent( // `beforeReportingState()` hook below. //Layer.updateLiveCompositeKeymap(key_addr, key); return EventHandlerResult::ABORT; + } else if (key == OneShot_MetaStickyKey) { + meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + //cancel(true); } } else { @@ -404,6 +441,10 @@ void OneShot::holdKey(KeyAddr key_addr) { void OneShot::releaseKey(KeyAddr key_addr) { glue_addrs_.clear(key_addr); temp_addrs_.clear(key_addr); + + if (live_keys[key_addr] == OneShot_MetaStickyKey) + meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); } diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index f900f587..17d1270e 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -60,6 +60,10 @@ #define OSM(kc) Key(kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).getKeyCode() - Key_LeftControl.getKeyCode()) #define OSL(n) Key(kaleidoscope::ranges::OSL_FIRST + n) +// ---------------------------------------------------------------------------- +// Key constants +constexpr Key OneShot_MetaStickyKey {kaleidoscope::ranges::OS_META_STICKY}; + namespace kaleidoscope { namespace plugin { @@ -234,6 +238,7 @@ class OneShot : public kaleidoscope::Plugin { static uint16_t stickable_keys_; static bool auto_modifiers_; static bool auto_layers_; + static KeyAddr meta_sticky_key_addr_; static KeyAddrBitfield temp_addrs_; static KeyAddrBitfield glue_addrs_; diff --git a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h index cd6ef682..7b52d36e 100644 --- a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h +++ b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h @@ -56,7 +56,6 @@ enum : uint16_t { OSL_FIRST, OSL_LAST = OSL_FIRST + 7, OS_LAST = OSL_LAST, - OS_CANCEL, DU_FIRST, DUM_FIRST = DU_FIRST, DUM_LAST = DUM_FIRST + (8 << 8), @@ -79,6 +78,8 @@ enum : uint16_t { TURBO, DYNAMIC_MACRO_FIRST, DYNAMIC_MACRO_LAST = DYNAMIC_MACRO_FIRST + 31, + OS_META_STICKY, + OS_CANCEL, SAFE_START, KALEIDOSCOPE_SAFE_START = SAFE_START From 52c1aef362d8c3a7c5d311836aadcb4992ab8ced Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Thu, 17 Sep 2020 10:39:00 -0500 Subject: [PATCH 028/108] Enable conditional compilation of PrepStickyKey & deprecated code Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/OneShot.cpp | 34 ++++++++++++++----- .../src/kaleidoscope/plugin/OneShot.h | 13 ++++++- 2 files changed, 37 insertions(+), 10 deletions(-) diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 27550cee..51cd54b4 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -32,9 +32,11 @@ uint16_t OneShot::hold_timeout_ = 250; int16_t OneShot::double_tap_timeout_ = -1; // Deprecated +#ifndef NDEPRECATED uint16_t OneShot::time_out = 2500; uint16_t OneShot::hold_time_out = 250; int16_t OneShot::double_tap_time_out = -1; +#endif // ---------------------------------------------------------------------------- // State variables @@ -44,8 +46,6 @@ uint16_t OneShot::stickable_keys_ = -1; bool OneShot::auto_modifiers_ = false; bool OneShot::auto_layers_ = false; -KeyAddr OneShot::meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; - KeyAddrBitfield OneShot::temp_addrs_; KeyAddrBitfield OneShot::glue_addrs_; @@ -53,6 +53,10 @@ uint16_t OneShot::start_time_ = 0; KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr; uint8_t OneShot::release_countdown_ = 0; +#ifndef ONESHOT_WITHOUT_METASTICKY +KeyAddr OneShot::meta_sticky_key_addr_ {KeyAddr::invalid_state}; +#endif + // ============================================================================ // Public interface @@ -142,8 +146,10 @@ bool OneShot::isStickable(Key key) { if (n < oneshot_key_count) { return bitRead(stickable_keys_, n); } +#ifndef ONESHOT_WITHOUT_METASTICKY } else if (key == OneShot_MetaStickyKey) { return true; +#endif } return false; } @@ -206,6 +212,7 @@ EventHandlerResult OneShot::onKeyswitchEvent( if (!temp && !glue) { // This key_addr is not in a OneShot state. +#ifndef ONESHOT_WITHOUT_METASTICKY if (meta_sticky_key_addr_.isValid()) { // If the meta key isn't sticky, release it bool ms_temp = temp_addrs_.read(meta_sticky_key_addr_); @@ -231,10 +238,15 @@ EventHandlerResult OneShot::onKeyswitchEvent( meta_sticky_key_addr_ = key_addr; temp_addrs_.set(key_addr); start_time_ = Runtime.millisAtCycleStart(); - - } else if (isOneShotKey(key) || - (auto_modifiers_ && isModifier(key)) || - (auto_layers_ && isLayerShift(key))) { + } else // NOLINT +#endif + // *INDENT-OFF* + // Because of the preceding #ifdef, indentation gets thrown off for astyle here. + // This is only an independent `if` block if `ONESHOT_WITHOUT_METASTICKY` + // is set (see above); otherwise it's an `else if`. + if (isOneShotKey(key) || + (auto_modifiers_ && isModifier(key)) || + (auto_layers_ && isLayerShift(key))) { // Replace the OneShot key with its corresponding normal key. pressKey(key_addr, key); return EventHandlerResult::ABORT; @@ -244,7 +256,7 @@ EventHandlerResult OneShot::onKeyswitchEvent( // pressed key is neither a modifier nor a layer shift. release_countdown_ = (1 << 1); } - // return EventHandlerResult::OK; + // *INDENT-ON* } else if (temp && glue) { // This key_addr is in the temporary OneShot state. @@ -307,9 +319,10 @@ EventHandlerResult OneShot::onKeyswitchEvent( // `beforeReportingState()` hook below. //Layer.updateLiveCompositeKeymap(key_addr, key); return EventHandlerResult::ABORT; +#ifndef ONESHOT_WITHOUT_METASTICKY } else if (key == OneShot_MetaStickyKey) { meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; - //cancel(true); +#endif } } else { @@ -367,12 +380,14 @@ EventHandlerResult OneShot::afterEachCycle() { release_countdown_ >>= 1; // Temporary fix for deprecated variables +#ifndef NDEPRECATED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" timeout_ = time_out; hold_timeout_ = hold_time_out; double_tap_timeout_ = double_tap_time_out; #pragma GCC diagnostic pop +#endif return EventHandlerResult::OK; } @@ -450,7 +465,7 @@ void OneShot::releaseKey(KeyAddr key_addr) { // ------------------------------------------------------------------------------ // Deprecated functions - +#ifndef NDEPRECATED void OneShot::inject(Key key, uint8_t key_state) { if (isOneShotKey(key)) { key = decodeOneShotKey(key); @@ -507,6 +522,7 @@ bool OneShot::isSticky(Key key) { } return false; } +#endif } // namespace plugin diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 17d1270e..edf2be7d 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -158,6 +158,7 @@ class OneShot : public kaleidoscope::Plugin { // -------------------------------------------------------------------------- // Deprecated functions +#ifndef NDEPRECATED DEPRECATED(ONESHOT_INJECT) void inject(Key key, uint8_t key_state); @@ -174,29 +175,36 @@ class OneShot : public kaleidoscope::Plugin { static bool isPressed() { return false; } +#endif // -------------------------------------------------------------------------- // Timeout onfiguration functions static void setTimeout(uint16_t ttl) { timeout_ = ttl; +#ifndef NDEPRECATED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" time_out = ttl; #pragma GCC diagnostic pop +#endif } static void setHoldTimeout(uint16_t ttl) { hold_timeout_ = ttl; +#ifndef NDEPRECATED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" hold_time_out = ttl; #pragma GCC diagnostic pop +#endif } static void setDoubleTapTimeout(int16_t ttl) { double_tap_timeout_ = ttl; +#ifndef NDEPRECATED #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wdeprecated-declarations" double_tap_time_out = ttl; #pragma GCC diagnostic pop +#endif } // -------------------------------------------------------------------------- @@ -238,7 +246,6 @@ class OneShot : public kaleidoscope::Plugin { static uint16_t stickable_keys_; static bool auto_modifiers_; static bool auto_layers_; - static KeyAddr meta_sticky_key_addr_; static KeyAddrBitfield temp_addrs_; static KeyAddrBitfield glue_addrs_; @@ -247,6 +254,10 @@ class OneShot : public kaleidoscope::Plugin { static KeyAddr prev_key_addr_; static uint8_t release_countdown_; +#ifndef ONESHOT_WITHOUT_METASTICKY + static KeyAddr meta_sticky_key_addr_; +#endif + // -------------------------------------------------------------------------- // Internal utility functions static bool hasTimedOut(uint16_t ttl) { From 58f56236a16129d4357de8a9e342c210f8147f7a Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 19 Oct 2020 23:30:26 -0500 Subject: [PATCH 029/108] Add `OneShot_ActiveStickyKey` This key makes any held key (or otherwise active key, most likely OneShot keys) sticky when it toggles on. Signed-off-by: Michael Richters --- .../plugin/LED-ActiveModColor.cpp | 12 +++++++++++ .../src/kaleidoscope/plugin/OneShot.cpp | 20 +++++++++++++++++++ .../src/kaleidoscope/plugin/OneShot.h | 1 + .../src/Kaleidoscope-Ranges.h | 1 + 4 files changed, 34 insertions(+) diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 30ed1c07..2e4155df 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -50,6 +50,18 @@ EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( ::OneShot.isActive(key_addr)) { mod_key_bits_.set(key_addr); } + if (key == OneShot_ActiveStickyKey) { + for (KeyAddr entry_addr : KeyAddr::all()) { + // Get the entry from the keymap cache + Key entry_key = Layer.lookup(entry_addr); + // Skip empty entries + if (entry_key == Key_Transparent || entry_key == Key_NoKey) { + continue; + } + // Highlight everything else + mod_key_bits_.set(entry_addr); + } + } } else if (keyToggledOff(key_state)) { // Things get a bit ugly here because this plugin might come // before OneShot in the order, so we can't just count on OneShot diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 51cd54b4..a8de1935 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -210,6 +210,26 @@ EventHandlerResult OneShot::onKeyswitchEvent( if (keyToggledOn(key_state)) { + // Make all held keys sticky if `OneShot_ActiveStickyKey` toggles on. + if (key == OneShot_ActiveStickyKey) { + // Skip the stickify key itself + for (KeyAddr entry_addr : KeyAddr::all()) { + if (entry_addr == key_addr) { + continue; + } + // Get the entry from the keymap cache + Key entry_key = live_keys[entry_addr]; + // Skip empty entries + if (entry_key == Key_Transparent || entry_key == Key_NoKey) { + continue; + } + // Make everything else sticky + temp_addrs_.clear(entry_addr); + glue_addrs_.set(entry_addr); + } + return EventHandlerResult::OK; + } + if (!temp && !glue) { // This key_addr is not in a OneShot state. #ifndef ONESHOT_WITHOUT_METASTICKY diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index edf2be7d..633f8d22 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -63,6 +63,7 @@ // ---------------------------------------------------------------------------- // Key constants constexpr Key OneShot_MetaStickyKey {kaleidoscope::ranges::OS_META_STICKY}; +constexpr Key OneShot_ActiveStickyKey {kaleidoscope::ranges::OS_ACTIVE_STICKY}; namespace kaleidoscope { namespace plugin { diff --git a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h index 7b52d36e..d17fecfd 100644 --- a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h +++ b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h @@ -79,6 +79,7 @@ enum : uint16_t { DYNAMIC_MACRO_FIRST, DYNAMIC_MACRO_LAST = DYNAMIC_MACRO_FIRST + 31, OS_META_STICKY, + OS_ACTIVE_STICKY, OS_CANCEL, SAFE_START, From bdfa7f5894be1408da90d5af12f49ac11892364e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 28 Apr 2021 14:45:02 -0500 Subject: [PATCH 030/108] Fix a bug in `Key.isLayerShift()` It was failing to exclude `MoveToLayer()` keys, so it would return `true` incorrectly for them. Signed-off-by: Michael Richters --- src/kaleidoscope/key_defs.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/kaleidoscope/key_defs.h b/src/kaleidoscope/key_defs.h index c0a0f06c..ef8358f3 100644 --- a/src/kaleidoscope/key_defs.h +++ b/src/kaleidoscope/key_defs.h @@ -223,7 +223,8 @@ class Key { // worth singling them out. constexpr bool isLayerShift() const { return (isLayerKey() && - (keyCode_ >= LAYER_SHIFT_OFFSET)); + keyCode_ >= LAYER_SHIFT_OFFSET && + keyCode_ < LAYER_MOVE_OFFSET); } private: From bde30b0831fc884e24f9966f65c6ea7f00a7f370 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 28 Apr 2021 14:45:32 -0500 Subject: [PATCH 031/108] Use `Key.isKeyboardModifier()` & `Key.isLayerShift()` in OneShot Instead of using a separate version defined in the plugin itself. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/OneShot.cpp | 25 +++---------------- .../src/kaleidoscope/plugin/OneShot.h | 2 -- 2 files changed, 4 insertions(+), 23 deletions(-) diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index a8de1935..9b5c7c5f 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -119,29 +119,12 @@ bool OneShot::isSticky() { // could potentially use three different color values for the three // states (sticky | active && !sticky | pressed && !active). -bool OneShot::isModifier(Key key) { - // Returns `true` if `key` is a modifier key, including modifiers - // with extra mod flags applied (e.g. `Key_Meh`). - if ((key.getFlags() & (SYNTHETIC | RESERVED)) != 0) { - return false; - } - return (key.getKeyCode() >= Key_LeftControl.getKeyCode() && - key.getKeyCode() <= Key_RightGui.getKeyCode()); -} - -bool OneShot::isLayerShift(Key key) { - // Returns `true` if `key` is a layer-shift key. - return (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP) && - key.getKeyCode() >= LAYER_SHIFT_OFFSET && - key.getKeyCode() < LAYER_MOVE_OFFSET); -} - bool OneShot::isStickable(Key key) { int8_t n; - if (isModifier(key)) { + if (key.isKeyboardModifier()) { n = key.getKeyCode() - Key_LeftControl.getKeyCode(); return bitRead(stickable_keys_, n); - } else if (isLayerShift(key)) { + } else if (key.isLayerShift()) { n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; if (n < oneshot_key_count) { return bitRead(stickable_keys_, n); @@ -434,9 +417,9 @@ uint8_t OneShot::getKeyIndex(Key key) { if (isOneShotKey(key)) { n = getOneShotKeyIndex(key); - } else if (isModifier(key)) { + } else if (key.isKeyboardModifier()) { n = key.getKeyCode() - Key_LeftControl.getKeyCode(); - } else if (isLayerShift(key)) { + } else if (key.isLayerShift()) { n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; } return n; diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 633f8d22..afad5c3a 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -144,8 +144,6 @@ class OneShot : public kaleidoscope::Plugin { return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && key.getRaw() <= kaleidoscope::ranges::OS_LAST); } - static bool isModifier(Key key); - static bool isLayerShift(Key key); static bool isStickable(Key key); // inline? From 682019493a665b1e0167111765b721937de973f9 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 14 Apr 2021 20:22:36 -0500 Subject: [PATCH 032/108] Update Kaleidoscope-Ranges testcase for new OneShot keys The new items have been added to the end of the list (before `SAFE_START`), where they belong. Signed-off-by: Michael Richters --- tests/issues/1010/test/testcase.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/tests/issues/1010/test/testcase.cpp b/tests/issues/1010/test/testcase.cpp index 100bf384..8d35b679 100644 --- a/tests/issues/1010/test/testcase.cpp +++ b/tests/issues/1010/test/testcase.cpp @@ -59,6 +59,9 @@ class Issue1010 : public ::testing::Test { TURBO, DYNAMIC_MACRO_FIRST, DYNAMIC_MACRO_LAST = DYNAMIC_MACRO_FIRST + 31, + OS_META_STICKY, + OS_ACTIVE_STICKY, + OS_CANCEL, SAFE_START, KALEIDOSCOPE_SAFE_START = SAFE_START From 1a63ade7068fea130d9f5884eae1edd2ed5a4e8f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Thu, 17 Sep 2020 11:48:00 -0500 Subject: [PATCH 033/108] Update documentation and example sketch Signed-off-by: Michael Richters --- docs/NEWS.md | 54 ++++ docs/UPGRADING.md | 39 +++ examples/Keystrokes/OneShot/OneShot.ino | 21 +- .../Kaleidoscope-LED-ActiveModColor/README.md | 18 +- plugins/Kaleidoscope-OneShot/README.md | 255 ++++++++++++++---- 5 files changed, 312 insertions(+), 75 deletions(-) diff --git a/docs/NEWS.md b/docs/NEWS.md index d745af99..ff2bef6d 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,60 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## New features +### New OneShot features + +#### Auto-OneShot modifiers & layers + +OneShot can now treat modifiers and layer-shift keys as automatic OneShot +keys. This includes modifiers with other modifier flags applied, so it is now +very simple to turn `Key_Meh` or `Key_Hyper` into a OneShot key. The feature is +controlled by the following new functions: + +- `OneShot.toggleAutoModifiers()`: Turn auto-OneShot modifiers on or off. +- `OneShot.toggleAutoLayers()`: Turn auto-OneShot layer shifts on or off. +- `OneShot.toggleAutoOneShot()`: Both of the above. + +There are also `enable` and `disable` versions of these functions. + +Note, it is still possible to define a modifier key in the keymap that will not +automatically become a OneShot key when pressed, by applying modifier flags to +`Key_NoKey` (e.g. `LSHIFT(Key_NoKey)`). + +#### Two new special OneShot keys + +OneShot can now also turn _any_ key into a sticky key, using either of two +special `Key` values that can be inserted in the keymap. + +##### `OneShot_MetaStickyKey` + +This is a special OneShot key (it behaves like other OneShot keys), but its +effect is to make any key pressed while it is active sticky. Press +`OneShot_MetaStickyKey`, then press `X`, and `X` will become sticky. Sticky +keys can be deactivated just like other OneShot keys, by pressing them +again. This works for any key value, so use it with caution. + +##### `OneShot_ActiveStickyKey` + +Like `OneShot_ActiveStickyKey`, this key makes other keys sticky, but rather than +affecting a subsequent key, it affects any keys already held when it is +pressed. Press `X`, press `OneShot_ActiveStickyKey`, and release `X`, and `X` +will be sticky until it is pressed again to deactivate it. Again, it works on +any key value, so use with caution. + +#### LED-ActiveModColor highlighting + +With the updates to OneShot, LED-ActiveModColor now recognizes and highlights +OneShot keys in three different states (along with normal modifiers): + +- one-shot (a key that's active after release, but will time out) +- sticky (a key that will stay active indefinitely after release) +- normal (a key that will stay active only while physically held; also applies + to normal modifier keys) + +The colors of theses three highlights are controlled by the properties +`ActiveModColorEffect.oneshot_color`, `ActiveModColorEffect.sticky_color`, and +`ActiveModColorEffect.highlight_color`, respectively. + ### Better protection against unintended modifiers from Qukeys Qukeys has two new configuration options for preventing unintended modifiers in diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 87714f0c..6203eb3f 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -18,6 +18,7 @@ If any of this does not make sense to you, or you have trouble updating your .in - [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state) - [HostOS](#hostos) - [MagicCombo](#magiccombo) + - [OneShot](#oneshot) - [Qukeys](#qukeys) - [TypingBreaks](#typingbreaks) - [Redial](#redial) @@ -580,6 +581,44 @@ If your actions made use of the `left_hand` or `right_hand` arguments of more involved to get to, out of scope for this simple migration guide. Please open an issue, or ask for help on the forums, and we'll help you. +### OneShot + +Older versions of the plugin were based on `Key` values; OneShot is now based on +`KeyAddr` coordinates instead, in order to improve reliability and +functionality. + +The following deprecated functions and variables will be removed after +**2021-04-31**. + +#### Deprecated functions + +- `OneShot.inject(key, key_state)`: This `Key`-based function still works, but + because OneShot keys are now required to have a valid `KeyAddr`, it will now + look for an idle key, and use that, masking whatever value was mapped to that + key. Most of the reasons for using this function are better addressed by using + the newer features of the plugin, such as automatic one-shot modifiers. Use is + very strongly discouraged. +- `OneShot.isActive(key)`: This `Key`-based function no longer makes sense now + that OneShot is `KeyAddr`-based. There is a `OneShot.isActive(key_addr)` + function that should be used instead. The deprecated function still works, but + its use is discouraged. +- `OneShot.isSticky(key)`: This `Key`-based function no longer makes sense now + that OneShot is `KeyAddr`-based. There is a `OneShot.isSticky(key_addr)` + function that should be used instead. The deprecated function still works, but + its use is discouraged. +- `OneShot.isPressed()`: This function no longer has any reason for existing. In + older versions, the Escape-OneShot companion plugin used it to solve a problem + that no longer exists. It now always returns `false`. +- `OneShot.isModifierActive(key)`: This function still works, but is not + perfectly reliable, because it now returns positive results for keys other + than OneShot modifiers. It should not be used. + +#### Deprecated variables + +- `OneShot.time_out`: Use `OneShot.setTimeout()` instead. +- `OneShot.hold_time_out`: Use `OneShot.setHoldTimeout()` instead. +- `OneShot.double_tap_time_out`: Use `OneShot.setDoubleTapTimeout()` instead. + ### Qukeys Older versions of the plugin used `row` and `col` indexing for defining `Qukey` diff --git a/examples/Keystrokes/OneShot/OneShot.ino b/examples/Keystrokes/OneShot/OneShot.ino index c5dfdb87..483d2c46 100644 --- a/examples/Keystrokes/OneShot/OneShot.ino +++ b/examples/Keystrokes/OneShot/OneShot.ino @@ -21,27 +21,27 @@ // Macros enum { - OSMALTCTRL, + TOGGLE_ONESHOT, }; // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED ( - Key_NoKey, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey, + M(TOGGLE_ONESHOT), Key_1, Key_2, Key_3, Key_4, Key_5, OneShot_MetaStickyKey, Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift), - M(OSMALTCTRL), + Key_Meh, - Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip, + OneShot_ActiveStickyKey, Key_6, Key_7, Key_8, Key_9, Key_0, ___, Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + ___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - OSM(RightShift), OSM(RightAlt), Key_Spacebar, OSM(RightControl), + Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, OSL(1)), [1] = KEYMAP_STACKED @@ -64,14 +64,13 @@ KEYMAPS( ) // *INDENT-ON* -void macroOneShotAltControl(uint8_t keyState) { - OneShot.inject(OSM(LeftAlt), keyState); - OneShot.inject(OSM(LeftControl), keyState); +void macroToggleOneShot() { + OneShot.toggleAutoOneShot(); } const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - if (macroIndex == OSMALTCTRL) { - macroOneShotAltControl(keyState); + if (macroIndex == TOGGLE_ONESHOT) { + macroToggleOneShot(); } return MACRO_NONE; diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/README.md b/plugins/Kaleidoscope-LED-ActiveModColor/README.md index 16dc6ba2..e2a2c709 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/README.md +++ b/plugins/Kaleidoscope-LED-ActiveModColor/README.md @@ -2,9 +2,8 @@ With this plugin, any active modifier on the keyboard will have the LED under it highlighted. No matter how the modifier got activated (a key press, a macro, -anything else), the coloring will apply. Layer keys, be them layer toggles, -momentary switches, or one-shot layer keys count as modifiers as far as the -plugin is concerned. +anything else), the coloring will apply. Layer shift keys and OneShot layer keys +count as modifiers as far as the plugin is concerned. ## Using the plugin @@ -37,11 +36,20 @@ properties: ### `.highlight_color` -> The color to use for highlighting the modifiers. Defaults to a white color. +> The color to use for highlighting normal modifier keys and +> layer-shift keys. Defaults to a white color. + +### `.oneshot_color` + +> The color to use for highlighting active one-shot keys. These are +> the keys that will time out or deactivate when a subsequent key is +> pressed. Defaults to a yellow color. ### `.sticky_color` -> The color to use for highlighting one-shot modifiers when they are sticky. Defaults to a red color. +> The color to use for highlighting "sticky" one-shot keys. These keys +> will remain active until they are pressed again. Defaults to a red +> color. ## Plugin methods diff --git a/plugins/Kaleidoscope-OneShot/README.md b/plugins/Kaleidoscope-OneShot/README.md index 539b997e..f0e8699d 100644 --- a/plugins/Kaleidoscope-OneShot/README.md +++ b/plugins/Kaleidoscope-OneShot/README.md @@ -23,7 +23,7 @@ active if another one-shot of the same type is tapped, so `Ctrl, Alt, b` becomes `Ctrl+Alt+b`, and `L1, L2, c` is turned into `L1+L2+c`. Furthermore, modifiers and other layer keys do not cancel the one-shot effect, either. -## Using One-Shot Keys +## Using One-Shot keys To enter one-shot mode, tap _quickly_ on a one-shot key. The next normal (non-one-shot) key you press will have the modifier applied, @@ -40,11 +40,30 @@ modifier will now stay on until you press it again. Continuing the Shift, d, e, f` will give you `ABCdef`. This can be a bit tricky; combining this plugin with -[LED-ActiveModColor](Kaleidoscope-LED-ActiveModColor.md) -will help you understand what state your one-shot is in; when a -one-shot key is active, it will have a white LED highlight; when -sticky, a red highlight. (These colors are configurable.) +[LED-ActiveModColor](Kaleidoscope-LED-ActiveModColor.md) will help you +understand what state your one-shot is in; when a one-shot key is active, it +will have a yellow LED highlight; when sticky, a red highlight. When it is in a +"held" state, but will be deactivated when released like any non-one-shot key, +it will have a white highlight. (These colors are configurable.) +## Special OneShot keys + +OneShot also comes with two special keys that can make any key on your keyboard +sticky: `OneShot_MetaStickyKey` & `OneShot_ActiveStickyKey`. These are both +`Key` values that can be used as entries in your sketch's keymap. + +### `OneShot_MetaStickyKey` + +This special OneShot key behaves like other OneShot keys, but its affect is to +make the next key pressed sticky. Tap `OneShot_MetaStickyKey`, then tap `X`, and +`X` will become sticky. Tap `X` again to deactivate it. + +### `OneShot_ActiveStickyKey` + +This special key doesn't act like a OneShot key, but instead makes any key(s) +currently held (or otherwise active) sticky. Press (and hold) `X`, tap +`OneShot_ActiveStickyKey`, then release `X`, and `X` will stay active until it +is tapped again to deactivate it. ## Using the plugin @@ -69,13 +88,13 @@ void setup() { There are two macros the plugin provides: -### `OSM(mod)` +#### `OSM(mod)` > A macro that takes a single argument, the name of the modifier: `LeftControl`, > `LeftShift`, `LeftAlt`, `LeftGui` or their right-side variant. When marked up > with this macro, the modifier will act as a one-shot modifier. -### `OSL(layer)` +#### `OSL(layer)` > Takes a layer number as argument, and sets up the key to act as a one-shot > layer key. @@ -83,85 +102,203 @@ There are two macros the plugin provides: > Please note that while `Kaleidoscope` supports more, one-shot layers are > limited to 8 layers only. +In addition, there is a special key: + +#### `Key_MetaSticky` + +> A key that behaves like a one-shot key, but while active, it makes +> other keys that are pressed become sticky, just like double-tapped +> one-shot keys. + ## Plugin methods The plugin provides one object, `OneShot`, which implements both one-shot modifiers and one-shot layer keys. It has the following methods: -### `.isActive()` +### Configuration methods: Timeouts -> Returns if any one-shot key is in flight. This makes it possible to -> differentiate between having a modifier or layer active, versus having them -> active only until after the next key getting pressed. And this, in turn, is -> useful for macros that need to fiddle with either modifier or layer state: if -> one-shots are not active, they need not restore the original state. +#### `.setTimeout(timeout)` -### `.isPressed()` +> OneShot keys will remain active after they're pressed for `timeout` +> milliseconds (or until a subsequent non-oneshot key is pressed). The +> default value is 2500 (2.5 seconds). -> Returns true if any one-shot key is still held. +#### `.setHoldTimeout(timeout)` -### `.isSticky(key)` +> If a one-shot key is held for longer than `timeout` milliseconds, it +> will behave like a normal key, and won't remain active after it is +> released. The default value is 250 (1/4 seconds). -> Returns if the key is currently sticky. +#### `.setDoubleTapTimeout(timeout)` -### `.isModifierActive(key)` +> If a one-shot key is double-tapped (pressed twice in a row) in less +> than `timeout` milliseconds, it wil become sticky, and will remain +> active until it is pressed a third time. The default value is -1, +> which indicates that it should use the same timeout as +> `.setTimeout()`. -> Returns if the modifier `key` has a one-shot state active. Use this together -> with `Kaleidoscope.hid().keyboard().isModifierKeyActive` to catch cases where -> a one-shot modifier is active, but not registered yet. +### Configuration methods: Stickability -### `.cancel([with_stickies])` +#### `.enableStickability(key...)` +#### `.disableStickability(key...)` -> The `cancel()` method can be used to cancel any pending one-shot effects, -> useful when one changed their minds, and does not wish to wait for the -> timeout. +> Enables/disables stickability for all keys listed. The keys should +> all be OneShot keys, modifier keys, or layer-shift keys, as +> specified on the keymap. For example: +> `OneShot.enableStickability(OSM(LeftShift), OSL(1), Key_RightGUI)`. +> `OneShot.disableStickability(OSM(RighttAlt), OSL(2), ShiftToLayer(4))`. > -> The optional `with_stickies` argument, if set to `true`, will also cancel -> sticky one-shot effects. If omitted, it defaults to `false`, and not canceling -> stickies. +> By default, all OneShot keys are stickable. -### `.inject(key, keyState)` +#### `.enableStickabilityForModifiers()` +#### `.enableStickabilityForLayers()` +#### `.disableStickabilityForModifiers()` +#### `.disableStickabilityForLayers()` -> Simulates a key event, specifically designed to inject one-shot keys into the -> event loop. The primary purpose of this method is to make it easier to trigger -> multiple one-shots at the same time. -> -> See the example sketch for more information about its use. +> Enables/disables stickability for all modifiers and layers, +> respectively. These are convenience methods for cases where one +> wants to enable stickability for a group of one-shot keys. -### `.enableStickability(key...)` +### Configuration methods: Automatic one-shot keys -> Enables stickability for all keys listed. The keys should all be OneShot keys, -> as if specified on the keymap. For example: -> `OneShot.enableStickability(OSM(LeftShift), OSL(1))`. -> -> By default, all oneshot keys are stickable. +#### `.enableAutoModifiers()` +#### `.disableAutoModifiers()` +#### `.toggleAutoModifiers()` -### `.enableStickabilityForModifiers()` -### `.enableStickabilityForLayers()` +> Enables/disables/toggles auto-oneshot functionality for modifier +> keys. When enabled, all normal modifier keys, including those with +> other modifier flags added to them (e.g. `LSHIFT(Key_LeftAlt)`, +> `Key_Meh`) will be automatically treated as one-shot keys, in +> addition to dedicated ones like `OSM(LeftGui)`. -> Enables stickability for all modifiers and layers, respectively. These are -> convenience methods for cases where one wants to enable stickability for a -> group of one-shot keys. +#### `.enableAutoLayers()` +#### `.disableAutoLayers()` +#### `.toggleAutoLayers()` -### `.disableStickability(key...)` +> Enables/disables/toggles auto-oneshot functionality for layer shift +> keys (see above). -> Disables stickability for all keys listed. The keys should all be OneShot keys, -> as if specified on the keymap. For example: -> `OneShot.disableStickability(OSM(LeftShift), OSL(1))`. -> -> By default, all oneshot keys are stickable. +#### `.enableAutoOneShot()` +#### `.disableAutoOneShot()` +#### `.toggleAutoOneShot()` + +> Enables/disables/toggles auto-oneshot functionality for all +> modifiers and layer shift keys. + +### Test methods + +#### `.isActive(key_addr)` + +> Returns `true` if the key at `key_addr` is in an active one-shot +> state. Note that if a key is still being held, but will be not +> remain active after it is released, it is not considered to be in a +> one-shot state, even if it had been earlier. + +#### `.isTemporary(key_addr)` + +> Returns `true` if the key at `key_addr` is in a temporary one-shot +> state. Such a key will eventually time out or get deactivated by a +> subsequent key press. + +#### `.isSticky(key_addr)` + +> Returns `true` if the key at `key_addr` is in a permanent one-shot +> state. Such a key will not be deactivated by subsequent keypresses, +> nor will it time out. It will only be deactivated by pressing it one +> more time, or by being cancelled by the `cancel()` method (see +> below). + +#### `.isActive()` + +> Returns `true` if there are any active one-shot keys. Note: it +> returns `false` if there are no currently active one-shot keys, but +> there are keys that were at one time in a one-shot state, but are +> still being held after that state has been cancelled. + +#### `.isSticky()` + +> Returns `true` if there are any sticky one-shot keys. + +#### `.isStickable(key)` + +> Returns `true` if a key of the specified value can be made sticky by +> double-tapping. + +#### `.isModifier(key)` + +> Returns `true` if the specified key is a modifier key. This does not +> include OneShot modifiers (e.g. `OSM(LeftShift)`), but it does +> include modifiers with additional modifier flags (e.g. `Key_Meh`, +> `LCTRL(Key_RightGui)`). + +#### `.isLayerShift(key)` + +> Returns `true` if the specified key is a layer-shift key +> (e.g. `ShiftToLayer(2)`). OneShot layer keys (e.g. `OSL(5)` are not +> included). + +#### `.isOneShotKey(key)` + +> Returns `true` if the specified key is a OneShot modifier or +> layer-shift key (e.g. `OSM(LeftAlt)`, `OSL(3)`). + +### Other methods + +#### `.cancel([with_stickies])` + +> Immediately deactivates the one-shot status of any _temporary_ +> one-shot keys. Any keys still being physically held will continue to +> function as normal modifier/layer-shift keys. +> +> If `with_stickies` is `true` (the default is `false`), _sticky_ +> one-shot keys will also be deactivated, in the same way. + +### Deprecated methods + +The following methods have been deprecated, and should no longer be +used, if possible. These functions made more sense when OneShot was +based on `Key` values; it has since be rewritten to be based on +`KeyAddr` values. + +#### `.inject(key, key_state)` + +> Finds an idle key on the keyboard, and turns it into a one-shot +> key. When OneShot was based on `Key` values, this made more sense, +> as it didn't need a valid `KeyAddr` to work. Since the main purpose +> of this method was to enable the triggering of multiple one-shot +> modifiers with a single key, it is much better to use automatic +> one-shot modifiers, if possible, because then it's not necessary to +> use a Macro to configure. + +#### `.isModifierActive(key)` + +> Returns `true` if a keymap cache entry with the current value of +> `key` is active (one-shot, sticky, or held). This should be a +> function that is not specific to OneShot. + +#### `.isActive(key)` + +> Returns `true` if a keymap cache entry with the current value of +> `key` is in an active one-shot state. Please use +> `.isActive(key_addr)` instead. + +#### `.isSticky(key)` + +> Returns `true` if a keymap cache entry with the current value of +> `key` is in a sticky one-shot state. Please use +> `.isSticky(key_addr)` instead. -### `.disableStickabilityForModifiers()` -### `.disableStickabilityForLayers()` +#### `.isPressed()` -> Disables stickability for all modifiers and layers, respectively. These are -> convenience methods for cases where one wants to disable stickability for a -> group of one-shot keys. +> Returns `false`. OneShot doesn't need to keep track of whether or +> not a one-shot key is still pressed any more. This function was +> mainly used by LED-ActiveModColor, which no longer needs it. -## Plugin properties +## Plugin properties **[DEPRECATED]** -Along with the methods listed above, the `OneShot` object has the following -properties too: +Along with the methods listed above, the `OneShot` object has the +following properties, too. [Note: these have all been deprecated, +please use the `.set*Timeout()` methods above instead.] ### `.time_out` From c4332f154577c5a0374fad773d63a3f00218bea1 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 20 Nov 2020 18:36:50 -0600 Subject: [PATCH 034/108] Update basic OneShot testcases Signed-off-by: Michael Richters --- tests/plugins/OneShot/basic/basic.ino | 6 +++--- tests/plugins/OneShot/basic/test.ktest | 7 +++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/plugins/OneShot/basic/basic.ino b/tests/plugins/OneShot/basic/basic.ino index 8adcfcc9..0e335dc9 100644 --- a/tests/plugins/OneShot/basic/basic.ino +++ b/tests/plugins/OneShot/basic/basic.ino @@ -44,9 +44,9 @@ KALEIDOSCOPE_INIT_PLUGINS(OneShot); void setup() { Kaleidoscope.setup(); - OneShot.time_out = 50; - OneShot.hold_time_out = 20; - OneShot.double_tap_time_out = 20; + OneShot.setTimeout(50); + OneShot.setHoldTimeout(20); + OneShot.setDoubleTapTimeout(20); } void loop() { diff --git a/tests/plugins/OneShot/basic/test.ktest b/tests/plugins/OneShot/basic/test.ktest index 8d04bba5..fdb05b11 100644 --- a/tests/plugins/OneShot/basic/test.ktest +++ b/tests/plugins/OneShot/basic/test.ktest @@ -32,7 +32,7 @@ RUN 10 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_LeftShift Key_A # There should be `shift`+`A` -RUN 1 cycle +RUN 2 cycle EXPECT keyboard-report Key_A # There should be only `A` RUN 5 ms RELEASE A @@ -76,11 +76,10 @@ EXPECT keyboard-report Key_LeftShift # The report should contain `shift` RUN 5 ms PRESS OS_shift -RUN 1 cycle -EXPECT keyboard-report empty # Report should be empty RUN 5 ms RELEASE OS_shift RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty # ============================================================================== NAME OneShot double tap timeout @@ -137,7 +136,7 @@ RUN 1 cycle EXPECT keyboard-report Key_LeftShift Key_A # There should be `shift`+`A` RUN 5 ms RELEASE OS_shift -RUN 2 cycles # 2 cycles? +RUN 1 cycle EXPECT keyboard-report Key_A # There should be only `A` RUN 5 ms RELEASE A From 4f243dbef6bc9af870f002eed97158d355fb144f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 20 Nov 2020 18:37:30 -0600 Subject: [PATCH 035/108] Update testcase for OneShot issue #896 Signed-off-by: Michael Richters --- tests/issues/896/896.ino | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/issues/896/896.ino b/tests/issues/896/896.ino index 36e1bc19..f78775d9 100644 --- a/tests/issues/896/896.ino +++ b/tests/issues/896/896.ino @@ -60,9 +60,9 @@ KALEIDOSCOPE_INIT_PLUGINS(OneShot); void setup() { Kaleidoscope.setup(); - OneShot.time_out = 50; - OneShot.hold_time_out = 20; - OneShot.double_tap_time_out = 20; + OneShot.setTimeout(50); + OneShot.setHoldTimeout(20); + OneShot.setDoubleTapTimeout(20); } void loop() { From 0db11e3e66677ce9798a03453767306aa9a98f4e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 21 Nov 2020 22:56:27 -0600 Subject: [PATCH 036/108] Update basic Escape-OneShot testcases Signed-off-by: Michael Richters --- tests/plugins/Escape-OneShot/basic/basic.ino | 6 +-- tests/plugins/Escape-OneShot/basic/test.ktest | 41 +++++++++---------- 2 files changed, 22 insertions(+), 25 deletions(-) diff --git a/tests/plugins/Escape-OneShot/basic/basic.ino b/tests/plugins/Escape-OneShot/basic/basic.ino index 372adca6..c963bf7f 100644 --- a/tests/plugins/Escape-OneShot/basic/basic.ino +++ b/tests/plugins/Escape-OneShot/basic/basic.ino @@ -45,9 +45,9 @@ KALEIDOSCOPE_INIT_PLUGINS(OneShot, EscapeOneShot); void setup() { Kaleidoscope.setup(); - OneShot.time_out = 50; - OneShot.hold_time_out = 20; - OneShot.double_tap_time_out = 20; + OneShot.setTimeout(50); + OneShot.setHoldTimeout(20); + OneShot.setDoubleTapTimeout(20); } void loop() { diff --git a/tests/plugins/Escape-OneShot/basic/test.ktest b/tests/plugins/Escape-OneShot/basic/test.ktest index 1ab61288..11822d25 100644 --- a/tests/plugins/Escape-OneShot/basic/test.ktest +++ b/tests/plugins/Escape-OneShot/basic/test.ktest @@ -15,34 +15,31 @@ RUN 5 ms RELEASE OSM_0 RUN 10 ms PRESS ESC -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty RUN 5 ms RELEASE ESC RUN 1 cycle # ============================================================================== -# The testcases below are commented out because they are currently failing. - -# # ============================================================================== -# NAME EscapeOneShot cancel sticky +NAME EscapeOneShot cancel sticky -# RUN 5 ms -# PRESS OSM_0 -# RUN 1 cycle -# EXPECT keyboard-report Key_LeftShift # The report should contain `shift` -# RUN 5 ms -# RELEASE OSM_0 +RUN 5 ms +PRESS OSM_0 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +RELEASE OSM_0 -# RUN 5 ms -# PRESS OSM_0 -# RUN 5 ms -# RELEASE OSM_0 +RUN 5 ms +PRESS OSM_0 +RUN 5 ms +RELEASE OSM_0 -# RUN 50 ms -# PRESS ESC -# RUN 2 cycles -# EXPECT keyboard-report empty # Report should be empty -# RUN 5 ms -# RELEASE ESC -# RUN 1 cycle +RUN 50 ms +PRESS ESC +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +RUN 5 ms +RELEASE ESC +RUN 1 cycle From d97221c5a15af514f6f9fc6c2042b56be9744e0c Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 11 Nov 2020 16:54:08 -0600 Subject: [PATCH 037/108] Add `remove(n)` method to `KeyAddrEventQueue` class This will be necessary to support the forthcoming Qukeys tap-repeat feature. Signed-off-by: Michael Richters --- src/kaleidoscope/KeyAddrEventQueue.h | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/kaleidoscope/KeyAddrEventQueue.h b/src/kaleidoscope/KeyAddrEventQueue.h index 8a646394..a4f53246 100644 --- a/src/kaleidoscope/KeyAddrEventQueue.h +++ b/src/kaleidoscope/KeyAddrEventQueue.h @@ -1,6 +1,6 @@ // -*- mode: c++ -*- /* Kaleidoscope - Firmware for computer input devices - * Copyright (C) 2013-2019 Keyboard.io, Inc. + * Copyright (C) 2013-2020 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 @@ -90,18 +90,31 @@ class KeyAddrEventQueue { ++length_; } - // Remove the first event from the head of the queue, shifting the - // others. This function actually shifts the queue by copying element values, + // Remove an event from the head of the queue, shifting the subsequent + // ones. This function actually shifts the queue by copying element values, // rather than using a ring buffer because we expect it will be called much // less often than the queue is searched via a for loop. - void shift() { - // assert(length > 0); + void remove(uint8_t n = 0) { + // assert(length > n); --length_; - for (uint8_t i{0}; i < length_; ++i) { + for (uint8_t i{n}; i < length_; ++i) { addrs_[i] = addrs_[i + 1]; timestamps_[i] = timestamps_[i + 1]; } + // mask = all ones for bits >= n, zeros otherwise + _Bitfield mask = _Bitfield(~0) << n; + // use the inverse mask to get just the low bits (that won't be shifted) + _Bitfield low_bits = release_event_bits_ & ~mask; + // shift the event bits release_event_bits_ >>= 1; + // use the mask to zero the low bits, leaving only the shifted high bits + release_event_bits_ &= mask; + // add the low bits back in + release_event_bits_ |= low_bits; + } + + void shift() { + remove(0); } void shift(uint8_t n) { From 94d26b8cb83484ace8004d117b3fd9df8a18320d Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 11 Nov 2020 16:55:37 -0600 Subject: [PATCH 038/108] Add Qukeys "tap-repeat" feature This change gives Qukeys the ability to repeat a primary keycode by tapping the key, then immediately pressing and holding it. While doing this, the extra release and press of the key are suppressed, so it looks to the host just like a simple press-and-hold event, which is particularly nice for users of macOS apps that use Cocoa, where holding letter keys is the "standard" way of accessing accented characters. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Qukeys.cpp | 122 +++++++++++++++++- .../src/kaleidoscope/plugin/Qukeys.h | 21 ++- 2 files changed, 138 insertions(+), 5 deletions(-) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp index 732c461f..d18b734d 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Qukeys -- Assign two keycodes to a single key - * Copyright (C) 2017-2019 Michael Richters + * Copyright (C) 2017-2020 Michael Richters * * 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 @@ -153,7 +153,6 @@ EventHandlerResult Qukeys::beforeReportingState() { // ----------------------------------------------------------------------------- - // This function contains most of the logic behind Qukeys. It gets called after // an event gets added to the queue, and again once per cycle. It returns `true` // if nothing more should be done, either because the queue is empty, or because @@ -174,8 +173,22 @@ bool Qukeys::processQueue() { // If that first event is a key release, it can be flushed right away. if (event_queue_.isRelease(0)) { - flushEvent(Key_NoKey); - return true; + // We can't unconditionally flush the release event, because it might be + // second half of a tap-repeat event. If the queue is full, we won't bother to + // check, but otherwise, ift `tap_repeat_.addr` is set (and matches), we call + // `shouldWaitForTapRepeat()` to determine whether or not to flush the key + // release event. + if (event_queue_.isFull() || + queue_head_addr != tap_repeat_.addr || + !shouldWaitForTapRepeat()) { + flushEvent(Key_NoKey); + return true; + } + // We now know that we're waiting to determine if we're getting a tap-repeat + // sequence, so we can't flush the release event at the head of the queue. + // Warning: Returning false here is only okay because we already checked to + // make sure the queue isn't full. + return false; } // We now know that the first event is a key press. If it's not a qukey, or if @@ -237,6 +250,13 @@ bool Qukeys::processQueue() { if (next_keypress_index == 0 || overlap_threshold_ == 0) { Key event_key = qukey_is_spacecadet ? queue_head_.alternate_key : queue_head_.primary_key; + // A qukey just got released in primary state; this might turn out to be + // the beginning of a tap-repeat sequence, so we set the tap-repeat + // address and start time to the time of the initial press event before + // flushing it from the queue. This will come into play when processing + // the corresponding release event later. + tap_repeat_.addr = queue_head_addr; + tap_repeat_.start_time = event_queue_.timestamp(0); flushEvent(event_key); return true; } @@ -420,6 +440,100 @@ bool Qukeys::isKeyAddrInQueueBeforeIndex(KeyAddr k, uint8_t index) const { } +// This question gets called early in `processQueue()` if a key release event is +// at the head of the queue, and the `tap_repeat_.addr` is the same as that +// event's KeyAddr. It returns true if `processQueue()` should wait for either +// subsequent events or a timeout instead of proceeding to flush the key release +// event immediately, and false if it is still waiting. It assumes that +// `event_queue_[0]` is a release event, and that `event_queue_[0].addr == +// tap_repeat_.addr`. (The latter should only be set to a valid KeyAddr if a qukey +// press event has been flushed with its primary Key value, and could still +// represent the start of a double-tap or tap-repeat sequeunce.) +bool Qukeys::shouldWaitForTapRepeat() { + // First, we set up a variable to store the queue index of a subsequent press + // of the same qukey addr, if any. + uint8_t second_press_index = 0; + + // Next, we search the event queue (starting at index 1 because the first + // event in the queue is known), trying to find a matching sequeunce for + // either a double-tap, or a tap-repeat. + for (uint8_t i{1}; i < event_queue_.length(); ++i) { + if (event_queue_.isPress(i)) { + // Found a keypress event following the release of the initial primary + // qukey. + if (event_queue_.addr(i) == tap_repeat_.addr) { + // The same qukey toggled on twice in a row, and because of the timeout + // check below, we know it was quick enough that it could represent a + // tap-repeat sequence. Now we update the start time (which had been set + // to the timestamp of the first press event) to the timestamp of the + // first release event (currently at the head of the queue), because we + // want to compare the release times of the two taps to determine if + // it's actually a double-tap sequence instead (otherwise it could be + // too difficult to tap it fast enough). + tap_repeat_.start_time = event_queue_.timestamp(0); + // We also record the index of this second press event. If it turns out + // that we've got a tap-repeat sequence, we want to silently suppress the + // first release and second press by removing them from the queue + // without flushing them. We don't know yet whether we'll be doing so. + second_press_index = i; + } else { + // Some other key was pressed. For it to be a tap-repeat sequence, we + // require that the same key be pressed twice in a row, with no + // intervening presses of other keys. Therefore, we can return false to + // signal that the release event at the head of the queue can be + // flushed. + return false; + } + + } else if (event_queue_.addr(i) == tap_repeat_.addr) { + // We've found a key release event in the queue, and it's the same key as + // the qukey at the head of the queue, so this is the second release that + // has occurred before timing out (see below for the timeout + // check). Therefore, this is a double-tap sequence (the second release + // happened very close to the first release), not a tap-repeat, so we + // clear the tap-repeat address, and return false to trigger the flush of + // the first release event. + tap_repeat_.addr = KeyAddr{KeyAddr::invalid_state}; + return false; + } + } + + // We've now searched the queue. Either we found only irrelevant key release + // events (for other keys that were pressed before the sequence began), or we + // found only a second key press event of the same qukey (but not a double + // tap). Next, we check the timeout. If we didn't find a second press event, + // the start time will still be that of the initial key press event (already + // flushed from the queue), but if the second press has been detected, the + // start time will be that of the key release event currently at the head of + // the queue. + if (Runtime.hasTimeExpired(tap_repeat_.start_time, tap_repeat_.timeout)) { + // Time has expired. The sequence represents either a single tap or a + // tap-repeat of the qukey's primary value. Either way, we can clear the + // stored address. + tap_repeat_.addr = KeyAddr{KeyAddr::invalid_state}; + + if (second_press_index > 0) { + // A second press was found (but it's release didn't come quick enough to + // be a double tap), so this is a tap-repeat event. To turn it into a + // single key press and hold, we need to remove the second press event and + // the first release event from the queue without flushing the + // events. Order matters here! + event_queue_.remove(second_press_index); + event_queue_.remove(0); + } else { + // The key was not pressed again, so the single tap has timed out. We + // return false to let the release event be flushed. + return false; + } + } + + // We haven't found a double-tap sequence, and the timeout hasn't expired, so + // we return true to signal that we should just wait until we get another + // event, or until time runs out. + return true; +} + + // ----------------------------------------------------------------------------- // This function is here to provide the test for a SpaceCadet-type qukey, which diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h index 80a89f4a..b2e4ca7e 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Qukeys -- Assign two keycodes to a single key - * Copyright (C) 2017-2019 Michael Richters + * Copyright (C) 2017-2020 Michael Richters * * 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 @@ -81,6 +81,17 @@ class Qukeys : public kaleidoscope::Plugin { hold_timeout_ = hold_timeout; } + // Set the timeout (in milliseconds) for the tap-repeat feature. If a qukey is + // tapped twice in a row in less time than this amount, it will allow the user + // to hold the key with its primary value (unless it's a SpaceCadet type key, + // in which case it will repeat the alternate value instead). This requires a + // quick tap immediately followed by a press and hold, and will result in a + // single press-and-hold on the host system. If a double tap is done instead, + // it will result in two independent taps. + void setMaxIntervalForTapRepeat(uint8_t ttl) { + tap_repeat_.timeout = ttl; + } + // Set the percentage of the duration of a subsequent key's press that must // overlap with the qukey preceding it above which the qukey will take on its // alternate key value. In other words, if the user presses qukey `Q`, then @@ -190,6 +201,14 @@ class Qukeys : public kaleidoscope::Plugin { bool isDualUseKey(Key key); bool releaseDelayed(uint16_t overlap_start, uint16_t overlap_end) const; bool isKeyAddrInQueueBeforeIndex(KeyAddr k, uint8_t index) const; + + // Tap-repeat feature support. + struct { + KeyAddr addr{KeyAddr::invalid_state}; + uint16_t start_time; + uint8_t timeout{200}; + } tap_repeat_; + bool shouldWaitForTapRepeat(); }; // This function returns true for any key that we expect to be used chorded with From 1f24088a69d952ec14262fbfc9c3e81d3229a200 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 11 Nov 2020 16:56:24 -0600 Subject: [PATCH 039/108] Add documentation and examples for Qukeys tap-repeat feature Signed-off-by: Michael Richters --- docs/NEWS.md | 18 ++++++++++++++++++ examples/Keystrokes/Qukeys/Qukeys.ino | 1 + plugins/Kaleidoscope-Qukeys/README.md | 26 ++++++++++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/docs/NEWS.md b/docs/NEWS.md index ff2bef6d..b00e3872 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,24 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## New features +### New Qukeys features + +#### Tap-repeat + +It is now possible to get the "tap" value of a qukey to repeat (as if that key +for that character was simply being held down on a normal keyboard) by tapping +the qukey, then quickly pressing and holding it down. The result on the OS will +be as if the key was pressed and held just once, so that users of macOS apps +that use the Cocoa input system can get the menu for characters with diacritics +without an extra character in the output. + +The maximum interval between the two keypresses that will trigger a tap repeat +can be configured via the `Qukeys.setMaxIntervalForTapRepeat(ms)` function, +where the argument specifies the number of milliseconds Qukeys will wait after a +qukey is tapped for it to be pressed a second time. If it is, but the qukey is +released within that same interval from the first tap's release, it will be +treated as a double-tap, and both taps will be sent to the OS. + ### New OneShot features #### Auto-OneShot modifiers & layers diff --git a/examples/Keystrokes/Qukeys/Qukeys.ino b/examples/Keystrokes/Qukeys/Qukeys.ino index 5cc8c8cc..c2cad0d3 100644 --- a/examples/Keystrokes/Qukeys/Qukeys.ino +++ b/examples/Keystrokes/Qukeys/Qukeys.ino @@ -74,6 +74,7 @@ void setup() { Qukeys.setOverlapThreshold(50); Qukeys.setMinimumHoldTime(100); Qukeys.setMinimumPriorInterval(80); + Qukeys.setMaxIntervalForTapRepeat(150); Kaleidoscope.setup(); } diff --git a/plugins/Kaleidoscope-Qukeys/README.md b/plugins/Kaleidoscope-Qukeys/README.md index 36d7c028..6a1beac6 100644 --- a/plugins/Kaleidoscope-Qukeys/README.md +++ b/plugins/Kaleidoscope-Qukeys/README.md @@ -75,6 +75,22 @@ likely to generate errors and out-of-order events. > > Defaults to `250`. +### `.setMaxIntervalForTapRepeat(timeout)` + +> Sets the time (in milliseconds) that limits the tap-repeat window. If the same +> qukey is pressed, released, and pressed again within this timeframe, then +> held, Qukeys will turn it into a single press and hold event, using the +> primary key value (which cannot otherwise be held). If the second press is +> also a tap, and the two _release_ events occur within the same timeframe, it +> will instead be treated as a double tap (of the primary key value). +> +> To effectively shut off the tap-repeat feature, set this value to `0`. The +> maximum value is `255`; anything higher than `250` could result in key repeat +> being triggered on the host before Qukeys determines whether it's a tap-repeat +> or a double-tap sequence, because most systems delay the key repeat by 500 ms. +> +> Defaults to `200`. + ### `.setOverlapThreshold(percentage)` > This sets a variable that allows the user to roll over from a qukey to a @@ -205,6 +221,16 @@ the given alternate value on all layers, regardless of what the primary value is for that key on the top currently active layer. +### Tap-Repeat Behaviour + +If a qukey is tapped, then immediately pressed and held, Qukeys will turn that +sequence of events into a single press and hold of the primary key value +(whereas merely holding the key yeilds the alternate value). This is +particularly useful on macOS apps that use Apple's Cocoa input system, where +holding a key gives the user access to a menu of accented characters, rather +than merely repeating the same character until the key is released. + + ## Design & Implementation When a qukey is pressed, it doesn't immediately add a corresponding keycode to From 20cb771dbfcd467801d3d78e14b504a53f4ccdb3 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 11 Nov 2020 17:03:31 -0600 Subject: [PATCH 040/108] Update Qukeys basic test suite tap-repeat configuration Signed-off-by: Michael Richters --- tests/plugins/Qukeys/basic/basic.ino | 1 + tests/plugins/Qukeys/basic/common.h | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/plugins/Qukeys/basic/basic.ino b/tests/plugins/Qukeys/basic/basic.ino index 335586a5..74d062d5 100644 --- a/tests/plugins/Qukeys/basic/basic.ino +++ b/tests/plugins/Qukeys/basic/basic.ino @@ -90,6 +90,7 @@ void setup() { Qukeys.setOverlapThreshold(kaleidoscope::testing::QUKEYS_OVERLAP_THRESHOLD); Qukeys.setMinimumHoldTime(kaleidoscope::testing::QUKEYS_MINIMUM_HOLD_TIME); Qukeys.setMinimumPriorInterval(kaleidoscope::testing::QUKEYS_MIN_PRIOR_INTERVAL); + Qukeys.setMaxIntervalForTapRepeat(kaleidoscope::testing::QUKEYS_MAX_TAP_REPEAT_INTERVAL); Kaleidoscope.setup(); } diff --git a/tests/plugins/Qukeys/basic/common.h b/tests/plugins/Qukeys/basic/common.h index 86c93412..2fd22a78 100644 --- a/tests/plugins/Qukeys/basic/common.h +++ b/tests/plugins/Qukeys/basic/common.h @@ -27,6 +27,7 @@ constexpr uint16_t QUKEYS_HOLD_TIMEOUT = 200; constexpr uint8_t QUKEYS_OVERLAP_THRESHOLD = 90; constexpr uint8_t QUKEYS_MINIMUM_HOLD_TIME = 10; constexpr uint8_t QUKEYS_MIN_PRIOR_INTERVAL = 20; +constexpr uint8_t QUKEYS_MAX_TAP_REPEAT_INTERVAL = 0; } } From 8b1bf403c38d02ae4d5fee6f3bea945bcb2ce9d4 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 11 Nov 2020 22:18:51 -0600 Subject: [PATCH 041/108] Add Qukeys tap-repeat feature basic test This is not complete, but it does test the two basic cases of a double-tap and a tap-then-hold (to produce a single primary key value hold in output) on all three types of qukeys (Generic, DualUse, SpaceCadet). Signed-off-by: Michael Richters --- tests/plugins/Qukeys/TapRepeat/TapRepeat.ino | 62 +++++++++ tests/plugins/Qukeys/TapRepeat/common.h | 33 +++++ tests/plugins/Qukeys/TapRepeat/sketch.json | 6 + tests/plugins/Qukeys/TapRepeat/test.ktest | 134 +++++++++++++++++++ 4 files changed, 235 insertions(+) create mode 100644 tests/plugins/Qukeys/TapRepeat/TapRepeat.ino create mode 100644 tests/plugins/Qukeys/TapRepeat/common.h create mode 100644 tests/plugins/Qukeys/TapRepeat/sketch.json create mode 100644 tests/plugins/Qukeys/TapRepeat/test.ktest diff --git a/tests/plugins/Qukeys/TapRepeat/TapRepeat.ino b/tests/plugins/Qukeys/TapRepeat/TapRepeat.ino new file mode 100644 index 00000000..45d6f239 --- /dev/null +++ b/tests/plugins/Qukeys/TapRepeat/TapRepeat.ino @@ -0,0 +1,62 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_A, Key_LeftAlt, ___, ___, ___, ___, ___, + SFT_T(J), ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +// Use Qukeys +KALEIDOSCOPE_INIT_PLUGINS(Qukeys); + +void setup() { + QUKEYS( + kaleidoscope::plugin::Qukey(0, KeyAddr(0, 0), Key_LeftGui), + kaleidoscope::plugin::Qukey(0, KeyAddr(0, 1), Key_B) + ) + Qukeys.setHoldTimeout(kaleidoscope::testing::QUKEYS_HOLD_TIMEOUT); + Qukeys.setOverlapThreshold(kaleidoscope::testing::QUKEYS_OVERLAP_THRESHOLD); + Qukeys.setMinimumHoldTime(kaleidoscope::testing::QUKEYS_MINIMUM_HOLD_TIME); + Qukeys.setMinimumPriorInterval(kaleidoscope::testing::QUKEYS_MIN_PRIOR_INTERVAL); + Qukeys.setMaxIntervalForTapRepeat(kaleidoscope::testing::QUKEYS_MAX_TAP_REPEAT_INTERVAL); + + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/Qukeys/TapRepeat/common.h b/tests/plugins/Qukeys/TapRepeat/common.h new file mode 100644 index 00000000..922d2ca2 --- /dev/null +++ b/tests/plugins/Qukeys/TapRepeat/common.h @@ -0,0 +1,33 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +constexpr uint16_t QUKEYS_HOLD_TIMEOUT = 200; +constexpr uint8_t QUKEYS_OVERLAP_THRESHOLD = 0; +constexpr uint8_t QUKEYS_MINIMUM_HOLD_TIME = 0; +constexpr uint8_t QUKEYS_MIN_PRIOR_INTERVAL = 0; +constexpr uint8_t QUKEYS_MAX_TAP_REPEAT_INTERVAL = 20; + +} +} diff --git a/tests/plugins/Qukeys/TapRepeat/sketch.json b/tests/plugins/Qukeys/TapRepeat/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/plugins/Qukeys/TapRepeat/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/plugins/Qukeys/TapRepeat/test.ktest b/tests/plugins/Qukeys/TapRepeat/test.ktest new file mode 100644 index 00000000..0cb8af66 --- /dev/null +++ b/tests/plugins/Qukeys/TapRepeat/test.ktest @@ -0,0 +1,134 @@ +VERSION 1 + +KEYSWITCH A 0 0 +KEYSWITCH B 0 1 +KEYSWITCH J 1 0 + +# ============================================================================== +# Qukey tap-repeat test +NAME TapRepeat Generic Qukey + +RUN 10 ms + +PRESS A +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only A +RUN 5 ms + +PRESS A +RUN 5 ms + +RELEASE A +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_A # Report should contain only A + +RUN 16 ms +EXPECT keyboard-report empty # Report should be empty + +RUN 100 ms + +PRESS A +RUN 5 ms + +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only A +RUN 5 ms + +PRESS A +RUN 50 ms + +RELEASE A +# I'm not sure why this takes 2 cycles instead of just one +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +# DualUse Qukey tap-repeat test +NAME TapRepeat DualUse Qukey + +RUN 10 ms + +PRESS J +RUN 5 ms + +RELEASE J +RUN 1 cycle +EXPECT keyboard-report Key_J # Report should contain only J +RUN 5 ms + +PRESS J +RUN 5 ms + +RELEASE J +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_J # Report should contain only J + +RUN 16 ms +EXPECT keyboard-report empty # Report should be empty + +RUN 100 ms + +PRESS J +RUN 5 ms + +RELEASE J +RUN 1 cycle +EXPECT keyboard-report Key_J # Report should contain only J +RUN 5 ms + +PRESS J +RUN 50 ms + +RELEASE J +# I'm not sure why this takes 2 cycles instead of just one +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +# SpaceCadet Qukey tap-repeat test +NAME TapRepeat SpaceCadet Qukey + +RUN 10 ms + +PRESS B +RUN 5 ms + +RELEASE B +RUN 1 cycle +EXPECT keyboard-report Key_B # Report should contain only B +RUN 5 ms + +PRESS B +RUN 5 ms + +RELEASE B +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_B # Report should contain only B + +RUN 16 ms +EXPECT keyboard-report empty # Report should be empty + +RUN 100 ms + +PRESS B +RUN 5 ms + +RELEASE B +RUN 1 cycle +EXPECT keyboard-report Key_B # Report should contain only B +RUN 5 ms + +PRESS B +RUN 50 ms + +RELEASE B +# I'm not sure why this takes 2 cycles instead of just one +RUN 2 cycles +EXPECT keyboard-report empty # Report should be empty From b8025a94f3249d642749bf7af7ccd6e5e34c9472 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 16 Nov 2020 13:06:27 -0600 Subject: [PATCH 042/108] Adjust configuration for issue #970 test sketch With a non-zero default for tap-repeat, the timing of events changed, causing this testcase to fail. Signed-off-by: Michael Richters --- tests/issues/970/970.ino | 1 + tests/issues/970/common.h | 1 + 2 files changed, 2 insertions(+) diff --git a/tests/issues/970/970.ino b/tests/issues/970/970.ino index 335586a5..02f3b1e9 100644 --- a/tests/issues/970/970.ino +++ b/tests/issues/970/970.ino @@ -90,6 +90,7 @@ void setup() { Qukeys.setOverlapThreshold(kaleidoscope::testing::QUKEYS_OVERLAP_THRESHOLD); Qukeys.setMinimumHoldTime(kaleidoscope::testing::QUKEYS_MINIMUM_HOLD_TIME); Qukeys.setMinimumPriorInterval(kaleidoscope::testing::QUKEYS_MIN_PRIOR_INTERVAL); + Qukeys.setMaxIntervalForTapRepeat(kaleidoscope::testing::QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT); Kaleidoscope.setup(); } diff --git a/tests/issues/970/common.h b/tests/issues/970/common.h index 86c93412..01ea3d07 100644 --- a/tests/issues/970/common.h +++ b/tests/issues/970/common.h @@ -27,6 +27,7 @@ constexpr uint16_t QUKEYS_HOLD_TIMEOUT = 200; constexpr uint8_t QUKEYS_OVERLAP_THRESHOLD = 90; constexpr uint8_t QUKEYS_MINIMUM_HOLD_TIME = 10; constexpr uint8_t QUKEYS_MIN_PRIOR_INTERVAL = 20; +constexpr uint8_t QUKEYS_MAX_INTERVAL_FOR_TAP_REPEAT = 0; } } From 3be1ea6531c7608eb343e792e35c276ef436ce35 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 00:19:00 -0500 Subject: [PATCH 043/108] Add KeyEvent data type The `KeyEvent` type will encapsulate all of the data that will be passed to the new generation of event handler functions. Signed-off-by: Michael Richters --- src/Kaleidoscope.h | 1 + src/kaleidoscope/KeyEvent.cpp | 23 ++++++++++++ src/kaleidoscope/KeyEvent.h | 68 +++++++++++++++++++++++++++++++++++ 3 files changed, 92 insertions(+) create mode 100644 src/kaleidoscope/KeyEvent.cpp create mode 100644 src/kaleidoscope/KeyEvent.h diff --git a/src/Kaleidoscope.h b/src/Kaleidoscope.h index 57dbbe81..bf457698 100644 --- a/src/Kaleidoscope.h +++ b/src/Kaleidoscope.h @@ -80,6 +80,7 @@ void setup(); #endif #include "kaleidoscope/KeyAddr.h" +#include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/key_events.h" #include "kaleidoscope/layers.h" #include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h" diff --git a/src/kaleidoscope/KeyEvent.cpp b/src/kaleidoscope/KeyEvent.cpp new file mode 100644 index 00000000..c9d01704 --- /dev/null +++ b/src/kaleidoscope/KeyEvent.cpp @@ -0,0 +1,23 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 "kaleidoscope/KeyEvent.h" + +namespace kaleidoscope { + +KeyEventId KeyEvent::last_id_ = 0; + +} // namespace kaleidoscope diff --git a/src/kaleidoscope/KeyEvent.h b/src/kaleidoscope/KeyEvent.h new file mode 100644 index 00000000..0cc97ab8 --- /dev/null +++ b/src/kaleidoscope/KeyEvent.h @@ -0,0 +1,68 @@ +/* Kaleidoscope - Firmware for computer input devices + * 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 . + */ + +#pragma once + +#include // for uint8_t, int8_t + +#include "kaleidoscope/key_defs.h" // for Key, Key_NoKey +#include "kaleidoscope/KeyAddr.h" // for KeyAddr + +namespace kaleidoscope { + +// It's important that this is a signed integer, not unsigned. It should be +// `int8_t`, but I changed it to 32 for debugging purposes. +typedef int8_t KeyEventId; + +struct KeyEvent { + + public: + // Constructor for plugin use when regenerating an event with specific ID: + KeyEvent(KeyAddr addr, uint8_t state, + Key key = Key_NoKey, KeyEventId id = last_id_) + : addr(addr), state(state), key(key), id_(id) {} + + KeyEvent() : id_(last_id_) {} + + // For use by keyscanner creating a new event from a physical keyswitch toggle + // on or off. + static KeyEvent next(KeyAddr addr, uint8_t state) { + return KeyEvent(addr, state, Key_NoKey, ++last_id_); + } + + KeyEventId id() const { + return id_; + } + void swapId(KeyEvent &other) { + KeyEventId tmp_id = id_; + id_ = other.id_; + other.id_ = tmp_id; + } + + KeyAddr addr = KeyAddr::none(); + uint8_t state = 0; + Key key = Key_NoKey; + + private: + // serial number of the event: + static KeyEventId last_id_; + KeyEventId id_; +}; + +} // namespace kaleidoscope + +typedef kaleidoscope::KeyEventId KeyEventId; +typedef kaleidoscope::KeyEvent KeyEvent; From 3be5c0fce424d7991959901b03572396f67723c3 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 00:23:50 -0500 Subject: [PATCH 044/108] Generalize KeyAddrEventQueue type to store EventId values This allows it to return correct `KeyEvent` values when used by plugins that need to track that information for delaying events. Signed-off-by: Michael Richters --- src/kaleidoscope/KeyAddrEventQueue.h | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/kaleidoscope/KeyAddrEventQueue.h b/src/kaleidoscope/KeyAddrEventQueue.h index a4f53246..48338e4d 100644 --- a/src/kaleidoscope/KeyAddrEventQueue.h +++ b/src/kaleidoscope/KeyAddrEventQueue.h @@ -21,6 +21,7 @@ //#include #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/KeyAddr.h" #include "kaleidoscope/keyswitch_state.h" @@ -43,7 +44,8 @@ class KeyAddrEventQueue { private: uint8_t length_{0}; - KeyAddr addrs_[_capacity]; + KeyEventId event_ids_[_capacity]; // NOLINT(runtime/arrays) + KeyAddr addrs_[_capacity]; // NOLINT(runtime/arrays) _Timestamp timestamps_[_capacity]; // NOLINT(runtime/arrays) _Bitfield release_event_bits_; @@ -61,6 +63,11 @@ class KeyAddrEventQueue { // Queue entry access methods. Note: the caller is responsible for bounds // checking, because it's expected that a for loop will be used when searching // the queue, which will terminate when `index >= queue.length()`. + KeyEventId id(uint8_t index) const { + // assert(index < length_); + return event_ids_[index]; + } + KeyAddr addr(uint8_t index) const { // assert(index < length_); return addrs_[index]; @@ -82,11 +89,12 @@ class KeyAddrEventQueue { // Append a new event on the end of the queue. Note: the caller is responsible // for bounds checking; we don't guard against it here. - void append(KeyAddr k, uint8_t keyswitch_state) { + void append(const KeyEvent& event) { // assert(length_ < _capacity); - addrs_[length_] = k; + event_ids_[length_] = event.id(); + addrs_[length_] = event.addr; timestamps_[length_] = Runtime.millisAtCycleStart(); - bitWrite(release_event_bits_, length_, keyToggledOff(keyswitch_state)); + bitWrite(release_event_bits_, length_, keyToggledOff(event.state)); ++length_; } @@ -98,6 +106,7 @@ class KeyAddrEventQueue { // assert(length > n); --length_; for (uint8_t i{n}; i < length_; ++i) { + event_ids_[i] = event_ids_[i + 1]; addrs_[i] = addrs_[i + 1]; timestamps_[i] = timestamps_[i + 1]; } @@ -135,6 +144,16 @@ class KeyAddrEventQueue { length_ = 0; release_event_bits_ = 0; } + + KeyEvent event(uint8_t i) const { + uint8_t state = isRelease(i) ? WAS_PRESSED : IS_PRESSED; + return KeyEvent{addr(i), state, Key_NoKey, id(i)}; + } + + // Only call this after `EventTracker::shouldIgnore()` returns `true`. + bool shouldAbort(const KeyEvent& event) const { + return (length_ != 0) && (event.id() - event_ids_[0] >= 0); + } }; } // namespace kaleidoscope From d5a8a6e20135cd3efa216fd58a0ab0682b6a7ef6 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 00:28:48 -0500 Subject: [PATCH 045/108] Add new `KeyEvent` event handler functions This defines four new event handlers for plugins to use with the forthcoming redesigned main event loop: - `onKeyEvent(KeyEvent &event)` - `onPhysicalKeyEvent(KeyEvent &event)` - `beforeReportingState(const KeyEvent &event)` - `onAddToReport(Key key)` Signed-off-by: Michael Richters --- src/kaleidoscope/event_handlers.h | 74 +++++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 4 deletions(-) diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index 535d4e61..4f7f630f 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -154,6 +154,51 @@ class SignatureCheckDummy {}; (Key &mappedKey, KeyAddr key_addr, uint8_t keyState), __NL__ \ (mappedKey, key_addr, keyState), ##__VA_ARGS__) __NL__ \ __NL__ \ + /* Function called for every physical keyswitch event (toggle on or */ __NL__ \ + /* off). The `event` parameter is passed by reference so its key */ __NL__ \ + /* value can be modified. If it returns EventHandlerResult::OK, the */ __NL__ \ + /* next handler will be passed the event; otherwise Kaleidoscope */ __NL__ \ + /* will stop processing the event. Plugins that implement this */ __NL__ \ + /* handler must not process the same event id twice in order to */ __NL__ \ + /* prevent handler loops. Events may be aborted or queued for later */ __NL__ \ + /* release (by calling `Runtime.handleKeyswitchEvent()`), but any */ __NL__ \ + /* plugin that does so must release events in ascending order, */ __NL__ \ + /* counting by ones. */ __NL__ \ + OPERATION(onKeyswitchEvent, __NL__ \ + 2, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _ABORTABLE, __NL__ \ + (),(),(), /* non template */ __NL__ \ + (KeyEvent &event), __NL__ \ + (event), ##__VA_ARGS__) __NL__ \ + __NL__ \ + /* Function called for every logical key event, including ones that */ __NL__ \ + /* originate from a physical keyswitch and ones that are injected */ __NL__ \ + /* by plugins. The `event` parameter is passed by reference so its */ __NL__ \ + /* key value can be modified. If it returns EventHandlerResult::OK, */ __NL__ \ + /* the next handler will be passed the event; otherwise */ __NL__ \ + /* Kaleidoscope will stop processing the event. */ __NL__ \ + OPERATION(onKeyEvent, __NL__ \ + 1, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _ABORTABLE, __NL__ \ + (),(),(), /* non template */ __NL__ \ + (KeyEvent &event), __NL__ \ + (event), ##__VA_ARGS__) __NL__ \ + __NL__ \ + /* Called when a new set of HID reports (Keyboard, Consumer */ __NL__ \ + /* Control, and System Control) is being constructed in response to */ __NL__ \ + /* a key event. This is mainly useful for plugins that need to add */ __NL__ \ + /* values to HID reports based on special `Key` values other than */ __NL__ \ + /* the builtin ones. */ __NL__ \ + OPERATION(onAddToReport, __NL__ \ + 1, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _ABORTABLE, __NL__ \ + (),(),(), /* non template */ __NL__ \ + (Key key), __NL__ \ + (key), ##__VA_ARGS__) __NL__ \ + __NL__ \ __NL__ \ /* Called by an external plugin (such as Kaleidoscope-FocusSerial) */ __NL__ \ /* via Kaleidoscope::onFocusEvent. This is where Focus events can */ __NL__ \ @@ -198,6 +243,17 @@ class SignatureCheckDummy {}; (),(),(), /* non template */ __NL__ \ (),(),##__VA_ARGS__) __NL__ \ __NL__ \ + /* Called before reporting our state to the host. This is the */ __NL__ \ + /* last point in a cycle where a plugin can alter what gets */ __NL__ \ + /* reported to the host. */ __NL__ \ + OPERATION(beforeReportingState, __NL__ \ + 2, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _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__ \ @@ -251,9 +307,18 @@ class SignatureCheckDummy {}; OP(beforeEachCycle, 1) __NL__ \ END(beforeEachCycle, 1) __NL__ \ __NL__ \ - START(onKeyswitchEvent, 1) __NL__ \ + START(onKeyswitchEvent, 1, 2) __NL__ \ OP(onKeyswitchEvent, 1) __NL__ \ - END(onKeyswitchEvent, 1) __NL__ \ + OP(onKeyswitchEvent, 2) __NL__ \ + END(onKeyswitchEvent, 1, 2) __NL__ \ + __NL__ \ + START(onKeyEvent, 1) __NL__ \ + OP(onKeyEvent, 1) __NL__ \ + END(onKeyEvent, 1) __NL__ \ + __NL__ \ + START(onAddToReport, 1) __NL__ \ + OP(onAddToReport, 1) __NL__ \ + END(onAddToReport, 1) __NL__ \ __NL__ \ START(onFocusEvent, 1) __NL__ \ OP(onFocusEvent, 1) __NL__ \ @@ -267,9 +332,10 @@ class SignatureCheckDummy {}; OP(onLEDModeChange, 1) __NL__ \ END(onLEDModeChange, 1) __NL__ \ __NL__ \ - START(beforeReportingState, 1) __NL__ \ + START(beforeReportingState, 1, 2) __NL__ \ OP(beforeReportingState, 1) __NL__ \ - END(beforeReportingState, 1) __NL__ \ + OP(beforeReportingState, 2) __NL__ \ + END(beforeReportingState, 1, 2) __NL__ \ __NL__ \ START(afterEachCycle, 1) __NL__ \ OP(afterEachCycle, 1) __NL__ \ From 236281fa75a11ee03e7da3eb40a2caa65a0ea91e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 14:05:48 -0500 Subject: [PATCH 046/108] Rewrite top-level event handling functions Signed-off-by: Michael Richters --- src/kaleidoscope/Runtime.cpp | 284 +++++++++++++++++- src/kaleidoscope/Runtime.h | 77 +++++ src/kaleidoscope/device/virtual/Virtual.cpp | 1 - src/kaleidoscope/driver/hid/base/Keyboard.h | 221 ++------------ .../driver/hid/keyboardio/Keyboard.h | 6 + .../driver/keyscanner/Base_Impl.h | 19 +- src/kaleidoscope/event_handlers.h | 5 +- src/kaleidoscope/hooks.h | 5 + src/kaleidoscope/key_events.cpp | 112 +------ src/kaleidoscope/key_events.h | 6 + src/kaleidoscope/layers.h | 58 ++-- src/kaleidoscope_internal/deprecations.h | 34 +++ 12 files changed, 492 insertions(+), 336 deletions(-) diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index ec353aef..0f91b954 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -1,5 +1,5 @@ /* Kaleidoscope - Firmware for computer input devices - * Copyright (C) 2013-2018 Keyboard.io, Inc. + * Copyright (C) 2013-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 @@ -22,10 +22,12 @@ namespace kaleidoscope { uint32_t Runtime_::millis_at_cycle_start_; +KeyAddr Runtime_::last_addr_toggled_on_ = KeyAddr::none(); Runtime_::Runtime_(void) { } +// ---------------------------------------------------------------------------- void Runtime_::setup(void) { // We are explicitly initializing the Serial port as early as possible to @@ -49,20 +51,294 @@ Runtime_::setup(void) { Layer.setup(); } +// ---------------------------------------------------------------------------- void Runtime_::loop(void) { millis_at_cycle_start_ = millis(); kaleidoscope::Hooks::beforeEachCycle(); +#ifndef NDEPRECATED + // For backwards compatibility. Some plugins rely on the handler for + // `beforeReportingState()` being called every cycle. In most cases, they can + // simply switch to using `afterEachCycle()`, but we don't want to simply + // break those plugins. + kaleidoscope::Hooks::beforeReportingState(); + // Also for backwards compatibility. If user code calls any code that directly + // changes the HID report(s) at any point between an event being detected and + // the end of `handleKeyEvent()` (most likely from `beforeReportingState()`), + // we need to make sure that report doesn't just get discarded. + hid().keyboard().sendReport(); +#endif + + // Next, we scan the keyswitches. Any toggle-on or toggle-off events will + // trigger a call to `handleKeyswitchEvent()`, which in turn will + // (conditionally) result in a HID report. Note that each event gets handled + // (and any resulting HID report(s) sent) as soon as it is detected. It is + // possible for more than one event to be handled like this in any given + // cycle, resulting in multiple HID reports, but guaranteeing that only one + // event is being handled at a time. device().scanMatrix(); - kaleidoscope::Hooks::beforeReportingState(); + kaleidoscope::Hooks::afterEachCycle(); +} - device().hid().keyboard().sendReport(); +// ---------------------------------------------------------------------------- +void +Runtime_::handleKeyswitchEvent(KeyEvent event) { + + // This function strictly handles physical key events. Any event without a + // valid `KeyAddr` gets ignored. + if (!event.addr.isValid()) + return; + + // Ignore any (non-)event that's not a state change. This check should be + // unnecessary, as we shouldn't call this function otherwise. + if (!(keyToggledOn(event.state) || keyToggledOff(event.state))) + return; + + // Set the `Key` value for this event. + if (keyToggledOff(event.state)) { + // When a key toggles off, set the event's key value to whatever the key's + // current value is in the live keys state array. + event.key = live_keys[event.addr]; + } else if (event.key == Key_NoKey) { + // When a key toggles on, unless the event already has a key value (i.e. we + // were called by a plugin rather than `actOnMatrixScan()`), we look up the + // value from the current keymap (overridden by `live_keys`). + event.key = lookupKey(event.addr); + } + + // Run the plugin event handlers + auto result = Hooks::onKeyswitchEvent(event); + + // If an event handler changed `event.key` to `Key_Masked` in order to mask + // that keyswitch, we need to propagate that, but since `handleKeyEvent()` + // will recognize that value as the signal to do a fresh lookup, so we need to + // set that value in `live_keys` now. The alternative would be changing it to + // some other sentinel value, and have `handleKeyEvent()` change it back to + // `Key_Masked`, but I think this makes more sense. + // + // Note: It is still important to let events with `Key_Masked` fall through to + // `handleKeyEvent()`, because some plugins might still care about the event + // regardless of its `Key` value, and more importantly, that's where we clear + // masked keys that have toggled off. Alternatively, we could call + // `live_keys.clear(addr)` for toggle-off events here, and `mask(addr)` for + // toggle-on events, then return, short-cutting the call to + // `handleKeyEvent()`. It should work, but some plugins might be able to use + // that information. + if (event.key == Key_Masked) + live_keys.mask(event.addr); + + // Now we check the result from the plugin event handlers, and stop processing + // if it was anything other than `OK`. + if (result != EventHandlerResult::OK) + return; + + // If all the plugin handlers returned OK, we proceed to the next step in + // processing the event. + handleKeyEvent(event); +} + +// ---------------------------------------------------------------------------- +void +Runtime_::handleKeyEvent(KeyEvent event) { + + // For events that didn't begin with `handleKeyswitchEvent()`, we need to look + // up the `Key` value from the keymap (maybe overridden by `live_keys`). + if (event.addr.isValid()) { + if (keyToggledOff(event.state) || event.key == Key_NoKey) { + event.key = lookupKey(event.addr); + } + } + + // If any `onKeyEvent()` handler returns `ABORT`, we return before updating + // the Live Keys state array; as if the event didn't happen. + auto result = Hooks::onKeyEvent(event); + if (result == EventHandlerResult::ABORT) + return; + + // Update the live keys array based on the new event. + if (event.addr.isValid()) { + if (keyToggledOff(event.state)) { + live_keys.clear(event.addr); + } else { + live_keys.activate(event.addr, event.key); + } + } + + // If any `onKeyEvent()` handler returned a value other than `OK`, stop + // processing now. Likewise if the event's `Key` value is a no-op. + if (result != EventHandlerResult::OK || + event.key == Key_Masked || + event.key == Key_NoKey || + event.key == Key_Transparent) + return; + + // If it's a built-in Layer key, we handle it here, and skip sending report(s) + if (event.key.isLayerKey()) { + Layer.handleKeymapKeyswitchEvent(event.key, event.state); + //return; + } + + // The System Control HID report contains only one keycode, and gets sent + // immediately on `pressSystemControl()` or `releaseSystemControl()`. This is + // significantly different from the way the other HID reports work, where held + // keys remain in effect for subsequent reports. + if (event.key.isSystemControlKey()) { + if (keyToggledOn(event.state)) { + hid().keyboard().pressSystemControl(event.key); + } else { /* if (keyToggledOff(key_state)) */ + hid().keyboard().releaseSystemControl(event.key); + } + return; + } + + // Until this point, the old report was still valid. Now we construct the new + // one, based on the contents of the `live_keys` state array. + prepareKeyboardReport(event); + +#ifndef NDEPRECATED + // Deprecated handlers might depend on values in the report, so we wait until + // the new report is otherwise complete before calling them. + auto old_result = Hooks::onKeyswitchEvent(event.key, event.addr, event.state); + if (old_result == EventHandlerResult::ABORT) + return; + + if (old_result != EventHandlerResult::OK || + event.key == Key_Masked || + event.key == Key_NoKey || + event.key == Key_Transparent) + return; +#endif + + // Finally, send the new keyboard report + sendKeyboardReport(event); +} + +// ---------------------------------------------------------------------------- +void +Runtime_::prepareKeyboardReport(const KeyEvent &event) { + // before building the new report, start clean device().hid().keyboard().releaseAllKeys(); - kaleidoscope::Hooks::afterEachCycle(); + // Build report from composite keymap cache. This can be much more efficient + // with a bitfield. What we should be doing here is going through the array + // and checking for HID values (Keyboard, Consumer, System) and directly + // adding them to their respective reports. This comes before the old plugin + // hooks are called for the new event so that the report will be full complete + // except for that new event. + for (KeyAddr key_addr : KeyAddr::all()) { + // Skip this event's key addr; we will deal with that later. This is most + // important in the case of a key release, because we can't safely remove + // any keycode(s) added to the report later. + if (key_addr == event.addr) + continue; + + Key key = live_keys[key_addr]; + + // If the key is idle or masked, we can ignore it. + if (key == Key_Inactive || key == Key_Masked) + continue; + +#ifndef NDEPRECATED + // Only run hooks for plugin keys. If a plugin needs to do something every + // cycle, it can use one of the every-cycle hooks and search for active keys + // of interest. + auto result = Hooks::onKeyswitchEvent(key, key_addr, IS_PRESSED | WAS_PRESSED); + if (result == EventHandlerResult::ABORT) + continue; + + if (key_addr == event.addr) { + // update active keys cache? + if (keyToggledOn(event.state)) { + live_keys.activate(event.addr, key); + } else { + live_keys.clear(event.addr); + } + } +#endif + + addToReport(key); + } +} + +// ---------------------------------------------------------------------------- +void +Runtime_::addToReport(Key key) { + // First, call any relevant plugin handlers, to give them a chance to add + // other values to the HID report directly and/or to abort the automatic + // adding of keycodes below. + auto result = Hooks::onAddToReport(key); + if (result == EventHandlerResult::ABORT) + return; + + if (key.isKeyboardKey()) { + // The only incidental Keyboard modifiers that are allowed are the ones on + // the key that generated the event, so we strip any others before adding + // them. This might turn out to be too simple to cover all the corner cases, + // but the OS should be helpful and do most of the masking we want for us. + if (!key.isKeyboardModifier()) + key.setFlags(0); + + hid().keyboard().pressKey(key); + return; + } + + if (key.isConsumerControlKey()) { + hid().keyboard().pressConsumerControl(key); + return; + } + + // System Control keys and Layer keys are not handled here because they only + // take effect on toggle-on or toggle-off events, they don't get added to HID + // reports when held. +} + +// ---------------------------------------------------------------------------- +void +Runtime_::sendKeyboardReport(const KeyEvent &event) { + // If the keycode for this key is already in the report, we need to send an + // extra report without that keycode in order to correctly process the + // rollover. It might be better to exempt modifiers from this rule, but it's + // not clear that would be better. + if (keyToggledOn(event.state) && + event.key.isKeyboardKey()) { + // last keyboard key toggled on + last_addr_toggled_on_ = event.addr; + if (hid().keyboard().isKeyPressed(event.key)) { + // The keycode (flags ignored) for `event.key` is active in the current + // report. Should this be `wasKeyPressed()` instead? I don't think so, + // because (if I'm right) the new event hasn't been added yet. + hid().keyboard().releaseKey(event.key); + hid().keyboard().sendReport(); + } + if (event.key.getFlags() != 0) { + hid().keyboard().pressModifiers(event.key); + hid().keyboard().sendReport(); + } + } else if (event.addr != last_addr_toggled_on_) { + // (not a keyboard key OR toggled off) AND not last keyboard key toggled on + Key last_key = live_keys[last_addr_toggled_on_]; + if (last_key.isKeyboardKey()) { + hid().keyboard().pressModifiers(last_key); + } + } + if (keyToggledOn(event.state)) { + addToReport(event.key); + } + +#ifndef NDEPRECATED + // Call old pre-report handlers: + Hooks::beforeReportingState(); +#endif + + // Call new pre-report handlers: + if (Hooks::beforeReportingState(event) == EventHandlerResult::ABORT) + return; + + // Finally, send the report: + device().hid().keyboard().sendReport(); } Runtime_ Runtime; diff --git a/src/kaleidoscope/Runtime.h b/src/kaleidoscope/Runtime.h index 7a4c0218..7ebff605 100644 --- a/src/kaleidoscope/Runtime.h +++ b/src/kaleidoscope/Runtime.h @@ -19,6 +19,9 @@ #include "kaleidoscope_internal/device.h" #include "kaleidoscope/event_handler_result.h" #include "kaleidoscope/hooks.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/LiveKeys.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { @@ -128,8 +131,82 @@ class Runtime_ { return kaleidoscope::Hooks::onFocusEvent(command); } + /** Handle a physical keyswitch event + * + * This method is called in response to physical keyswitch state changes. Its + * job is to call the `onKeyswitchEvent()` plugin handler functions, used by + * plugins that are particularly concerned about the timing of those + * events. It takes only one parameter, of type `KeyEvent`, which encapsulates + * the information about that event: + * + * - `event.key_addr`: The address of the key that was pressed or released. + * - `event.state`: The state of the keyswitch event (toggled on or off). + * - `event.key`: The `Key` value for this event. + * - `event.id`: A semi-unique ID value for the event. + * + * The ID value is used to help plugins that delay events to coordinate with + * each other so that they can avoid re-processing the same event, possibly + * causing endless loops. + */ + void handleKeyswitchEvent(KeyEvent event); + + /** Handle a logical key event + * + * This method triggers the handling of a logical "key event". Ususally that + * event is the result of a call to `handleKeyswitchEvent()`, but it can also + * be called by plugins that need to generate extra events without a 1:1 + * mapping to physical keyswitch state transitions. + */ + void handleKeyEvent(KeyEvent event); + + /** Prepare a new set of USB HID reports + * + * This method gets called when a key event results in at least one new HID + * report being sent to the host, usually as a result of a call to + * `handleKeyEvent()`. It clears the keyboard report (after plugins have + * already responded to the new event that triggered the forthcoming report), + * then populates the new report based on the values stored in the `live_keys` + * state array. + */ + void prepareKeyboardReport(const KeyEvent &event); + + /** Add keycode(s) to a USB HID report + * + * This method gets called from `prepareKeyboardReport()` to add keycodes + * corresponding to active keys in the `live_keys` state array to the Keyboard + * & Consumer Control HID reports. It calls the `onAddToReport()` plugin + * handlers first to give them a chance to abort. + */ + void addToReport(Key key); + + /** Send the new USB HID report(s) + * + * This method is called by `handleKeyEvent()` after `prepareKeyboardReport()` + * is done. It uses the information about the new event to guard against + * modifier and mod-flags rollover issues, and calls the + * `beforeReportingState()` plugin handler functions before sending the + * complete Keyboard and Consumer Control HID reports. + */ + void sendKeyboardReport(const KeyEvent &event); + + /** Get the current value of a keymap entry + * + * Returns the `Key` value for a given `KeyAddr` entry in the current keymap, + * overridden by any active entry in the `live_keys` array. + */ + Key lookupKey(KeyAddr key_addr) { + // First, check for an active key value in the `live_keys` array. + Key key = live_keys[key_addr]; + // If that entry is clear, look up the entry from the active keymap layers. + if (key == Key_Transparent) { + key = Layer.lookupOnActiveLayer(key_addr); + } + return key; + } + private: static uint32_t millis_at_cycle_start_; + static KeyAddr last_addr_toggled_on_; }; extern kaleidoscope::Runtime_ Runtime; diff --git a/src/kaleidoscope/device/virtual/Virtual.cpp b/src/kaleidoscope/device/virtual/Virtual.cpp index 11123f9d..82fef177 100644 --- a/src/kaleidoscope/device/virtual/Virtual.cpp +++ b/src/kaleidoscope/device/virtual/Virtual.cpp @@ -24,7 +24,6 @@ #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/MatrixAddr.h" #include "kaleidoscope/key_defs.h" -#include "kaleidoscope/key_events.h" #include "HIDReportObserver.h" diff --git a/src/kaleidoscope/driver/hid/base/Keyboard.h b/src/kaleidoscope/driver/hid/base/Keyboard.h index 8ebeb894..0b2a02cb 100644 --- a/src/kaleidoscope/driver/hid/base/Keyboard.h +++ b/src/kaleidoscope/driver/hid/base/Keyboard.h @@ -135,64 +135,14 @@ class Keyboard { } void sendReport() __attribute__((noinline)) { - // Before sending the report, we add any modifier flags that are currently - // allowed, based on the latest keypress: - pressModifiers(requested_modifier_flags & modifier_flag_mask); - - // If a key has been toggled on in this cycle, we might need to send an extra - // HID report to the host, because that key might have the same keycode as - // another key that was already in the report on the previous cycle. For - // example, a user could have two `Key_E` keys in their keymap, in order to - // avoid repeating the same key with one finger. Or one might have a - // `LCTRL(Key_S)` and a plain `Key_S`, and have a reason to press them in - // rapid succession. In order to make this work, we need to call `release()` & - // `sendReport()` to send a release event to the host so that its normal - // repeat-rate-limiting behaviour won't effectively mask the second keypress. - // Then we call `press()` to add the keycode back in before sending the normal - // report. - // - // In most cases, this won't result in any difference from the previous report - // (because the newly-toggled-on keycode won't be in the previous report), so - // no extra report will be sent (because we suppress duplicate reports in - // KeyboardioHID). If there is a difference in the modifiers byte, an extra - // report would be sent later, regardless (also in KeyboardioHID). - // - // Furthermore, we need to send a report without the keycode for the - // newly-toggled-on key, but with any masked modifiers from flags removed. For - // example, if we roll over from `LSHIFT(Key_A)` to `Key_B`, we need to first - // send a report without the `shift`, then a second report with the `B`. If - // both of those bits are changed in the same report, at least one major OS - // processes the `B` keypress first, and we end up with `AB` instead of `Ab` - // in the output. - if (boot_keyboard_.getProtocol() == HID_BOOT_PROTOCOL) { - if (last_keycode_toggled_on) { - boot_keyboard_.release(last_keycode_toggled_on); - boot_keyboard_.sendReport(); - boot_keyboard_.press(last_keycode_toggled_on); - last_keycode_toggled_on = 0; - } boot_keyboard_.sendReport(); return; } - - if (last_keycode_toggled_on) { - // It would be good if KeyboardioHID's Keyboard object offered a way to - // compare the modifiers bytes of the current and previous key reports. That - // would allow us to only send these extra reports when either - // `last_keycode_toggled_on` was already held, or the modifiers byte - // changed. Likewise for BootKeyboard above. - nkro_keyboard_.release(last_keycode_toggled_on); - nkro_keyboard_.sendReport(); - nkro_keyboard_.press(last_keycode_toggled_on); - last_keycode_toggled_on = 0; - } - nkro_keyboard_.sendReport(); consumer_control_.sendReport(); } void releaseAllKeys() __attribute__((noinline)) { - resetModifierTracking(); if (boot_keyboard_.getProtocol() == HID_BOOT_PROTOCOL) { boot_keyboard_.releaseAll(); } else { @@ -218,71 +168,30 @@ class Keyboard { } } - // pressKey takes a Key, as well as optional boolean 'toggledOn' which defaults - // to 'true' - - // If toggled_on is not set to false, this routine adds the modifier flags on - // this key to the bitmask of modifiers that are allowed to be added to the - // upcoming report. We do this so that when we roll over from a key with a - // modifier flag to one without it, that modifier flag won't affect the new - // keypress. - - // If the key we're processing is a modifier key, any modifier flags attached to - // it are added directly to the report along with the modifier from its keycode - // byte. - // - // (A 'modifier key' is one of the eight modifier keys defined by the HID - // standard: left and right variants of Control, Shift, Alt, and GUI.) - - // Eventually it calls pressRawKey. - - void pressKey(Key pressed_key, boolean toggled_on = true) __attribute__((noinline)) { - if (toggled_on) { - // If two keys are toggled on during the same USB report, we would ideally - // send an extra USB report to help the host handle each key correctly, but - // this is problematic. - - // If we simply allow modifiers associated with the second newly-pressed - // key, it is possible to drop a modifier before the report is sent. - // Instead, we send modifiers associated with any newly-pressed keys. - - // The downside of this behavior is that in cases where the user presses - // down keys with conflicting modifiers at the exact same moment, they may - // get unexpected behavior. - - // If this is the first 'new' keycode being pressed in this cycle, reset the - // bitmask of modifiers we're willing to attach to USB HID keyboard reports - if (!last_keycode_toggled_on) { - modifier_flag_mask = 0; - } - - // Add any modifiers attached to this key to the bitmask of modifiers we're - // willing to attach to USB HID keyboard reports - modifier_flag_mask |= pressed_key.getFlags(); - - if (!isModifierKey(pressed_key)) { - last_keycode_toggled_on = pressed_key.getKeyCode(); - } - } - - - if (isModifierKey(pressed_key)) { - // If the key is a modifier key with additional modifiers attached to it as - // flags (as one might when creating a 'Hyper' key or a "Control Alt" key, - // we assume that all those modifiers are intended to modify other keys - // pressed while this key is held, so they are never masked out. - pressModifiers(pressed_key.getFlags()); - } else { - // If, instead, the modifiers are attached to a 'printable' or non-modifier - // key, we assume that they're not intended to modify other keys, so we add - // them to requested_modifier_flags, and only allow them to affect the report if - // the most recent keypress includes those modifiers. - requestModifiers(pressed_key.getFlags()); - } + DEPRECATED(HID_KEYBOARD_PRESSKEY_TOGGLEDON) + void pressKey(Key pressed_key, bool toggled_on) __attribute__((noinline)) { + pressKey(pressed_key); + } + void pressKey(Key pressed_key) __attribute__((noinline)) { + pressModifiers(pressed_key); pressRawKey(pressed_key); } + void pressModifiers(Key pressed_key) { + pressModifiers(pressed_key.getFlags()); + } + void releaseModifiers(Key released_key) { + releaseModifiers(released_key.getFlags()); + } + void clearModifiers() { + releaseRawKey(Key_LeftShift); + releaseRawKey(Key_LeftControl); + releaseRawKey(Key_LeftAlt); + releaseRawKey(Key_RightAlt); + releaseRawKey(Key_LeftGui); + } + // pressRawKey takes a Key object and calles KeyboardioHID's ".press" method // with its keycode. It does no processing of any flags or modifiers on the key void pressRawKey(Key pressed_key) { @@ -304,29 +213,17 @@ class Keyboard { } void releaseKey(Key released_key) { - // Remove any modifiers attached to this key from the bitmask of modifiers we're - // willing to attach to USB HID keyboard reports - modifier_flag_mask &= ~(released_key.getFlags()); - - if (!isModifierKey(released_key)) { - - // TODO(anyone): this code is incomplete, but is better than nothing - // If we're toggling off the most recently toggled on key, clear - // last_keycode_toggled_on - if (last_keycode_toggled_on == released_key.getKeyCode()) { - last_keycode_toggled_on = 0; - } - - // If the modifiers are attached to a 'printable' or non-modifier - // key, we need to clean up after the key press which would have requested - // the modifiers be pressed if the most recent keypress includes those modifiers. - cancelModifierRequest(released_key.getFlags()); - } - releaseModifiers(released_key.getFlags()); releaseRawKey(released_key); } + bool isKeyPressed(Key key) { + if (boot_keyboard_.getProtocol() == HID_BOOT_PROTOCOL) { + return boot_keyboard_.isKeyPressed(key.getKeyCode()); + } + return nkro_keyboard_.isKeyPressed(key.getKeyCode()); + } + boolean isModifierKeyActive(Key modifier_key) { if (boot_keyboard_.getProtocol() == HID_BOOT_PROTOCOL) { return boot_keyboard_.isModifierActive(modifier_key.getKeyCode()); @@ -384,70 +281,6 @@ class Keyboard { // not be a valid System Control keycode. uint8_t last_system_control_keycode_ = 0; - // modifier_flag_mask is a bitmask of modifiers that we found attached to - // keys that were newly pressed down during the most recent cycle with any new - // keypresses. - - // This is used to determine which modifier flags will be allowed to be added to - // the current keyboard HID report. It gets set during any cycle where one or - // more new keys is toggled on and presists until the next cycle with a newly - // detected keypress. - - uint8_t modifier_flag_mask = 0; - - // The functions in this namespace are primarily to solve the problem of - // rollover from a key with a modifier flag (e.g. `LSHIFT(Key_T)`) to one - // without (e.g. `Key_H`), which used to result in the mod flag being applied to - // keys other than the one with the flag. By using `modifier_flag_mask`, we can - // mask out any modifier flags that aren't attached to modifier keys or keys - // pressed or held in the most recent cycle, mitigating the rollover problem, - // and getting the intended `The` instead of `THe`. - - // requested_modifier_flags is bitmap of the modifiers attached to any non-modifier - // key found to be pressed during the most recent cycle. For example, it would - // include modifiers attached to Key_A, but not modifiers attached to - // Key_LeftControl - - uint8_t requested_modifier_flags = 0; - - // last_keycode_toggled_on is the keycode of the key most recently toggled on - // for this report. This is set when a keypress is first detected and cleared - // after the report is sent. If multiple keys are toggled on during a single - // cycle, this contains the most recently handled one. - - uint8_t last_keycode_toggled_on = 0; - - void resetModifierTracking(void) { - last_keycode_toggled_on = 0; - requested_modifier_flags = 0; - } - - // isModifierKey takes a Key and returns true if the key is a - // keyboard key corresponding to a modifier like Control, Alt or Shift - // TODO(anyone): This function should be lifted to the Kaleidoscope core, somewhere. - - bool isModifierKey(Key key) { - // If it's not a keyboard key, return false - if (key.getFlags() & (SYNTHETIC | RESERVED)) return false; - - return (key.getKeyCode() >= HID_KEYBOARD_FIRST_MODIFIER && - key.getKeyCode() <= HID_KEYBOARD_LAST_MODIFIER); - } - - // requestModifiers takes a bitmap of modifiers that might apply - // to the next USB HID report and adds them to a bitmap of all such modifiers. - - void requestModifiers(byte flags) { - requested_modifier_flags |= flags; - } - - // cancelModifierRequest takes a bitmap of modifiers that should no longer apply - // to the next USB HID report and removes them from the bitmap of all such modifiers. - - void cancelModifierRequest(byte flags) { - requested_modifier_flags &= ~flags; - } - // pressModifiers takes a bitmap of modifier keys that must be included in // the upcoming USB HID report and passes them through to KeyboardioHID // immediately diff --git a/src/kaleidoscope/driver/hid/keyboardio/Keyboard.h b/src/kaleidoscope/driver/hid/keyboardio/Keyboard.h index 8023eee2..6db99718 100644 --- a/src/kaleidoscope/driver/hid/keyboardio/Keyboard.h +++ b/src/kaleidoscope/driver/hid/keyboardio/Keyboard.h @@ -69,6 +69,9 @@ class BootKeyboardWrapper { BootKeyboard.releaseAll(); } + bool isKeyPressed(uint8_t code) { + return BootKeyboard.isKeyPressed(code); + } bool isModifierActive(uint8_t code) { return BootKeyboard.isModifierActive(code); } @@ -108,6 +111,9 @@ class NKROKeyboardWrapper { Keyboard.releaseAll(); } + bool isKeyPressed(uint8_t code) { + return Keyboard.isKeyPressed(code); + } bool isModifierActive(uint8_t code) { return Keyboard.isModifierActive(code); } diff --git a/src/kaleidoscope/driver/keyscanner/Base_Impl.h b/src/kaleidoscope/driver/keyscanner/Base_Impl.h index e2f17c23..fb958e0f 100644 --- a/src/kaleidoscope/driver/keyscanner/Base_Impl.h +++ b/src/kaleidoscope/driver/keyscanner/Base_Impl.h @@ -19,15 +19,30 @@ #include "kaleidoscope/driver/keyscanner/Base.h" #include "kaleidoscope/device/device.h" +#include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/Runtime.h" namespace kaleidoscope { namespace driver { namespace keyscanner { template<> -void Base::handleKeyswitchEvent(Key mappedKey, kaleidoscope::Device::Props::KeyScannerProps::KeyAddr key_addr, uint8_t keyState) { - ::handleKeyswitchEvent(mappedKey, key_addr, keyState); +void Base::handleKeyswitchEvent( + Key key __attribute__((unused)), + kaleidoscope::Device::Props::KeyScannerProps::KeyAddr key_addr, + uint8_t key_state) { + + // Because the `KeyEvent` constructor invoked below assigns a new `EventId` + // each time it's called, and plugins that implement `onKeyswitchEvent()` and + // use those event ID numbers to determine whether or not an event is new, + // it's critical that we do the test for keyswitches toggling on or off first. + if (keyToggledOn(key_state) || keyToggledOff(key_state)) { + auto event = KeyEvent::next(key_addr, key_state); + kaleidoscope::Runtime.handleKeyswitchEvent(event); + } } + } // namespace keyscanner } // namespace driver } // namespace kaleidoscope diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index 4f7f630f..ae24ab01 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -139,6 +139,7 @@ class SignatureCheckDummy {}; (),(),(), /* non template */ __NL__ \ (), (), ##__VA_ARGS__) __NL__ \ __NL__ \ + /* DEPRECATED */ __NL__ \ /* Function called for every non-idle key, every cycle, so it */ __NL__ \ /* can decide what to do with it. It can modify the key (which is */ __NL__ \ /* passed by reference for this reason), and decide whether */ __NL__ \ @@ -148,7 +149,7 @@ class SignatureCheckDummy {}; /* will stop processing there. */ __NL__ \ OPERATION(onKeyswitchEvent, __NL__ \ 1, __NL__ \ - _CURRENT_IMPLEMENTATION, __NL__ \ + DEPRECATED(ON_KEYSWITCH_EVENT_V1), __NL__ \ _ABORTABLE, __NL__ \ (),(),(), /* non template */ __NL__ \ (Key &mappedKey, KeyAddr key_addr, uint8_t keyState), __NL__ \ @@ -238,7 +239,7 @@ class SignatureCheckDummy {}; /* reported to the host. */ __NL__ \ OPERATION(beforeReportingState, __NL__ \ 1, __NL__ \ - _CURRENT_IMPLEMENTATION, __NL__ \ + DEPRECATED(BEFORE_REPORTING_STATE_V1), __NL__ \ _NOT_ABORTABLE, __NL__ \ (),(),(), /* non template */ __NL__ \ (),(),##__VA_ARGS__) __NL__ \ diff --git a/src/kaleidoscope/hooks.h b/src/kaleidoscope/hooks.h index d59ffccf..cda1710e 100644 --- a/src/kaleidoscope/hooks.h +++ b/src/kaleidoscope/hooks.h @@ -23,13 +23,16 @@ class Key; } #include "kaleidoscope/KeyAddr.h" +#include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/plugin.h" #include "kaleidoscope/event_handlers.h" // Forward declaration required to enable friend declarations // in class Hooks. class kaleidoscope_; +#ifndef NDEPRECATED extern void handleKeyswitchEvent(kaleidoscope::Key mappedKey, KeyAddr key_addr, uint8_t keyState); +#endif namespace kaleidoscope { namespace plugin { @@ -67,9 +70,11 @@ class Hooks { friend class ::kaleidoscope::plugin::LEDControl; friend void ::kaleidoscope::sketch_exploration::pluginsExploreSketch(); +#ifndef NDEPRECATED // ::handleKeyswitchEvent(...) calls Hooks::onKeyswitchEvent. friend void ::handleKeyswitchEvent(kaleidoscope::Key mappedKey, KeyAddr key_addr, uint8_t keyState); +#endif private: diff --git a/src/kaleidoscope/key_events.cpp b/src/kaleidoscope/key_events.cpp index 55987049..eae4a95b 100644 --- a/src/kaleidoscope/key_events.cpp +++ b/src/kaleidoscope/key_events.cpp @@ -14,115 +14,21 @@ * this program. If not, see . */ +#ifndef NDEPRECATED #include "kaleidoscope/Runtime.h" #include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/hooks.h" #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/layers.h" +#include "kaleidoscope/event_handler_result.h" -static bool handleSyntheticKeyswitchEvent(Key mappedKey, uint8_t keyState) { - if (mappedKey.getFlags() & RESERVED) - return false; - - if (!(mappedKey.getFlags() & SYNTHETIC)) - return false; - - using kaleidoscope::Runtime; - - if (mappedKey.getFlags() & IS_CONSUMER) { - if (keyIsPressed(keyState)) - Runtime.hid().keyboard().pressConsumerControl(mappedKey); - } else if (mappedKey.getFlags() & IS_SYSCTL) { - if (keyToggledOn(keyState)) { - Runtime.hid().keyboard().pressSystemControl(mappedKey); - } else if (keyToggledOff(keyState)) { - Runtime.hid().keyboard().releaseSystemControl(mappedKey); - } - } else if (mappedKey.getFlags() & IS_INTERNAL) { - return false; - } else if (mappedKey.getFlags() & SWITCH_TO_KEYMAP) { - Layer.handleKeymapKeyswitchEvent(mappedKey, keyState); - } - - return true; -} - -static bool handleKeyswitchEventDefault(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { - using kaleidoscope::Runtime; - //for every newly pressed button, figure out what logical key it is and send a key down event - // for every newly released button, figure out what logical key it is and send a key up event - - if (mappedKey.getFlags() & SYNTHETIC) { - handleSyntheticKeyswitchEvent(mappedKey, keyState); - } else if (keyToggledOn(keyState)) { - Runtime.hid().keyboard().pressKey(mappedKey); - } else if (keyIsPressed(keyState)) { - Runtime.hid().keyboard().pressKey(mappedKey, false); - } else if (keyToggledOff(keyState) && (keyState & INJECTED)) { - Runtime.hid().keyboard().releaseKey(mappedKey); - } - return true; -} - -void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { - - using kaleidoscope::Runtime; - - /* These first steps are only done for keypresses that have a valid key_addr. - * In particular, doing them for keypresses with out-of-bounds key_addr - * would cause out-of-bounds array accesses in Layer.lookup(), - * Layer.updateLiveCompositeKeymap(), etc. - * Note that many INJECTED keypresses use UnknownKeyswitchLocation - * which gives us an invalid key_addr here. Therefore, it's legitimate that - * we may have keypresses with out-of-bounds key_addr. - * However, some INJECTED keypresses do have valid key_addr if they are - * injecting an event tied to a physical keyswitch - and we want them to go - * through this lookup. - * So we can't just test for INJECTED here, we need to test the key_addr - * directly. - * Note also that this key_addr test avoids out-of-bounds accesses in *core*, - * but doesn't guarantee anything about event handlers - event handlers may - * still receive out-of-bounds key_addr, and handling that properly is on - * them. - */ - if (key_addr.isValid()) { - // If the caller did not supply a `Key` value, get it from the keymap - // cache. If that value is transparent, look it up from the active layer for - // that key address. - if (mappedKey == Key_NoKey) { - // Note: If the next line returns `Key_NoKey`, that will effectively mask - // the key. - mappedKey = Layer.lookup(key_addr); - } - - } // key_addr valid - - // Keypresses with out-of-bounds key_addr start here in the processing chain - auto result = kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState); - - // If any plugin returns `ABORT`, stop here and don't update the active keys - // cache entry. +// Deprecated. See `Runtime.handleKeyEvent()` +void handleKeyswitchEvent(Key key, KeyAddr key_addr, uint8_t key_state) { + // Perhaps we should call deprecated plugin event handlers here? + auto result = kaleidoscope::Hooks::onKeyswitchEvent(key, key_addr, key_state); if (result == kaleidoscope::EventHandlerResult::ABORT) return; - - // Update the keyboard state array - if (key_addr.isValid()) { - if (keyToggledOn(keyState)) { - kaleidoscope::live_keys.activate(key_addr, mappedKey); - } else if (keyToggledOff(keyState)) { - kaleidoscope::live_keys.clear(key_addr); - } - } - - // Only continue if all plugin handlers returned `OK`. - if (result != kaleidoscope::EventHandlerResult::OK) - return; - - // If the key has been masked (i.e. `Key_NoKey`), or it's a plugin-specific - // key (`RESERVED`), don't bother continuing. - if (mappedKey == Key_NoKey || (mappedKey.getFlags() & RESERVED) != 0) - return; - - // Handle all built-in key types. - handleKeyswitchEventDefault(mappedKey, key_addr, keyState); + if (keyIsPressed(key_state)) + kaleidoscope::Runtime.addToReport(key); } +#endif diff --git a/src/kaleidoscope/key_events.h b/src/kaleidoscope/key_events.h index 45d7ca8e..3a0ade75 100644 --- a/src/kaleidoscope/key_events.h +++ b/src/kaleidoscope/key_events.h @@ -65,4 +65,10 @@ * currentState may be flagged INJECTED, which signals that the event was * injected, and is not a direct result of a keypress, coming from the scanner. */ + +#ifndef NDEPRECATED + +DEPRECATED(HANDLE_KEYSWITCH_EVENT) void handleKeyswitchEvent(Key mappedKey, kaleidoscope::Device::Props::KeyScannerProps::KeyAddr key_addr, uint8_t keyState); + +#endif diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index d12fbdf1..e2265a92 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -20,12 +20,15 @@ #include "kaleidoscope/key_defs.h" #include "kaleidoscope/keymaps.h" #include "kaleidoscope/device/device.h" -#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope_internal/device.h" #include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h" #include "kaleidoscope_internal/shortname.h" #include "kaleidoscope_internal/deprecations.h" +#ifndef NDEPRECATED +#include "kaleidoscope/LiveKeys.h" +#endif + #define START_KEYMAPS __NL__ \ constexpr Key keymaps_linear[][kaleidoscope_internal::device.matrix_rows * kaleidoscope_internal::device.matrix_columns] PROGMEM = { @@ -54,35 +57,30 @@ class Layer_ { void setup(); - /* There are two lookup functions, because we have two caches, and different - * parts of the firmware will want to use either this or that (or perhaps - * both, in rare cases). - * - * First of all, we use caches because looking up a key through all the layers - * is costy, and the cost increases dramatically the more layers we have. - * - * Then, we have the `liveCompositeKeymap`, because to have layer behaviours - * we want, that is, if you hold a key on a layer, release the layer key but - * continue holding the other, we want for the layered keycode to continue - * repeating. - * - * At the same time, we want other keys to not be affected by the - * now-turned-off layer. So we update the keycode in the cache on-demand, when - * the key is pressed. (see the top of `handleKeyswitchEvent`). - * - * On the other hand, we also have plugins that scan the whole keymap, and do - * things based on that information, such as highlighting keys that changed - * between layers. These need to be able to look at a state of where the - * keymap *should* be, not necessarily where it is. The `liveCompositeKeymap` - * is not useful here. So we use `activeLayers` which we update whenever - * layers change (see `Layer.on` and `Layer.off`), and it updates the cache to - * show how the keymap should look, without the `liveCompositeKeymap`-induced - * behaviour. - * - * Thus, if we are curious about what a given key will do, use `lookup`. If we - * are curious what the active layer state describes the key as, use - * `lookupOnActiveLayer`. - */ + // There are two lookup functions here, for historical reasons. Previously, + // Kaleidoscope would need to look up a value for each active keyswitch in + // every cycle, and pass that value on to the "event" handlers. Most of these + // lookups were for keys that were being held, not toggled on or off. Because + // these lookups were so frequent, a cache was used to speed them up. + // + // We no longer need to look up these values every cycle for keys that are + // held, because Kaleidoscope now only acts on key events that are actual + // toggle-on or toggle-off events, so the speed of the lookups here is not so + // critical. However, the old "live composite keymap" cache was also used by + // some plugins (and certain parts of Kaleidoscope itself) to override values + // in the keymap, and these plugins might use calls to `Layer.lookup()`, + // expecting to get the override values. + // + // Therefore, the `lookup()` function below first checks the `live_keys` array + // (the keyboard state array that has replaced the keymap cache). This should + // allow old code to continue working, until all the associated code (mostly + // the `onKeyswitchEvent()` handlers) is replaced, at which point we can + // remove dependence on `live_keys` entirely from this class. + // + // The `Runtime.lookupKey()` function replaces this one, for plugins that + // still want to do this same check. + + DEPRECATED(LAYER_LOOKUP) static Key lookup(KeyAddr key_addr) { // First check the keyboard state array Key key = live_keys[key_addr]; diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index 5cae97ee..93dcebe3 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -38,3 +38,37 @@ #define _DEPRECATED_MESSAGE_LAYER_EVENTHANDLER __NL__ \ "`Layer.eventHandler()` is deprecated.\n" __NL__ \ "Please use `Layer.handleKeymapKeyswitchEvent()` instead." + +#define _DEPRECATED_MESSAGE_LAYER_LOOKUP __NL__ \ + "`Layer.lookup(key_addr)` is deprecated.\n" __NL__ \ + "Please use `Runtime.lookupKey(key_addr)` instead. Alternatively, if you\n" __NL__ \ + "need to look up the current keymap entry without regard to current live\n" __NL__ \ + "key state(s) (i.e. the `live_keys` array, which normally overrides the\n" __NL__ \ + "keymap), you can use `Layer.lookupOnActiveLayer(key_addr)`." + +#define _DEPRECATED_MESSAGE_HANDLE_KEYSWITCH_EVENT __NL__ \ + "`handleKeyswitchEvent()` has been deprecated.\n" __NL__ \ + "Please use `Runtime.handleKeyEvent()` instead." + +#define _DEPRECATED_MESSAGE_ON_KEYSWITCH_EVENT_V1 __NL__ \ + "The `onKeyswitchEvent()` event handler is deprecated.\n" __NL__ \ + "Please replace it with an `onKeyEvent()` handler. See the documentation\n" __NL__ \ + "in UPGRADING.md and docs/api-reference/event-handler-hooks.md for more\n" __NL__ \ + "information on what changes are needed to adapt old plugins to the new\n" __NL__ \ + "event handler API." + +#define _DEPRECATED_MESSAGE_BEFORE_REPORTING_STATE_V1 __NL__ \ + "This `beforeReportingState()` event handler version is deprecated.\n" __NL__ \ + "There is a new `beforeReportingState(KeyEvent)` handler that can be used\n" __NL__ \ + "instead, for plugins that need to execute code before each new HID\n" __NL__ \ + "report is sent. However, the new handler does not run every cycle, but\n" __NL__ \ + "only in response to key events. If you have code that is intended to run\n" __NL__ \ + "every scan cycle, it should be moved to the `afterEachCycle()` event\n" __NL__ \ + "handler instead." + +#define _DEPRECATED_MESSAGE_HID_KEYBOARD_PRESSKEY_TOGGLEDON __NL__ \ + "The `Keyboard::pressKey(key, toggled_on)` function is deprecated.\n" __NL__ \ + "Please use `Keyboard::pressKey(key)` without the second argument\n" __NL__ \ + "instead. The version with two arguments handled rollover events, and\n" __NL__ \ + "this is now handled more completely by the event handling functions in\n" __NL__ \ + "`Runtime`." From 7d16958a7ab1c8bf0b47c68a070b02712f2e00b0 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 7 Apr 2021 00:33:34 -0500 Subject: [PATCH 047/108] Replace `handleKeymapKeyswitchEvent()` with `handleLayerKeyEvent()` The new version of the layer change `Key` handler is more consistent with the other `KeyEvent` handling functions, and properly checks for a second layer shift key being held when releasing the first one. Signed-off-by: Michael Richters --- src/kaleidoscope/Runtime.cpp | 2 +- src/kaleidoscope/layers.cpp | 72 ++++++++++++++++-------- src/kaleidoscope/layers.h | 6 ++ src/kaleidoscope_internal/deprecations.h | 4 ++ 4 files changed, 60 insertions(+), 24 deletions(-) diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index 0f91b954..b8ab4f10 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -177,7 +177,7 @@ Runtime_::handleKeyEvent(KeyEvent event) { // If it's a built-in Layer key, we handle it here, and skip sending report(s) if (event.key.isLayerKey()) { - Layer.handleKeymapKeyswitchEvent(event.key, event.state); + Layer.handleLayerKeyEvent(event); //return; } diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index 12c4ffc7..a5d47722 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -18,6 +18,8 @@ #include "kaleidoscope/hooks.h" #include "kaleidoscope/layers.h" #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/LiveKeys.h" // The maximum number of layers allowed. `layer_state_`, which stores // the on/off status of the layers in a bitfield has only 32 bits, and @@ -54,30 +56,36 @@ void Layer_::setup() { Layer.updateActiveLayers(); } -void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { - if (keymapEntry.getKeyCode() >= LAYER_MOVE_OFFSET) { +void Layer_::handleLayerKeyEvent(const KeyEvent &event) { + // The caller is responsible for checking that this is a Layer `Key`, so we + // skip checking for it here. + uint8_t key_code = event.key.getKeyCode(); + uint8_t target_layer; + + if (key_code >= LAYER_MOVE_OFFSET) { // MoveToLayer() - if (keyToggledOn(keyState)) { - move(keymapEntry.getKeyCode() - LAYER_MOVE_OFFSET); + if (keyToggledOn(event.state)) { + target_layer = key_code - LAYER_MOVE_OFFSET; + move(target_layer); } - } else if (keymapEntry.getKeyCode() >= LAYER_SHIFT_OFFSET) { - // layer shift keys - uint8_t target = keymapEntry.getKeyCode() - LAYER_SHIFT_OFFSET; + } else if (key_code >= LAYER_SHIFT_OFFSET) { + // layer shift keys (two types) + target_layer = key_code - LAYER_SHIFT_OFFSET; - switch (target) { + switch (target_layer) { case KEYMAP_NEXT: // Key_KeymapNext_Momentary - if (keyToggledOn(keyState)) + if (keyToggledOn(event.state)) activateNext(); - else if (keyToggledOff(keyState)) + else deactivateMostRecent(); break; case KEYMAP_PREVIOUS: // Key_KeymapPrevious_Momentary - if (keyToggledOn(keyState)) + if (keyToggledOn(event.state)) deactivateMostRecent(); - else if (keyToggledOff(keyState)) + else activateNext(); break; @@ -97,30 +105,48 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) { * that does turn the layer off, but with the other still being held, the * layer will toggle back on in the same cycle. */ - if (keyIsPressed(keyState)) { - if (!Layer.isActive(target)) - activate(target); - } else if (keyToggledOff(keyState)) { - deactivate(target); + if (keyToggledOn(event.state)) { + // Re-think this: maybe we want to bring an already-active layer to the + // top when a layer shift key is pressed. + if (!isActive(target_layer)) + activate(target_layer); + } else { + // If there's another layer shift key keeping the target layer active, + // we need to abort before deactivating it. + for (Key key : live_keys.all()) { + if (key == event.key) { + return; + } + } + // No other layer shift key for the target layer is pressed; deactivate + // it now. + deactivate(target_layer); } break; } - } else if (keyToggledOn(keyState)) { + } else if (keyToggledOn(event.state)) { // LockLayer()/UnlockLayer() - uint8_t target = keymapEntry.getKeyCode(); + target_layer = key_code; // switch keymap and stay there - if (Layer.isActive(target)) - deactivate(keymapEntry.getKeyCode()); + if (isActive(target_layer)) + deactivate(target_layer); else - activate(keymapEntry.getKeyCode()); + activate(target_layer); } } +#ifndef NDEPRECATED +void Layer_::handleKeymapKeyswitchEvent(Key key, uint8_t key_state) { + if (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP)) + handleLayerKeyEvent(KeyEvent(KeyAddr::none(), key_state, key)); +} + Key Layer_::eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState) { if (mappedKey.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP)) - handleKeymapKeyswitchEvent(mappedKey, keyState); + handleLayerKeyEvent(KeyEvent(key_addr, keyState, mappedKey)); return mappedKey; } +#endif Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) { return keyFromKeymap(layer, key_addr); diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index e2265a92..f5401b71 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -19,6 +19,7 @@ #include #include "kaleidoscope/key_defs.h" #include "kaleidoscope/keymaps.h" +#include "kaleidoscope/KeyEvent.h" #include "kaleidoscope/device/device.h" #include "kaleidoscope_internal/device.h" #include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h" @@ -109,10 +110,15 @@ class Layer_ { } static boolean isActive(uint8_t layer); + static void handleLayerKeyEvent(const KeyEvent &event); + +#ifndef NDEPRECATED + DEPRECATED(LAYER_HANDLE_KEYMAP_KEYSWITCH_EVENT) static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState); DEPRECATED(LAYER_EVENTHANDLER) static Key eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState); +#endif typedef Key(*GetKeyFunction)(uint8_t layer, KeyAddr key_addr); static GetKeyFunction getKey; diff --git a/src/kaleidoscope_internal/deprecations.h b/src/kaleidoscope_internal/deprecations.h index 93dcebe3..c3b8b8c1 100644 --- a/src/kaleidoscope_internal/deprecations.h +++ b/src/kaleidoscope_internal/deprecations.h @@ -39,6 +39,10 @@ "`Layer.eventHandler()` is deprecated.\n" __NL__ \ "Please use `Layer.handleKeymapKeyswitchEvent()` instead." +#define _DEPRECATED_MESSAGE_LAYER_HANDLE_KEYMAP_KEYSWITCH_EVENT __NL__ \ + "`Layer.handleKeymapKeyswitchEvent()` is deprecated.\n" __NL__ \ + "Please use `Layer.handleLayerKeyEvent()` instead." + #define _DEPRECATED_MESSAGE_LAYER_LOOKUP __NL__ \ "`Layer.lookup(key_addr)` is deprecated.\n" __NL__ \ "Please use `Runtime.lookupKey(key_addr)` instead. Alternatively, if you\n" __NL__ \ From 4c47ce118532974bf0b46bf7d8f202fc8beabfa2 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 9 Apr 2021 15:00:13 -0500 Subject: [PATCH 048/108] Terminate event handling after calling `Layer.handleLayerKeyEvent()` There's no need to trigger a keyboard HID report after processing a layer change, so stop processing before calling `prepareKeyboardReport()` if `event.key` is a layer change `Key`. Signed-off-by: Michael Richters --- src/kaleidoscope/Runtime.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/kaleidoscope/Runtime.cpp b/src/kaleidoscope/Runtime.cpp index b8ab4f10..6d534cbe 100644 --- a/src/kaleidoscope/Runtime.cpp +++ b/src/kaleidoscope/Runtime.cpp @@ -178,7 +178,7 @@ Runtime_::handleKeyEvent(KeyEvent event) { // If it's a built-in Layer key, we handle it here, and skip sending report(s) if (event.key.isLayerKey()) { Layer.handleLayerKeyEvent(event); - //return; + return; } // The System Control HID report contains only one keycode, and gets sent From 1946e1de0b4e7c78df42ec4a30c560a31521ccc0 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 7 Apr 2021 00:38:44 -0500 Subject: [PATCH 049/108] Compile deprecated Layer code conditionally Signed-off-by: Michael Richters --- src/kaleidoscope/layers.cpp | 12 ------------ src/kaleidoscope/layers.h | 8 +++++++- 2 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/kaleidoscope/layers.cpp b/src/kaleidoscope/layers.cpp index a5d47722..0f9b492a 100644 --- a/src/kaleidoscope/layers.cpp +++ b/src/kaleidoscope/layers.cpp @@ -152,18 +152,6 @@ Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) { return keyFromKeymap(layer, key_addr); } -// Deprecated -void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) { - // We could update the active keys cache here (as commented below), but I - // think that's unlikely to serve whatever purpose the caller had in - // mind. `Layer.lookup()` will still give the correct result, and without a - // `Key` value is specified, this function no longer serves a purpose. - - // #include "kaleidoscope/LiveKeys.h" - // int8_t layer = active_layer_keymap_[key_addr.toInt()]; - // live_keys.activate(key_addr, (*getKey)(layer, key_addr)); -} - void Layer_::updateActiveLayers(void) { // First, set every entry in the active layer keymap to point to the default // layer (layer 0). diff --git a/src/kaleidoscope/layers.h b/src/kaleidoscope/layers.h index f5401b71..0aa995a1 100644 --- a/src/kaleidoscope/layers.h +++ b/src/kaleidoscope/layers.h @@ -81,6 +81,7 @@ class Layer_ { // The `Runtime.lookupKey()` function replaces this one, for plugins that // still want to do this same check. +#ifndef NDEPRECATED DEPRECATED(LAYER_LOOKUP) static Key lookup(KeyAddr key_addr) { // First check the keyboard state array @@ -91,6 +92,8 @@ class Layer_ { } return key; } +#endif + static Key lookupOnActiveLayer(KeyAddr key_addr) { uint8_t layer = active_layer_keymap_[key_addr.toInt()]; return (*getKey)(layer, key_addr); @@ -125,12 +128,15 @@ class Layer_ { static Key getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr); +#ifndef NDEPRECATED DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP) static void updateLiveCompositeKeymap(KeyAddr key_addr, Key mappedKey) { live_keys.activate(key_addr, mappedKey); } DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP) - static void updateLiveCompositeKeymap(KeyAddr key_addr); + static void updateLiveCompositeKeymap(KeyAddr key_addr) {} +#endif + static void updateActiveLayers(void); private: From 015b8e3140cf3e0441df19ed850776380421c45e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 14:11:03 -0500 Subject: [PATCH 050/108] Add KeyEventTracker helper class This class should help plugins that implement `onKeyswitchEvent()` to ensure that they won't process the same event more than once. Signed-off-by: Michael Richters --- src/kaleidoscope/KeyEventTracker.h | 104 +++++++++++++++++++++++++++++ 1 file changed, 104 insertions(+) create mode 100644 src/kaleidoscope/KeyEventTracker.h diff --git a/src/kaleidoscope/KeyEventTracker.h b/src/kaleidoscope/KeyEventTracker.h new file mode 100644 index 00000000..ae7c1666 --- /dev/null +++ b/src/kaleidoscope/KeyEventTracker.h @@ -0,0 +1,104 @@ +/* Kaleidoscope - Firmware for computer input devices + * 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 . + */ + +#pragma once + +#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult +#include "kaleidoscope/KeyEvent.h" // for KeyEvent, KeyEventId + +namespace kaleidoscope { + +/// An event tracker for plugins that implement `onKeyswitchEvent()` +/// +/// Plugins that implement the `onKeyswitchEvent()` are required to agree to a +/// contract wherein they promise to only process each event once. They may +/// delay an event by returning `EventHandlerResult::ABORT` when they first +/// encounter an event, then re-start it (in response to a subsequent event or a +/// timeout) by calling `Runtime.handleKeyswitchEvent()`. When they do so, they +/// must take pains to ensure that they ignore that event when it gets passed to +/// their `onKeyswitchEvent()` handler a second time. In addition, a subsequent +/// plugin's `onKeyswitchEvent()` handler might do the same thing, and the event +/// must be ignored in that case as well. This `KeyEventTracker` class is a +/// helper that makes it easy for plugins to abide by the terms of the +/// `onKeyswitchEvent()` contract. +/// +/// All that's required is adding a private member variable to the plugin's +/// class definition, as follows: +/// +/// ```c++ +/// class MyPlugin : public Plugin { +/// private: +/// KeyEventTracker event_tracker_; +/// public: +/// EventHandlerResult onKeyswitchEvent(KeyEvent &event); +/// }; +/// ``` +/// +/// Then, in the definition of that plugin's `onKeyswitchEvent()` function, use +/// the tracker's `shouldIgnore()` function to let already-processed events pass +/// through: +/// +/// ```c++ +/// EventHandlerResult MyPlugin::onKeyswitchEvent(KeyEvent &event) { +/// if (event_tracker_.shouldIgnore(event)) +/// return EventHandlerResult::OK; +/// ... +/// } +/// ``` +/// +/// As a side effect, the `shouldIgnore()` function will record the `id` of the +/// event, if it is not one that the tracker has seen "recently". It works by +/// keeping track of the "newest" event ID that it has seen, by subtracting the +/// ID of the event passed to it from the current "newest" event ID. If the +/// result is greater than zero, the event is considered to be "new" and the +/// tracked ID is updated. The mechanism isn't perfect; if 128 (`KeyEventId` +/// stores ony byte of data) new event IDs are generated without any of them +/// reaching the plugin's `onKeyswitchEvent()` handler, it is theoretically +/// possible to overflow and a new event will be treated as old. + +class KeyEventTracker { + + private: + // The ID of the "newest" event this tracker has seen. + KeyEventId last_id_seen_{-1}; + + public: + /// Check if an event should be ignored by the client plugin + /// + /// This function should be called by a plugin's `onKeyswitchEvent()` handler + /// function to determined if the `event` has an `id` member that this tracker + /// has seen "recently". + bool shouldIgnore(const KeyEvent &event) { + return !isNew(event); + } + + /// Report if a given event is "new" to this tracker + /// + /// Returns `true` if the input event is newer than the latest event the + /// tracker has seen. If it is newer, also update the tracker to reflect the + /// new event ID. + bool isNew(const KeyEvent &event) { + KeyEventId offset = event.id() - last_id_seen_; + if (offset > 0) { + last_id_seen_ = event.id(); + return true; + } + return false; + } + +}; + +} // namespace kaleidoscope From 7756be1a6d7e65fc7b13f0663873a9db79df813e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 14:17:10 -0500 Subject: [PATCH 051/108] Add `beforeSyncingLeds()` event handler hook function This allows plugins to override the current LED mode just before the LED sync is done (i.e. after the mode sets the LED colors, but before those changes are pushed to the hardware.) Signed-off-by: Michael Richters --- src/kaleidoscope/event_handlers.h | 13 +++++++++++++ src/kaleidoscope/plugin/LEDControl.cpp | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/kaleidoscope/event_handlers.h b/src/kaleidoscope/event_handlers.h index ae24ab01..b3765601 100644 --- a/src/kaleidoscope/event_handlers.h +++ b/src/kaleidoscope/event_handlers.h @@ -234,6 +234,15 @@ class SignatureCheckDummy {}; _NOT_ABORTABLE, __NL__ \ (),(),(), /* non template */ __NL__ \ (), (), ##__VA_ARGS__) __NL__ \ + /* Called immediately before the LEDs get updated. This is for */ __NL__ \ + /* plugins that override the current LED mode. */ __NL__ \ + OPERATION(beforeSyncingLeds, __NL__ \ + 1, __NL__ \ + _CURRENT_IMPLEMENTATION, __NL__ \ + _NOT_ABORTABLE, __NL__ \ + (),(),(), /* non template */ __NL__ \ + (), (), ##__VA_ARGS__) __NL__ \ + /* DEPRECATED */ __NL__ \ /* Called before reporting our state to the host. This is the */ __NL__ \ /* last point in a cycle where a plugin can alter what gets */ __NL__ \ /* reported to the host. */ __NL__ \ @@ -333,6 +342,10 @@ class SignatureCheckDummy {}; OP(onLEDModeChange, 1) __NL__ \ END(onLEDModeChange, 1) __NL__ \ __NL__ \ + START(beforeSyncingLeds, 1) __NL__ \ + OP(beforeSyncingLeds, 1) __NL__ \ + END(beforeSyncingLeds, 1) __NL__ \ + __NL__ \ START(beforeReportingState, 1, 2) __NL__ \ OP(beforeReportingState, 1) __NL__ \ OP(beforeReportingState, 2) __NL__ \ diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index ecebc803..c2d87bad 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -124,6 +124,11 @@ void LEDControl::syncLeds(void) { if (!enabled_) return; + // This would be a good spot to introduce a new hook function so that a plugin + // that needs to override the color of an LED used by an LED mode can do so + // efficiently. + Hooks::beforeSyncingLeds(); + Runtime.device().syncLeds(); } From e4b64990708f088d101696d5548f3b7f31190206 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 16:20:00 -0500 Subject: [PATCH 052/108] Adapt LEDControl to new KeyEvent handlers Signed-off-by: Michael Richters --- src/kaleidoscope/plugin/LEDControl.cpp | 53 ++++++++++++++------------ src/kaleidoscope/plugin/LEDControl.h | 3 +- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index c2d87bad..ffb6ec4f 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -18,6 +18,7 @@ #include "Kaleidoscope-FocusSerial.h" #include "kaleidoscope_internal/LEDModeManager.h" #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/LiveKeys.h" using namespace kaleidoscope::internal; // NOLINT(build/namespaces) @@ -32,7 +33,6 @@ LEDMode *LEDControl::cur_led_mode_ = nullptr; uint8_t LEDControl::syncDelay = 32; uint16_t LEDControl::syncTimer = 0; bool LEDControl::enabled_ = true; -Key LEDControl::pending_next_prev_key_ = Key_NoKey; LEDControl::LEDControl(void) { } @@ -156,16 +156,33 @@ void LEDControl::enable() { Runtime.device().syncLeds(); } -kaleidoscope::EventHandlerResult LEDControl::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { - if (mappedKey.getFlags() != (SYNTHETIC | IS_INTERNAL | LED_TOGGLE)) - return kaleidoscope::EventHandlerResult::OK; +kaleidoscope::EventHandlerResult LEDControl::onKeyEvent(KeyEvent &event) { + if (event.key.getFlags() != (SYNTHETIC | IS_INTERNAL | LED_TOGGLE)) + return EventHandlerResult::OK; - if (keyToggledOn(keyState)) { - if (mappedKey == Key_LEDEffectNext || mappedKey == Key_LEDEffectPrevious) { - // Handling of these keys is delayed into `beforeReportingState` - // so that we can incorporate the shift modifier state. - pending_next_prev_key_ = mappedKey; - } else if (mappedKey == Key_LEDToggle) { + if (keyToggledOn(event.state)) { + if (event.key == Key_LEDEffectNext || event.key == Key_LEDEffectPrevious) { + // First, check for an active shift key. + bool shift_active = false; + // This change should be back-ported to #904 + for (Key active_key : live_keys.all()) { + if (active_key.isKeyboardShift()) { + shift_active = true; + break; + } + } + // Next, record which key (next or previous) was pressed as a boolean. + bool key_is_next = (event.key == Key_LEDEffectNext); + // This is basically an XOR with two booleans. If the "next" key was + // pressed and no shift key is active, or if the "previous" key was + // pressed and a shift key is active, we activate the next LED + // Mode. Otherwise, we activate the previous mode. + if (key_is_next != shift_active) { + next_mode(); + } else { + prev_mode(); + } + } else if (event.key == Key_LEDToggle) { if (enabled_) disable(); else @@ -176,24 +193,10 @@ kaleidoscope::EventHandlerResult LEDControl::onKeyswitchEvent(Key &mappedKey, Ke return kaleidoscope::EventHandlerResult::EVENT_CONSUMED; } -kaleidoscope::EventHandlerResult LEDControl::beforeReportingState(void) { +kaleidoscope::EventHandlerResult LEDControl::afterEachCycle() { if (!enabled_) return kaleidoscope::EventHandlerResult::OK; - if (pending_next_prev_key_ != Key_NoKey) { - bool is_shifted = - kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_LeftShift) || - kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(Key_RightShift); - - if ((pending_next_prev_key_ == Key_LEDEffectNext && !is_shifted) || - (pending_next_prev_key_ == Key_LEDEffectPrevious && is_shifted)) { - next_mode(); - } else { - prev_mode(); - } - pending_next_prev_key_ = Key_NoKey; - } - if (Runtime.hasTimeExpired(syncTimer, syncDelay)) { syncLeds(); syncTimer += syncDelay; diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index cb5264ac..1abe371a 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -95,7 +95,7 @@ class LEDControl : public kaleidoscope::Plugin { static uint8_t syncDelay; kaleidoscope::EventHandlerResult onSetup(); - kaleidoscope::EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState); + kaleidoscope::EventHandlerResult onKeyEvent(KeyEvent &event); kaleidoscope::EventHandlerResult beforeReportingState(); static void disable(); @@ -117,7 +117,6 @@ class LEDControl : public kaleidoscope::Plugin { static uint8_t num_led_modes_; static LEDMode *cur_led_mode_; static bool enabled_; - static Key pending_next_prev_key_; }; class FocusLEDCommand : public Plugin { From f6e989669703834cd25a0a0e661fa30d561eba53 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 16:28:23 -0500 Subject: [PATCH 053/108] Remove some unnecessary namespace qualifiers from LEDControl Signed-off-by: Michael Richters --- src/kaleidoscope/plugin/LEDControl.cpp | 14 +++++++------- src/kaleidoscope/plugin/LEDControl.h | 6 +++--- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index ffb6ec4f..7f80b126 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -71,7 +71,7 @@ LEDControl::set_mode(uint8_t mode_) { refreshAll(); - kaleidoscope::Hooks::onLEDModeChange(); + Hooks::onLEDModeChange(); } void LEDControl::activate(LEDModeInterface *plugin) { @@ -132,7 +132,7 @@ void LEDControl::syncLeds(void) { Runtime.device().syncLeds(); } -kaleidoscope::EventHandlerResult LEDControl::onSetup() { +EventHandlerResult LEDControl::onSetup() { set_all_leds_to({0, 0, 0}); LEDModeManager::setupPersistentLEDModes(); @@ -156,7 +156,7 @@ void LEDControl::enable() { Runtime.device().syncLeds(); } -kaleidoscope::EventHandlerResult LEDControl::onKeyEvent(KeyEvent &event) { +EventHandlerResult LEDControl::onKeyEvent(KeyEvent &event) { if (event.key.getFlags() != (SYNTHETIC | IS_INTERNAL | LED_TOGGLE)) return EventHandlerResult::OK; @@ -190,12 +190,12 @@ kaleidoscope::EventHandlerResult LEDControl::onKeyEvent(KeyEvent &event) { } } - return kaleidoscope::EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::EVENT_CONSUMED; } -kaleidoscope::EventHandlerResult LEDControl::afterEachCycle() { +EventHandlerResult LEDControl::afterEachCycle() { if (!enabled_) - return kaleidoscope::EventHandlerResult::OK; + return EventHandlerResult::OK; if (Runtime.hasTimeExpired(syncTimer, syncDelay)) { syncLeds(); @@ -203,7 +203,7 @@ kaleidoscope::EventHandlerResult LEDControl::afterEachCycle() { update(); } - return kaleidoscope::EventHandlerResult::OK; + return EventHandlerResult::OK; } EventHandlerResult FocusLEDCommand::onFocusEvent(const char *command) { diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index 1abe371a..b7e2e2c3 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -94,9 +94,9 @@ class LEDControl : public kaleidoscope::Plugin { static uint8_t syncDelay; - kaleidoscope::EventHandlerResult onSetup(); - kaleidoscope::EventHandlerResult onKeyEvent(KeyEvent &event); - kaleidoscope::EventHandlerResult beforeReportingState(); + EventHandlerResult onSetup(); + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult afterEachCycle(); static void disable(); static void enable(); From 26f197297667c551b265b0358e5582e3b4fd094b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 16:32:39 -0500 Subject: [PATCH 054/108] Deprecate public `LEDControl.syncDelay` variable It is now replaced with `LEDControl.setSyncInterval()` to set the LED refresh rate. Signed-off-by: Michael Richters --- src/kaleidoscope/plugin/LEDControl.cpp | 18 ++++++++++++++---- src/kaleidoscope/plugin/LEDControl.h | 24 +++++++++++++++++++++++- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index 7f80b126..92b3e196 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -30,12 +30,16 @@ static constexpr uint8_t uninitialized_mode_id = 255; uint8_t LEDControl::mode_id = uninitialized_mode_id; uint8_t LEDControl::num_led_modes_ = LEDModeManager::numLEDModes(); LEDMode *LEDControl::cur_led_mode_ = nullptr; -uint8_t LEDControl::syncDelay = 32; -uint16_t LEDControl::syncTimer = 0; bool LEDControl::enabled_ = true; LEDControl::LEDControl(void) { } +uint8_t LEDControl::sync_interval_ = 32; +uint16_t LEDControl::last_sync_time_ = 0; + +#ifndef NDEPRECATED +uint8_t LEDControl::syncDelay = LEDControl::sync_interval_; +#endif void LEDControl::next_mode(void) { mode_id++; @@ -197,9 +201,15 @@ EventHandlerResult LEDControl::afterEachCycle() { if (!enabled_) return EventHandlerResult::OK; - if (Runtime.hasTimeExpired(syncTimer, syncDelay)) { + if (Runtime.hasTimeExpired(last_sync_time_, sync_interval_)) { +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + sync_interval_ = syncDelay; +#pragma GCC diagnostic pop +#endif syncLeds(); - syncTimer += syncDelay; + last_sync_time_ += sync_interval_; update(); } diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index b7e2e2c3..3c5ba9b2 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -19,6 +19,14 @@ #include "kaleidoscope/Runtime.h" #include "kaleidoscope/plugin/LEDMode.h" +#ifndef NDEPRECATED + +#define _DEPRECATED_MESSAGE_LEDCONTROL_SYNCDELAY __NL__ \ + "The `LEDControl.syncDelay` variable has been deprecated.\n" __NL__ \ + "Please use the `LEDControl.setInterval()` function instead." + +#endif + #define LED_TOGGLE B00000001 // Synthetic, internal #define Key_LEDEffectNext Key(0, KEY_FLAGS | SYNTHETIC | IS_INTERNAL | LED_TOGGLE) @@ -92,7 +100,20 @@ class LEDControl : public kaleidoscope::Plugin { // static void activate(LEDModeInterface *plugin); +#ifndef NDEPRECATED + DEPRECATED(LEDCONTROL_SYNCDELAY) static uint8_t syncDelay; +#endif + + static void setSyncInterval(uint8_t interval) { + sync_interval_ = interval; +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + syncDelay = interval; +#pragma GCC diagnostic pop +#endif + } EventHandlerResult onSetup(); EventHandlerResult onKeyEvent(KeyEvent &event); @@ -112,8 +133,9 @@ class LEDControl : public kaleidoscope::Plugin { } private: - static uint16_t syncTimer; static uint8_t mode_id; + static uint16_t last_sync_time_; + static uint8_t sync_interval_; static uint8_t num_led_modes_; static LEDMode *cur_led_mode_; static bool enabled_; From 73c9fa7e96477823c4f441448202818a040300c9 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 16:36:11 -0500 Subject: [PATCH 055/108] Standardize private variable names in LEDControl Signed-off-by: Michael Richters --- src/kaleidoscope/plugin/LEDControl.cpp | 33 +++++++++++++------------- src/kaleidoscope/plugin/LEDControl.h | 4 ++-- 2 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/kaleidoscope/plugin/LEDControl.cpp b/src/kaleidoscope/plugin/LEDControl.cpp index 92b3e196..18a0d67a 100644 --- a/src/kaleidoscope/plugin/LEDControl.cpp +++ b/src/kaleidoscope/plugin/LEDControl.cpp @@ -27,7 +27,7 @@ namespace plugin { static constexpr uint8_t uninitialized_mode_id = 255; -uint8_t LEDControl::mode_id = uninitialized_mode_id; +uint8_t LEDControl::mode_id_ = uninitialized_mode_id; uint8_t LEDControl::num_led_modes_ = LEDModeManager::numLEDModes(); LEDMode *LEDControl::cur_led_mode_ = nullptr; bool LEDControl::enabled_ = true; @@ -41,25 +41,26 @@ uint16_t LEDControl::last_sync_time_ = 0; uint8_t LEDControl::syncDelay = LEDControl::sync_interval_; #endif -void LEDControl::next_mode(void) { - mode_id++; - if (mode_id >= num_led_modes_) { +void LEDControl::next_mode() { + ++mode_id_; + + if (mode_id_ >= num_led_modes_) { return set_mode(0); } - return set_mode(mode_id); + return set_mode(mode_id_); } -void LEDControl::prev_mode(void) { - if (mode_id == 0) { +void LEDControl::prev_mode() { + if (mode_id_ == 0) { // wrap around - mode_id = num_led_modes_ - 1; + mode_id_ = num_led_modes_ - 1; } else { - mode_id--; + mode_id_--; } - return set_mode(mode_id); + return set_mode(mode_id_); } void @@ -67,11 +68,11 @@ LEDControl::set_mode(uint8_t mode_) { if (mode_ >= num_led_modes_) return; - mode_id = mode_; + mode_id_ = mode_; // Cache the LED mode // - cur_led_mode_ = LEDModeManager::getLEDMode(mode_id); + cur_led_mode_ = LEDModeManager::getLEDMode(mode_id_); refreshAll(); @@ -141,7 +142,7 @@ EventHandlerResult LEDControl::onSetup() { LEDModeManager::setupPersistentLEDModes(); - if (mode_id == uninitialized_mode_id) { + if (mode_id_ == uninitialized_mode_id) { set_mode(0); } @@ -298,10 +299,10 @@ EventHandlerResult FocusLEDCommand::onFocusEvent(const char *command) { } else if (peek == 'p') { ::LEDControl.prev_mode(); } else { - uint8_t mode_id; + uint8_t mode_id_; - ::Focus.read(mode_id); - ::LEDControl.set_mode(mode_id); + ::Focus.read(mode_id_); + ::LEDControl.set_mode(mode_id_); } break; } diff --git a/src/kaleidoscope/plugin/LEDControl.h b/src/kaleidoscope/plugin/LEDControl.h index 3c5ba9b2..388f0f96 100644 --- a/src/kaleidoscope/plugin/LEDControl.h +++ b/src/kaleidoscope/plugin/LEDControl.h @@ -61,7 +61,7 @@ class LEDControl : public kaleidoscope::Plugin { } static void set_mode(uint8_t mode_id); static uint8_t get_mode_index() { - return mode_id; + return mode_id_; } static LEDMode *get_mode() { return cur_led_mode_; @@ -133,9 +133,9 @@ class LEDControl : public kaleidoscope::Plugin { } private: - static uint8_t mode_id; static uint16_t last_sync_time_; static uint8_t sync_interval_; + static uint8_t mode_id_; static uint8_t num_led_modes_; static LEDMode *cur_led_mode_; static bool enabled_; From 328edcfc64c05590c947225824c97e8b3e8f438b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:45:11 -0500 Subject: [PATCH 056/108] Add `KeyAddrBitField::clear()` method Signed-off-by: Michael Richters --- src/kaleidoscope/KeyAddrBitfield.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/kaleidoscope/KeyAddrBitfield.h b/src/kaleidoscope/KeyAddrBitfield.h index cf4cc66c..bd764ca3 100644 --- a/src/kaleidoscope/KeyAddrBitfield.h +++ b/src/kaleidoscope/KeyAddrBitfield.h @@ -70,6 +70,9 @@ class KeyAddrBitfield { // assert(k.toInt() < size); bitWrite(data_[blockIndex(k)], bitIndex(k), value); } + void clear() { + memset(data_, 0, sizeof(data_)); + } // This function returns the number of set bits in the bitfield up to and // including the bit at index `k`. Two important things to note: it doesn't From 0498a88a24fe24c713336e1c6608e3b87e6c7047 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:54:26 -0500 Subject: [PATCH 057/108] Replace AppSwitcher Macros example with custom plugin This example sketch is now a fairly good demonstration of the power and simplicity of the new KeyEvent handlers, and an example of a custom plugin written directly in the sketch file. Signed-off-by: Michael Richters --- examples/Features/AppSwitcher/AppSwitcher.cpp | 79 +++++++++++++++++++ .../AppSwitcher/{Macros.h => AppSwitcher.h} | 30 +++++-- examples/Features/AppSwitcher/AppSwitcher.ino | 24 ++---- examples/Features/AppSwitcher/Macros.cpp | 65 --------------- 4 files changed, 107 insertions(+), 91 deletions(-) create mode 100644 examples/Features/AppSwitcher/AppSwitcher.cpp rename examples/Features/AppSwitcher/{Macros.h => AppSwitcher.h} (53%) delete mode 100644 examples/Features/AppSwitcher/Macros.cpp diff --git a/examples/Features/AppSwitcher/AppSwitcher.cpp b/examples/Features/AppSwitcher/AppSwitcher.cpp new file mode 100644 index 00000000..e75c94bf --- /dev/null +++ b/examples/Features/AppSwitcher/AppSwitcher.cpp @@ -0,0 +1,79 @@ +/* -*- mode: c++ -*- + * AppSwitcher -- A Kaleidoscope Example + * Copyright (C) 2021 Keyboardio, 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 . + */ + +#define KALEIDOSCOPE_HOSTOS_GUESSER 1 + +#include + +#include "AppSwitcher.h" + +namespace kaleidoscope { +namespace plugin { + +EventHandlerResult AppSwitcher::onKeyEvent(KeyEvent &event) { + // Ignore all key releases + if (keyToggledOff(event.state)) + return EventHandlerResult::OK; + + if (event.key == AppSwitcher_Next || event.key == AppSwitcher_Prev) { + bool add_shift_flag = false; + if (event.key == AppSwitcher_Prev) { + add_shift_flag = true; + } + + // For good measure: + event.state |= INJECTED; + + // If AppSwitcher was not already active, hold its modifier first. + if (!active_addr_.isValid()) { + if (::HostOS.os() == hostos::OSX) { + event.key = Key_LeftGui; + } else { + event.key = Key_LeftAlt; + } + Runtime.handleKeyEvent(event); + } + + // Clear the event's key address so we don't clobber the modifier. + event.addr.clear(); + event.key = Key_Tab; + if (add_shift_flag) + event.key.setFlags(SHIFT_HELD); + // Press tab + Runtime.handleKeyEvent(event); + // Change state to release; this will get processed when we return OK below. + event.state = WAS_PRESSED | INJECTED; + } else if (active_addr_.isValid()) { + // If any non-AppSwitcher key is pressed while AppSwitcher is active, that + // will close AppSwitcher instead of processing that keypress. We mask the + // address of the key that closed AppSwitcher so that its release doesn't + // have any effect. Then we turn the event for that key's press into an + // event for the release of the AppSwitcher's modifier key. + live_keys.mask(event.addr); + event.addr = active_addr_; + event.state = WAS_PRESSED | INJECTED; + event.key = live_keys[event.addr]; + // Turn off AppSwitcher: + active_addr_.clear(); + } + return EventHandlerResult::OK; +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::AppSwitcher AppSwitcher; diff --git a/examples/Features/AppSwitcher/Macros.h b/examples/Features/AppSwitcher/AppSwitcher.h similarity index 53% rename from examples/Features/AppSwitcher/Macros.h rename to examples/Features/AppSwitcher/AppSwitcher.h index 1099bb03..00de27a6 100644 --- a/examples/Features/AppSwitcher/Macros.h +++ b/examples/Features/AppSwitcher/AppSwitcher.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * AppSwitcher -- A Kaleidoscope Example - * Copyright (C) 2016-2018 Keyboardio, Inc. + * Copyright (C) 2021 Keyboardio, 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 @@ -17,13 +17,27 @@ #pragma once -#include +#include +#include "Kaleidoscope-Ranges.h" + +constexpr Key AppSwitcher_Next{kaleidoscope::ranges::SAFE_START}; +constexpr uint16_t _prev_val = AppSwitcher_Next.getRaw() + 1; +constexpr Key AppSwitcher_Prev{_prev_val}; + +namespace kaleidoscope { +namespace plugin { + +class AppSwitcher : public kaleidoscope::Plugin { + + public: + EventHandlerResult onKeyEvent(KeyEvent &event); + + private: + KeyAddr active_addr_ = KeyAddr::none(); -enum { - M_APPSWITCH, - M_APPCANCEL, }; -const macro_t *macroAppSwitch(uint8_t keyState); -const macro_t *macroAppCancel(uint8_t keyState); -void macroAppSwitchLoop(); +} // namespace plugin +} // namespace kaleidoscope + +extern kaleidoscope::plugin::AppSwitcher AppSwitcher; diff --git a/examples/Features/AppSwitcher/AppSwitcher.ino b/examples/Features/AppSwitcher/AppSwitcher.ino index ad46fefc..56f3c0ac 100644 --- a/examples/Features/AppSwitcher/AppSwitcher.ino +++ b/examples/Features/AppSwitcher/AppSwitcher.ino @@ -17,10 +17,10 @@ #include "Kaleidoscope.h" #include "Kaleidoscope-EEPROM-Settings.h" -#include "Kaleidoscope-Macros.h" #include "Kaleidoscope-HostOS.h" +#include "Kaleidoscope-Ranges.h" -#include "Macros.h" +#include "AppSwitcher.h" /* *INDENT-OFF* */ KEYMAPS( @@ -32,7 +32,7 @@ KEYMAPS( Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - M(M_APPSWITCH), + AppSwitcher_Next, Key_skip, Key_6, Key_7, Key_8, Key_9, Key_0, Key_skip, Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, @@ -40,24 +40,13 @@ KEYMAPS( Key_skip, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, - M(M_APPCANCEL) + AppSwitcher_Prev ), ) /* *INDENT-ON* */ -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { - case M_APPSWITCH: - return macroAppSwitch(keyState); - case M_APPCANCEL: - return macroAppCancel(keyState); - } - return MACRO_NONE; -} - -KALEIDOSCOPE_INIT_PLUGINS(EEPROMSettings, - HostOS, - Macros); +KALEIDOSCOPE_INIT_PLUGINS(HostOS, + AppSwitcher); void setup() { Kaleidoscope.setup(); @@ -67,6 +56,5 @@ void setup() { } void loop() { - macroAppSwitchLoop(); Kaleidoscope.loop(); } diff --git a/examples/Features/AppSwitcher/Macros.cpp b/examples/Features/AppSwitcher/Macros.cpp deleted file mode 100644 index 9baaafa2..00000000 --- a/examples/Features/AppSwitcher/Macros.cpp +++ /dev/null @@ -1,65 +0,0 @@ -/* -*- mode: c++ -*- - * AppSwitcher -- A Kaleidoscope Example - * Copyright (C) 2016-2018 Keyboardio, 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 . - */ - -#define KALEIDOSCOPE_HOSTOS_GUESSER 1 - -#include - -#include "Macros.h" - -namespace H = kaleidoscope::hostos; - -static bool appSwitchActive = false; - -const macro_t *macroAppSwitch(uint8_t keyState) { - appSwitchActive = true; - - // Key was just pressed, or is being held - if (keyIsPressed(keyState)) { - if (HostOS.os() == H::OSX) - return MACRO(Dr(Key_LeftGui), D(Tab)); - else - return MACRO(Dr(Key_LeftAlt), D(Tab)); - } - // Key was just released - if (keyToggledOff(keyState)) { - if (HostOS.os() == H::OSX) - return MACRO(U(Tab), Dr(Key_LeftGui)); - else - return MACRO(U(Tab), Dr(Key_LeftAlt)); - } - // otherwise we do nothing - return MACRO_NONE; -} - -const macro_t *macroAppCancel(uint8_t keyState) { - if (keyToggledOn(keyState)) - appSwitchActive = false; - return MACRO_NONE; -} - -void macroAppSwitchLoop() { - Key mod = Key_LeftAlt; - - if (HostOS.os() == H::OSX) - mod = Key_LeftGui; - - // if appSwitchActive is true, we continue holding Alt. - if (appSwitchActive) { - handleKeyswitchEvent(mod, UnknownKeyswitchLocation, IS_PRESSED); - } -} From 6e2f3e88438e1687adf4c723a0fef91d93b35e7b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 14 Apr 2021 14:55:45 -0500 Subject: [PATCH 058/108] Include `stdint.h` in Kaleidoscope-Ranges.h This makes it unnecessary to include `Arduino.h` (or `stdint.h`, or some other header that includes it) before including Kaleidoscope-Ranges.h. Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h index d17fecfd..0a3630f2 100644 --- a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h +++ b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h @@ -17,6 +17,8 @@ #pragma once +#include // for uint16_t + // Included for definition of legacy Macros plugin key range: #include "kaleidoscope/key_defs.h" From 8d4967db8d93854b4c21f3203d2853980734e039 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:32:04 -0500 Subject: [PATCH 059/108] Adapt Leader plugin to KeyEvent handlers Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Leader/README.md | 3 - .../src/kaleidoscope/plugin/Leader.cpp | 61 ++++++++----------- .../src/kaleidoscope/plugin/Leader.h | 6 +- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/plugins/Kaleidoscope-Leader/README.md b/plugins/Kaleidoscope-Leader/README.md index 0643ae3e..c79cefa5 100644 --- a/plugins/Kaleidoscope-Leader/README.md +++ b/plugins/Kaleidoscope-Leader/README.md @@ -58,9 +58,6 @@ The dictionary is made up of a list of keys, and an action callback. Using the `LEADER_DICT` and `LEADER_SEQ` helpers is recommended. The dictionary *must* be marked `PROGMEM`! -**Note** that we need to use the `Leader` object before any other that adds or -changes key behaviour! Failing to do so may result in unpredictable behaviour. - ## Plugin methods The plugin provides the `Leader` object, with the following methods and properties: diff --git a/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.cpp b/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.cpp index 4c832f15..edad67b5 100644 --- a/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.cpp +++ b/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.cpp @@ -20,12 +20,14 @@ #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" +#include "kaleidoscope/KeyEventTracker.h" namespace kaleidoscope { namespace plugin { // --- state --- Key Leader::sequence_[LEADER_MAX_SEQUENCE_LENGTH + 1]; +KeyEventTracker Leader::event_tracker_; uint8_t Leader::sequence_pos_; uint16_t Leader::start_time_ = 0; uint16_t Leader::time_out = 1000; @@ -81,8 +83,9 @@ void Leader::reset(void) { sequence_[0] = Key_NoKey; } +// DEPRECATED void Leader::inject(Key key, uint8_t key_state) { - onKeyswitchEvent(key, UnknownKeyswitchLocation, key_state); + Runtime.handleKeyEvent(KeyEvent(KeyAddr::none(), key_state | INJECTED, key)); } // --- hooks --- @@ -90,61 +93,49 @@ EventHandlerResult Leader::onNameQuery() { return ::Focus.sendName(F("Leader")); } -EventHandlerResult Leader::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - if (keyState & INJECTED) +EventHandlerResult Leader::onKeyswitchEvent(KeyEvent &event) { + // If the plugin has already processed and released this event, ignore it. + // There's no need to update the event tracker explicitly. + if (event_tracker_.shouldIgnore(event)) return EventHandlerResult::OK; - if (!isActive() && !isLeader(mapped_key)) + if (keyToggledOff(event.state) || event.state & INJECTED) return EventHandlerResult::OK; if (!isActive()) { - // Must be a leader key! - - if (keyToggledOff(keyState)) { - // not active, but a leader key = start the sequence on key release! - start_time_ = Runtime.millisAtCycleStart(); - sequence_pos_ = 0; - sequence_[sequence_pos_] = mapped_key; - } - - // If the sequence was not active yet, ignore the key. - return EventHandlerResult::EVENT_CONSUMED; - } - - // active - int8_t action_index = lookup(); - - if (keyToggledOn(keyState)) { - sequence_pos_++; - if (sequence_pos_ > LEADER_MAX_SEQUENCE_LENGTH) { - reset(); + if (!isLeader(event.key)) return EventHandlerResult::OK; - } start_time_ = Runtime.millisAtCycleStart(); - sequence_[sequence_pos_] = mapped_key; - action_index = lookup(); + sequence_pos_ = 0; + sequence_[sequence_pos_] = event.key; - if (action_index >= 0) { - return EventHandlerResult::EVENT_CONSUMED; - } - } else if (keyIsPressed(keyState)) { - // held, no need for anything here. - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; } + ++sequence_pos_; + if (sequence_pos_ > LEADER_MAX_SEQUENCE_LENGTH) { + reset(); + return EventHandlerResult::OK; + } + + start_time_ = Runtime.millisAtCycleStart(); + sequence_[sequence_pos_] = event.key; + int8_t action_index = lookup(); + if (action_index == NO_MATCH) { reset(); return EventHandlerResult::OK; } if (action_index == PARTIAL_MATCH) { - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; } action_t leaderAction = (action_t) pgm_read_ptr((void const **) & (dictionary[action_index].action)); (*leaderAction)(action_index); + reset(); - return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::ABORT; } EventHandlerResult Leader::afterEachCycle() { diff --git a/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.h b/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.h index fd0dd928..f84b4752 100644 --- a/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.h +++ b/plugins/Kaleidoscope-Leader/src/kaleidoscope/plugin/Leader.h @@ -17,8 +17,9 @@ #pragma once -#include "kaleidoscope/Runtime.h" #include +#include "kaleidoscope/KeyEventTracker.h" +#include "kaleidoscope/plugin.h" #define LEADER_MAX_SEQUENCE_LENGTH 4 @@ -47,11 +48,12 @@ class Leader : public kaleidoscope::Plugin { void inject(Key key, uint8_t key_state); EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyswitchEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); private: static Key sequence_[LEADER_MAX_SEQUENCE_LENGTH + 1]; + static KeyEventTracker event_tracker_; static uint8_t sequence_pos_; static uint16_t start_time_; From 2842b377e93034cfa1fa4171c5b188b24f218982 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:24:29 -0500 Subject: [PATCH 060/108] Adapt Qukeys plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Qukeys.cpp | 127 ++++++------------ .../src/kaleidoscope/plugin/Qukeys.h | 9 +- 2 files changed, 44 insertions(+), 92 deletions(-) diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp index d18b734d..97a6fbf1 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp @@ -23,7 +23,9 @@ #include #include "kaleidoscope/progmem_helpers.h" #include "kaleidoscope/layers.h" - +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/KeyEventTracker.h" +#include "kaleidoscope/KeyAddrEventQueue.h" namespace kaleidoscope { namespace plugin { @@ -34,68 +36,37 @@ EventHandlerResult Qukeys::onNameQuery() { // This is the event handler. It ignores certain events, but mostly just adds // them to the Qukeys event queue. -EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_state) { - // If k is not a physical key, ignore it; some other plugin injected it. - if (! k.isValid() || (key_state & INJECTED) != 0) { +EventHandlerResult Qukeys::onKeyswitchEvent(KeyEvent &event) { + // If the plugin has already processed and released this event, ignore it. + // There's no need to update the event tracker explicitly. + if (event_tracker_.shouldIgnore(event)) { + // We should never get an event that's in our queue here, but just in case + // some other plugin sends one, abort. + if (event_queue_.shouldAbort(event)) + return EventHandlerResult::ABORT; return EventHandlerResult::OK; } - // If the key was injected (from the queue being flushed), we need to ignore - // it. - if (flushing_queue_) { + // If event.addr is not a physical key, ignore it; some other plugin injected it. + if (! event.addr.isValid() || (event.state & INJECTED) != 0) { return EventHandlerResult::OK; } // If Qukeys is turned off, continue to next plugin. if (! active_) { - if (isDualUseKey(key)) { - key = queue_head_.primary_key; + if (isDualUseKey(event.key)) { + event.key = queue_head_.primary_key; } return EventHandlerResult::OK; } - // Deal with keyswitch state changes. - if (keyToggledOn(key_state) || keyToggledOff(key_state)) { - // If the user rolled over from a non-modifier key to a qukey, let the - // release event for that key skip the queue. This prevents unintended - // repeat characters for the tapped key, which would otherwise have its - // release event delayed. - if (keyToggledOff(key_state) && event_queue_.length() == 1 && - k != event_queue_.addr(0) && !isModifierKey(key)) { - return EventHandlerResult::OK; - } - // If we can't trivially ignore the event, just add it to the queue. - event_queue_.append(k, key_state); - // In order to prevent overflowing the queue, process it now. - if (event_queue_.isFull()) { - processQueue(); - } - // Any event that gets added to the queue gets re-processed later, so we - // need to abort processing now. - return EventHandlerResult::ABORT; - } - - // The key is being held. We need to determine if we should block it because - // its key press event is still in the queue, waiting to be - // flushed. Therefore, we search the event queue for the same key. If the - // first event we find there is a key press, that means we need to suppress - // this hold, because it's still waiting on an earlier event. - for (uint8_t i{0}; i < event_queue_.length(); ++i) { - if (event_queue_.addr(i) == k) { - // If the first matching event is a release, we do not suppress it, - // because its press event has already been flushed. - if (event_queue_.isRelease(i)) { - break; - } - // Otherwise, the first matching event was a key press, so we need to - // suppress it for now. - return EventHandlerResult::ABORT; - } - } - - // Either this key doesn't have an event in the queue at all, or its first - // event in the queue is a release. We treat the key as a normal held key. - return EventHandlerResult::OK; + // If we can't trivially ignore the event, just add it to the queue. + event_queue_.append(event); + // In order to prevent overflowing the queue, process it now. + while (processQueue()); + // Any event that gets added to the queue gets re-processed later, so we + // need to abort processing now. + return EventHandlerResult::ABORT; } @@ -103,27 +74,8 @@ EventHandlerResult Qukeys::onKeyswitchEvent(Key& key, KeyAddr k, uint8_t key_sta // queue is ready to be flushed. It only allows one event to be flushed per // cycle, because the keyboard HID report can't store all of the information // necessary to correctly handle all of the rollover corner cases. -EventHandlerResult Qukeys::beforeReportingState() { - // For keys that have been physically released, but whose release events are - // still waiting to be flushed from the queue, we need to restore them, - // because `handleKeyswitchEvent()` didn't get called for those KeyAddrs. - for (uint8_t i{0}; i < event_queue_.length(); ++i) { - if (event_queue_.isRelease(i)) { - KeyAddr k = event_queue_.addr(i); - // Now for the tricky bit. Before "restoring" this key hold, we need to - // make sure that its key press event has already been flushed from the - // queue, so we need to search for a matching key press event preceding - // this release event. If we find one, we need to ignore it. - if (isKeyAddrInQueueBeforeIndex(k, i)) { - continue; - } - flushing_queue_ = true; - handleKeyswitchEvent(Key_NoKey, k, IS_PRESSED | WAS_PRESSED); - flushing_queue_ = false; - } - } - - // Next, if there hasn't been a keypress in a while, update the prior keypress +EventHandlerResult Qukeys::afterEachCycle() { + // If there hasn't been a keypress in a while, update the prior keypress // timestamp to avoid integer overflow issues: if (Runtime.hasTimeExpired(prior_keypress_timestamp_, minimum_prior_interval_)) { @@ -131,12 +83,14 @@ EventHandlerResult Qukeys::beforeReportingState() { Runtime.millisAtCycleStart() - (minimum_prior_interval_ + 1); } - // If any events get flushed from the queue, stop there; we can only safely - // send the one report per cycle. - if (processQueue()) { + // If there's nothing in the queue, there's nothing more to do. + if (event_queue_.isEmpty()) { return EventHandlerResult::OK; } + // Process as many events as we can from the queue. + while (processQueue()); + // If we get here, that means that the first event in the queue is a qukey // press. All that's left to do is to check if it's been held long enough that // it has timed out. @@ -161,11 +115,9 @@ EventHandlerResult Qukeys::beforeReportingState() { // overflow, but those are both rare cases, and should not cause any serious // problems even when they do come up. bool Qukeys::processQueue() { - // If the queue is empty, signal that the beforeReportingState() process - // should abort before checking for a hold timeout (since there's nothing to - // do). + // If there's nothing in the queue, abort. if (event_queue_.isEmpty()) { - return true; + return false; } // In other cases, we will want the KeyAddr of the first event in the queue. @@ -194,6 +146,7 @@ bool Qukeys::processQueue() { // We now know that the first event is a key press. If it's not a qukey, or if // it's only there because the plugin was just turned off, we can flush it // immediately. + // Should be able to remove the `active_` check once `deactivate()` gets updated if (! isQukey(queue_head_addr) || ! active_) { flushEvent(queue_head_.primary_key); return true; @@ -211,8 +164,8 @@ bool Qukeys::processQueue() { // key, so we don't need to do it repeatedly later. bool qukey_is_spacecadet = isModifierKey(queue_head_.primary_key); - // If the qukey press is followed a printable key too closely, it's not - // eligible to take on its alternate value unless it's a SpaceCadet-type key. + // If the qukey press followed a printable key too closely, it's not eligible + // to take on its alternate value unless it's a SpaceCadet-type key. if (!Runtime.hasTimeExpired(prior_keypress_timestamp_, minimum_prior_interval_) && !qukey_is_spacecadet) { @@ -323,6 +276,7 @@ void Qukeys::flushEvent(Key event_key) { // First we record the address and state of the event: KeyAddr queue_head_addr = event_queue_.addr(0); uint8_t keyswitch_state = event_queue_.isRelease(0) ? WAS_PRESSED : IS_PRESSED; + KeyEvent event{queue_head_addr, keyswitch_state, event_key, event_queue_.id(0)}; // If the flushed event is a keypress of a printable symbol, record its // timestamp. This lets us suppress some unintended alternate values seen by @@ -334,12 +288,11 @@ void Qukeys::flushEvent(Key event_key) { prior_keypress_timestamp_ = event_queue_.timestamp(0); } - // Remove the head event from the queue: + // Remove the head event from the queue, then call `handleKeyswitchEvent()` to + // resume processing of the event. It's important to remove the event from the + // queue first; otherwise `onKeyswitchEvent()` will abort it. event_queue_.shift(); - // This ensures that the flushed event will be ignored by the event handler hook: - flushing_queue_ = true; - handleKeyswitchEvent(event_key, queue_head_addr, keyswitch_state); - flushing_queue_ = false; + Runtime.handleKeyswitchEvent(event); } @@ -350,7 +303,7 @@ bool Qukeys::isQukey(KeyAddr k) { // First, look up the value from the keymap. This value should be // correct in the cache, even if there's been a layer change since // the key was pressed. - Key key = Layer.lookup(k); + Key key = Runtime.lookupKey(k); // Next, we check to see if this is a DualUse-type qukey (defined in the keymap) if (isDualUseKey(key)) { diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h index b2e4ca7e..d40aeeaf 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.h @@ -21,6 +21,7 @@ #include "kaleidoscope/Runtime.h" #include #include "kaleidoscope/KeyAddrEventQueue.h" +#include "kaleidoscope/KeyEventTracker.h" // DualUse Key definitions for Qukeys in the keymap #define MT(mod, key) Key( \ @@ -139,10 +140,8 @@ class Qukeys : public kaleidoscope::Plugin { // Kaleidoscope hook functions. EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, - KeyAddr key_addr, - uint8_t key_state); - EventHandlerResult beforeReportingState(); + EventHandlerResult onKeyswitchEvent(KeyEvent &event); + EventHandlerResult afterEachCycle(); private: // An array of Qukey objects in PROGMEM. @@ -185,7 +184,7 @@ class Qukeys : public kaleidoscope::Plugin { // This is a guard against re-processing events when qukeys flushes them from // its event queue. We can't just use an "injected" key state flag, because // that would cause other plugins to also ignore the event. - bool flushing_queue_{false}; + KeyEventTracker event_tracker_; // A cache of the current qukey's primary and alternate key values, so we // don't have to keep looking them up from PROGMEM. From b5a006c228f6be75f8a0e8f82ce5b658e64c6d7e Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:27:29 -0500 Subject: [PATCH 061/108] Adapt SpaceCadet plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/SpaceCadet.cpp | 342 ++++++++---------- .../src/kaleidoscope/plugin/SpaceCadet.h | 54 ++- 2 files changed, 178 insertions(+), 218 deletions(-) diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index f369dbb7..6135c320 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -21,261 +21,203 @@ #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" +//#include + namespace kaleidoscope { namespace plugin { -//Constructor with input and output, and assume default timeout -SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_) { - input = input_; - output = output_; -} +// Constructor with input and output, and assume default timeout +SpaceCadet::KeyBinding::KeyBinding(Key input, Key output) + : input(input), output(output) {} -//Constructor with all three set -SpaceCadet::KeyBinding::KeyBinding(Key input_, Key output_, uint16_t timeout_) { - input = input_; - output = output_; - timeout = timeout_; -} +// Constructor with all three set +SpaceCadet::KeyBinding::KeyBinding(Key input, Key output, uint16_t timeout) + : input(input), output(output), timeout(timeout) {} + +// ============================================================================= +// Space Cadet class variables + +// ----------------------------------------------------------------------------- +// Plugin configuration variables -//Space Cadet SpaceCadet::KeyBinding * SpaceCadet::map; -uint16_t SpaceCadet::time_out = 1000; +uint16_t SpaceCadet::time_out = 200; + +// ----------------------------------------------------------------------------- +// State variables + bool SpaceCadet::disabled = false; +// These variables are used to keep track of any pending unresolved SpaceCadet +// key that has been pressed. If `pending_map_index_` is negative, it means +// there is no such pending keypress. Otherwise, it holds the value of the index +// of that key in the array. +int8_t SpaceCadet::pending_map_index_ = -1; + +KeyEventTracker SpaceCadet::event_tracker_; + +// ============================================================================= +// SpaceCadet functions + +// Constructor SpaceCadet::SpaceCadet() { static SpaceCadet::KeyBinding initialmap[] = { - //By default, respect the default timeout - {Key_LeftShift, Key_LeftParen, 0} - , {Key_RightShift, Key_RightParen, 0} - //These may be uncommented, added, or set in the main sketch - /*,{Key_LeftGui,Key_LeftCurlyBracket, 250} - ,{Key_RightAlt,Key_RightCurlyBracket, 250} - ,{Key_LeftControl,Key_LeftBracket, 250} - ,{Key_RightControl,Key_RightBracket, 250}*/ - , SPACECADET_MAP_END + // By default, respect the default timeout + {Key_LeftShift, Key_LeftParen, 0}, + {Key_RightShift, Key_RightParen, 0}, + // These may be uncommented, added, or set in the main sketch + /* + {Key_LeftGui, Key_LeftCurlyBracket, 250}, + {Key_RightAlt, Key_RightCurlyBracket, 250}, + {Key_LeftControl, Key_LeftBracket, 250}, + {Key_RightControl, Key_RightBracket, 250}, + */ + SPACECADET_MAP_END }; map = initialmap; } -//Function to enable SpaceCadet behavior +// ----------------------------------------------------------------------------- +// Function to determine whether SpaceCadet is active (useful for Macros and +// other plugins). +bool SpaceCadet::active() { + return !disabled; +} + +// Function to enable SpaceCadet behavior void SpaceCadet::enable() { disabled = false; } -//Function to disable SpaceCadet behavior +// Function to disable SpaceCadet behavior void SpaceCadet::disable() { disabled = true; } -//Function to determine whether SpaceCadet is active (useful for Macros and other plugins) -bool SpaceCadet::active() { - return !disabled; -} +// ============================================================================= +// Event handler hook functions +// ----------------------------------------------------------------------------- EventHandlerResult SpaceCadet::onNameQuery() { return ::Focus.sendName(F("SpaceCadet")); } -EventHandlerResult SpaceCadet::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - //Handle our synthetic keys for enabling and disabling functionality - if (mapped_key.getRaw() >= kaleidoscope::ranges::SC_FIRST && - mapped_key.getRaw() <= kaleidoscope::ranges::SC_LAST) { - //Only fire the activate / deactivate on the initial press (not held or release) - if (keyToggledOn(key_state)) { - if (mapped_key == Key_SpaceCadetEnable) { - enable(); - } else if (mapped_key == Key_SpaceCadetDisable) { - disable(); - } - } - - return EventHandlerResult::EVENT_CONSUMED; +// ----------------------------------------------------------------------------- +EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { + // If SpaceCadet has already processed and released this event, ignore + // it. There's no need to update the event tracker in this case. + if (event_tracker_.shouldIgnore(event)) { + // We should never get an event that's in our queue here, but just in case + // some other plugin sends one, abort. + if (event_queue_.shouldAbort(event)) + return EventHandlerResult::ABORT; + return EventHandlerResult::OK; } - //if SpaceCadet is disabled, this was an injected key, it was NoKey, - //or if they key somehow came here without being either pressed or released, - //return the mapped key and just get out of here. - if ( - disabled - || (key_state & INJECTED) - || mapped_key == Key_NoKey - || (!keyIsPressed(key_state) && !keyWasPressed(key_state)) - ) { + // If event.addr is not a physical key, ignore it; some other plugin injected + // it. This check should be unnecessary. + if (!event.addr.isValid() || (event.state & INJECTED) != 0) { return EventHandlerResult::OK; } - // If a key has been just toggled on... - if (keyToggledOn(key_state)) { - - //check to see if we found a valid key. Assume not valid. - bool valid_key = false; - bool other_mapped_key_flagged = false; - - //Check the current map to see if any other key has been already flagged - //Exit condition is if we reach the special SPACECADET_MAP_END sentinel - for ( - uint8_t i = 0 ; - !( - map[i].input == Key_NoKey - && map[i].output == Key_NoKey - && map[i].timeout == 0 - ) ; - ++i - ) { - - if (map[i].flagged - && map[i].input != mapped_key) { - other_mapped_key_flagged = true; - break; - } - } - - //This will only set one key, and, if it isn't in our map, it clears everything for the non-pressed key - //Exit condition is if we reach the special SPACECADET_MAP_END sentinel - for ( - uint8_t i = 0 ; - !( - map[i].input == Key_NoKey - && map[i].output == Key_NoKey - && map[i].timeout == 0 - ) ; - ++i - ) { - - if (mapped_key == map[i].input) { - //Only activate this as part of the mapping if there isn't already a - //key waiting for timeout. This allows us to return OK later and for - //this loop to inject all the other flagged keys - if (!other_mapped_key_flagged) { - //The keypress was valid and a match. Mark it as flagged and reset the counter - map[i].flagged = true; - map[i].start_time = Runtime.millisAtCycleStart(); - - //yes, we found a valid key - valid_key = true; - } - } else { - //If the key entry we're looking at was flagged previously, add it to the - //report before we do anything else (this handles the situation where we - //hit another key after this -- if it's a modifier, we want the modifier - //key to be added to the report, for things like ctrl, alt, shift, etc) - if (map[i].flagged) { - handleKeyswitchEvent(map[i].input, UnknownKeyswitchLocation, IS_PRESSED | INJECTED); - } - - //The keypress wasn't a match, so we need to mark it as not flagged and - //reset the timer for it to disable everything. - map[i].flagged = false; - map[i].start_time = 0; - } + // Turn SpaceCadet on or off. + if (keyToggledOn(event.state)) { + if (event.key == Key_SpaceCadetEnable) { + enable(); + return EventHandlerResult::EVENT_CONSUMED; } - - //If we found a valid key in our map, we don't actually want to send anything. - //This gets around an issue in Windows if we map a SpaceCadet function on top - //of Alt -- sending Alt by itself activates the menubar. We don't want to send - //anything until we know that we're either sending the alternate key or we - //know for sure that we want to send the originally pressed key. - if (valid_key) { + if (event.key == Key_SpaceCadetDisable) { + disable(); return EventHandlerResult::EVENT_CONSUMED; } - - //this is all we need to do on keypress, let the next handler do its thing too. - //This case assumes we weren't a valid key that we were watching, so we don't - //need to do anything else. - return EventHandlerResult::OK; } - // if the state is empty, that means that either an activating key wasn't pressed, - // or we used another key in the interim. in both cases, nothing special to do. - bool valid_key = false; - bool pressed_key_was_valid = false; - uint8_t index = 0; - - //Look to see if any keys in our map are currently flagged. - //Exit condition is if we reach the special SPACECADET_MAP_END sentinel - for ( - uint8_t i = 0 ; - !( - map[i].input == Key_NoKey - && map[i].output == Key_NoKey - && map[i].timeout == 0 - ); - ++i - ) { - - //The key we're looking at was previously flagged (so perform action) - if (map[i].flagged) { - valid_key = true; - index = i; - } + // Do nothing if disabled, but keep the event tracker current. + if (disabled) + return EventHandlerResult::OK; - //the key we're looking at was valid (in the map) - if (map[i].input == mapped_key) { - pressed_key_was_valid = true; + if (!event_queue_.isEmpty()) { + // There's an unresolved SpaceCadet key press. + if (keyToggledOff(event.state)) { + if (event.addr == event_queue_.addr(0)) { + // SpaceCadet key released before timing out; send the event with the + // SpaceCadet key's alternate `Key` value before flushing the rest of + // the queue (see below). + flushEvent(true); + } else if (!event_queue_.isFull()) { + // Queue not full; add event and abort + event_queue_.append(event); + return EventHandlerResult::ABORT; + } } + // Either a new key was pressed, or the SpaceCadet key was released and has + // been flushed (see above), or the queue is full and is about to overflow. + // In all cases, we fulsh the whole queue now. + flushQueue(); } - //If no valid mapped keys were pressed, simply return the key that - //was originally passed in. - if (!valid_key) { - return EventHandlerResult::OK; - } - - //use the map index to find the local timeout for this key - uint16_t current_timeout = map[index].timeout; - //If that isn't set, use the global timeout setting. - if (current_timeout == 0) { - current_timeout = time_out; + // Event queue is now empty + if (keyToggledOn(event.state)) { + // Check for a SpaceCadet key + pending_map_index_ = getSpaceCadetKeyIndex(event.key); + if (pending_map_index_ >= 0) { + // A SpaceCadet key was pressed + event_queue_.append(event); + return EventHandlerResult::ABORT; + } } - //Check to determine if we have surpassed our timeout for holding this key - if (Runtime.hasTimeExpired(map[index].start_time, current_timeout)) { - // if we timed out, that means we need to keep pressing the mapped - // key, but we won't need to send the alternative key in the end - map[index].flagged = false; - map[index].start_time = 0; - - //Just return this key itself (we won't run alternative keys check) - return EventHandlerResult::OK; - } + return EventHandlerResult::OK; +} - // If the key that was pressed isn't one of our mapped keys, just - // return. This can happen when another key is released, and that should not - // interrupt us. - if (!pressed_key_was_valid) { +// ----------------------------------------------------------------------------- +EventHandlerResult SpaceCadet::afterEachCycle() { + // If there's no pending event, return. + if (event_queue_.isEmpty()) return EventHandlerResult::OK; - } - // if a key toggled off (and that must be one of the mapped keys at this point), - // send the alternative key instead (if we were interrupted, we bailed out earlier). - if (keyToggledOff(key_state)) { - Key alternate_key = map[index].output; + // Get timeout value for the pending key. + uint16_t pending_timeout = time_out; + if (map[pending_map_index_].timeout != 0) + pending_timeout = map[pending_map_index_].timeout; + uint16_t start_time = event_queue_.timestamp(0); - //Since we are sending the actual key (no need for shift, etc), - //only need to send that key and not the original key. + if (Runtime.hasTimeExpired(start_time, pending_timeout)) { + // The timer has expired; release the pending event unchanged. + flushQueue(); + } + return EventHandlerResult::OK; +} - //inject our new key - handleKeyswitchEvent(alternate_key, key_addr, IS_PRESSED | INJECTED); +// ============================================================================= +// Private helper function(s) - //Unflag the key so we don't try this again. - map[index].flagged = false; - map[index].start_time = 0; +int8_t SpaceCadet::getSpaceCadetKeyIndex(Key key) const { + for (uint8_t i = 0; !map[i].isEmpty(); ++i) { + if (map[i].input == key) { + return i; + } } + return -1; +} - //Special case here for if we had a valid key that's continuing to be held. - //If it's a valid key, and it's continuing to be held, return NoKey. - //This prevents us from accidentally triggering a keypress that we didn't - //mean to handle. - if (valid_key) { - return EventHandlerResult::EVENT_CONSUMED; +void SpaceCadet::flushQueue() { + while (!event_queue_.isEmpty()) { + flushEvent(false); } - - //Finally, as a final sanity check, simply return the passed-in key as-is. - return EventHandlerResult::OK; } +void SpaceCadet::flushEvent(bool is_tap) { + KeyEvent event = event_queue_.event(0); + if (is_tap && pending_map_index_ >= 0) { + event.key = map[pending_map_index_].output; + } + event_queue_.shift(); + Runtime.handleKeyswitchEvent(event); } -} + +} // namespace plugin +} // namespace kaleidoscope kaleidoscope::plugin::SpaceCadet SpaceCadet; diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 682ecdb1..971bb540 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -19,6 +19,8 @@ #pragma once #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyEventTracker.h" +#include "kaleidoscope/KeyAddrEventQueue.h" #include #ifndef SPACECADET_MAP_END @@ -33,44 +35,60 @@ namespace plugin { class SpaceCadet : public kaleidoscope::Plugin { public: - //Internal Class - //Declarations for the modifier key mapping + // Internal Class + // Declarations for the modifier key mapping class KeyBinding { public: - //Empty constructor; set the vars separately - KeyBinding(void) {} - //Constructor with input and output - KeyBinding(Key input_, Key output_); - //Constructor with all three set - KeyBinding(Key input_, Key output_, uint16_t timeout_); - //The key that is pressed + // Empty constructor; set the vars separately + KeyBinding() {} + // Constructor with input and output + KeyBinding(Key input, Key output); + // Constructor with all three set + KeyBinding(Key input, Key output, uint16_t timeout); + // The key that is pressed Key input; - //the key that is sent + // the key that is sent Key output; - //The timeout (default to global timeout) + // The timeout (default to global timeout) uint16_t timeout = 0; - //The flag (set to 0) - bool flagged = false; - //the start time for this key press - uint16_t start_time = 0; + // to check for the end of a list (SPACECADET_MAP_END) + bool isEmpty() const { + return (input == Key_NoKey && output == Key_NoKey && timeout == 0); + } }; SpaceCadet(void); - //Methods + // Methods static void enable(void); static void disable(void); static bool active(void); - //Publically accessible variables + // Publically accessible variables static uint16_t time_out; // The global timeout in milliseconds static SpaceCadet::KeyBinding * map; // The map of key bindings EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyswitchEvent(KeyEvent &event); + EventHandlerResult afterEachCycle(); private: static bool disabled; + + static KeyEventTracker event_tracker_; + + // The maximum number of events in the queue at a time. + static constexpr uint8_t queue_capacity_{4}; + + // The event queue stores a series of press and release events. + KeyAddrEventQueue event_queue_; + + static int8_t pending_map_index_; + + int8_t getSpaceCadetKeyIndex(Key key) const; + + void flushEvent(bool is_tap = false); + void flushQueue(); }; } From 9352117e7456363fd134b49ee885f344a6cfdff7 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:28:27 -0500 Subject: [PATCH 062/108] Adapt Redial plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Redial.cpp | 12 ++++++------ .../src/kaleidoscope/plugin/Redial.h | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp index d81a0534..35e1dc7f 100644 --- a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp +++ b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.cpp @@ -28,12 +28,12 @@ EventHandlerResult Redial::onNameQuery() { return ::Focus.sendName(F("Redial")); } -EventHandlerResult Redial::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (keyToggledOn(key_state)) { - if (mapped_key == Key_Redial) { - mapped_key = last_key_; - } else if (shouldRemember(mapped_key)) { - last_key_ = mapped_key; +EventHandlerResult Redial::onKeyEvent(KeyEvent &event) { + if (keyToggledOn(event.state)) { + if (event.key == Key_Redial) { + event.key = last_key_; + } else if (shouldRemember(event.key)) { + last_key_ = event.key; } } return EventHandlerResult::OK; diff --git a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h index 701796e5..e2514eda 100644 --- a/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h +++ b/plugins/Kaleidoscope-Redial/src/kaleidoscope/plugin/Redial.h @@ -32,7 +32,7 @@ class Redial : public kaleidoscope::Plugin { static bool shouldRemember(Key mappedKey); EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); private: static Key last_key_; From abba88125720ff1b4d0600ad303e95dff32e303f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:48:17 -0500 Subject: [PATCH 063/108] Adapt ShapeShifter plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/ShapeShifter.cpp | 12 +++++------- .../src/kaleidoscope/plugin/ShapeShifter.h | 2 +- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp index 3692c8c3..0890fc0e 100644 --- a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp +++ b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.cpp @@ -22,12 +22,10 @@ namespace kaleidoscope { namespace plugin { -const ShapeShifter::dictionary_t *ShapeShifter::dictionary = NULL; +const ShapeShifter::dictionary_t *ShapeShifter::dictionary = nullptr; -EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - // Only act on keys that toggle on to prevent cycles (if the dictionary has - // two keys mapped to each other). - if (!keyToggledOn(key_state)) +EventHandlerResult ShapeShifter::onKeyEvent(KeyEvent &event) { + if (dictionary == nullptr) return EventHandlerResult::OK; if (!dictionary) @@ -41,7 +39,7 @@ EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_a orig = dictionary[i].original.readFromProgmem(); i++; } while (orig != Key_NoKey && - orig != mapped_key); + orig != event.key); i--; // If not found, bail out. @@ -60,7 +58,7 @@ EventHandlerResult ShapeShifter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_a repl = dictionary[i].replacement.readFromProgmem(); // If found, handle the alternate key instead - mapped_key = repl; + event.key = repl; return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h index 3b8ad7da..b113cf49 100644 --- a/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h +++ b/plugins/Kaleidoscope-ShapeShifter/src/kaleidoscope/plugin/ShapeShifter.h @@ -32,7 +32,7 @@ class ShapeShifter : public kaleidoscope::Plugin { static const dictionary_t *dictionary; - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); }; } From 0bf128be09682e790b4e7c02509257fb84912208 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:48:43 -0500 Subject: [PATCH 064/108] Adapt TopsyTurvy plugin to KeyEvent handlers fixes #990 Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/TopsyTurvy.cpp | 91 ++++++++++--------- .../src/kaleidoscope/plugin/TopsyTurvy.h | 7 +- 2 files changed, 53 insertions(+), 45 deletions(-) diff --git a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp index e587c7d2..55fc2ffc 100644 --- a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp +++ b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.cpp @@ -17,68 +17,77 @@ #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/LiveKeys.h" namespace kaleidoscope { namespace plugin { KeyAddr TopsyTurvy::tt_addr_ = KeyAddr::none(); -bool TopsyTurvy::shift_detected_ = false; -EventHandlerResult TopsyTurvy::beforeEachCycle() { - // Clear the shift detection state before each scan cycle. - shift_detected_ = false; - return EventHandlerResult::OK; -} +EventHandlerResult TopsyTurvy::onKeyEvent(KeyEvent &event) { + if (keyToggledOff(event.state)) { + if (event.addr == tt_addr_) + tt_addr_.clear(); + return EventHandlerResult::OK; + } -EventHandlerResult TopsyTurvy::beforeReportingState() { - // If no TopsyTurvy key is active, there's nothing to do. - if (! tt_addr_.isValid()) + if (event.key.isKeyboardModifier()) return EventHandlerResult::OK; - // A TopsyTurvy key is active. That means we need to reverse the shift state, - // whether it was on or off. - if (shift_detected_) { - kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_LeftShift); - kaleidoscope::Runtime.hid().keyboard().releaseKey(Key_RightShift); + if (isTopsyTurvyKey(event.key)) { + event.key.setRaw(event.key.getRaw() - ranges::TT_FIRST); + tt_addr_ = event.addr; } else { - kaleidoscope::Runtime.hid().keyboard().pressKey(Key_LeftShift); + live_keys.activate(tt_addr_, Key_NoKey); + tt_addr_.clear(); + } + + if (tt_addr_.isValid()) { + for (KeyAddr key_addr : KeyAddr::all()) { + if (key_addr == event.addr) + continue; + + Key active_key = live_keys[key_addr]; + if (active_key == Key_Transparent) + continue; + + if (active_key.isKeyboardKey() && !active_key.isKeyboardModifier()) { + live_keys.activate(key_addr, Key_NoKey); + } + } } return EventHandlerResult::OK; } -EventHandlerResult TopsyTurvy::onKeyswitchEvent(Key &key, - KeyAddr key_addr, - uint8_t key_state) { - // If a modifer key (including combo modifiers, but not non-modifier keys with - // mod flags) is active, and it includes `shift` (either from its keycode or a - // mod flag), record that we detected an "intentional shift". - if (key.isKeyboardShift() && keyIsPressed(key_state)) - shift_detected_ = true; +EventHandlerResult TopsyTurvy::beforeReportingState(const KeyEvent &event) { - // If the active TopsyTurvy key toggles off, clear the stored address to - // record that. - if (keyToggledOff(key_state)) { - if (key_addr == tt_addr_) { - tt_addr_.clear(); - } + if (!tt_addr_.isValid()) { return EventHandlerResult::OK; } - - if (keyToggledOn(key_state)) { - if (isTopsyTurvyKey(key)) { - // If a TopsyTurvy key toggles on, store its address to indicate that it's - // active, and decode its key value to store in the active keys cache. - tt_addr_ = key_addr; - key = Key(key.getRaw() - ranges::TT_FIRST); - } else { - // If any other key toggles on, clear the active TopsyTurvy address. - tt_addr_.clear(); + // If a TopsyTurvy key is being held, no other KeyboardKey should be able to + // toggle off, because those keys were masked. It's possible for other plugins + // to change that, but those types of complex plugin interactions can't be + // guaranteed to be safe, anyway. Therefore, we assume that if `tt_addr` is + // valid, it is also the last key pressed. + bool shift_detected = false; + for (KeyAddr key_addr : KeyAddr::all()) { + if (live_keys[key_addr].isKeyboardShift()) { + shift_detected = true; + break; } } + + if (shift_detected) { + Runtime.hid().keyboard().releaseKey(Key_LeftShift); + Runtime.hid().keyboard().releaseKey(Key_RightShift); + } else { + Runtime.hid().keyboard().pressKey(Key_LeftShift); + } + return EventHandlerResult::OK; } -} -} +} // namespace plugin +} // namespace kaleidoscope kaleidoscope::plugin::TopsyTurvy TopsyTurvy; diff --git a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h index a1903615..9a3ab9ca 100644 --- a/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h +++ b/plugins/Kaleidoscope-TopsyTurvy/src/kaleidoscope/plugin/TopsyTurvy.h @@ -29,9 +29,8 @@ class TopsyTurvy: public kaleidoscope::Plugin { public: TopsyTurvy(void) {} - EventHandlerResult beforeEachCycle(); - EventHandlerResult beforeReportingState(); - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult beforeReportingState(const KeyEvent &event); static bool isTopsyTurvyKey(Key key) { return (key >= ranges::TT_FIRST && @@ -40,8 +39,8 @@ class TopsyTurvy: public kaleidoscope::Plugin { private: static KeyAddr tt_addr_; - static bool shift_detected_; }; + } } From f931a59efc108a8b3c89643d1ea91275cfd91d14 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:29:37 -0500 Subject: [PATCH 065/108] Adapt TapDance plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/TapDance.cpp | 180 ++++++++---------- .../src/kaleidoscope/plugin/TapDance.h | 15 +- 2 files changed, 87 insertions(+), 108 deletions(-) diff --git a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp index 34d359c8..458b829e 100644 --- a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp +++ b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.cpp @@ -19,6 +19,7 @@ #include #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/layers.h" +#include "kaleidoscope/KeyEventTracker.h" namespace kaleidoscope { namespace plugin { @@ -26,153 +27,124 @@ namespace plugin { // --- config --- uint16_t TapDance::time_out = 200; -KeyAddr TapDance::release_addr_ = KeyAddr{KeyAddr::invalid_state}; +uint8_t TapDance::tap_count_ = 0; + +KeyEventTracker TapDance::event_tracker_; // --- api --- void TapDance::actionKeys(uint8_t tap_count, - ActionType tap_dance_action, + ActionType action, uint8_t max_keys, const Key tap_keys[]) { + if (event_queue_.isEmpty()) + return; + if (tap_count > max_keys) tap_count = max_keys; - Key key = tap_keys[tap_count - 1].readFromProgmem(); + KeyEvent event = event_queue_.event(0); + event.key = tap_keys[tap_count - 1].readFromProgmem(); - switch (tap_dance_action) { - case Tap: - break; - case Interrupt: - case Timeout: - if (event_queue_.isEmpty()) - break; - { - KeyAddr td_addr = event_queue_.addr(0); - bool key_released = (live_keys[td_addr] == Key_Transparent); - handleKeyswitchEvent(key, td_addr, IS_PRESSED | INJECTED); - if (key_released) - release_addr_ = td_addr; - } - break; - case Hold: - case Release: - break; + if (action == Interrupt || action == Timeout) { + event_queue_.shift(); + Runtime.handleKeyswitchEvent(event); + } else if (action == Tap && tap_count == max_keys) { + tap_count_ = 0; + event_queue_.clear(); + Runtime.handleKeyswitchEvent(event); } } +void TapDance::flushQueue(KeyAddr ignored_addr) { + while (! event_queue_.isEmpty()) { + KeyEvent queued_event = event_queue_.event(0); + event_queue_.shift(); + if (queued_event.addr != ignored_addr) + Runtime.handleKeyswitchEvent(queued_event); + } +} + // --- hooks --- EventHandlerResult TapDance::onNameQuery() { return ::Focus.sendName(F("TapDance")); } -EventHandlerResult TapDance::onKeyswitchEvent(Key &key, - KeyAddr key_addr, - uint8_t key_state) { - if (key_state & INJECTED) +EventHandlerResult TapDance::onKeyswitchEvent(KeyEvent &event) { + // If the plugin has already processed and released this event, ignore it. + // There's no need to update the event tracker explicitly. + if (event_tracker_.shouldIgnore(event)) { + // We should never get an event that's in our queue here, but just in case + // some other plugin sends one, abort. + if (event_queue_.shouldAbort(event)) + return EventHandlerResult::ABORT; return EventHandlerResult::OK; + } - if (event_queue_.isEmpty()) { - if (keyToggledOn(key_state) && isTapDanceKey(key)) { - // Begin a new TapDance sequence: - uint8_t td_id = key.getRaw() - ranges::TD_FIRST; - tapDanceAction(td_id, key_addr, 1, Tap); - event_queue_.append(key_addr, key_state); - return EventHandlerResult::EVENT_CONSUMED; - } + // If event.addr is not a physical key, ignore it; some other plugin injected it. + if (! event.addr.isValid() || (event.state & INJECTED) != 0) { return EventHandlerResult::OK; } - uint8_t td_count = event_queue_.length(); + if (keyToggledOff(event.state)) { + if (event_queue_.isEmpty()) + return EventHandlerResult::OK; + event_queue_.append(event); + return EventHandlerResult::ABORT; + } + + if (event_queue_.isEmpty() && !isTapDanceKey(event.key)) + return EventHandlerResult::OK; + KeyAddr td_addr = event_queue_.addr(0); - Key td_key = Layer.lookup(td_addr); + Key td_key = Layer.lookupOnActiveLayer(td_addr); uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST; - if (keyToggledOn(key_state)) { - if (key_addr == td_addr) { - // The same TapDance key was pressed again; continue the sequence: - tapDanceAction(td_id, td_addr, ++td_count, Tap); - } else { - // A different key was pressed; interrupt the sequeunce: - tapDanceAction(td_id, td_addr, td_count, Interrupt); - event_queue_.clear(); - // If the sequence was interrupted by another TapDance key, start the new - // sequence: - if (isTapDanceKey(Layer.lookup(key_addr))) { - td_id = key.getRaw() - ranges::TD_FIRST; - tapDanceAction(td_id, key_addr, 1, Tap); - } - } - // Any key that toggles on while a TapDance sequence is live gets added to - // the queue. If it interrupted the queue, we need to hold off on processing - // that event until the next cycle to guarantee that the events appear in - // order on the host. - event_queue_.append(key_addr, key_state); - if (isTapDanceKey(key)) - return EventHandlerResult::EVENT_CONSUMED; - return EventHandlerResult::ABORT; - } else if (keyIsPressed(key_state)) { - // Until a key press event has been released from the queue, its "hold - // event" must be suppressed every cycle. - for (uint8_t i{0}; i < event_queue_.length(); ++i) { - if (event_queue_.addr(i) == key_addr) { - return EventHandlerResult::ABORT; - } - } + if (! event_queue_.isEmpty() && + event.addr != event_queue_.addr(0)) { + // Interrupt: Call `tapDanceAction()` first, so it will have access to the + // TapDance key press event that needs to be sent, then flush the queue. + tapDanceAction(td_id, td_addr, tap_count_, Interrupt); + flushQueue(); + tap_count_ = 0; + // If the event isn't another TapDance key, let it proceed. If it is, fall + // through to the next block, which handles "Tap" actions. + if (! isTapDanceKey(event.key)) + return EventHandlerResult::OK; } - // We always indicate that other plugins don't need to handle TapDance keys, - // but we do allow them to show up as active keys when they're held. This way, - // when one times out, if it's not being held any longer, we can send the - // release event. - if (isTapDanceKey(key)) - return EventHandlerResult::EVENT_CONSUMED; - // This key is being held, but is not in the queue, or it toggled off, but is - // not (currently) a TapDance key. - return EventHandlerResult::OK; + + // Tap: First flush the queue, ignoring the previous press and release events + // for the TapDance key, then add the new tap to the queue (it becomes the + // first entry). + flushQueue(event.addr); + event_queue_.append(event); + tapDanceAction(td_id, td_addr, ++tap_count_, Tap); + return EventHandlerResult::ABORT; } EventHandlerResult TapDance::afterEachCycle() { - if (release_addr_.isValid()) { - handleKeyswitchEvent(Key_NoKey, release_addr_, WAS_PRESSED | INJECTED); - release_addr_ = KeyAddr{KeyAddr::invalid_state}; - } - Key event_key; - // Purge any non-TapDance key events from the front of the queue. - while (! event_queue_.isEmpty()) { - KeyAddr event_addr = event_queue_.addr(0); - event_key = Layer.lookup(event_addr); - if (isTapDanceKey(event_key)) { - break; - } - handleKeyswitchEvent(event_key, event_addr, IS_PRESSED | INJECTED); - event_queue_.shift(); - } - + // If there's no active TapDance sequence, there's nothing to do. if (event_queue_.isEmpty()) return EventHandlerResult::OK; // The first event in the queue is now guaranteed to be a TapDance key. - uint8_t td_id = event_key.getRaw() - ranges::TD_FIRST; KeyAddr td_addr = event_queue_.addr(0); + Key td_key = Layer.lookupOnActiveLayer(td_addr); + uint8_t td_id = td_key.getRaw() - ranges::TD_FIRST; // Check for timeout - uint8_t td_count = event_queue_.length(); - uint16_t start_time = event_queue_.timestamp(td_count - 1); + uint16_t start_time = event_queue_.timestamp(0); if (Runtime.hasTimeExpired(start_time, time_out)) { - tapDanceAction(td_id, td_addr, td_count, Timeout); - event_queue_.clear(); - // There's still a race condition here, but it's a minor one. If a TapDance - // sequence times out in the `afterEachCycle()` handler, then another key - // toggles on in the following scan cycle, and that key is handled first, - // the two events could show up out of order on the host. The probability of - // this happening is low, and event-driven Kaleidoscope will fix it - // completely, so I'm willing to accept the risk for now. + tapDanceAction(td_id, td_addr, tap_count_, Timeout); + flushQueue(); + tap_count_ = 0; } return EventHandlerResult::OK; } -} -} +} // namespace plugin +} // namespace kaleidoscope __attribute__((weak)) void tapDanceAction(uint8_t tap_dance_index, KeyAddr key_addr, uint8_t tap_count, kaleidoscope::plugin::TapDance::ActionType tap_dance_action) { diff --git a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h index 9eef4c25..fccac435 100644 --- a/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h +++ b/plugins/Kaleidoscope-TapDance/src/kaleidoscope/plugin/TapDance.h @@ -20,7 +20,9 @@ #include "kaleidoscope/Runtime.h" #include "kaleidoscope/LiveKeys.h" #include +#include "kaleidoscope/KeyAddr.h" #include "kaleidoscope/KeyAddrEventQueue.h" +#include "kaleidoscope/KeyEventTracker.h" #define TD(n) Key(kaleidoscope::ranges::TD_FIRST + n) @@ -49,7 +51,7 @@ class TapDance : public kaleidoscope::Plugin { void actionKeys(uint8_t tap_count, ActionType tap_dance_action, uint8_t max_keys, const Key tap_keys[]); EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyswitchEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); static constexpr bool isTapDanceKey(Key key) { @@ -64,12 +66,17 @@ class TapDance : public kaleidoscope::Plugin { // The event queue stores a series of press and release events. KeyAddrEventQueue event_queue_; - static KeyAddr release_addr_; + static KeyEventTracker event_tracker_; + + // The number of taps in the current TapDance sequence. + static uint8_t tap_count_; + + void flushQueue(KeyAddr ignored_addr = KeyAddr::none()); }; -} -} +} // namespace plugin +} // namespace kaleidoscope void tapDanceAction(uint8_t tap_dance_index, KeyAddr key_addr, uint8_t tap_count, kaleidoscope::plugin::TapDance::ActionType tap_dance_action); From 3e304a2a3f8320dfb46ce72999e71079d8b400ba Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:30:40 -0500 Subject: [PATCH 066/108] Adapt OneShot plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/OneShot.cpp | 262 ++++++++---------- .../src/kaleidoscope/plugin/OneShot.h | 11 +- 2 files changed, 125 insertions(+), 148 deletions(-) diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 9b5c7c5f..46234785 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -51,7 +51,6 @@ KeyAddrBitfield OneShot::glue_addrs_; uint16_t OneShot::start_time_ = 0; KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr; -uint8_t OneShot::release_countdown_ = 0; #ifndef ONESHOT_WITHOUT_METASTICKY KeyAddr OneShot::meta_sticky_key_addr_ {KeyAddr::invalid_state}; @@ -177,30 +176,28 @@ EventHandlerResult OneShot::onNameQuery() { return ::Focus.sendName(F("OneShot")); } -EventHandlerResult OneShot::onKeyswitchEvent( - Key &key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { - // Ignore injected key events. This prevents re-processing events - // that the hook functions generate (by calling `injectNormalKey()` - // via one of the `*OneShot()` functions). There are more robust - // ways to do this, but since OneShot is intended to react to only - // physical keypresses, this is adequate. - if (key_state & INJECTED) + // Ignore injected key events. This prevents re-processing events that the + // hook functions generate (by calling `injectNormalKey()` via one of the + // `*OneShot()` functions). There are more robust ways to do this, but since + // OneShot is intended to react to only physical keypresses, this is adequate. + if (event.state & INJECTED) return EventHandlerResult::OK; - bool temp = temp_addrs_.read(key_addr); - bool glue = glue_addrs_.read(key_addr); + bool temp = temp_addrs_.read(event.addr); + bool glue = glue_addrs_.read(event.addr); - if (keyToggledOn(key_state)) { + if (keyToggledOn(event.state)) { // Make all held keys sticky if `OneShot_ActiveStickyKey` toggles on. - if (key == OneShot_ActiveStickyKey) { + if (event.key == OneShot_ActiveStickyKey) { // Skip the stickify key itself for (KeyAddr entry_addr : KeyAddr::all()) { - if (entry_addr == key_addr) { + if (entry_addr == event.addr) { continue; } - // Get the entry from the keymap cache + // Get the entry from the keyboard state array Key entry_key = live_keys[entry_addr]; // Skip empty entries if (entry_key == Key_Transparent || entry_key == Key_NoKey) { @@ -210,177 +207,147 @@ EventHandlerResult OneShot::onKeyswitchEvent( temp_addrs_.clear(entry_addr); glue_addrs_.set(entry_addr); } + prev_key_addr_ = event.addr; return EventHandlerResult::OK; } if (!temp && !glue) { - // This key_addr is not in a OneShot state. + // The key is in the "normal" state. The first thing we need to do is + // convert OneShot keys to their equivalent values, and record the fact + // that the key that just toggled on should transition to the "pending" + // state. + bool is_oneshot = false; + if (isOneShotKey(event.key)) { + event.key = decodeOneShotKey(event.key); + is_oneshot = true; + } + #ifndef ONESHOT_WITHOUT_METASTICKY - if (meta_sticky_key_addr_.isValid()) { + bool is_meta_sticky_key_active = meta_sticky_key_addr_.isValid(); + if (is_meta_sticky_key_active) { // If the meta key isn't sticky, release it bool ms_temp = temp_addrs_.read(meta_sticky_key_addr_); bool ms_glue = glue_addrs_.read(meta_sticky_key_addr_); if (ms_temp) { if (ms_glue) { - // ms key is temp one-shot + // The meta key is in the "one-shot" state; release it immediately. releaseKey(meta_sticky_key_addr_); - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; } else { - // ms key is held + // The meta key is in the "pending" state; cancel that, and let it + // deactivate on release. temp_addrs_.clear(meta_sticky_key_addr_); } - } else { - // ms key is sticky } - glue_addrs_.set(key_addr); - //prev_key_addr_ = key_addr; - start_time_ = Runtime.millisAtCycleStart(); - //return EventHandlerResult::OK; - - } else if (key == OneShot_MetaStickyKey) { - meta_sticky_key_addr_ = key_addr; - temp_addrs_.set(key_addr); + glue_addrs_.set(event.addr); + } else if (event.key == OneShot_MetaStickyKey) { + meta_sticky_key_addr_ = event.addr; + temp_addrs_.set(event.addr); + } + if (is_meta_sticky_key_active || (event.key == OneShot_MetaStickyKey)) { + prev_key_addr_ = event.addr; start_time_ = Runtime.millisAtCycleStart(); - } else // NOLINT + return EventHandlerResult::OK; + } #endif - // *INDENT-OFF* - // Because of the preceding #ifdef, indentation gets thrown off for astyle here. - // This is only an independent `if` block if `ONESHOT_WITHOUT_METASTICKY` - // is set (see above); otherwise it's an `else if`. - if (isOneShotKey(key) || - (auto_modifiers_ && isModifier(key)) || - (auto_layers_ && isLayerShift(key))) { - // Replace the OneShot key with its corresponding normal key. - pressKey(key_addr, key); - return EventHandlerResult::ABORT; - } else if (!isModifier(key) && !isLayerShift(key)) { - // Only trigger release of temporary OneShot keys if the - // pressed key is neither a modifier nor a layer shift. - release_countdown_ = (1 << 1); + if (is_oneshot || + (auto_modifiers_ && event.key.isKeyboardModifier()) || + (auto_layers_ && event.key.isLayerShift())) { + temp_addrs_.set(event.addr); + start_time_ = Runtime.millisAtCycleStart(); + } else if (!event.key.isKeyboardModifier() && + !event.key.isLayerShift()) { + // Only trigger release of temporary one-shot keys if the pressed key is + // neither a modifier nor a layer shift. We need the actual release of + // those keys to happen after the current event is finished, however, so + // we trigger it by back-dating the start time, so that the timeout + // check will trigger in the afterEachCycle() hook. + start_time_ -= timeout_; } - // *INDENT-ON* } else if (temp && glue) { - // This key_addr is in the temporary OneShot state. - if (key_addr == prev_key_addr_) { - // The same OneShot key has been pressed twice in a row. It - // will either become sticky (if it has been double-tapped), - // or it will be become a normal key. Either way, its `temp` - // state will be cleared. - temp_addrs_.clear(key_addr); - - // Derive the true double-tap timeout value if we're using the default. - uint16_t dtto = (double_tap_timeout_ < 0) ? timeout_ : double_tap_timeout_; - - // If the key is not stickable, or the double-tap timeout has - // expired, clear the `glue` state, as well; this OneShot key - // has been cancelled, and will become a normal key. - if (!isStickable(key) || hasTimedOut(dtto)) { - glue_addrs_.clear(key_addr); - } else { - return EventHandlerResult::ABORT; - } + // temporary one-shot + temp_addrs_.clear(event.addr); + + if (event.addr == prev_key_addr_ && + isStickable(event.key) && + !hasDoubleTapTimedOut()) { + // The same stickable key has been double-tapped within the double-tap + // timeout window. We cancel the second press event, emulating a single + // press-and-hold. This doesn't interfere with `prev_key_addr_`, since + // it's the same key again. + return EventHandlerResult::ABORT; } else { - // This is a temporary OneShot key, but has not been pressed - // twice in a row, so we need to clear its state. - temp_addrs_.clear(key_addr); - glue_addrs_.clear(key_addr); + // A second tap that's not a double-tap cancels the one-shot state + glue_addrs_.clear(event.addr); } } else if (!temp && glue) { - // This is a sticky OneShot key that has been pressed. Clear - // state now, so it will become a normal key. - glue_addrs_.clear(key_addr); - // Then replace the key toggled on event with a key held event. - holdKey(key_addr); - return EventHandlerResult::EVENT_CONSUMED; - - } else { // (temp && !glue) - // A key has been pressed that is in the "pending" OneShot - // state. Since this key should have entered the "temporary" - // OneShot state as soon as it was released (from its first - // press), it should only be possible to release a key that's in - // this state. + // sticky state + temp_addrs_.clear(event.addr); + glue_addrs_.clear(event.addr); + + } else { /* if (temp && !glue) */ + // A key has been pressed that is in the "pending" one-shot state. Since + // this key should have entered the "temporary" one-shot state as soon as + // it was released (from its first press), it should only be possible to + // release a key that's in this state. } - // Always record the address of a keypress. It might be useful for - // other plugins, so this could perhaps be tracked in the - // Kaleidoscope core. - prev_key_addr_ = key_addr; + // Always record the address of a keypress. It might be useful for other + // plugins, so this could perhaps be tracked in the Kaleidoscope core. + prev_key_addr_ = event.addr; - } else if (keyToggledOff(key_state)) { + } else { + // Key toggled off if (temp || glue) { - // Any key in the "pending" OneShot state needs its `glue` state - // bit set to make it "temporary". If it's in the "sticky" - // OneShot state, this is redundant, but we're trading time - // efficiency to get smaller binary size. - glue_addrs_.set(key_addr); - // This is an active OneShot key that has just been released. We - // need to stop that event from sending a report, and instead - // send a "hold" event. This is handled in the - // `beforeReportingState()` hook below. - //Layer.updateLiveCompositeKeymap(key_addr, key); + // Any key in the "pending" one-shot state needs its `glue` state bit set + // to make it "temporary". If it's in the "sticky" OneShot state, this is + // redundant, but we're trading execution speed to get a smaller binary. + glue_addrs_.set(event.addr); + // This is an active one-shot key that has just been released. We need to + // stop that event from sending a report, and instead send a "hold" + // event. This is handled in the `beforeReportingState()` hook below. return EventHandlerResult::ABORT; #ifndef ONESHOT_WITHOUT_METASTICKY - } else if (key == OneShot_MetaStickyKey) { - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + } else if (event.key == OneShot_MetaStickyKey) { + // Turn off the meta key if it's released in its "normal" state. + meta_sticky_key_addr_ = KeyAddr::none(); #endif } - } else { - // This key is being held. - if (temp && !glue) { - // This key is in the "pending" OneShot state. We need to check - // its hold timeout, and turn it back into a normal key if it - // has timed out. - if (hasTimedOut(hold_timeout_)) { - temp_addrs_.clear(key_addr); - } - } - - if (isOneShotKey(key)) { - // whoops! someone cancelled a oneshot key while it was being - // held; reactivate it, but set it as a normal modifier - // instead. Or better yet, mask it, in case of a layer change. - } } return EventHandlerResult::OK; } +// ---------------------------------------------------------------------------- +EventHandlerResult OneShot::afterEachCycle() { -// For any active OneShot modifier keys, keep those modifiers active -// in the keyboard HID report. -EventHandlerResult OneShot::beforeReportingState() { - for (KeyAddr key_addr : glue_addrs_) { - holdKey(key_addr); - } - return EventHandlerResult::OK; -} + bool oneshot_expired = hasTimedOut(timeout_); + bool hold_expired = hasTimedOut(hold_timeout_); + bool any_temp_keys = false; + for (KeyAddr key_addr : temp_addrs_) { + any_temp_keys = true; -EventHandlerResult OneShot::afterEachCycle() { - // If a normal, non-modifier key has been pressed, or if active, - // non-sticky OneShot keys have timed out, this is where they get - // released. Release is triggered when `release_countdown_` gets to - // 1, not 0, because most of the time it will be 0 (see below). It - // gets set to 2 on the press of a normal key when there are any - // active OneShot keys; that way, the OneShot keys will stay active - // long enough to apply to the newly-pressed key. - if ((release_countdown_ == 1) || hasTimedOut(timeout_)) { - for (KeyAddr key_addr : temp_addrs_) { - if (glue_addrs_.read(key_addr)) { + if (glue_addrs_.read(key_addr)) { + // Release keys in "one-shot" state that have timed out or been cancelled + // by another key press. + if (oneshot_expired) releaseKey(key_addr); - } - temp_addrs_.clear(key_addr); + } else { + // Cancel "pending" state of keys held longer than the hold timeout. + if (hold_expired) + temp_addrs_.clear(key_addr); } } - // Also, advance the counter for OneShot keys that have been - // cancelled by the press of a non-OneShot, non-modifier key. An - // unconditional bit shift should be more efficient than checking - // for zero to avoid underflow. - release_countdown_ >>= 1; + + // Keep the start time from getting stale; if there are no keys waiting for a + // timeout, it's safe to advance the timer to the current time. + if (!any_temp_keys) { + start_time_ = Runtime.millisAtCycleStart(); + } // Temporary fix for deprecated variables #ifndef NDEPRECATED @@ -449,21 +416,26 @@ void OneShot::pressKey(KeyAddr key_addr, Key key) { prev_key_addr_ = key_addr; start_time_ = Runtime.millisAtCycleStart(); temp_addrs_.set(key_addr); - handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED); + KeyEvent event{key_addr, IS_PRESSED | INJECTED, key}; + Runtime.handleKeyEvent(event); } void OneShot::holdKey(KeyAddr key_addr) { - handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | IS_PRESSED | INJECTED); + KeyEvent event{key_addr, WAS_PRESSED | IS_PRESSED | INJECTED}; + Runtime.handleKeyEvent(event); } void OneShot::releaseKey(KeyAddr key_addr) { glue_addrs_.clear(key_addr); temp_addrs_.clear(key_addr); +#ifndef ONESHOT_WITHOUT_METASTICKY if (live_keys[key_addr] == OneShot_MetaStickyKey) - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + meta_sticky_key_addr_ = KeyAddr::none(); +#endif - handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); + KeyEvent event{key_addr, WAS_PRESSED | INJECTED}; + Runtime.handleKeyEvent(event); } // ------------------------------------------------------------------------------ diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index afad5c3a..5ad18b1d 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -208,19 +208,20 @@ class OneShot : public kaleidoscope::Plugin { // -------------------------------------------------------------------------- // Configuration variables (should probably be private) +#ifndef NDEPRECATED DEPRECATED(ONESHOT_TIMEOUT) static uint16_t time_out; DEPRECATED(ONESHOT_HOLD_TIMEOUT) static uint16_t hold_time_out; DEPRECATED(ONESHOT_DOUBLE_TAP_TIMEOUT) static int16_t double_tap_time_out; +#endif // -------------------------------------------------------------------------- // Plugin hook functions EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); - EventHandlerResult beforeReportingState(); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); private: @@ -251,7 +252,6 @@ class OneShot : public kaleidoscope::Plugin { static uint16_t start_time_; static KeyAddr prev_key_addr_; - static uint8_t release_countdown_; #ifndef ONESHOT_WITHOUT_METASTICKY static KeyAddr meta_sticky_key_addr_; @@ -262,6 +262,11 @@ class OneShot : public kaleidoscope::Plugin { static bool hasTimedOut(uint16_t ttl) { return Runtime.hasTimeExpired(start_time_, ttl); } + static bool hasDoubleTapTimedOut() { + // Derive the true double-tap timeout value if we're using the default. + uint16_t dtto = (double_tap_timeout_ < 0) ? timeout_ : double_tap_timeout_; + return hasTimedOut(dtto); + } static uint8_t getOneShotKeyIndex(Key oneshot_key); static uint8_t getKeyIndex(Key key); static Key decodeOneShotKey(Key oneshot_key); From 4e445817fcbfc176a1b2d1fa0371b9c4210ba9a1 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:32:46 -0500 Subject: [PATCH 067/108] Adapt MagicCombo plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/MagicCombo.cpp | 2 +- .../src/kaleidoscope/plugin/MagicCombo.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.cpp b/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.cpp index 1c307023..6210e201 100644 --- a/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.cpp +++ b/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.cpp @@ -28,7 +28,7 @@ EventHandlerResult MagicCombo::onNameQuery() { return ::Focus.sendName(F("MagicCombo")); } -EventHandlerResult MagicCombo::beforeReportingState() { +EventHandlerResult MagicCombo::afterEachCycle() { for (byte i = 0; i < magiccombo::combos_length; i++) { bool match = true; byte j; diff --git a/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.h b/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.h index d1473bde..1f95fbac 100644 --- a/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.h +++ b/plugins/Kaleidoscope-MagicCombo/src/kaleidoscope/plugin/MagicCombo.h @@ -49,7 +49,7 @@ class MagicCombo : public kaleidoscope::Plugin { static uint16_t min_interval; EventHandlerResult onNameQuery(); - EventHandlerResult beforeReportingState(); + EventHandlerResult afterEachCycle(); private: static uint16_t start_time_; From 3a78581bc2707323c9ceeb842877976f30094b84 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:33:56 -0500 Subject: [PATCH 068/108] Adapt Syster plugin to KeyEvent handlers Signed-off-by: Michael Richters --- examples/Keystrokes/Syster/Syster.ino | 5 +- .../src/kaleidoscope/plugin/Syster.cpp | 108 +++++++++++++----- .../src/kaleidoscope/plugin/Syster.h | 16 ++- 3 files changed, 92 insertions(+), 37 deletions(-) diff --git a/examples/Keystrokes/Syster/Syster.ino b/examples/Keystrokes/Syster/Syster.ino index e38fef25..ba82a04b 100644 --- a/examples/Keystrokes/Syster/Syster.ino +++ b/examples/Keystrokes/Syster/Syster.ino @@ -49,10 +49,7 @@ void systerAction(kaleidoscope::plugin::Syster::action_t action, const char *sym Unicode.type(0x2328); break; case kaleidoscope::plugin::Syster::EndAction: - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, IS_PRESSED | INJECTED); - Kaleidoscope.hid().keyboard().sendReport(); - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, WAS_PRESSED | INJECTED); - Kaleidoscope.hid().keyboard().sendReport(); + kaleidoscope::eraseChars(1); break; case kaleidoscope::plugin::Syster::SymbolAction: Kaleidoscope.serialPort().print("systerAction: symbol="); diff --git a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp index 22764b76..3eb487c6 100644 --- a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp +++ b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.cpp @@ -49,54 +49,81 @@ EventHandlerResult Syster::onNameQuery() { return ::Focus.sendName(F("Syster")); } -EventHandlerResult Syster::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { +EventHandlerResult Syster::onKeyEvent(KeyEvent &event) { if (!is_active_) { - if (!isSyster(mapped_key)) + // If Syster isn't actively matching an input sequence, we're only looking + // for the special Syster `Key` value; anything else gets passed through + // immediately. + if (!isSyster(event.key)) return EventHandlerResult::OK; - if (keyToggledOn(keyState)) { + // It's a Syster Key; activate the plugin as soon as it toggles on, so we + // process rollover correctly. + if (keyToggledOn(event.state)) { is_active_ = true; - systerAction(StartAction, NULL); + systerAction(StartAction, nullptr); } return EventHandlerResult::EVENT_CONSUMED; } - if (keyState & INJECTED) + // Always ignore events marked as artificially injected (it might actually be + // better to drop this, but it's not really clear). + if (event.state & INJECTED) return EventHandlerResult::OK; - if (isSyster(mapped_key)) { + // If a Syster key gets pressed while we're reading an input sequence, ignore + // it. This could be turned into a "reset" where we erase the abandoned input. + if (isSyster(event.key)) { return EventHandlerResult::EVENT_CONSUMED; } - if (mapped_key == Key_Backspace && symbol_pos_ == 0) { - return EventHandlerResult::EVENT_CONSUMED; + // If the user presses a backspace key while at the beginning of the input + // string, suppress it to prevent erasing past the start of the sequence. + // Again, this could be changed to do a reset. + if (event.key == Key_Backspace && symbol_pos_ == 0) { + return EventHandlerResult::ABORT; } - if (keyToggledOff(keyState)) { - if (mapped_key == Key_Spacebar) { - for (uint8_t i = 0; i <= symbol_pos_; i++) { - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, IS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, WAS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); - } - - systerAction(EndAction, NULL); - + // We only pay attention to key press events while parsing input. If the user + // holds a key down long enough to trigger repeating characters in the OS, + // we'll end up erasing fewer characters than we should. This could be + // addressed by immediately sending the corresponding release event, but + // that's probably too much trouble to be worthwhile. + if (keyToggledOn(event.state)) { + // Pressing the spacebar ends the input sequence. + if (event.key == Key_Spacebar) { + // First, we erase all the typed characters in the symbol sequence. + eraseChars(symbol_pos_); + + // Next, we call the user-defined end action. + systerAction(EndAction, nullptr); + + // Then we null-terminate the `symbol_` string, and call the user-defined + // symbol action. symbol_[symbol_pos_] = 0; systerAction(SymbolAction, symbol_); + + // Finally, we're done, so we reset, deactivating Syster until the next + // press of a Syster key. reset(); - return EventHandlerResult::EVENT_CONSUMED; - } - } + // Returning ABORT here stops the spacebar from activating. Alternatively, + // we could remove this return statement, and instead allow the spacebar + // to take effect, resulting in a space in the output, which would follow + // any symbols produced by the symbol action. + return EventHandlerResult::ABORT; - if (keyToggledOn(keyState)) { - if (mapped_key == Key_Backspace) { + } else if (event.key == Key_Backspace) { + // If the user erases any typos, we keep the Syster symbol string in sync + // with what's on the screen. Again, this doesn't account for a key held + // long enough to trigger repeat in the OS. if (symbol_pos_ > 0) - symbol_pos_--; + --symbol_pos_; + } else { - const char c = keyToChar(mapped_key); + // An ordinary keypress, with Syster active. We add its corresponding + // character to the symbol string. + const char c = keyToChar(event.key); if (c) symbol_[symbol_pos_++] = c; } @@ -105,8 +132,35 @@ EventHandlerResult Syster::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, u return EventHandlerResult::OK; } +} // namespace plugin + +void eraseChars(int8_t n) { + // For the `event.addr`, we could track the address of the Syster key, but it + // might be on a layer that's no longer active by the time this gets + // called. We could search the active keymap for an existing `Key_Backspace`, + // but there might not be one. We could hijack the first idle key we find in + // the keymap, but we probably don't need to. Even if some other plugin reacts + // by inserting another event between these two, it's very unlikely that will + // cause a user-visible error. + auto event = KeyEvent(KeyAddr::none(), INJECTED, Key_Backspace); + while (n > 0) { + event.state = IS_PRESSED | INJECTED; + Runtime.handleKeyEvent(event); + event.state = WAS_PRESSED | INJECTED; + Runtime.handleKeyEvent(event); + --n; + } + Runtime.handleKeyEvent(event); + // Change the event from a press to a release, but use the same id. This does + // come with a small risk that another plugin will be tracking events, but + // also ignoring event ids that it has seen before, but it's more likely to + // avoid an error than to cause one. + event.state = WAS_PRESSED | INJECTED; + Runtime.handleKeyEvent(event); } -} + +} // namespace kaleidoscope + __attribute__((weak)) const char keyToChar(Key key) { if (key.getFlags() != 0) diff --git a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.h b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.h index 5720f747..ab47c0ea 100644 --- a/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.h +++ b/plugins/Kaleidoscope-Syster/src/kaleidoscope/plugin/Syster.h @@ -35,25 +35,29 @@ class Syster : public kaleidoscope::Plugin { SymbolAction } action_t; - Syster(void) {} + Syster() {} - static void reset(void); + static void reset(); - bool is_active(void); + bool is_active(); EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyEvent(KeyEvent &event); private: static char symbol_[SYSTER_MAX_SYMBOL_LENGTH + 1]; static uint8_t symbol_pos_; static bool is_active_; }; -} -} +} // namespace plugin + +void eraseChars(int8_t n); + +} // namespace kaleidoscope const char keyToChar(Key key); + void systerAction(kaleidoscope::plugin::Syster::action_t action, const char *symbol); extern kaleidoscope::plugin::Syster Syster; From aa6d4acf25da714d554063e308aabda1291a3863 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:52:14 -0500 Subject: [PATCH 069/108] Adapt GhostInTheFirmware plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../GhostInTheFirmware/GhostInTheFirmware.ino | 128 +++++++----------- .../Kaleidoscope-GhostInTheFirmware/README.md | 23 ++-- .../plugin/GhostInTheFirmware.cpp | 65 +++++---- .../kaleidoscope/plugin/GhostInTheFirmware.h | 18 +-- 4 files changed, 104 insertions(+), 130 deletions(-) diff --git a/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino b/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino index aa6b417a..ca74d94d 100644 --- a/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino +++ b/examples/Features/GhostInTheFirmware/GhostInTheFirmware.ino @@ -20,115 +20,85 @@ #include #include +// This sketch is set up to demonstrate the GhostInTheFirmware plugin. The left +// palm key will activate the plugin, virtually pressing each key on the bottom +// row in sequence, and lighting up the keys using the Stalker LED effect. It +// will type out the letters from A to N, but the right palm key can be used to +// toggle the custom EventDropper plugin to suppress USB output. + // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED - (___, ___, ___, ___, ___, ___, M(0), + (___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, + Key_A, Key_B, Key_C, Key_D, Key_E, Key_F, Key_G, ___, ___, ___, ___, - ___, + M(0), ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, + Key_H, Key_I, Key_J, Key_K, Key_L, Key_M, Key_N, ___, ___, ___, ___, - ___), + M(1)), ) // *INDENT-ON* -class EventDropper_ : public kaleidoscope::Plugin { - public: - EventDropper_() {} +namespace kaleidoscope { +namespace plugin { - kaleidoscope::EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - return kaleidoscope::EventHandlerResult::EVENT_CONSUMED; +class EventDropper : public Plugin { + public: + EventHandlerResult onKeyEvent(KeyEvent &event) { + if (active_) + return EventHandlerResult::EVENT_CONSUMED; + return EventHandlerResult::OK; + } + void toggle() { + active_ = !active_; } + private: + bool active_ = false; }; -static EventDropper_ EventDropper; +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::EventDropper EventDropper; -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - if (macro_index == 0 && keyToggledOn(key_state)) +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (macro_id == 0 && keyToggledOn(event.state)) GhostInTheFirmware.activate(); + if (macro_id == 1 && keyToggledOn(event.state)) + EventDropper.toggle(); return MACRO_NONE; } static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = { - {0, 6, 200, 50}, - {0, 5, 200, 50}, - {0, 4, 200, 50}, - {0, 3, 200, 50}, - {0, 2, 200, 50}, - {0, 1, 200, 50}, - {0, 0, 200, 50}, - {1, 0, 200, 50}, - {1, 1, 200, 50}, - {1, 2, 200, 50}, - {1, 3, 200, 50}, - {1, 4, 200, 50}, - {1, 5, 200, 50}, - {1, 6, 200, 50}, - {2, 6, 200, 50}, - {2, 5, 200, 50}, - {2, 4, 200, 50}, - {2, 3, 200, 50}, - {2, 2, 200, 50}, - {2, 1, 200, 50}, - {2, 0, 200, 50}, - {3, 0, 200, 50}, - {3, 1, 200, 50}, - {3, 3, 200, 50}, - {3, 4, 200, 50}, - {3, 5, 200, 50}, - {0, 7, 200, 50}, - {1, 7, 200, 50}, - {2, 7, 200, 50}, - {3, 7, 200, 50}, - {3, 6, 200, 50}, - - {3, 9, 200, 50}, - {3, 8, 200, 50}, - {2, 8, 200, 50}, - {1, 8, 200, 50}, - {0, 8, 200, 50}, - {3, 10, 200, 50}, - {3, 11, 200, 50}, - {3, 12, 200, 50}, - {3, 13, 200, 50}, - {3, 14, 200, 50}, - {3, 15, 200, 50}, - {2, 15, 200, 50}, - {2, 14, 200, 50}, - {2, 13, 200, 50}, - {2, 12, 200, 50}, - {2, 11, 200, 50}, - {2, 10, 200, 50}, - {2, 9, 200, 50}, - {1, 9, 200, 50}, - {1, 10, 200, 50}, - {1, 11, 200, 50}, - {1, 12, 200, 50}, - {1, 13, 200, 50}, - {1, 14, 200, 50}, - {1, 15, 200, 50}, - {0, 15, 200, 50}, - {0, 14, 200, 50}, - {0, 13, 200, 50}, - {0, 12, 200, 50}, - {0, 11, 200, 50}, - {0, 10, 200, 50}, - {0, 9, 200, 50}, - - {0, 0, 0, 0} + {KeyAddr(3, 0), 200, 50}, + {KeyAddr(3, 1), 200, 50}, + {KeyAddr(3, 2), 200, 50}, + {KeyAddr(3, 3), 200, 50}, + {KeyAddr(3, 4), 200, 50}, + {KeyAddr(3, 5), 200, 50}, + {KeyAddr(2, 6), 200, 50}, + {KeyAddr(2, 9), 200, 50}, + {KeyAddr(3, 10), 200, 50}, + {KeyAddr(3, 11), 200, 50}, + {KeyAddr(3, 12), 200, 50}, + {KeyAddr(3, 13), 200, 50}, + {KeyAddr(3, 14), 200, 50}, + {KeyAddr(3, 15), 200, 50}, + + {KeyAddr::none(), 0, 0} }; KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware, + LEDControl, StalkerEffect, Macros, EventDropper); diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/README.md b/plugins/Kaleidoscope-GhostInTheFirmware/README.md index a726ccea..28aed430 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/README.md +++ b/plugins/Kaleidoscope-GhostInTheFirmware/README.md @@ -22,16 +22,16 @@ that. #include #include -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - if (macro_index == 0 && keyToggledOn(key_state)) +const macro_t *macroAction(uint8_t macro_id, KeyEvent& event) { + if (macro_id == 0 && keyToggledOn(event.state)) GhostInTheFirmware.activate(); return MACRO_NONE; } static const kaleidoscope::plugin::GhostInTheFirmware::GhostKey ghost_keys[] PROGMEM = { - {0, 0, 200, 50}, - {0, 0, 0} + {KeyAddr(0, 0), 200, 50}, + {KeyAddr::none(), 0, 0} }; KALEIDOSCOPE_INIT_PLUGINS(GhostInTheFirmware, @@ -59,13 +59,14 @@ methods and properties: ### `.ghost_keys` > Set this property to the sequence of keys to press, by assigning a sequence to -> this variable. Each element is a quartett of `row`, `column`, a `pressTime`, -> and a `delay`. Each of these will be pressed in different cycles, unlike -> macros which play back within a single cycle. -> -> The key at `row`, `column` will be held for `pressTime` milliseconds, and -> after an additional `delay` milliseconds, the plugin will move on to the next -> entry in the sequence. +> this variable. Each element is a `GhostKey` object, comprised of a `KeyAddr` +> (the location of a key on the keyboard), a duration of the key press (in +> milliseconds), and a delay after the key release until the next one is pressed +> (also in milliseconds). + +> This `ghost_keys` array *MUST* end with the sentinal value of +> `{KeyAddr::none(), 0, 0}` to ensure that GhostInTheFirmware doesn't read past +> the end of the array. > > The sequence *MUST* reside in `PROGMEM`. diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp index 2929f07c..978ea3e8 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp +++ b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.cpp @@ -18,54 +18,63 @@ #include "kaleidoscope/Runtime.h" #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/progmem_helpers.h" namespace kaleidoscope { namespace plugin { const GhostInTheFirmware::GhostKey *GhostInTheFirmware::ghost_keys; -bool GhostInTheFirmware::is_active_; -bool GhostInTheFirmware::is_pressed_; -uint16_t GhostInTheFirmware::current_pos_; -uint32_t GhostInTheFirmware::start_time_; -uint16_t GhostInTheFirmware::press_timeout_; -uint16_t GhostInTheFirmware::delay_timeout_; +bool GhostInTheFirmware::is_active_ = false; +uint16_t GhostInTheFirmware::current_pos_ = 0; +uint16_t GhostInTheFirmware::start_time_; void GhostInTheFirmware::activate(void) { is_active_ = true; } -EventHandlerResult GhostInTheFirmware::beforeReportingState() { +EventHandlerResult GhostInTheFirmware::afterEachCycle() { if (!is_active_) return EventHandlerResult::OK; - if (press_timeout_ == 0) { - press_timeout_ = pgm_read_word(&(ghost_keys[current_pos_].pressTime)); - delay_timeout_ = pgm_read_word(&(ghost_keys[current_pos_].delay)); + // This stores the current GhostKey in the active sequence. + static GhostKey ghost_key{KeyAddr::none(), 0, 0}; - if (press_timeout_ == 0) { + // When a ghost key has finished playing, it sets its delay to zero, + // indicating that it's time to read the next one from memory. + if (ghost_key.delay == 0) { + // Read the settings for the key from PROGMEM: + loadFromProgmem(ghost_keys[current_pos_], ghost_key); + // The end of the sequence is marked by a GhostKey with an invalid KeyAddr + // value (i.e. KeyAddr::none()). If we read this sentinel value, reset and + // deactivate. + if (!ghost_key.addr.isValid()) { current_pos_ = 0; + ghost_key.delay = 0; is_active_ = false; return EventHandlerResult::OK; } - is_pressed_ = true; + // If we're not at the end of the sequence, send the first keypress event, + // and start the timer. + Runtime.handleKeyEvent(KeyEvent(ghost_key.addr, IS_PRESSED)); start_time_ = Runtime.millisAtCycleStart(); - } else { - if (is_pressed_ && Runtime.hasTimeExpired(start_time_, press_timeout_)) { - is_pressed_ = false; - start_time_ = Runtime.millisAtCycleStart(); - - byte row = pgm_read_byte(&(ghost_keys[current_pos_].row)); - byte col = pgm_read_byte(&(ghost_keys[current_pos_].col)); - - handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), WAS_PRESSED); - } else if (is_pressed_) { - byte row = pgm_read_byte(&(ghost_keys[current_pos_].row)); - byte col = pgm_read_byte(&(ghost_keys[current_pos_].col)); - handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), IS_PRESSED); - } else if (Runtime.hasTimeExpired(start_time_, delay_timeout_)) { - current_pos_++; - press_timeout_ = 0; + } else if (ghost_key.addr.isValid()) { + // If the ghost key's address is still valid, that means that the virtual + // key is still being held. + if (Runtime.hasTimeExpired(start_time_, ghost_key.press_time)) { + // The key press has timed out, so we send the release event. + Runtime.handleKeyEvent(KeyEvent(ghost_key.addr, WAS_PRESSED)); + // Next, we invalidate the ghost key's address to prevent checking the + // hold timeout again, then restart the timer for checking the delay. + ghost_key.addr.clear(); + start_time_ = Runtime.millisAtCycleStart(); } + + } else if (Runtime.hasTimeExpired(start_time_, ghost_key.delay)) { + // The ghost key has been (virtually) pressed and released, and its delay + // has now elapsed, so we set the delay to zero and increment the index + // value to indicate that the next key should be loaded in the next cycle. + ghost_key.delay = 0; + ++current_pos_; } return EventHandlerResult::OK; diff --git a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h index 38dd5598..76dab510 100644 --- a/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h +++ b/plugins/Kaleidoscope-GhostInTheFirmware/src/kaleidoscope/plugin/GhostInTheFirmware.h @@ -23,29 +23,23 @@ namespace kaleidoscope { namespace plugin { class GhostInTheFirmware : public kaleidoscope::Plugin { public: - typedef struct { - byte row; - byte col; - uint16_t pressTime; + struct GhostKey { + KeyAddr addr; + uint16_t press_time; uint16_t delay; - } GhostKey; + }; static const GhostKey *ghost_keys; GhostInTheFirmware(void) {} static void activate(void); - EventHandlerResult beforeReportingState(); + EventHandlerResult afterEachCycle(); private: static bool is_active_; - static bool is_pressed_; static uint16_t current_pos_; - static uint32_t start_time_; - static uint16_t press_timeout_; - static uint16_t delay_timeout_; - - static void loopHook(bool is_post_clear); + static uint16_t start_time_; }; } } From ba383a18ade15a2ce6c7eff9c87fc898ab5d52cb Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:53:10 -0500 Subject: [PATCH 070/108] Adapt DynamicTapDance plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/DynamicTapDance.cpp | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/plugins/Kaleidoscope-DynamicTapDance/src/kaleidoscope/plugin/DynamicTapDance.cpp b/plugins/Kaleidoscope-DynamicTapDance/src/kaleidoscope/plugin/DynamicTapDance.cpp index ef2702e1..27ecd3c9 100644 --- a/plugins/Kaleidoscope-DynamicTapDance/src/kaleidoscope/plugin/DynamicTapDance.cpp +++ b/plugins/Kaleidoscope-DynamicTapDance/src/kaleidoscope/plugin/DynamicTapDance.cpp @@ -19,6 +19,8 @@ #include #include "Kaleidoscope-FocusSerial.h" +#include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyEvent.h" namespace kaleidoscope { namespace plugin { @@ -72,14 +74,14 @@ bool DynamicTapDance::dance(uint8_t tap_dance_index, KeyAddr key_addr, break; case TapDance::Interrupt: case TapDance::Timeout: - handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED); + Runtime.handleKeyEvent(KeyEvent(key_addr, IS_PRESSED, key)); break; case TapDance::Hold: - handleKeyswitchEvent(key, key_addr, IS_PRESSED | WAS_PRESSED | INJECTED); + Runtime.handleKeyEvent(KeyEvent(key_addr, IS_PRESSED | WAS_PRESSED, key)); break; case TapDance::Release: - kaleidoscope::Runtime.hid().keyboard().sendReport(); - handleKeyswitchEvent(key, key_addr, WAS_PRESSED | INJECTED); + //kaleidoscope::Runtime.hid().keyboard().sendReport(); + Runtime.handleKeyEvent(KeyEvent(key_addr, WAS_PRESSED, key)); break; } From 619edaa299448bd5f8c534f62569ac8b723b68fb Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:36:27 -0500 Subject: [PATCH 071/108] Adapt Macros plugin to KeyEvent handlers Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Macros/README.md | 62 ++-- plugins/Kaleidoscope-Macros/UPGRADING.md | 215 ++++++++++++ .../src/kaleidoscope/plugin/Macros.cpp | 305 ++++++++++++------ .../src/kaleidoscope/plugin/Macros.h | 161 ++++++--- .../kaleidoscope/plugin/Macros/MacroKeyDefs.h | 1 + .../kaleidoscope/plugin/Macros/MacroSteps.h | 12 +- 6 files changed, 580 insertions(+), 176 deletions(-) create mode 100644 plugins/Kaleidoscope-Macros/UPGRADING.md diff --git a/plugins/Kaleidoscope-Macros/README.md b/plugins/Kaleidoscope-Macros/README.md index 780e39e2..c5391f18 100644 --- a/plugins/Kaleidoscope-Macros/README.md +++ b/plugins/Kaleidoscope-Macros/README.md @@ -34,8 +34,8 @@ enum { M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) // later in the Sketch: -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case MACRO_MODEL01: return MACRODOWN(I(25), D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), @@ -43,12 +43,12 @@ const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { W(100), T(0), T(1) ); case MACRO_HELLO: - if (keyToggledOn(keyState)) { + if (keyToggledOn(event.state)) { return Macros.type(PSTR("Hello "), PSTR("world!")); } break; case MACRO_SPECIAL: - if (keyToggledOn(keyState)) { + if (keyToggledOn(event.state)) { // Do something special } break; @@ -101,15 +101,28 @@ The plugin provides a `Macros` object, with the following methods and properties > easiest way to do that is to wrap the string in a `PSTR()` helper. See the > program code at the beginning of this documentation for an example! -### `.row`, `.col` +### `.press(key)`/`.release(key)` -> The `row` and `col` properties describe the physical position a macro was -> triggered from if it was triggered by a key. The playback functions -> do not use these properties, but they are available, would one want to create -> a macro that needs to know which key triggered it. +> Used in `Macros.play()`, these methods press virtual keys in a small +> supplemental `Key` array for the purpose of keeping keys active for complex +> macro sequences where it's important to have overlapping key presses. > -> When the macro was not triggered by a key the value of these properties are -> unspecified. +> `Macros.press(key)` sends a key press event, and will keep that virtual key +> active until either `Macros.release(key)` is called, or a Macros key is +> released. If you use `Macros.press(key)` in a macro, but also change the value +> of `event.key`, you will need to make sure to also call `Macros.release(key)` +> at some point to prevent that key from getting "stuck" on. + +### `.clear()` + +> Releases all virtual keys held by macros. This both empties the supplemental +> `Key` array (see above) and sends a release event for each key stored there. + +### `.tap(key)` + +> Sends an immediate press and release event for `key` with no delay, using an +> invalid key address. + ## Macro helpers @@ -164,7 +177,11 @@ In most cases, one is likely use normal keys for the steps, so the `D`, `U`, and `T` steps apply the `Key_` prefix. This allows us to write `MACRO(T(X))` instead of `MACRO(Tr(Key_X))` - making the macro definition shorter, and more readable. -The compact variant (`Dc`, `Uc`, and `Tc`) prefix the argument with `Key_` too, +The "raw" variants (`Dr`/`Ur`/`Tr`) use the full name of the `Key` object, +without adding the `Key_` prefix to the argument given. `Tr(Key_X)` is the same +as `T(X)`. + +The "compact" variants (`Dc`/`Uc`/`Tc`) prefix the argument with `Key_` too, but unlike `D`, `U`, and `T`, they ignore the `flags` component of the key, and as such, are limited to ordinary keys. Mouse keys, consumer- or system keys are not supported by this compact representation. @@ -188,26 +205,9 @@ them in order. with `Key_`, and they ignore the `flags` component of a key, and as such, are limited to ordinary keys. -### Controlling when to send reports - -While the plugin will - by default - send a report after every step, that is not -always desirable. For this reason, we allow turning this implicit reporting off, -and switching to explicit reporting instead. Note that the tap steps (`T()`, -`Tr()`, and `Tc()`) will always send an implicit report, and so will -`Macros.type()`. - -To control when to send reports, the following steps can be used: - -* `WITH_EXPLICIT_REPORT`: Prevents the plugin from sending an implicit report - after every step. To send a report, one needs to have a `SEND_REPORT` step - too. -* `WITH_IMPLICIT_REPORT`: Enables sending an implicit report after every step - (the default). -* `SEND_REPORT`: Send a report. - -## Overrideable methods +## Overrideable functions -### `macroAction(macroIndex, keyState)` +### `macroAction(uint8_t macro_id, KeyEvent &event)` > The `macroAction` method is the brain of the macro support in Kaleidoscope: > this function tells the plugin what sequence to play when given a macro index diff --git a/plugins/Kaleidoscope-Macros/UPGRADING.md b/plugins/Kaleidoscope-Macros/UPGRADING.md new file mode 100644 index 00000000..9fa3f6c6 --- /dev/null +++ b/plugins/Kaleidoscope-Macros/UPGRADING.md @@ -0,0 +1,215 @@ +# Upgrading Macros code + +This is a guide to upgrading existing Macros code to use the new version of +Kaleidoscope and the Macros plugin. + +## New `macroAction()` function + +There is a new version of the `macroAction()` function, which is the entry point +for user-defined Macros code. The old version takes two integer parameters, with +the following call signature: + +```c++ +const macro_t* macroAction(uint8_t macro_id, uint8_t key_state) +``` + +If your sketch has this function, with a `key_state` bitfield parameter, it +might still work as expected, but depending on the specifics of the code that +gets called from it, your macros might not work as expected. Either way, you +should update that function to the new version, which takes a `KeyEvent` +reference as its second parameter: + +```c++ +const macro_t* macroAction(uint8_t macro_id, KeyEvent &event) +``` + +For simple macros, it is a simple matter of replacing `key_state` in the body of +the `macroAction()` code with `event.state`. This covers most cases where all +that's done is a call to `Macros.type()`, or a `MACRO()` or `MACRODOWN()` +sequence is returned. + + +## Code that calls `handleKeyswitchEvent()` or `pressKey()` + +It is very likely that if you have custom code that calls +`handleKeyswitchEvent()` or `pressKey()` directly, it will no longer function +properly after upgrading. To adapt this code to the new `KeyEvent` system +requires a deeper understanding of the changes to Kaleidoscope, but likely +results in much simpler Macros code. + +The first thing that is important to understand is that the `macroAction()` +function will now only be called when a Macros `Key` toggles on or off, not once +per cycle while the key is held. This is because the new event handling code in +Kaleidoscope only calls plugin handlers in those cases, dealing with one event +at a time, in a single pass through the plugin event handlers (rather than one +pass per active key)--and only sends a keyboard HID report in response to those +events, not once per scan cycle. + +This means that any Macros code that is meant to keep keycodes in the keyboard +HID report while the Macros key is held needs to be changed. For example, if a +macro contained the following code: + +```c++ +if (keyIsPressed(key_state)) { + Runtime.hid().keyboard().pressKey(Key_LeftShift); +} +``` + +...that wouldn't work quite as expected, because as soon as the next key is +pressed, a new report would be generated without ever calling `macroAction()`, +and therefore that change to the HID report would not take place, effectively +turning off the `shift` modifier immediately before sending the report with the +keycode that it was intended to modify. + +Furthermore, that `shift` modifier would never even get sent in the first place, +because the HID report no longer gets cleared at the beginning of every +cycle. Now it doesn't get cleared until _after_ the plugin event handlers get +called (in the case of Macros, that's `onKeyEvent()`, which calls the +user-defined `macroAction()` function), so any changes made to the HID report +from that function will be discarded before it's sent. + +Instead of the above, there are two new mechanisms for keeping keys active while +a Macros key is pressed: + +### Alter the `event.key` value + +If your macro only needs to keep a single `Key` value active after running some +code, and doesn't need to run any custom code when the key is released, the +simplest thing to do is to override the event's `Key` value: + +```c++ +if (keyToggledOn(event.state)) { + // do some macro action(s) + event.key = Key_LeftShift; +} +``` + +This will (temporarily) replace the Macros key with the value assigned (in this +case, `Key_LeftShift`), starting immediately after the `macroAction()` function +returns, and lasting until the key is released. This key value can include +modifier flags, or it can be a layer-shift, or any other valid `Key` value +(though it won't get processed by plugins that are initialized before Macros in +`KALEIDOSCOPE_INIT_PLUGINS()`, and Macros itself won't act on the value, if it +gets replaced by a different Macros key). + +### Use the supplemental Macros `Key` array + +The Macros plugin now contains a small array of `Key` values that will be +included when building HID reports triggered by subsequent, non-Macros +events. To use it, just call one (or more) of the following methods: + +```c++ +Macros.press(key); +Macros.release(key); +Macros.tap(key) +``` + +Each one of these functions generates a new artificial key event, and processes +it (including sending a HID report, if necessary). For `press()` and +`release()`, it also stores the specified key's value in the Macros supplemental +`Key` array. In the case of the `tap()` function, it generates matching press +and release events, but skips storing them, assuming that no plugin will +generate an intervening event. All of the events generated by these functions +will be marked `INJECTED`, which will cause Macros itself (and many other +plugins) to ignore them. + +This will allow you to keep multiple `Key` values active while a Macros key is +held, while leaving the Macros key itself active, enabling more custom code to +be called on its release. Note that whenever a Macros key is released, the +supplemental key array is cleared to minimize the chances of keycodes getting +"stuck". It is still possible to write a macro that will cause values to persist +in this array, however, by combining both a sequence that uses key presses +without matched releases _and_ replacing `event.key` (see above) in the same +macro. + +### Borrow an idle key (not recommended) + +It's also possible to "borrow" one (or more) idle keys on the keyboard by +searching the `live_keys[]` array for an empty entry, and generating a new event +with the address of that key. This is not recommended because surprising things +can happen if that key is then pressed and released, but it's still an option +for people who like to live dangerously. + + +## Code that calls `sendReport()` + +Calling `sendReport()` directly from a macro is now almost always unnecessary. +Instead, a call to `Runtime.handleKeyEvent()` will result in a keyboard HID +report being sent in response to the generated event without needing to make it +explicit. + + +## Code that uses `Macros.key_addr` + +This variable is deprecated. Instead, using the new `macroAction(id, event)` +function, the address of the Macros key is available via the `event.addr` +variable. + + +## Working with other plugins + +### Plugin-specific `Key` values + +When the the Macros plugin generates events, it marks the event state as +`INJECTED` in order to prevent unbounded recursion (Macros ignores injected +events). This causes most other plugins to ignore the event, as well. +Therefore, including a plugin-specific key (e.g. a OneShot modifier such as +`OSM(LeftAlt)`) will most likely be ignored by the target plugin, and will +therefore not have the desired effect. This applies to any calls to +`Macros.play()` (including returning `MACRO()` from `macroAction()`), +`Macros.tap()`, `Macros.press()`, and `Macros.release()`. + +### Physical event plugins + +Macros cannot usefully produce events handled by plugins that implement the +`onKeyswitchEvent()` handler, such as Qukeys, TapDance, and Leader. To make +those plugins work with Macros, it's necessary to have the other plugin produce +a Macros key, not the other way around. A `macroAction()` function must not call +`Runtime.handleKeyswitchEvent()`. + +### OneShot + +This is one plugin that you might specifically want to use with a macro, +generally at the end of a sequence. For example, a macro for ending one +sentence and beginning the next one might print a period followed by a space +(`. `), then a OneShot shift key tap, so that the next character will be +automatically capitalized. The problem, as mentioned before is that the +following won't work: + +```c++ +MACRO(Tc(Period), Tc(Spacebar), Tr(OSM(LeftShift))) +``` + +...because OneShot will ignore the `INJECTED` event. One solution is to change +the value of `event.key`, turning the pressed Macros key into a OneShot +modifier. This will only work if Macros is registered before OneShot in +`KALEIDOSCOPE_INIT_PLUGINS()`: + +```c++ +const macro_t* macroNewSentence(KeyEvent &event) { + if (keyToggledOn(event.state)) { + event.key = OSM(LeftShift); + return MACRO(Tc(Period), Tc(Spacebar)); + } + return MACRO_NONE; +} +``` + +A more robust solution is to explicitly call `Runtime.handleKeyEvent()`, but +this is more complex, because you'll need to prevent the Macros key from +clobbering the OneShot key in the `live_keys[]` array: + +```c++ +void macroNewSentence(KeyEvent &event) { + if (keyToggledOn(event.state)) { + Macros.tap(Key_Period); + Macros.tap(Key_Spacebar); + event.key = OSM(LeftShift); + kaleidoscope::Runtime.handleKeyEvent(event); + // Last, we invalidate the current event's key address to prevent the Macros + // key value from clobbering the OneShot shift. + event.key = Key_NoKey; + event.addr.clear(); + } +} +``` diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp index 55bf73d7..83532917 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp @@ -19,65 +19,106 @@ #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" +// ============================================================================= +// Default `macroAction()` function definitions __attribute__((weak)) -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { return MACRO_NONE; } +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +__attribute__((weak)) +const macro_t *macroAction(uint8_t macro_id, uint8_t key_state) { + return MACRO_NONE; +} +#pragma GCC diagnostic pop +#endif + +// ============================================================================= +// `Macros` plugin code namespace kaleidoscope { namespace plugin { -MacroKeyEvent Macros_::active_macros[]; -byte Macros_::active_macro_count; -KeyAddr Macros_::key_addr; - -void playMacroKeyswitchEvent(Key key, uint8_t keyswitch_state, bool explicit_report) { - handleKeyswitchEvent(key, UnknownKeyswitchLocation, keyswitch_state | INJECTED); - - if (explicit_report) - return; - - kaleidoscope::Runtime.hid().keyboard().sendReport(); - kaleidoscope::Runtime.hid().mouse().sendReport(); +constexpr uint8_t press_state = IS_PRESSED | INJECTED; +constexpr uint8_t release_state = WAS_PRESSED | INJECTED; + +// Initialized to zeroes (i.e. `Key_NoKey`) +Key Macros::active_macro_keys_[]; + +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +MacroKeyEvent Macros::active_macros[]; +byte Macros::active_macro_count; +KeyAddr Macros::key_addr = KeyAddr::none(); +#pragma GCC diagnostic pop +#endif + +// ----------------------------------------------------------------------------- +// Public helper functions + +void Macros::press(Key key) { + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), press_state, key}); + // This key may remain active for several subsequent events, so we need to + // store it in the active macro keys array. + for (Key ¯o_key : active_macro_keys_) { + if (macro_key == Key_NoKey) { + macro_key = key; + break; + } + } } -static void playKeyCode(Key key, uint8_t keyStates, bool explicit_report) { - if (keyIsPressed(keyStates)) { - playMacroKeyswitchEvent(key, IS_PRESSED, explicit_report); - } - if (keyWasPressed(keyStates)) { - playMacroKeyswitchEvent(key, WAS_PRESSED, explicit_report); +void Macros::release(Key key) { + // Before sending the release event, we need to remove the key from the active + // macro keys array, or it will get inserted into the report anyway. + for (Key ¯o_key : active_macro_keys_) { + if (macro_key == key) { + macro_key = Key_NoKey; + } } + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, key}); } -static void readKeyCodeAndPlay(const macro_t *macro_p, uint8_t flags, uint8_t keyStates, bool explicit_report) { - Key key(pgm_read_byte(macro_p++), // key_code - flags); +void Macros::clear() { + // Clear the active macro keys array. + for (Key ¯o_key : active_macro_keys_) { + if (macro_key == Key_NoKey) + continue; + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, macro_key}); + macro_key = Key_NoKey; + } +} - playKeyCode(key, keyStates, explicit_report); +void Macros::tap(Key key) const { + // No need to call `press()` & `release()`, because we're immediately + // releasing the key after pressing it. It is possible for some other plugin + // to insert an event in between, but very unlikely. + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), press_state, key}); + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), release_state, key}); } -void Macros_::play(const macro_t *macro_p) { +void Macros::play(const macro_t *macro_p) { macro_t macro = MACRO_ACTION_END; uint8_t interval = 0; - uint8_t flags; - bool explicit_report = false; + Key key; - if (!macro_p) + if (macro_p == MACRO_NONE) return; while (true) { switch (macro = pgm_read_byte(macro_p++)) { + // These are unlikely to be useful now that we have KeyEvent. I think the + // whole `explicit_report` came about as a result of scan-order bugs. case MACRO_ACTION_STEP_EXPLICIT_REPORT: - explicit_report = true; - break; case MACRO_ACTION_STEP_IMPLICIT_REPORT: - explicit_report = false; - break; case MACRO_ACTION_STEP_SEND_REPORT: - kaleidoscope::Runtime.hid().keyboard().sendReport(); - kaleidoscope::Runtime.hid().mouse().sendReport(); break; + // End legacy macro step commands + + // Timing case MACRO_ACTION_STEP_INTERVAL: interval = pgm_read_byte(macro_p++); break; @@ -86,46 +127,59 @@ void Macros_::play(const macro_t *macro_p) { delay(wait); break; } + case MACRO_ACTION_STEP_KEYDOWN: - flags = pgm_read_byte(macro_p++); - readKeyCodeAndPlay(macro_p++, flags, IS_PRESSED, explicit_report); + key.setFlags(pgm_read_byte(macro_p++)); + key.setKeyCode(pgm_read_byte(macro_p++)); + press(key); break; case MACRO_ACTION_STEP_KEYUP: - flags = pgm_read_byte(macro_p++); - readKeyCodeAndPlay(macro_p++, flags, WAS_PRESSED, explicit_report); + key.setFlags(pgm_read_byte(macro_p++)); + key.setKeyCode(pgm_read_byte(macro_p++)); + release(key); break; case MACRO_ACTION_STEP_TAP: - flags = pgm_read_byte(macro_p++); - readKeyCodeAndPlay(macro_p++, flags, IS_PRESSED | WAS_PRESSED, false); + key.setFlags(pgm_read_byte(macro_p++)); + key.setKeyCode(pgm_read_byte(macro_p++)); + tap(key); break; case MACRO_ACTION_STEP_KEYCODEDOWN: - readKeyCodeAndPlay(macro_p++, 0, IS_PRESSED, explicit_report); + key.setFlags(0); + key.setKeyCode(pgm_read_byte(macro_p++)); + press(key); break; case MACRO_ACTION_STEP_KEYCODEUP: - readKeyCodeAndPlay(macro_p++, 0, WAS_PRESSED, explicit_report); + key.setFlags(0); + key.setKeyCode(pgm_read_byte(macro_p++)); + release(key); break; case MACRO_ACTION_STEP_TAPCODE: - readKeyCodeAndPlay(macro_p++, 0, IS_PRESSED | WAS_PRESSED, false); + key.setFlags(0); + key.setKeyCode(pgm_read_byte(macro_p++)); + tap(key); break; case MACRO_ACTION_STEP_TAP_SEQUENCE: { - uint8_t keyCode; - do { - flags = pgm_read_byte(macro_p++); - keyCode = pgm_read_byte(macro_p++); - playKeyCode(Key(keyCode, flags), IS_PRESSED | WAS_PRESSED, false); + while (true) { + key.setFlags(0); + key.setKeyCode(pgm_read_byte(macro_p++)); + if (key == Key_NoKey) + break; + tap(key); delay(interval); - } while (!(flags == 0 && keyCode == 0)); + } break; } case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: { - uint8_t keyCode; - do { - keyCode = pgm_read_byte(macro_p++); - playKeyCode(Key(keyCode, 0), IS_PRESSED | WAS_PRESSED, false); + while (true) { + key.setFlags(0); + key.setKeyCode(pgm_read_byte(macro_p++)); + if (key.getKeyCode() == 0) + break; + tap(key); delay(interval); - } while (keyCode != 0); + } break; } @@ -138,6 +192,26 @@ void Macros_::play(const macro_t *macro_p) { } } +const macro_t *Macros::type(const char *string) const { + while (true) { + uint8_t ascii_code = pgm_read_byte(string++); + if (ascii_code == 0) + break; + + Key key = lookupAsciiCode(ascii_code); + + if (key == Key_NoKey) + continue; + + tap(key); + } + + return MACRO_NONE; +} + +// ----------------------------------------------------------------------------- +// Translation from ASCII to keycodes + static const Key ascii_to_key_map[] PROGMEM = { // 0x21 - 0x30 LSHIFT(Key_1), @@ -181,8 +255,7 @@ static const Key ascii_to_key_map[] PROGMEM = { LSHIFT(Key_Backtick), }; - -Key Macros_::lookupAsciiCode(uint8_t ascii_code) { +Key Macros::lookupAsciiCode(uint8_t ascii_code) const { Key key = Key_NoKey; switch (ascii_code) { @@ -224,67 +297,89 @@ Key Macros_::lookupAsciiCode(uint8_t ascii_code) { return key; } -const macro_t *Macros_::type(const char *string) { - while (true) { - uint8_t ascii_code = pgm_read_byte(string++); - if (!ascii_code) - break; - - Key key = lookupAsciiCode(ascii_code); - - - if (key == Key_NoKey) - continue; +// ----------------------------------------------------------------------------- +// Event handlers - playMacroKeyswitchEvent(key, IS_PRESSED, false); - playMacroKeyswitchEvent(key, WAS_PRESSED, false); +EventHandlerResult Macros::onKeyEvent(KeyEvent &event) { + // Ignore everything except Macros keys + if (!isMacrosKey(event.key)) + return EventHandlerResult::OK; +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // Set `Macros.key_addr` so that code in the `macroAction()` function can have + // access to it. This is not such a good solution, but it's done this way for + // backwards compatability. At some point, we should introduce a new + // `macroAction(KeyEvent)` function. + if (event.addr.isValid()) { + key_addr = event.addr; + } else { + key_addr = KeyAddr::none(); } - - return MACRO_NONE; -} - -bool Macros_::isMacroKey(Key key) { - if (key >= ranges::MACRO_FIRST && key <= ranges::MACRO_LAST) - return true; - return false; -} - -EventHandlerResult Macros_::onNameQuery() { - return ::Focus.sendName(F("Macros")); -} - -EventHandlerResult Macros_::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { - if (! isMacroKey(mappedKey)) +#pragma GCC diagnostic pop +#endif + + // Decode the macro ID from the Macros `Key` value. + uint8_t macro_id = event.key.getRaw() - ranges::MACRO_FIRST; + + // Call the new `macroAction(event)` function. + const macro_t* macro_ptr = macroAction(macro_id, event); + +#ifndef NDEPRECATED +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + // If the new `macroAction()` returned nothing, try the legacy version. + if (macro_ptr == MACRO_NONE) + macro_ptr = macroAction(macro_id, event.state); +#pragma GCC diagnostic pop +#endif + + // Play back the macro pointed to by `macroAction()` + play(macro_ptr); + + if (keyToggledOff(event.state) || !isMacrosKey(event.key)) { + // If we toggled off or if the value of `event.key` has changed, send + // release events for any active macro keys. Simply clearing + // `active_macro_keys_` might be sufficient, but it's probably better to + // send the toggle off events so that other plugins get a chance to act on + // them. + clear(); + + // Return `OK` to let Kaleidoscope finish processing this event as + // normal. This is so that, if the user-defined `macroAction(id, &event)` + // function changes the value of `event.key`, it will take effect properly. return EventHandlerResult::OK; + } - uint8_t macro_index = mappedKey.getRaw() - ranges::MACRO_FIRST; - addActiveMacroKey(macro_index, key_addr.toInt(), keyState); - - return EventHandlerResult::EVENT_CONSUMED; -} - -EventHandlerResult Macros_::afterEachCycle() { - active_macro_count = 0; - + // No other plugin should be handling Macros keys, and there's nothing more + // for Kaleidoscope to do with a key press of a Macros key, so we return + // `EVENT_CONSUMED`, causing Kaleidoscope to update `live_keys[]` correctly, + // ensuring that the above block will clear `active_macro_keys_` on release, + // but not allowing any other plugins to change the `event.key` value, which + // would interfere. + //return EventHandlerResult::EVENT_CONSUMED; return EventHandlerResult::OK; } -EventHandlerResult Macros_::beforeReportingState() { - for (byte i = 0; i < active_macro_count; ++i) { - if (active_macros[i].key_id == 0xFF) { - key_addr = UnknownKeyswitchLocation; - } else { - key_addr = KeyAddr(active_macros[i].key_id); - } - const macro_t *m = macroAction(active_macros[i].key_code, - active_macros[i].key_state); - Macros.play(m); +EventHandlerResult Macros::beforeReportingState(const KeyEvent &event) { + // Do this in beforeReportingState(), instead of `onAddToReport()` because + // `live_keys` won't get updated until after the macro sequence is played from + // the keypress. This could be changed by either updating `live_keys` manually + // ahead of time, or by executing the macro sequence on key release instead of + // key press. This is probably the simplest solution. + for (Key key : active_macro_keys_) { + if (key != Key_NoKey) + Runtime.addToReport(key); } return EventHandlerResult::OK; } +EventHandlerResult Macros::onNameQuery() { + return ::Focus.sendName(F("Macros")); } -} -kaleidoscope::plugin::Macros_ Macros; +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::Macros Macros; diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h index f857c4ee..d90463a2 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h @@ -23,68 +23,151 @@ #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState); +// ============================================================================= +// Deprecated Macros code +#ifndef NDEPRECATED + +#define _DEPRECATED_MESSAGE_MACROS_ACTIVE_MACRO_COUNT __NL__ \ + "The `Macros.active_macro_count` variable is deprecated. It no longer has\n" __NL__ \ + "any functional purpose, and can be safely removed from your code." + +#define _DEPRECATED_MESSAGE_MACROS_ACTIVE_MACROS __NL__ \ + "The `Macros.active_macros` array is deprecated. It no longer serves any\n" __NL__ \ + "functional purpose, and can be safely removed from your code." + +#define _DEPRECATED_MESSAGE_MACROS_ADD_ACTIVE_MACRO_KEY __NL__ \ + "The `Macros.addActiveMacroKey()` function is deprecated. It no longer\n" __NL__ \ + "has any functional purpose, and can be safely removed from your code." + +#define _DEPRECATED_MESSAGE_MACRO_ACTION_FUNCTION_V1 __NL__ \ + "The old `macroAction(macro_id, key_state)` is deprecated.\n" __NL__ \ + "Please define the new `macroAction()` function instead:\n" __NL__ \ + "\n" __NL__ \ + "const macro_t* macroAction(uint8_t macro_id, KeyEvent &event);\n" __NL__ \ + "\n" __NL__ \ + "In the body of the new function, replace the `key_state` value with\n" __NL__ \ + "`event.state`. Also, note that the new function gives you access to the\n" __NL__ \ + "`KeyAddr` of the Macros key event (`event.addr`), and the `Key` value\n" __NL__ \ + "(`event.key`). Because the event is passed by reference, it is now\n" __NL__ \ + "possible to assign to `event.key` on a toggled-on event, causing that\n" __NL__ \ + "`Key` value to persist after the macro finishes playing, leaving that\n" __NL__ \ + "value active until the key is released." + +#define _DEPRECATED_MESSAGE_MACROS_KEY_ADDR __NL__ \ + "The `Macros.key_addr` public variable is deprecated.\n" __NL__ \ + "Instead of using this to get the `KeyAddr` of the current macro from\n" __NL__ \ + "`macroAction()`, please use the new version of `macroAction()`, which\n" __NL__ \ + "uses a `KeyEvent` as its second parameter, giving access to the address\n" __NL__ \ + "of the event in the `event.addr` member variable." -#if !defined(MAX_CONCURRENT_MACROS) -#define MAX_CONCURRENT_MACROS 8 #endif +// ============================================================================= +// Define this function in a Kaleidoscope sketch in order to trigger Macros. +const macro_t* macroAction(uint8_t macro_id, KeyEvent &event); + +#ifndef NDEPRECATED +DEPRECATED(MACRO_ACTION_FUNCTION_V1) +const macro_t* macroAction(uint8_t macro_id, uint8_t key_state); + struct MacroKeyEvent { byte key_code; byte key_id; byte key_state; }; +#endif + +// The number of simultaneously-active `Key` values that a macro can have +// running during a call to `Macros.play()`. I don't know if it's actually +// possible to override this by defining it in a sketch before including +// "Kaleidoscope-Macros.h", but probably not. +#if !defined(MAX_CONCURRENT_MACRO_KEYS) +#define MAX_CONCURRENT_MACRO_KEYS 8 +#endif namespace kaleidoscope { namespace plugin { -class Macros_ : public kaleidoscope::Plugin { +class Macros : public kaleidoscope::Plugin { public: - Macros_(void) {} - - static MacroKeyEvent active_macros[MAX_CONCURRENT_MACROS]; - static byte active_macro_count; - static void addActiveMacroKey(byte key_code, byte key_id, byte key_state) { - // If we've got too many active macros, give up: - if (active_macro_count >= MAX_CONCURRENT_MACROS) { - return; - } - active_macros[active_macro_count].key_code = key_code; - active_macros[active_macro_count].key_id = key_id; - active_macros[active_macro_count].key_state = key_state; - ++active_macro_count; - } - EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState); - EventHandlerResult beforeReportingState(); - EventHandlerResult afterEachCycle(); - - void play(const macro_t *macro_p); - - /* What follows below, is a bit of template magic that allows us to use - Macros.type() with any number of arguments, without having to use a - sentinel. See the comments on Runtime.use() for more details - this is - the same trick. - */ - inline const macro_t *type() { + /// Send a key press event from a Macro + /// + /// Generates a new `KeyEvent` and calls `Runtime.handleKeyEvent()` with the + /// specified `key`, then stores that `key` in an array of active macro key + /// values. This allows the macro to press one key and keep it active when a + /// subsequent key event is sent as part of the same macro sequence. + void press(Key key); + + /// Send a key release event from a Macro + /// + /// Generates a new `KeyEvent` and calls `Runtime.handleKeyEvent()` with the + /// specified `key`, then removes that key from the array of active macro + /// keys (see `Macros.press()`). + void release(Key key); + + /// Clear all virtual keys held by Macros + /// + /// This function clears the active macro keys array, sending a release event + /// for each key stored there. + void clear(); + + /// Send a key "tap event" from a Macro + /// + /// Generates two new `KeyEvent` objects, one each to press and release the + /// specified `key`, passing both in sequence to `Runtime.handleKeyEvent()`. + void tap(Key key) const; + + /// Play a macro sequence of key events + void play(const macro_t* macro_ptr); + + // Templates provide a `type()` function that takes a variable number of + // `char*` (string) arguments, in the form of a list of strings stored in + // PROGMEM, of the form `Macros.type(PSTR("Hello "), PSTR("world!"))`. + inline const macro_t* type() const { return MACRO_NONE; } - const macro_t *type(const char *string); + const macro_t* type(const char* string) const; template - const macro_t *type(const char *first, Strings&&... strings) { + const macro_t* type(const char* first, Strings&&... strings) const { type(first); return type(strings...); } - static KeyAddr key_addr; + // --------------------------------------------------------------------------- + // Event handlers + EventHandlerResult onNameQuery(); + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult beforeReportingState(const KeyEvent &event); private: - Key lookupAsciiCode(uint8_t ascii_code); - bool isMacroKey(Key key); + // An array of key values that are active while a macro sequence is playing + static Key active_macro_keys_[MAX_CONCURRENT_MACRO_KEYS]; + + // Translate and ASCII character value to a corresponding `Key` + Key lookupAsciiCode(uint8_t ascii_code) const; + + // Test for a key that encodes a macro ID + bool isMacrosKey(Key key) const { + if (key >= ranges::MACRO_FIRST && key <= ranges::MACRO_LAST) + return true; + return false; + } + +#ifndef NDEPRECATED + public: + DEPRECATED(MACROS_ACTIVE_MACROS) + static MacroKeyEvent active_macros[0]; + DEPRECATED(MACROS_ACTIVE_MACRO_COUNT) + static uint8_t active_macro_count; + DEPRECATED(MACROS_ADD_ACTIVE_MACRO_KEY) + static void addActiveMacroKey(uint8_t macro_id, KeyAddr key_addr, uint8_t key_state) {} + DEPRECATED(MACROS_KEY_ADDR) + static KeyAddr key_addr; +#endif }; -} -} +} // namespace plugin +} // namespace kaleidosocpe -extern kaleidoscope::plugin::Macros_ Macros; +extern kaleidoscope::plugin::Macros Macros; diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroKeyDefs.h b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroKeyDefs.h index ede4ce7a..22ec5003 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroKeyDefs.h +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroKeyDefs.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- /* Kaleidoscope-Macros - Macro keys for Kaleidoscope. * Copyright (C) 2017-2018 Keyboard.io, Inc. * diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h index 68b74720..46c1d280 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- /* Kaleidoscope-Macros - Macro keys for Kaleidoscope. * Copyright (C) 2017-2019 Keyboard.io, Inc. * @@ -41,7 +42,16 @@ typedef enum { typedef uint8_t macro_t; #define MACRO_NONE 0 -#define MACRO(...) ({static const macro_t __m[] PROGMEM = { __VA_ARGS__, MACRO_ACTION_END }; &__m[0]; }) + +#define MACRO(...) ( \ + { \ + static const macro_t __m[] PROGMEM = { \ + __VA_ARGS__, \ + MACRO_ACTION_END \ + }; \ + &__m[0]; \ + }) + #define MACRODOWN(...) (keyToggledOn(keyState) ? MACRO(__VA_ARGS__) : MACRO_NONE) #define I(n) MACRO_ACTION_STEP_INTERVAL, n From f619f1bc7831c60c551e55b6536f4b7f7626e0cc Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Wed, 28 Apr 2021 21:33:34 -0500 Subject: [PATCH 072/108] Deprecate MACRODOWN preprocessor macro Its utility is very limited now that `macroAction()` only gets called when a Macros key toggles on or off, and it uses a symbol that breaks an abstraction barrier (a local variable of the `macroAction()` function). Signed-off-by: Michael Richters --- plugins/Kaleidoscope-Macros/README.md | 30 ++++++------------- .../src/kaleidoscope/plugin/Macros.cpp | 6 ++++ .../src/kaleidoscope/plugin/Macros.h | 7 +++++ .../kaleidoscope/plugin/Macros/MacroSteps.h | 5 +++- 4 files changed, 26 insertions(+), 22 deletions(-) diff --git a/plugins/Kaleidoscope-Macros/README.md b/plugins/Kaleidoscope-Macros/README.md index c5391f18..39c2062e 100644 --- a/plugins/Kaleidoscope-Macros/README.md +++ b/plugins/Kaleidoscope-Macros/README.md @@ -37,11 +37,14 @@ M(MACRO_MODEL01), M(MACRO_HELLO), M(MACRO_SPECIAL) const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { switch (macro_id) { case MACRO_MODEL01: - return MACRODOWN(I(25), - D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), - T(Spacebar), - W(100), - T(0), T(1) ); + if (keyToggledOn(event.state)) { + return MACRO(I(25), + D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), + T(Spacebar), + W(100), + T(0), T(1) ); + } + break; case MACRO_HELLO: if (keyToggledOn(event.state)) { return Macros.type(PSTR("Hello "), PSTR("world!")); @@ -128,8 +131,7 @@ The plugin provides a `Macros` object, with the following methods and properties Macros need to be able to simulate key down and key up events for any key - even keys that may not be on the keymap otherwise. For this reason and others, we -need to define them in a special way, using the `MACRO` helper (or its -`MACRODOWN()` variant, see below): +need to define them in a special way, using the `MACRO` helper. ### `MACRO(steps...)` @@ -141,20 +143,6 @@ need to define them in a special way, using the `MACRO` helper (or its > that end with END will still work correctly, but new code should not use END; > usage of END is deprecated. -### `MACRODOWN(steps...)` - -> The same as the `MACRO()` helper above, but it will create a special sequence, -> where the steps are only played back when the triggering key was just pressed. -> That is, the macro will not be performed when the key is released, or held, or -> not pressed at all. -> -> Use this over `MACRO()` when you only want to perform an action when the key -> actuates, and no action should be taken when it is held, released, or when it -> is not pressed at all. For a lot of macros that emit a sequence without any -> other side effects, `MACRODOWN()` is usually the better choice. -> -> Can only be used from the `macroAction()` overrideable method. - ## `MACRO` steps Macro steps can be divided into the following groups: diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp index 83532917..c70e936f 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.cpp @@ -33,6 +33,12 @@ __attribute__((weak)) const macro_t *macroAction(uint8_t macro_id, uint8_t key_state) { return MACRO_NONE; } + +const macro_t* deprecatedMacroDown(uint8_t key_state, const macro_t* macro_p) { + if (keyToggledOn(key_state)) + return macro_p; + return MACRO_NONE; +} #pragma GCC diagnostic pop #endif diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h index d90463a2..3847ebef 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros.h @@ -60,6 +60,13 @@ "uses a `KeyEvent` as its second parameter, giving access to the address\n" __NL__ \ "of the event in the `event.addr` member variable." +#define _DEPRECATED_MESSAGE_MACROS_MACRODOWN __NL__ \ + "The `MACRODOWN()` preprocessor macro is deprecated. Please use `MACRO()`\n" __NL__ \ + "with a test for `keyToggledOn(event.state)` instead." + +DEPRECATED(MACROS_MACRODOWN) +const macro_t* deprecatedMacroDown(uint8_t key_state, const macro_t* macro_p); + #endif // ============================================================================= diff --git a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h index 46c1d280..549c4108 100644 --- a/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h +++ b/plugins/Kaleidoscope-Macros/src/kaleidoscope/plugin/Macros/MacroSteps.h @@ -52,7 +52,10 @@ typedef uint8_t macro_t; &__m[0]; \ }) -#define MACRODOWN(...) (keyToggledOn(keyState) ? MACRO(__VA_ARGS__) : MACRO_NONE) +#ifndef NDEPRECATED +#define MACRODOWN(...) \ + deprecatedMacroDown(event.state, MACRO(__VA_ARGS__)); +#endif #define I(n) MACRO_ACTION_STEP_INTERVAL, n #define W(n) MACRO_ACTION_STEP_WAIT, n From 5db82fd7c851ac5d66a45d951d8bb182c669191f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:37:12 -0500 Subject: [PATCH 073/108] Adapt DynamicMacros plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/DynamicMacros.cpp | 132 ++++++++++-------- .../src/kaleidoscope/plugin/DynamicMacros.h | 16 ++- 2 files changed, 86 insertions(+), 62 deletions(-) diff --git a/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.cpp b/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.cpp index 94ea4dcb..9b4a6e43 100644 --- a/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.cpp +++ b/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.cpp @@ -25,34 +25,35 @@ namespace plugin { uint16_t DynamicMacros::storage_base_; uint16_t DynamicMacros::storage_size_; uint16_t DynamicMacros::map_[]; - -static void playMacroKeyswitchEvent(Key key, uint8_t keyswitch_state, bool explicit_report) { - handleKeyswitchEvent(key, UnknownKeyswitchLocation, keyswitch_state | INJECTED); - - if (explicit_report) - return; - - kaleidoscope::Runtime.hid().keyboard().sendReport(); - kaleidoscope::Runtime.hid().mouse().sendReport(); +Key DynamicMacros::active_macro_keys_[]; + +// ============================================================================= +// It might be possible to use Macros instead of reproducing it +void DynamicMacros::press(Key key) { + Runtime.handleKeyEvent(KeyEvent(KeyAddr::none(), IS_PRESSED | INJECTED, key)); + for (Key &mkey : active_macro_keys_) { + if (mkey == Key_NoKey) { + mkey = key; + break; + } + } } -static void playKeyCode(Key key, uint8_t keyStates, bool explicit_report) { - if (keyIsPressed(keyStates)) { - playMacroKeyswitchEvent(key, IS_PRESSED, explicit_report); - } - if (keyWasPressed(keyStates)) { - playMacroKeyswitchEvent(key, WAS_PRESSED, explicit_report); +void DynamicMacros::release(Key key) { + for (Key &mkey : active_macro_keys_) { + if (mkey == key) { + mkey = Key_NoKey; + } } + Runtime.handleKeyEvent(KeyEvent(KeyAddr::none(), WAS_PRESSED | INJECTED, key)); } -static void readKeyCodeAndPlay(uint16_t pos, uint8_t flags, uint8_t keyStates, bool explicit_report) { - Key key(Runtime.storage().read(pos++), // key_code - flags); - - playKeyCode(key, keyStates, explicit_report); +void DynamicMacros::tap(Key key) { + Runtime.handleKeyEvent(KeyEvent(KeyAddr::none(), IS_PRESSED | INJECTED, key)); + Runtime.handleKeyEvent(KeyEvent(KeyAddr::none(), WAS_PRESSED | INJECTED, key)); } -void DynamicMacros::updateDynamicMacroCache(void) { +void DynamicMacros::updateDynamicMacroCache() { uint16_t pos = storage_base_; uint8_t current_id = 0; macro_t macro = MACRO_ACTION_END; @@ -116,27 +117,22 @@ void DynamicMacros::updateDynamicMacroCache(void) { } } +// public void DynamicMacros::play(uint8_t macro_id) { macro_t macro = MACRO_ACTION_END; uint8_t interval = 0; - uint8_t flags; - bool explicit_report = false; uint16_t pos; + Key key; pos = storage_base_ + map_[macro_id]; while (true) { switch (macro = Runtime.storage().read(pos++)) { case MACRO_ACTION_STEP_EXPLICIT_REPORT: - explicit_report = true; - break; case MACRO_ACTION_STEP_IMPLICIT_REPORT: - explicit_report = false; - break; case MACRO_ACTION_STEP_SEND_REPORT: - kaleidoscope::Runtime.hid().keyboard().sendReport(); - kaleidoscope::Runtime.hid().mouse().sendReport(); break; + case MACRO_ACTION_STEP_INTERVAL: interval = Runtime.storage().read(pos++); break; @@ -145,46 +141,59 @@ void DynamicMacros::play(uint8_t macro_id) { delay(wait); break; } + case MACRO_ACTION_STEP_KEYDOWN: - flags = Runtime.storage().read(pos++); - readKeyCodeAndPlay(pos++, flags, IS_PRESSED, explicit_report); + key.setFlags(Runtime.storage().read(pos++)); + key.setKeyCode(Runtime.storage().read(pos++)); + press(key); break; case MACRO_ACTION_STEP_KEYUP: - flags = Runtime.storage().read(pos++); - readKeyCodeAndPlay(pos++, flags, WAS_PRESSED, explicit_report); + key.setFlags(Runtime.storage().read(pos++)); + key.setKeyCode(Runtime.storage().read(pos++)); + release(key); break; case MACRO_ACTION_STEP_TAP: - flags = Runtime.storage().read(pos++); - readKeyCodeAndPlay(pos++, flags, IS_PRESSED | WAS_PRESSED, false); + key.setFlags(Runtime.storage().read(pos++)); + key.setKeyCode(Runtime.storage().read(pos++)); + tap(key); break; case MACRO_ACTION_STEP_KEYCODEDOWN: - readKeyCodeAndPlay(pos++, 0, IS_PRESSED, explicit_report); + key.setFlags(0); + key.setKeyCode(Runtime.storage().read(pos++)); + press(key); break; case MACRO_ACTION_STEP_KEYCODEUP: - readKeyCodeAndPlay(pos++, 0, WAS_PRESSED, explicit_report); + key.setFlags(0); + key.setKeyCode(Runtime.storage().read(pos++)); + release(key); break; case MACRO_ACTION_STEP_TAPCODE: - readKeyCodeAndPlay(pos++, 0, IS_PRESSED | WAS_PRESSED, false); + key.setFlags(0); + key.setKeyCode(Runtime.storage().read(pos++)); + tap(key); break; case MACRO_ACTION_STEP_TAP_SEQUENCE: { - uint8_t keyCode; - do { - flags = Runtime.storage().read(pos++); - keyCode = Runtime.storage().read(pos++); - playKeyCode(Key(keyCode, flags), IS_PRESSED | WAS_PRESSED, false); + while (true) { + key.setFlags(0); + key.setKeyCode(pgm_read_byte(pos++)); + if (key == Key_NoKey) + break; + tap(key); delay(interval); - } while (!(flags == 0 && keyCode == 0)); + } break; } case MACRO_ACTION_STEP_TAP_CODE_SEQUENCE: { - uint8_t keyCode; - do { - keyCode = Runtime.storage().read(pos++); - playKeyCode(Key(keyCode, 0), IS_PRESSED | WAS_PRESSED, false); + while (true) { + key.setFlags(0); + key.setKeyCode(pgm_read_byte(pos++)); + if (key.getKeyCode() == 0) + break; + tap(key); delay(interval); - } while (keyCode != 0); + } break; } @@ -197,12 +206,23 @@ void DynamicMacros::play(uint8_t macro_id) { } } -EventHandlerResult DynamicMacros::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { - if (mappedKey.getRaw() < ranges::DYNAMIC_MACRO_FIRST || mappedKey.getRaw() > ranges::DYNAMIC_MACRO_LAST) +bool isDynamicMacrosKey(Key key) { + return (key.getRaw() >= ranges::DYNAMIC_MACRO_FIRST && + key.getRaw() <= ranges::DYNAMIC_MACRO_LAST); +} + +// ----------------------------------------------------------------------------- +EventHandlerResult DynamicMacros::onKeyEvent(KeyEvent &event) { + if (!isDynamicMacrosKey(event.key)) return EventHandlerResult::OK; - if (keyToggledOn(keyState)) { - play(mappedKey.getRaw() - ranges::DYNAMIC_MACRO_FIRST); + if (keyToggledOn(event.state)) { + uint8_t macro_id = event.key.getRaw() - ranges::DYNAMIC_MACRO_FIRST; + play(macro_id); + } else { + for (Key key : active_macro_keys_) { + release(key); + } } return EventHandlerResult::EVENT_CONSUMED; @@ -249,14 +269,14 @@ EventHandlerResult DynamicMacros::onFocusEvent(const char *command) { return EventHandlerResult::EVENT_CONSUMED; } +// public void DynamicMacros::reserve_storage(uint16_t size) { storage_base_ = ::EEPROMSettings.requestSlice(size); storage_size_ = size; updateDynamicMacroCache(); } - -} -} +} // namespace plugin +} // namespace kaleidoscope kaleidoscope::plugin::DynamicMacros DynamicMacros; diff --git a/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.h b/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.h index ed2240cb..5499dab0 100644 --- a/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.h +++ b/plugins/Kaleidoscope-DynamicMacros/src/kaleidoscope/plugin/DynamicMacros.h @@ -24,15 +24,15 @@ #define DM(n) Key(kaleidoscope::ranges::DYNAMIC_MACRO_FIRST + n) +#define MAX_CONCURRENT_DYNAMIC_MACRO_KEYS 8 + namespace kaleidoscope { namespace plugin { class DynamicMacros : public kaleidoscope::Plugin { public: - DynamicMacros(void) {} - - EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState); EventHandlerResult onNameQuery(); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult onFocusEvent(const char *command); static void reserve_storage(uint16_t size); @@ -43,10 +43,14 @@ class DynamicMacros : public kaleidoscope::Plugin { static uint16_t storage_base_; static uint16_t storage_size_; static uint16_t map_[31]; - static void updateDynamicMacroCache(void); + static void updateDynamicMacroCache(); + static Key active_macro_keys_[MAX_CONCURRENT_DYNAMIC_MACRO_KEYS]; + static void press(Key key); + static void release(Key key); + static void tap(Key key); }; -} -} +} // namespace plugin +} // namespace kaleidoscope extern kaleidoscope::plugin::DynamicMacros DynamicMacros; From f91d2a30a374604b9a225a1f2a164d7d038f55d2 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:37:41 -0500 Subject: [PATCH 074/108] Adapt MouseKeys plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/MouseKeys.cpp | 268 +++++++++++------- .../src/kaleidoscope/plugin/MouseKeys.h | 14 +- 2 files changed, 169 insertions(+), 113 deletions(-) diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp index f6fb6bf6..508cb56f 100644 --- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp +++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.cpp @@ -24,8 +24,6 @@ namespace kaleidoscope { namespace plugin { -uint8_t MouseKeys_::mouseMoveIntent; - uint8_t MouseKeys_::speed = 1; uint16_t MouseKeys_::speedDelay = 1; @@ -39,6 +37,9 @@ uint16_t MouseKeys_::move_start_time_; uint16_t MouseKeys_::accel_start_time_; uint16_t MouseKeys_::wheel_start_time_; +// ============================================================================= +// Configuration functions + void MouseKeys_::setWarpGridSize(uint8_t grid_size) { MouseWrapper.warp_grid_size = grid_size; } @@ -47,150 +48,199 @@ void MouseKeys_::setSpeedLimit(uint8_t speed_limit) { MouseWrapper.speedLimit = speed_limit; } -void MouseKeys_::scrollWheel(uint8_t keyCode) { - if (!Runtime.hasTimeExpired(wheel_start_time_, wheelDelay)) - return; +// ============================================================================= +// Key variant tests - wheel_start_time_ = Runtime.millisAtCycleStart(); +bool MouseKeys_::isMouseKey(const Key& key) const { + return (key.getFlags() == (SYNTHETIC | IS_MOUSE_KEY)); +} - if (keyCode & KEY_MOUSE_UP) - kaleidoscope::Runtime.hid().mouse().move(0, 0, wheelSpeed); - else if (keyCode & KEY_MOUSE_DOWN) - kaleidoscope::Runtime.hid().mouse().move(0, 0, -wheelSpeed); - else if (keyCode & KEY_MOUSE_LEFT) - kaleidoscope::Runtime.hid().mouse().move(0, 0, 0, -wheelSpeed); - else if (keyCode & KEY_MOUSE_RIGHT) - kaleidoscope::Runtime.hid().mouse().move(0, 0, 0, wheelSpeed); +bool MouseKeys_::isMouseButtonKey(const Key& key) const { + uint8_t variant = key.getKeyCode() & (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP); + return variant == KEY_MOUSE_BUTTON; } -EventHandlerResult MouseKeys_::onNameQuery() { - return ::Focus.sendName(F("MouseKeys")); +bool MouseKeys_::isMouseMoveKey(const Key& key) const { + uint8_t mask = (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP | KEY_MOUSE_WHEEL); + uint8_t variant = key.getKeyCode() & mask; + return variant == 0; } -EventHandlerResult MouseKeys_::afterEachCycle() { - kaleidoscope::Runtime.hid().mouse().sendReport(); - kaleidoscope::Runtime.hid().mouse().releaseAllButtons(); - mouseMoveIntent = 0; +bool MouseKeys_::isMouseWarpKey(const Key& key) const { + return (key.getKeyCode() & KEY_MOUSE_WARP) != 0; +} - return EventHandlerResult::OK; +bool MouseKeys_::isMouseWheelKey(const Key& key) const { + uint8_t mask = (KEY_MOUSE_BUTTON | KEY_MOUSE_WARP | KEY_MOUSE_WHEEL); + uint8_t variant = key.getKeyCode() & mask; + return variant == KEY_MOUSE_WHEEL; } -EventHandlerResult MouseKeys_::beforeReportingState() { - if (mouseMoveIntent == 0) { - MouseWrapper.accelStep = 0; - return EventHandlerResult::OK; - } +// ============================================================================= +// Event Handlers - if (!Runtime.hasTimeExpired(move_start_time_, speedDelay)) - return EventHandlerResult::OK; +// ----------------------------------------------------------------------------- +EventHandlerResult MouseKeys_::onNameQuery() { + return ::Focus.sendName(F("MouseKeys")); +} - move_start_time_ = Runtime.millisAtCycleStart(); +// ----------------------------------------------------------------------------- +EventHandlerResult MouseKeys_::onSetup(void) { + kaleidoscope::Runtime.hid().mouse().setup(); + kaleidoscope::Runtime.hid().absoluteMouse().setup(); - int8_t moveX = 0, moveY = 0; + return EventHandlerResult::OK; +} +// ----------------------------------------------------------------------------- +EventHandlerResult MouseKeys_::afterEachCycle() { + // Check timeout for accel update interval. if (Runtime.hasTimeExpired(accel_start_time_, accelDelay)) { + accel_start_time_ = Runtime.millisAtCycleStart(); + // `accelStep` determines the movement speed of the mouse pointer, and gets + // reset to zero when no mouse movement keys is pressed (see below). if (MouseWrapper.accelStep < 255 - accelSpeed) { MouseWrapper.accelStep += accelSpeed; } - accel_start_time_ = Runtime.millisAtCycleStart(); } - if (mouseMoveIntent & KEY_MOUSE_UP) - moveY -= speed; - if (mouseMoveIntent & KEY_MOUSE_DOWN) - moveY += speed; + // Check timeout for position update interval. + bool update_position = Runtime.hasTimeExpired(move_start_time_, speedDelay); + if (update_position) { + move_start_time_ = Runtime.millisAtCycleStart(); + // Determine which mouse movement directions are active by searching through + // all the currently active keys for mouse movement keys, and adding them to + // a bitfield (`directions`). + uint8_t directions = 0; + int8_t vx = 0; + int8_t vy = 0; + for (Key key : live_keys.all()) { + if (isMouseKey(key) && isMouseMoveKey(key)) { + directions |= key.getKeyCode(); + } + } - if (mouseMoveIntent & KEY_MOUSE_LEFT) - moveX -= speed; - if (mouseMoveIntent & KEY_MOUSE_RIGHT) - moveX += speed; + if (directions == 0) { + // If there are no mouse movement keys held, reset speed to zero. + MouseWrapper.accelStep = 0; + } else { + // For each active direction, add the mouse movement speed. + if (directions & KEY_MOUSE_LEFT) + vx -= speed; + if (directions & KEY_MOUSE_RIGHT) + vx += speed; + if (directions & KEY_MOUSE_UP) + vy -= speed; + if (directions & KEY_MOUSE_DOWN) + vy += speed; + + // Prepare the mouse report. + MouseWrapper.move(vx, vy); + // Send the report. + Runtime.hid().mouse().sendReport(); + } + } + + // Check timeout for scroll report interval. + bool update_wheel = Runtime.hasTimeExpired(wheel_start_time_, wheelDelay); + if (update_wheel) { + wheel_start_time_ = Runtime.millisAtCycleStart(); + // Determine which scroll wheel keys are active, and add their directions to + // a bitfield (`directions`). + uint8_t directions = 0; + int8_t vx = 0; + int8_t vy = 0; + for (Key key : live_keys.all()) { + if (isMouseKey(key) && isMouseWheelKey(key)) { + directions |= key.getKeyCode(); + } + } - MouseWrapper.move(moveX, moveY); + if (directions != 0) { + // Horizontal scroll wheel: + if (directions & KEY_MOUSE_LEFT) + vx -= wheelSpeed; + if (directions & KEY_MOUSE_RIGHT) + vx += wheelSpeed; + // Vertical scroll wheel (note coordinates are opposite movement): + if (directions & KEY_MOUSE_UP) + vy += wheelSpeed; + if (directions & KEY_MOUSE_DOWN) + vy -= wheelSpeed; + + // Add scroll wheel changes to HID report. + Runtime.hid().mouse().move(0, 0, vy, vx); + // Send the report. + Runtime.hid().mouse().sendReport(); + } + } return EventHandlerResult::OK; } -EventHandlerResult MouseKeys_::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { - if (mappedKey.getFlags() != (SYNTHETIC | IS_MOUSE_KEY)) +// ----------------------------------------------------------------------------- +EventHandlerResult MouseKeys_::onKeyEvent(KeyEvent &event) { + if (!isMouseKey(event.key)) return EventHandlerResult::OK; - if (mappedKey.getKeyCode() & KEY_MOUSE_BUTTON && !(mappedKey.getKeyCode() & KEY_MOUSE_WARP)) { - uint8_t button = mappedKey.getKeyCode() & ~KEY_MOUSE_BUTTON; + if (isMouseButtonKey(event.key)) { + sendMouseButtonReport(event); - if (keyIsPressed(keyState)) { - // Reset warp state on initial mouse button key-down only so we can use - // warp keys to drag-and-drop: - if (keyToggledOn(keyState)) { - MouseWrapper.reset_warping(); - } - - kaleidoscope::Runtime.hid().mouse().pressButtons(button); - } else if (keyToggledOff(keyState)) { - kaleidoscope::Runtime.hid().mouse().releaseButtons(button); - MouseWrapper.end_warping(); - } - } else if (!(mappedKey.getKeyCode() & KEY_MOUSE_WARP)) { - if (keyToggledOn(keyState)) { - move_start_time_ = Runtime.millisAtCycleStart(); - accel_start_time_ = Runtime.millisAtCycleStart(); - wheel_start_time_ = Runtime.millisAtCycleStart() - wheelDelay; + } else if (isMouseWarpKey(event.key)) { + if (keyToggledOn(event.state)) { + sendMouseWarpReport(event); } - if (keyIsPressed(keyState)) { - if (mappedKey.getKeyCode() & KEY_MOUSE_WHEEL) { - scrollWheel(mappedKey.getKeyCode()); - } else { - mouseMoveIntent |= mappedKey.getKeyCode(); - } - } else if (keyToggledOff(keyState)) { - /* If a mouse key toggles off, we want to explicitly stop moving (or - * scrolling) in that direction. We want to do this to support use-cases - * where we send multiple reports per cycle (such as macros), and can't - * rely on the main loop clearing the report for us. We do not want to - * clear the whole report either, because we want any other mouse keys - * to still have their desired effect. Therefore, we selectively stop - * movement or scrolling. */ - mouseMoveIntent &= ~mappedKey.getKeyCode(); - bool x = false, y = false, vWheel = false, hWheel = false; - - if (mappedKey.getKeyCode() & KEY_MOUSE_UP || - mappedKey.getKeyCode() & KEY_MOUSE_DOWN) { - if (mappedKey.getKeyCode() & KEY_MOUSE_WHEEL) { - vWheel = true; - } else { - y = true; - } - } else if (mappedKey.getKeyCode() & KEY_MOUSE_LEFT || - mappedKey.getKeyCode() & KEY_MOUSE_RIGHT) { - if (mappedKey.getKeyCode() & KEY_MOUSE_WHEEL) { - hWheel = true; - } else { - x = true; - } - } - kaleidoscope::Runtime.hid().mouse().stop(x, y, vWheel, hWheel); - } - } else if (keyToggledOn(keyState)) { - if (mappedKey.getKeyCode() & KEY_MOUSE_WARP && mappedKey.getFlags() & IS_MOUSE_KEY) { - MouseWrapper.warp(((mappedKey.getKeyCode() & KEY_MOUSE_WARP_END) ? WARP_END : 0x00) | - ((mappedKey.getKeyCode() & KEY_MOUSE_UP) ? WARP_UP : 0x00) | - ((mappedKey.getKeyCode() & KEY_MOUSE_DOWN) ? WARP_DOWN : 0x00) | - ((mappedKey.getKeyCode() & KEY_MOUSE_LEFT) ? WARP_LEFT : 0x00) | - ((mappedKey.getKeyCode() & KEY_MOUSE_RIGHT) ? WARP_RIGHT : 0x00)); - } + } else if (isMouseMoveKey(event.key)) { + // No report is sent here; that's handled in `afterEachCycle()`. + move_start_time_ = Runtime.millisAtCycleStart() - speedDelay; + accel_start_time_ = Runtime.millisAtCycleStart(); + + } else if (isMouseWheelKey(event.key)) { + // No report is sent here; that's handled in `afterEachCycle()`. + wheel_start_time_ = Runtime.millisAtCycleStart() - wheelDelay; } return EventHandlerResult::EVENT_CONSUMED; } -EventHandlerResult MouseKeys_::onSetup(void) { - kaleidoscope::Runtime.hid().mouse().setup(); - kaleidoscope::Runtime.hid().absoluteMouse().setup(); - - return EventHandlerResult::OK; +// ============================================================================= +// HID report helper functions + +// ----------------------------------------------------------------------------- +void MouseKeys_::sendMouseButtonReport(const KeyEvent &event) const { + // Get ready to send a new mouse report by building it from live_keys. Note + // that this also clears the movement and scroll values, but since those are + // relative, that's what we want. + Runtime.hid().mouse().releaseAllButtons(); + + uint8_t buttons = 0; + for (KeyAddr key_addr : KeyAddr::all()) { + if (key_addr == event.addr) + continue; + Key key = live_keys[key_addr]; + if (isMouseKey(key) && isMouseButtonKey(key)) { + buttons |= key.getKeyCode(); + } + } + if (keyToggledOn(event.state)) + buttons |= event.key.getKeyCode(); + buttons &= ~KEY_MOUSE_BUTTON; + Runtime.hid().mouse().pressButtons(buttons); + Runtime.hid().mouse().sendReport(); } +// ----------------------------------------------------------------------------- +void MouseKeys_::sendMouseWarpReport(const KeyEvent &event) const { + MouseWrapper.warp( + ((event.key.getKeyCode() & KEY_MOUSE_WARP_END) ? WARP_END : 0x00) | + ((event.key.getKeyCode() & KEY_MOUSE_UP) ? WARP_UP : 0x00) | + ((event.key.getKeyCode() & KEY_MOUSE_DOWN) ? WARP_DOWN : 0x00) | + ((event.key.getKeyCode() & KEY_MOUSE_LEFT) ? WARP_LEFT : 0x00) | + ((event.key.getKeyCode() & KEY_MOUSE_RIGHT) ? WARP_RIGHT : 0x00)); } -} + +} // namespace plugin +} // namespace kaleidoscope kaleidoscope::plugin::MouseKeys_ MouseKeys; diff --git a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h index 1787a7bc..9f547998 100644 --- a/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h +++ b/plugins/Kaleidoscope-MouseKeys/src/kaleidoscope/plugin/MouseKeys.h @@ -39,17 +39,23 @@ class MouseKeys_ : public kaleidoscope::Plugin { EventHandlerResult onSetup(); EventHandlerResult onNameQuery(); - EventHandlerResult beforeReportingState(); EventHandlerResult afterEachCycle(); - EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyEvent(KeyEvent &event); private: - static uint8_t mouseMoveIntent; static uint16_t move_start_time_; static uint16_t accel_start_time_; static uint16_t wheel_start_time_; - static void scrollWheel(uint8_t keyCode); + bool isMouseKey(const Key &key) const; + bool isMouseButtonKey(const Key &key) const; + bool isMouseMoveKey(const Key &key) const; + bool isMouseWarpKey(const Key &key) const; + bool isMouseWheelKey(const Key &key) const; + + void sendMouseButtonReport(const KeyEvent &event) const; + void sendMouseWarpReport(const KeyEvent &event) const; + }; } } From b535d203a65a58428022e05eb4b5115507bbf755 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:39:01 -0500 Subject: [PATCH 075/108] Adapt Turbo plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Turbo.cpp | 128 ++++++++++-------- .../src/kaleidoscope/plugin/Turbo.h | 22 ++- 2 files changed, 80 insertions(+), 70 deletions(-) diff --git a/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.cpp b/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.cpp index a31758f2..2164600d 100644 --- a/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.cpp +++ b/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.cpp @@ -25,16 +25,14 @@ namespace kaleidoscope { namespace plugin { uint16_t Turbo::interval_ = 10; -uint16_t Turbo::flashInterval_ = 69; +uint16_t Turbo::flash_interval_ = 69; bool Turbo::sticky_ = false; bool Turbo::flash_ = true; -cRGB Turbo::activeColor_ = CRGB(160, 0, 0); +cRGB Turbo::active_color_ = CRGB(160, 0, 0); -bool Turbo::enable = false; -uint32_t Turbo::startTime = 0; -uint32_t Turbo::flashStartTime = 0; -KeyAddr Turbo::keyPositions[4]; -uint16_t Turbo::numKeys = 0; +bool Turbo::active_ = false; +uint32_t Turbo::start_time_ = 0; +uint32_t Turbo::flash_start_time_ = 0; uint16_t Turbo::interval() { return interval_; @@ -44,10 +42,10 @@ void Turbo::interval(uint16_t newVal) { } uint16_t Turbo::flashInterval() { - return flashInterval_; + return flash_interval_; } void Turbo::flashInterval(uint16_t newVal) { - flashInterval_ = newVal; + flash_interval_ = newVal; } bool Turbo::sticky() { @@ -65,75 +63,91 @@ void Turbo::flash(bool newVal) { } cRGB Turbo::activeColor() { - return activeColor_; + return active_color_; } void Turbo::activeColor(cRGB newVal) { - activeColor_ = newVal; + active_color_ = newVal; } -void Turbo::findKeyPositions() { - numKeys = 0; - - for (auto key_addr : KeyAddr::all()) { - if (Layer.lookupOnActiveLayer(key_addr) == Key_Turbo) { - keyPositions[numKeys++] = key_addr; - } +EventHandlerResult Turbo::onKeyEvent(KeyEvent &event) { + if (active_ && flash_ && keyToggledOff(event.state)) { + if (event.key.isKeyboardKey()) + LEDControl::refreshAt(event.addr); } -} - -EventHandlerResult Turbo::onSetup() { - Turbo::findKeyPositions(); - return EventHandlerResult::OK; -} - -EventHandlerResult Turbo::onNameQuery() { - return ::Focus.sendName(F("Turbo")); -} -EventHandlerResult Turbo::onLayerChange() { - Turbo::findKeyPositions(); - return EventHandlerResult::OK; -} + if (event.key != Key_Turbo) + return EventHandlerResult::OK; -EventHandlerResult Turbo::onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) { - if (key != Key_Turbo) return EventHandlerResult::OK; - enable = sticky_ ? (keyIsPressed(key_state) ? enable : !enable) : keyIsPressed(key_state); - if (!enable) { - for (uint16_t i = 0; i < numKeys; i++) { - LEDControl::refreshAt(KeyAddr(keyPositions[i])); - } + if (keyToggledOn(event.state)) { + active_ = true; + start_time_ = Runtime.millisAtCycleStart() - interval_; + } else { + active_ = false; + if (flash_) + LEDControl::refreshAll(); } return EventHandlerResult::EVENT_CONSUMED; } EventHandlerResult Turbo::afterEachCycle() { - if (enable) { - if (Runtime.millisAtCycleStart() - startTime > interval_) { - kaleidoscope::Runtime.hid().keyboard().sendReport(); - startTime = Runtime.millisAtCycleStart(); - } - - if (flash_) { - if (Runtime.millisAtCycleStart() - flashStartTime > flashInterval_ * 2) { - for (uint16_t i = 0; i < numKeys; i++) { - LEDControl::setCrgbAt(KeyAddr(keyPositions[i]), activeColor_); + if (active_) { + if (Runtime.hasTimeExpired(start_time_, interval_)) { + // Reset the timer. + start_time_ = Runtime.millisAtCycleStart(); + + // Clear the existing Keyboard HID report. It might be nice to keep the + // modifiers active, but I'll save that for another time. + Runtime.hid().keyboard().releaseAllKeys(); + // Send the empty report to register the release of all the held keys. + Runtime.hid().keyboard().sendReport(); + + // Just in case the Turbo key has been wiped from `live_keys[]` without + // `onKeyEvent()` being called with a toggle-off: + active_ = false; + + // Go through the `live_keys[]` array and add any Keyboard HID keys to the + // new report. + for (Key key : live_keys.all()) { + if (key == Key_Turbo) { + active_ = true; } - flashStartTime = Runtime.millisAtCycleStart(); - } else if (Runtime.millisAtCycleStart() - flashStartTime > flashInterval_) { - for (uint16_t i = 0; i < numKeys; i++) { - LEDControl::setCrgbAt(KeyAddr(keyPositions[i]), {0, 0, 0}); + if (key.isKeyboardKey()) { + Runtime.addToReport(key); } } - LEDControl::syncLeds(); - } else { - for (uint16_t i = 0; i < numKeys; i++) { - LEDControl::setCrgbAt(KeyAddr(keyPositions[i]), activeColor_); + + // Send the re-populated keyboard report. + Runtime.hid().keyboard().sendReport(); + } + } + return EventHandlerResult::OK; +} + +EventHandlerResult Turbo::beforeSyncingLeds() { + if (flash_ && active_) { + static bool leds_on = false; + cRGB color = CRGB(0, 0, 0); + if (leds_on) { + color = active_color_; + } + if (Runtime.hasTimeExpired(flash_start_time_, flash_interval_)) { + flash_start_time_ = Runtime.millisAtCycleStart(); + leds_on = !leds_on; + } + for (KeyAddr key_addr : KeyAddr::all()) { + Key key = live_keys[key_addr]; + if (key.isKeyboardKey()) { + LEDControl::setCrgbAt(key_addr, color); } } } return EventHandlerResult::OK; } +EventHandlerResult Turbo::onNameQuery() { + return ::Focus.sendName(F("Turbo")); +} + } } diff --git a/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.h b/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.h index bf22e29c..463b4434 100644 --- a/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.h +++ b/plugins/Kaleidoscope-Turbo/src/kaleidoscope/plugin/Turbo.h @@ -21,7 +21,7 @@ #pragma once -#define Key_Turbo Key{kaleidoscope::ranges::TURBO } +#define Key_Turbo Key{kaleidoscope::ranges::TURBO} namespace kaleidoscope { namespace plugin { @@ -44,25 +44,21 @@ class Turbo : public kaleidoscope::Plugin { cRGB activeColor(); void activeColor(cRGB newVal); - EventHandlerResult onSetup(); EventHandlerResult onNameQuery(); - EventHandlerResult onLayerChange(); - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); - private: - void findKeyPositions(); + EventHandlerResult beforeSyncingLeds(); + private: static uint16_t interval_; - static uint16_t flashInterval_; + static uint16_t flash_interval_; static bool sticky_; static bool flash_; - static cRGB activeColor_; + static cRGB active_color_; - static bool enable; - static uint32_t startTime; - static uint32_t flashStartTime; - static KeyAddr keyPositions[4]; - static uint16_t numKeys; + static bool active_; + static uint32_t start_time_; + static uint32_t flash_start_time_; }; } } From 8d8a9f7f443a056640a67a266d075e13a3dc6908 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:40:07 -0500 Subject: [PATCH 076/108] Adapt TypingBreaks plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/TypingBreaks.cpp | 21 +++++++++++-------- .../src/kaleidoscope/plugin/TypingBreaks.h | 2 +- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.cpp b/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.cpp index 04eea441..fe5f312f 100644 --- a/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.cpp +++ b/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.cpp @@ -39,16 +39,21 @@ uint16_t TypingBreaks::left_hand_keys_; uint16_t TypingBreaks::right_hand_keys_; uint16_t TypingBreaks::settings_base_; -EventHandlerResult TypingBreaks::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult TypingBreaks::onKeyEvent(KeyEvent &event) { uint32_t lock_length = settings.lock_length * 1000; uint32_t idle_time_limit = settings.idle_time_limit * 1000; uint32_t lock_time_out = settings.lock_time_out * 1000; + // Let key release events through regardless, so the last key pressed (and any + // other held keys) finish getting processed when they're released. + if (keyToggledOff(event.state)) + return EventHandlerResult::OK; + // If we are locked... if (keyboard_locked_) { // ...and the lock has not expired yet if (!Runtime.hasTimeExpired(lock_start_time_, lock_length)) { - return EventHandlerResult::EVENT_CONSUMED; // remain locked + return EventHandlerResult::ABORT; } // ...otherwise clear the lock @@ -90,14 +95,12 @@ EventHandlerResult TypingBreaks::onKeyswitchEvent(Key &mapped_key, KeyAddr key_a // So it seems we did not need to lock up. In this case, lets increase key // counters if need be. + if (event.addr.col() <= Runtime.device().matrix_columns / 2) + left_hand_keys_++; + else + right_hand_keys_++; - if (keyToggledOn(key_state)) { - if (key_addr.col() <= Runtime.device().matrix_columns / 2) - left_hand_keys_++; - else - right_hand_keys_++; - last_key_time_ = Runtime.millisAtCycleStart(); - } + last_key_time_ = Runtime.millisAtCycleStart(); return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.h b/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.h index 23a41ef4..7383c421 100644 --- a/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.h +++ b/plugins/Kaleidoscope-TypingBreaks/src/kaleidoscope/plugin/TypingBreaks.h @@ -37,7 +37,7 @@ class TypingBreaks : public kaleidoscope::Plugin { static settings_t settings; EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult onFocusEvent(const char *command); EventHandlerResult onSetup(); From b4ec77d6e1e322fce91996882bec41d21d38c1af Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:56:14 -0500 Subject: [PATCH 077/108] Adapt WinKeyToggle plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/WinKeyToggle.cpp | 7 +++---- .../src/kaleidoscope/plugin/WinKeyToggle.h | 2 +- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp index 2cd3a328..50c376fc 100644 --- a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp +++ b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.cpp @@ -23,13 +23,12 @@ namespace plugin { bool WinKeyToggle::enabled_; -EventHandlerResult WinKeyToggle::onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult WinKeyToggle::onKeyEvent(KeyEvent &event) { if (!enabled_) return EventHandlerResult::OK; - if (key == Key_LeftGui || key == Key_RightGui) { - key = Key_NoKey; - return EventHandlerResult::EVENT_CONSUMED; + if (event.key == Key_LeftGui || event.key == Key_RightGui) { + return EventHandlerResult::ABORT; } return EventHandlerResult::OK; diff --git a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.h b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.h index 6c2dff90..baeb48da 100644 --- a/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.h +++ b/plugins/Kaleidoscope-WinKeyToggle/src/kaleidoscope/plugin/WinKeyToggle.h @@ -25,7 +25,7 @@ class WinKeyToggle: public kaleidoscope::Plugin { public: WinKeyToggle() {} - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); void toggle() { enabled_ = !enabled_; } From a890b3ccf1021982a955b42400f926b7bed3fe86 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:41:01 -0500 Subject: [PATCH 078/108] Adapt Steno(GeminiPR) plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/GeminiPR.cpp | 11 +++++------ .../src/kaleidoscope/plugin/GeminiPR.h | 11 ++++++----- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.cpp b/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.cpp index ef7fa286..7f2f1620 100644 --- a/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.cpp +++ b/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.cpp @@ -30,17 +30,16 @@ EventHandlerResult GeminiPR::onNameQuery() { return ::Focus.sendName(F("GeminiPR")); } -EventHandlerResult GeminiPR::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - if (mapped_key < geminipr::START || - mapped_key > geminipr::END) +EventHandlerResult GeminiPR::onKeyEvent(KeyEvent &event) { + if (event.key < geminipr::START || event.key > geminipr::END) return EventHandlerResult::OK; - if (keyToggledOn(keyState)) { - uint8_t key = mapped_key.getRaw() - geminipr::START; + if (keyToggledOn(event.state)) { + uint8_t key = event.key.getRaw() - geminipr::START; ++keys_held_; state_[key / 7] |= 1 << (6 - (key % 7)); - } else if (keyToggledOff(keyState)) { + } else { --keys_held_; if (keys_held_ == 0) { diff --git a/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.h b/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.h index c6eec519..e1657835 100644 --- a/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.h +++ b/plugins/Kaleidoscope-Steno/src/kaleidoscope/plugin/GeminiPR.h @@ -30,7 +30,8 @@ class GeminiPR : public kaleidoscope::Plugin { GeminiPR(void) {} EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyEvent(KeyEvent &event); + private: static uint8_t keys_held_; static uint8_t state_[6]; @@ -88,9 +89,9 @@ enum { ZR, END = ZR, }; -} -} -} -} +} // namespace geminipr +} // namespace steno +} // namespace plugin +} // namespace kaleidoscope extern kaleidoscope::plugin::steno::GeminiPR GeminiPR; From d7a71f92ba4c2e52fcc82b24374a09ec0a67dc71 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 10 Apr 2021 14:41:40 -0500 Subject: [PATCH 079/108] Adapt Cycle plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Cycle.cpp | 42 ++++++++----------- .../src/kaleidoscope/plugin/Cycle.h | 3 +- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.cpp b/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.cpp index ad0ff5d5..93c447d4 100644 --- a/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.cpp +++ b/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.cpp @@ -25,6 +25,7 @@ namespace kaleidoscope { namespace plugin { // --- state --- Key Cycle::last_non_cycle_key_; +KeyAddr Cycle::cycle_key_addr_{KeyAddr::invalid_state}; uint8_t Cycle::current_modifier_flags_; uint8_t Cycle::cycle_count_; @@ -35,15 +36,12 @@ uint8_t Cycle::cycle_count_; // --- api --- void Cycle::replace(Key key) { - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, IS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); - handleKeyswitchEvent(Key_Backspace, UnknownKeyswitchLocation, WAS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); - - handleKeyswitchEvent(key, UnknownKeyswitchLocation, IS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); - handleKeyswitchEvent(key, UnknownKeyswitchLocation, WAS_PRESSED | INJECTED); - kaleidoscope::Runtime.hid().keyboard().sendReport(); + if (cycle_key_addr_ == KeyAddr{KeyAddr::invalid_state}) + return; + Runtime.handleKeyEvent(KeyEvent{cycle_key_addr_, IS_PRESSED | INJECTED, Key_Backspace}); + Runtime.handleKeyEvent(KeyEvent{cycle_key_addr_, WAS_PRESSED | INJECTED, Key_Backspace}); + Runtime.handleKeyEvent(KeyEvent{cycle_key_addr_, IS_PRESSED | INJECTED, key}); + Runtime.handleKeyEvent(KeyEvent{cycle_key_addr_, WAS_PRESSED | INJECTED, key}); } void Cycle::replace(uint8_t cycle_size, const Key cycle_steps[]) { @@ -57,35 +55,29 @@ EventHandlerResult Cycle::onNameQuery() { return ::Focus.sendName(F("Cycle")); } -EventHandlerResult Cycle::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (key_state & INJECTED) +EventHandlerResult Cycle::onKeyEvent(KeyEvent &event) { + if (event.state & INJECTED) return EventHandlerResult::OK; - if (!keyIsPressed(key_state) && !keyWasPressed(key_state)) { - if (isCycle(mapped_key)) { - return EventHandlerResult::EVENT_CONSUMED; - } - return EventHandlerResult::OK; - } - - if (!isCycle(mapped_key)) { - if (keyToggledOn(key_state)) { - current_modifier_flags_ |= toModFlag(mapped_key.getKeyCode()); - last_non_cycle_key_.setKeyCode(mapped_key.getKeyCode()); + if (!isCycle(event.key)) { + if (keyToggledOn(event.state)) { + current_modifier_flags_ |= toModFlag(event.key.getKeyCode()); + last_non_cycle_key_.setKeyCode(event.key.getKeyCode()); last_non_cycle_key_.setFlags(current_modifier_flags_); cycle_count_ = 0; } - if (keyToggledOff(key_state)) { - current_modifier_flags_ &= ~toModFlag(mapped_key.getKeyCode()); + if (keyToggledOff(event.state)) { + current_modifier_flags_ &= ~toModFlag(event.key.getKeyCode()); } return EventHandlerResult::OK; } - if (!keyToggledOff(key_state)) { + if (!keyToggledOff(event.state)) { return EventHandlerResult::EVENT_CONSUMED; } ++cycle_count_; + cycle_key_addr_ = event.addr; cycleAction(last_non_cycle_key_, cycle_count_); return EventHandlerResult::EVENT_CONSUMED; } diff --git a/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.h b/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.h index 10eec174..ee7d013c 100644 --- a/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.h +++ b/plugins/Kaleidoscope-Cycle/src/kaleidoscope/plugin/Cycle.h @@ -37,11 +37,12 @@ class Cycle : public kaleidoscope::Plugin { static void replace(uint8_t cycle_size, const Key cycle_steps[]); EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); private: static uint8_t toModFlag(uint8_t keyCode); static Key last_non_cycle_key_; + static KeyAddr cycle_key_addr_; static uint8_t cycle_count_; static uint8_t current_modifier_flags_; }; From 48e1130dced6557f4bcf4bb6237deb2fe267af1f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:57:37 -0500 Subject: [PATCH 080/108] Adapt EEPROM-Keymap-Programmer plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../plugin/EEPROM-Keymap-Programmer.cpp | 26 +++++++++---------- .../plugin/EEPROM-Keymap-Programmer.h | 2 +- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp b/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp index 07fa56e6..bbc6a578 100644 --- a/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp +++ b/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.cpp @@ -53,27 +53,27 @@ void EEPROMKeymapProgrammer::cancel(void) { state_ = INACTIVE; } -EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult EEPROMKeymapProgrammer::onKeyEvent(KeyEvent &event) { if (state_ == INACTIVE) return EventHandlerResult::OK; if (state_ == WAIT_FOR_KEY) { - if (keyToggledOn(key_state)) { - update_position_ = Layer.mostRecent() * Runtime.device().numKeys() + key_addr.toInt(); + if (keyToggledOn(event.state)) { + update_position_ = Layer.mostRecent() * Runtime.device().numKeys() + event.addr.toInt(); } - if (keyToggledOff(key_state)) { - if ((uint16_t)(Layer.mostRecent() * Runtime.device().numKeys() + key_addr.toInt()) == update_position_) + if (keyToggledOff(event.state)) { + if ((uint16_t)(Layer.mostRecent() * Runtime.device().numKeys() + event.addr.toInt()) == update_position_) nextState(); } return EventHandlerResult::EVENT_CONSUMED; } if (state_ == WAIT_FOR_SOURCE_KEY) { - if (keyToggledOn(key_state)) { - new_key_ = Layer.getKeyFromPROGMEM(Layer.mostRecent(), key_addr); + if (keyToggledOn(event.state)) { + new_key_ = Layer.getKeyFromPROGMEM(Layer.mostRecent(), event.addr); } - if (keyToggledOff(key_state)) { - if (new_key_ == Layer.getKeyFromPROGMEM(Layer.mostRecent(), key_addr)) + if (keyToggledOff(event.state)) { + if (new_key_ == Layer.getKeyFromPROGMEM(Layer.mostRecent(), event.addr)) nextState(); } return EventHandlerResult::EVENT_CONSUMED; @@ -81,18 +81,18 @@ EventHandlerResult EEPROMKeymapProgrammer::onKeyswitchEvent(Key &mapped_key, Key // WAIT_FOR_CODE state - if (mapped_key < Key_1 || mapped_key > Key_0) + if (event.key < Key_1 || event.key > Key_0) return EventHandlerResult::OK; - if (!keyToggledOn(key_state)) { + if (!keyToggledOn(event.state)) { return EventHandlerResult::EVENT_CONSUMED; } uint8_t n; - if (mapped_key.getKeyCode() == Key_0.getKeyCode()) + if (event.key.getKeyCode() == Key_0.getKeyCode()) n = 0; else - n = mapped_key.getKeyCode() - Key_1.getKeyCode() + 1; + n = event.key.getKeyCode() - Key_1.getKeyCode() + 1; new_key_.setRaw(new_key_.getRaw() * 10 + n); diff --git a/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.h b/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.h index 79823164..1b6aa02b 100644 --- a/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.h +++ b/plugins/Kaleidoscope-EEPROM-Keymap-Programmer/src/kaleidoscope/plugin/EEPROM-Keymap-Programmer.h @@ -38,7 +38,7 @@ class EEPROMKeymapProgrammer : public kaleidoscope::Plugin { static void nextState(void); static void cancel(void); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult onFocusEvent(const char *command); private: From 1ff9bb81c230f24a11c7f5d8f1e3813f39f0278b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:59:31 -0500 Subject: [PATCH 081/108] Adapt Escape-OneShot plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/Escape-OneShot.cpp | 11 +++++------ .../src/kaleidoscope/plugin/Escape-OneShot.h | 2 +- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp index 2f60bf5d..1e6a9637 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp @@ -26,21 +26,20 @@ namespace plugin { Key EscapeOneShot::cancel_oneshot_key_{Key_Escape}; -EventHandlerResult EscapeOneShot::onKeyswitchEvent( - Key &key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult EscapeOneShot::onKeyEvent(KeyEvent &event) { // We only act on an escape key (or `cancel_oneshot_key_`, if that has been // set) that has just been pressed, and not generated by some other // plugin. Also, only if at least one OneShot key is active and/or // sticky. Last, only if there are no OneShot keys currently being held. - if (key == cancel_oneshot_key_ && - keyToggledOn(key_state) && - !(key_state & INJECTED) && + if (event.key == cancel_oneshot_key_ && + keyToggledOn(event.state) && + (event.state & INJECTED) == 0 && ::OneShot.isActive()) { // Cancel all OneShot keys ::OneShot.cancel(true); // Change the cancellation key to a blank key, and signal that event // processing is complete. - key = Key_NoKey; + event.key = Key_NoKey; return EventHandlerResult::EVENT_CONSUMED; } diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h index 4e0393cd..ce9af5b0 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h @@ -27,7 +27,7 @@ class EscapeOneShot : public kaleidoscope::Plugin { public: EscapeOneShot(void) {} - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); void setCancelKey(Key cancel_key) { cancel_oneshot_key_ = cancel_key; From 3ddd12ea040d3d6778ae070709d729de5ab84bd3 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 18:59:50 -0500 Subject: [PATCH 082/108] Adapt FocusSerial plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/FocusSerial.cpp | 2 +- .../src/kaleidoscope/plugin/FocusSerial.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.cpp b/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.cpp index de97e2be..abd7ee6e 100644 --- a/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.cpp +++ b/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.cpp @@ -32,7 +32,7 @@ void FocusSerial::drain(void) { Runtime.serialPort().read(); } -EventHandlerResult FocusSerial::beforeReportingState() { +EventHandlerResult FocusSerial::afterEachCycle() { if (Runtime.serialPort().available() == 0) return EventHandlerResult::OK; diff --git a/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.h b/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.h index 944c42a0..0c39937b 100644 --- a/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.h +++ b/plugins/Kaleidoscope-FocusSerial/src/kaleidoscope/plugin/FocusSerial.h @@ -91,7 +91,7 @@ class FocusSerial : public kaleidoscope::Plugin { static constexpr char NEWLINE = '\n'; /* Hooks */ - EventHandlerResult beforeReportingState(); + EventHandlerResult afterEachCycle(); EventHandlerResult onFocusEvent(const char *command); private: From 352fa3fb70b0fe60ff796193457e8e413b447773 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:00:06 -0500 Subject: [PATCH 083/108] Adapt Kaleidoscope-Hardware-EZ-ErgoDox to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/device/ez/ErgoDox.cpp | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox.cpp b/plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox.cpp index 68ae5ede..f9d241c6 100644 --- a/plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox.cpp +++ b/plugins/Kaleidoscope-Hardware-EZ-ErgoDox/src/kaleidoscope/device/ez/ErgoDox.cpp @@ -27,10 +27,12 @@ #ifndef KALEIDOSCOPE_VIRTUAL_BUILD #ifdef ARDUINO_AVR_ERGODOX -#include "kaleidoscope/Runtime.h" #include + #include "kaleidoscope/device/ez/ErgoDox/ErgoDoxScanner.h" -#include "kaleidoscope/key_events.h" +#include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/Runtime.h" namespace kaleidoscope { namespace device { @@ -112,10 +114,12 @@ void __attribute__((optimize(3))) ErgoDox::readMatrix() { void __attribute__((optimize(3))) ErgoDox::actOnMatrixScan() { for (byte row = 0; row < matrix_rows; row++) { for (byte col = 0; col < matrix_columns; col++) { - uint8_t keyState = (bitRead(previousKeyState_[row], col) << 0) | - (bitRead(keyState_[row], col) << 1); - if (keyState) - handleKeyswitchEvent(Key_NoKey, KeyAddr(row, col), keyState); + uint8_t key_state = (bitRead(previousKeyState_[row], col) << 0) | + (bitRead(keyState_[row], col) << 1); + if (keyToggledOn(key_state) || keyToggledOff(key_state)) { + auto event = KeyEvent::next(KeyAddr(row, col), key_state); + kaleidoscope::Runtime.handleKeyswitchEvent(event); + } } previousKeyState_[row] = keyState_[row]; } From a734d6d8b46b23d1441e16b1c77600ba2973087d Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:01:04 -0500 Subject: [PATCH 084/108] Adapt IdleLEDs plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.cpp | 3 +-- .../Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.h | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.cpp b/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.cpp index 3825c3eb..e7afb0b4 100644 --- a/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.cpp +++ b/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.cpp @@ -49,8 +49,7 @@ EventHandlerResult IdleLEDs::beforeEachCycle() { return EventHandlerResult::OK; } -EventHandlerResult IdleLEDs::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - +EventHandlerResult IdleLEDs::onKeyEvent(KeyEvent &event) { if (idle_) { ::LEDControl.enable(); idle_ = false; diff --git a/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.h b/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.h index c60ac64a..fecc126c 100644 --- a/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.h +++ b/plugins/Kaleidoscope-IdleLEDs/src/kaleidoscope/plugin/IdleLEDs.h @@ -33,7 +33,7 @@ class IdleLEDs: public kaleidoscope::Plugin { static void setIdleTimeoutSeconds(uint32_t new_limit); EventHandlerResult beforeEachCycle(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); private: static bool idle_; From 889b664a4a6b605cd6c774b6fe69e68728e84853 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:01:25 -0500 Subject: [PATCH 085/108] Adapt LED-ActiveModColor plugin to KeyEvent handlers Signed-off-by: Michael Richters --- .../LED-ActiveModColor/LED-ActiveModColor.ino | 2 +- .../plugin/LED-ActiveModColor.cpp | 62 ++++++++++--------- .../kaleidoscope/plugin/LED-ActiveModColor.h | 39 ++++++++++-- 3 files changed, 68 insertions(+), 35 deletions(-) diff --git a/examples/LEDs/LED-ActiveModColor/LED-ActiveModColor.ino b/examples/LEDs/LED-ActiveModColor/LED-ActiveModColor.ino index 5f3cd0d9..816d07f6 100644 --- a/examples/LEDs/LED-ActiveModColor/LED-ActiveModColor.ino +++ b/examples/LEDs/LED-ActiveModColor/LED-ActiveModColor.ino @@ -47,7 +47,7 @@ KALEIDOSCOPE_INIT_PLUGINS(LEDControl, void setup() { Kaleidoscope.setup(); - ActiveModColorEffect.highlight_color = CRGB(0x00, 0xff, 0xff); + ActiveModColorEffect.setHighlightColor(CRGB(0x00, 0xff, 0xff)); } void loop() { diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 2e4155df..069a2208 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -17,6 +17,7 @@ #include #include +#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/layers.h" #include "kaleidoscope/keyswitch_state.h" @@ -30,30 +31,27 @@ cRGB ActiveModColorEffect::highlight_color = CRGB(160, 160, 160); cRGB ActiveModColorEffect::oneshot_color = CRGB(160, 160, 0); cRGB ActiveModColorEffect::sticky_color = CRGB(160, 0, 0); -EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( - Key &key, - KeyAddr key_addr, - uint8_t key_state) { +// ----------------------------------------------------------------------------- +EventHandlerResult ActiveModColorEffect::onKeyEvent(KeyEvent &event) { - // If `key_addr` is not a physical key address, ignore it: - if (! key_addr.isValid()) { + // If `event.addr` is not a physical key address, ignore it: + if (! event.addr.isValid()) { return EventHandlerResult::OK; } - if (keyToggledOn(key_state)) { - // If a key toggles on, we check its value. If it's a OneShot key, - // it will get highlighted. Conditionally (if - // `highlight_normal_modifiers_` is set), we also highlight - // modifier and layer-shift keys. - if (::OneShot.isModifier(key) || - ::OneShot.isLayerShift(key) || - ::OneShot.isActive(key_addr)) { - mod_key_bits_.set(key_addr); + if (keyToggledOn(event.state)) { + // If a key toggles on, we check its value. If it's a OneShot key, it will + // get highlighted. Conditionally (if `highlight_normal_modifiers_` is set), + // we also highlight modifier and layer-shift keys. + if (event.key.isKeyboardModifier() || + event.key.isLayerShift() || + ::OneShot.isActive(event.addr)) { + mod_key_bits_.set(event.addr); } - if (key == OneShot_ActiveStickyKey) { + if (event.key == OneShot_ActiveStickyKey) { for (KeyAddr entry_addr : KeyAddr::all()) { - // Get the entry from the keymap cache - Key entry_key = Layer.lookup(entry_addr); + // Get the entry from the live keys array + Key entry_key = live_keys[entry_addr]; // Skip empty entries if (entry_key == Key_Transparent || entry_key == Key_NoKey) { continue; @@ -62,25 +60,28 @@ EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( mod_key_bits_.set(entry_addr); } } - } else if (keyToggledOff(key_state)) { - // Things get a bit ugly here because this plugin might come - // before OneShot in the order, so we can't just count on OneShot - // stopping the suppressed release event before we see it here. - if (mod_key_bits_.read(key_addr) && !::OneShot.isActive(key_addr)) { - mod_key_bits_.clear(key_addr); - ::LEDControl.refreshAt(key_addr); + } else { // if (keyToggledOff(event.state)) + // Things get a bit ugly here because this plugin might come before OneShot + // in the order, so we can't just count on OneShot stopping the suppressed + // release event before we see it here. + if (mod_key_bits_.read(event.addr) && !::OneShot.isActive(event.addr)) { + mod_key_bits_.clear(event.addr); + ::LEDControl.refreshAt(event.addr); } } return EventHandlerResult::OK; } -EventHandlerResult ActiveModColorEffect::beforeReportingState() { +// ----------------------------------------------------------------------------- +EventHandlerResult ActiveModColorEffect::beforeSyncingLeds() { - // This loop iterates through only the `key_addr`s that have their - // bits in the `mod_key_bits_` bitfield set. + // This loop iterates through only the `key_addr`s that have their bits in the + // `mod_key_bits_` bitfield set. for (KeyAddr key_addr : mod_key_bits_) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" if (::OneShot.isTemporary(key_addr)) { // Temporary OneShot keys get one color: ::LEDControl.setCrgbAt(key_addr, oneshot_color); @@ -91,12 +92,13 @@ EventHandlerResult ActiveModColorEffect::beforeReportingState() { // Normal modifiers get a third color: ::LEDControl.setCrgbAt(key_addr, highlight_color); } +#pragma GCC diagnostic pop } return EventHandlerResult::OK; } -} -} +} // namespace plugin +} // namespace kaleidoscope kaleidoscope::plugin::ActiveModColorEffect ActiveModColorEffect; diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h index bdbc3bdd..bed341be 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h @@ -23,24 +23,55 @@ #define MAX_MODS_PER_LAYER 16 +// ============================================================================= +#define _DEPRECATED_MESSAGE_ACTIVEMODCOLOR_COLORS \ + "The `ActiveModColorEffect` public class variables have been deprecated. \n" \ + "Please use the following methods instead: \n" \ + " - for `highlight_color` => `setHighlightColor(color)` \n" \ + " - for `oneshot_color` => `setOneShotColor(color)` \n" \ + " - for `sticky_color` => `setStickyColor(color)`" + namespace kaleidoscope { namespace plugin { class ActiveModColorEffect : public kaleidoscope::Plugin { public: ActiveModColorEffect(void) {} + // When removing access to these variables, don't delete them. Instead, make + // them private, and add trailing underscores here and in + // LED-ActiveModColor.cpp. + DEPRECATED(ACTIVEMODCOLOR_COLORS) static cRGB highlight_color; + DEPRECATED(ACTIVEMODCOLOR_COLORS) static cRGB oneshot_color; + DEPRECATED(ACTIVEMODCOLOR_COLORS) static cRGB sticky_color; + static void setHighlightColor(cRGB color) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + highlight_color = color; +#pragma GCC diagnostic pop + } + static void setOneShotColor(cRGB color) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + oneshot_color = color; +#pragma GCC diagnostic pop + } + static void setOnestickyColor(cRGB color) { +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" + sticky_color = color; +#pragma GCC diagnostic pop + } + static void highlightNormalModifiers(bool value) { highlight_normal_modifiers_ = value; } - EventHandlerResult onKeyswitchEvent(Key &key, - KeyAddr key_addr, - uint8_t key_state); - EventHandlerResult beforeReportingState(); + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult beforeSyncingLeds(); private: static bool highlight_normal_modifiers_; From 2073c4f85537e07b14d44b589ef44d98a003776d Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:02:20 -0500 Subject: [PATCH 086/108] Adapt miscellaneous LED mode plugins to KeyEvent handlers Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/FingerPainter.cpp | 18 ++++++++------ .../src/kaleidoscope/plugin/FingerPainter.h | 2 +- .../src/kaleidoscope/plugin/Heatmap.cpp | 24 ++++++++++--------- .../src/kaleidoscope/plugin/Heatmap.h | 4 ++-- .../plugin/LED-AlphaSquare/Effect.cpp | 20 ++++++++-------- .../plugin/LED-AlphaSquare/Effect.h | 2 +- .../src/kaleidoscope/plugin/LED-Stalker.cpp | 11 +++++---- .../src/kaleidoscope/plugin/LED-Stalker.h | 2 +- .../src/kaleidoscope/plugin/LED-Wavepool.cpp | 15 ++++++------ .../src/kaleidoscope/plugin/LED-Wavepool.h | 4 ++-- .../src/kaleidoscope/plugin/TriColor.cpp | 2 +- 11 files changed, 56 insertions(+), 48 deletions(-) diff --git a/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.cpp b/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.cpp index fef12cdf..268b8801 100644 --- a/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.cpp +++ b/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.cpp @@ -50,20 +50,22 @@ void FingerPainter::toggle(void) { edit_mode_ = !edit_mode_; } -EventHandlerResult FingerPainter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult FingerPainter::onKeyEvent(KeyEvent &event) { if (!Runtime.has_leds || !edit_mode_) return EventHandlerResult::OK; - if (!keyToggledOn(key_state)) { - return EventHandlerResult::EVENT_CONSUMED; + if (keyToggledOff(event.state)) { + return EventHandlerResult::OK; } - if (!key_addr.isValid()) - return EventHandlerResult::EVENT_CONSUMED; + if (!event.addr.isValid()) + return EventHandlerResult::OK; // TODO(anyone): The following works only for keyboards with LEDs for each key. - uint8_t color_index = ::LEDPaletteTheme.lookupColorIndexAtPosition(color_base_, Runtime.device().getLedIndex(key_addr)); + uint8_t color_index = ::LEDPaletteTheme + .lookupColorIndexAtPosition(color_base_, + Runtime.device().getLedIndex(event.addr)); // Find the next color in the palette that is different. // But do not loop forever! @@ -80,7 +82,9 @@ EventHandlerResult FingerPainter::onKeyswitchEvent(Key &mapped_key, KeyAddr key_ new_color = ::LEDPaletteTheme.lookupPaletteColor(color_index); } - ::LEDPaletteTheme.updateColorIndexAtPosition(color_base_, Runtime.device().getLedIndex(key_addr), color_index); + ::LEDPaletteTheme.updateColorIndexAtPosition(color_base_, + Runtime.device().getLedIndex(event.addr), + color_index); return EventHandlerResult::EVENT_CONSUMED; } diff --git a/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.h b/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.h index d20cb06b..40dd2670 100644 --- a/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.h +++ b/plugins/Kaleidoscope-FingerPainter/src/kaleidoscope/plugin/FingerPainter.h @@ -32,7 +32,7 @@ class FingerPainter : public LEDMode { static void toggle(void); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult onFocusEvent(const char *command); EventHandlerResult onSetup(); EventHandlerResult onNameQuery(); diff --git a/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.cpp b/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.cpp index 2a492d2c..a67a3767 100644 --- a/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.cpp +++ b/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.cpp @@ -133,36 +133,38 @@ void Heatmap::TransientLEDMode::resetMap() { highest_ = 1; } -EventHandlerResult Heatmap::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { +// It may be better to use `onKeyswitchEvent()` here +EventHandlerResult Heatmap::onKeyEvent(KeyEvent &event) { + // If the keyboard has no LEDs, return if (!Runtime.has_leds) return EventHandlerResult::OK; - // this methode is called frequently by Kaleidoscope - // even if the module isn't activated + // If the event doesn't correspond to a physical key, skip it + if (!event.addr.isValid()) + return EventHandlerResult::OK; // if it is a synthetic key, skip it - if (key_state & INJECTED) + if (event.state & INJECTED) return EventHandlerResult::OK; // if the key is not toggled on, skip it - if (!keyToggledOn(key_state)) + if (!keyToggledOn(event.state)) return EventHandlerResult::OK; // if the LED mode is not current, skip it if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; - return ::LEDControl.get_mode() - ->onKeyswitchEvent(mapped_key, key_addr, key_state); + return ::LEDControl.get_mode()->onKeyEvent(event); } -EventHandlerResult Heatmap::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult Heatmap::TransientLEDMode::onKeyEvent(KeyEvent &event) { // increment the heatmap_ value related to the key - heatmap_[key_addr.toInt()]++; + heatmap_[event.addr.toInt()]++; // check highest_ - if (highest_ < heatmap_[key_addr.toInt()]) { - highest_ = heatmap_[key_addr.toInt()]; + if (highest_ < heatmap_[event.addr.toInt()]) { + highest_ = heatmap_[event.addr.toInt()]; // if highest_ (and so heatmap_ value related to the key) // is close to overflow: call shiftStats diff --git a/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.h b/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.h index 53774b97..dafa8c03 100644 --- a/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.h +++ b/plugins/Kaleidoscope-Heatmap/src/kaleidoscope/plugin/Heatmap.h @@ -33,7 +33,7 @@ class Heatmap : public Plugin, static uint8_t heat_colors_length; void resetMap(void); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult beforeEachCycle(); // This class' instance has dynamic lifetime @@ -48,7 +48,7 @@ class Heatmap : public Plugin, explicit TransientLEDMode(const Heatmap *parent); void resetMap(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult beforeEachCycle(); protected: diff --git a/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp b/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp index 5667e4ca..45660d55 100644 --- a/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp +++ b/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.cpp @@ -61,40 +61,40 @@ void AlphaSquareEffect::TransientLEDMode::refreshAt(KeyAddr key_addr) { ::LEDControl.setCrgbAt(key_addr, CRGB(0, 0, 0)); } -EventHandlerResult AlphaSquareEffect::onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState) { +EventHandlerResult AlphaSquareEffect::onKeyEvent(KeyEvent &event) { if (!Runtime.has_leds) return EventHandlerResult::OK; if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; - if (keyState & INJECTED) + if (event.state & INJECTED) return EventHandlerResult::OK; - if (mappedKey < Key_A || mappedKey > Key_0) + if (event.key < Key_A || event.key > Key_0) return EventHandlerResult::OK; - if (!keyIsPressed(keyState)) - return EventHandlerResult::OK; + // if (!keyIsPressed(event.state)) + // return EventHandlerResult::OK; uint8_t display_col = 2; auto this_led_mode = ::LEDControl.get_mode(); Key prev_key = this_led_mode->last_key_left_; - if (key_addr.col() < Runtime.device().matrix_columns / 2) { - this_led_mode->last_key_left_ = mappedKey; + if (event.addr.col() < Runtime.device().matrix_columns / 2) { + this_led_mode->last_key_left_ = event.key; this_led_mode->start_time_left_ = Runtime.millisAtCycleStart(); } else { prev_key = this_led_mode->last_key_right_; - this_led_mode->last_key_right_ = mappedKey; + this_led_mode->last_key_right_ = event.key; this_led_mode->start_time_right_ = Runtime.millisAtCycleStart(); display_col = 10; } - if (prev_key != mappedKey) + if (prev_key != event.key) ::AlphaSquare.clear(prev_key, display_col); - ::AlphaSquare.display(mappedKey, display_col); + ::AlphaSquare.display(event.key, display_col); return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h b/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h index fffe6861..dc197fa8 100644 --- a/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h +++ b/plugins/Kaleidoscope-LED-AlphaSquare/src/kaleidoscope/plugin/LED-AlphaSquare/Effect.h @@ -30,7 +30,7 @@ class AlphaSquareEffect : public Plugin, static uint16_t length; - EventHandlerResult onKeyswitchEvent(Key &mappedKey, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyEvent(KeyEvent &event); // This class' instance has dynamic lifetime // diff --git a/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.cpp b/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.cpp index 9a59f93f..e8cc5ac3 100644 --- a/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.cpp +++ b/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.cpp @@ -34,19 +34,20 @@ StalkerEffect::TransientLEDMode::TransientLEDMode(const StalkerEffect *parent) map_{} {} -EventHandlerResult StalkerEffect::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { +EventHandlerResult StalkerEffect::onKeyEvent(KeyEvent &event) { if (!Runtime.has_leds) return EventHandlerResult::OK; - if (!key_addr.isValid()) + if (!event.addr.isValid()) return EventHandlerResult::OK; if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; - if (keyIsPressed(keyState)) { - ::LEDControl.get_mode()->map_[key_addr.toInt()] = 0xff; - } + // The simplest thing to do is trigger on both press and release. The color + // will fade while the key is held, and get restored to full brightness when + // it's released. + ::LEDControl.get_mode()->map_[event.addr.toInt()] = 0xff; return EventHandlerResult::OK; } diff --git a/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.h b/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.h index 8da6022a..6556e7c9 100644 --- a/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.h +++ b/plugins/Kaleidoscope-LED-Stalker/src/kaleidoscope/plugin/LED-Stalker.h @@ -39,7 +39,7 @@ class StalkerEffect : public Plugin, static uint16_t step_length; static cRGB inactive_color; - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); + EventHandlerResult onKeyEvent(KeyEvent &event); // This class' instance has dynamic lifetime // diff --git a/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.cpp b/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.cpp index 4f70e8c9..73e784a9 100644 --- a/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.cpp +++ b/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.cpp @@ -45,20 +45,21 @@ WavepoolEffect::TransientLEDMode::TransientLEDMode(const WavepoolEffect *parent) page_(0) {} -EventHandlerResult WavepoolEffect::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (!key_addr.isValid()) +EventHandlerResult WavepoolEffect::onKeyEvent(KeyEvent &event) { + if (!event.addr.isValid()) return EventHandlerResult::OK; if (::LEDControl.get_mode_index() != led_mode_id_) return EventHandlerResult::OK; - return ::LEDControl.get_mode() - ->onKeyswitchEvent(mapped_key, key_addr, key_state); + return ::LEDControl.get_mode()->onKeyEvent(event); } -EventHandlerResult WavepoolEffect::TransientLEDMode::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state) { - if (keyIsPressed(key_state)) { - surface_[page_][pgm_read_byte(rc2pos + key_addr.toInt())] = 0x7f; +EventHandlerResult WavepoolEffect::TransientLEDMode::onKeyEvent(KeyEvent &event) { + // It might be better to trigger on both toggle-on and toggle-off, but maybe + // just the former. + if (keyIsPressed(event.state)) { + surface_[page_][pgm_read_byte(rc2pos + event.addr.toInt())] = 0x7f; frames_since_event_ = 0; } diff --git a/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.h b/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.h index 1ff8faf9..86e2de61 100644 --- a/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.h +++ b/plugins/Kaleidoscope-LED-Wavepool/src/kaleidoscope/plugin/LED-Wavepool.h @@ -34,7 +34,7 @@ class WavepoolEffect : public Plugin, public: WavepoolEffect(void) {} - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); // ms before idle animation starts after last keypress static uint16_t idle_timeout; @@ -53,7 +53,7 @@ class WavepoolEffect : public Plugin, // explicit TransientLEDMode(const WavepoolEffect *parent); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t key_state); + EventHandlerResult onKeyEvent(KeyEvent &event); protected: diff --git a/plugins/Kaleidoscope-LEDEffects/src/kaleidoscope/plugin/TriColor.cpp b/plugins/Kaleidoscope-LEDEffects/src/kaleidoscope/plugin/TriColor.cpp index 81534cd7..4da14b21 100644 --- a/plugins/Kaleidoscope-LEDEffects/src/kaleidoscope/plugin/TriColor.cpp +++ b/plugins/Kaleidoscope-LEDEffects/src/kaleidoscope/plugin/TriColor.cpp @@ -29,7 +29,7 @@ TriColor::TriColor(cRGB base_color, cRGB mod_color, cRGB esc_color) { void TriColor::TransientLEDMode::update(void) { for (auto key_addr : KeyAddr::all()) { - Key k = Layer.lookup(key_addr); + Key k = Layer.lookupOnActiveLayer(key_addr); // Special keys are always mod_color if (k.getFlags() != 0) { From a2f720e36515b7b7135f556d37f699b98b2c34f7 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:09:30 -0500 Subject: [PATCH 087/108] Add testcases for rollover conditions on Keyboard HID keys Signed-off-by: Michael Richters --- tests/features/rollover/common.h | 27 +++++++++++ tests/features/rollover/rollover.ino | 48 +++++++++++++++++++ tests/features/rollover/sketch.json | 6 +++ tests/features/rollover/test.ktest | 70 ++++++++++++++++++++++++++++ 4 files changed, 151 insertions(+) create mode 100644 tests/features/rollover/common.h create mode 100644 tests/features/rollover/rollover.ino create mode 100644 tests/features/rollover/sketch.json create mode 100644 tests/features/rollover/test.ktest diff --git a/tests/features/rollover/common.h b/tests/features/rollover/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/features/rollover/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/features/rollover/rollover.ino b/tests/features/rollover/rollover.ino new file mode 100644 index 00000000..e474f49c --- /dev/null +++ b/tests/features/rollover/rollover.ino @@ -0,0 +1,48 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + LSHIFT(Key_A), LSHIFT(Key_B), LSHIFT(LCTRL(LALT(Key_X))), ___, ___, ___, ___, + Key_A, Key_B, ___, ___, ___, ___, ___, + Key_LeftShift, Key_RightShift, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/features/rollover/sketch.json b/tests/features/rollover/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/features/rollover/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/features/rollover/test.ktest b/tests/features/rollover/test.ktest new file mode 100644 index 00000000..43f911ba --- /dev/null +++ b/tests/features/rollover/test.ktest @@ -0,0 +1,70 @@ +VERSION 1 + +KEYSWITCH SFT_A 0 0 +KEYSWITCH SFT_B 0 1 +KEYSWITCH MEH_X 0 2 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 +KEYSWITCH LSFT 2 0 +KEYSWITCH RSFT 2 1 + +# ============================================================================== +NAME Key with modifier flag alone + +RUN 4 ms +PRESS SFT_A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_A # The report should contain `shift` + `A` + +RUN 4 ms +RELEASE SFT_A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME Key with multiple modifier flags alone + +RUN 4 ms +PRESS MEH_X +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_LeftControl Key_LeftAlt # The report should contain three modifiers +EXPECT keyboard-report Key_LeftShift Key_LeftControl Key_LeftAlt Key_X # The report should add `X` + +RUN 4 ms +RELEASE MEH_X +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_LeftControl Key_LeftAlt # The report should contain three modifiers +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME No mod flags to mod flags + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # The report should contain `A` + +RUN 4 ms +PRESS SFT_B +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # The report should contain `shift` + `A` +EXPECT keyboard-report Key_LeftShift Key_A Key_B # The report should contain `shift`, `A` & `B` + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_B # The report should contain `shift` + `B` + +RUN 4 ms +RELEASE SFT_B +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms From f13e08c77ae008ecb9230b9e869e87e39dba93ef Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:05:21 -0500 Subject: [PATCH 088/108] Update testcases: events/keyboard-state/release-cleared Signed-off-by: Michael Richters --- .../release-cleared/release-cleared.ino | 12 ++++----- .../keyboard-state/release-cleared/test.ktest | 25 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/features/events/keyboard-state/release-cleared/release-cleared.ino b/tests/features/events/keyboard-state/release-cleared/release-cleared.ino index b3d4c0c5..cc63cb1d 100644 --- a/tests/features/events/keyboard-state/release-cleared/release-cleared.ino +++ b/tests/features/events/keyboard-state/release-cleared/release-cleared.ino @@ -64,16 +64,16 @@ namespace plugin { class ConvertXtoY : public kaleidoscope::Plugin { public: - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) { - if (keyToggledOn(key_state)) { - if (key == Key_X) - key = Key_Y; + EventHandlerResult onKeyEvent(KeyEvent &event) { + if (keyToggledOn(event.state)) { + if (event.key == Key_X) + event.key = Key_Y; } // It should be impossible to get a `Key_X` at this point, because the // previous block changes any `Key_X` to `Key_Y`, which results in the // active keys cache storing that value. On subsequent cycles (while the key // is still pressed), the `key` value should be `Key_Y`. - if (keyIsPressed(key_state) && key == Key_X) { + if (keyIsPressed(event.state) && event.key == Key_X) { std::cerr << "t=" << Runtime.millisAtCycleStart() << ": " << "Error: we shouldn't see a key with value `X`" << std::endl; } @@ -81,7 +81,7 @@ class ConvertXtoY : public kaleidoscope::Plugin { // active keys cache entry should be cleared. If it's not, then a subsequent // press of the same key without the layer shift in effect will still result // in `Key_Y` instead of `Key_A`. - if (keyToggledOff(key_state) && (key == Key_Y)) { + if (keyToggledOff(event.state) && (event.key == Key_Y)) { return EventHandlerResult::EVENT_CONSUMED; } return EventHandlerResult::OK; diff --git a/tests/features/events/keyboard-state/release-cleared/test.ktest b/tests/features/events/keyboard-state/release-cleared/test.ktest index d44f6d4c..778e4b85 100644 --- a/tests/features/events/keyboard-state/release-cleared/test.ktest +++ b/tests/features/events/keyboard-state/release-cleared/test.ktest @@ -7,24 +7,21 @@ KEYSWITCH LAYER_SHIFT 1 0 # Keyboard state array NAME Keyboard state cleared -RUN 10 ms - +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` -RUN 5 ms - +RUN 4 ms RELEASE A RUN 1 cycle EXPECT keyboard-report empty # Report should be empty RUN 5 ms - # Press and hold `ShiftToLayer(1)`, changing the `A` key to `X` PRESS LAYER_SHIFT -RUN 5 ms +RUN 4 ms # Press `X`, which gets converted to `Y` by the `ConvertXtoY` plugin defined in # the sketch. The plugin simply changes the value of the key, which causes it to # get set to `Key_Y` in the keyboard state array. @@ -37,23 +34,25 @@ RUN 5 ms # Release the `X` key (on Layer 1), which has become a `Y` key in the keyboard # state array. This should clear its entry. RELEASE A -RUN 1 cycle -EXPECT keyboard-report empty # Report should be empty - -RUN 5 ms +# No report is expected here, however, because the release should trigger the +# `ConvertXtoY` plugin to return `EVENT_CONSUMED`. The active keys cache should +# be updated, but there shouldn't be a report. +RUN 4 ms # Release `ShiftToLayer(1)`. This should restore the `A` key to its base layer # value on subsequent presses, unless the Macros key gets "stuck" in the # keyboard state array because it returns `EVENT_CONSUMED`. RELEASE LAYER_SHIFT -RUN 5 ms +RUN 1 cycle +# Now we still don't expect a report, because pressing or releasing a +# layer-change key doesn't trigger a keyboard HID report. +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` -RUN 5 ms - +RUN 4 ms RELEASE A RUN 1 cycle EXPECT keyboard-report empty # Report should be empty From c1e64d0a606ddfdd88cd894e39b67ab967c87e36 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:06:08 -0500 Subject: [PATCH 089/108] Update testcases: examples/basic-keypress Signed-off-by: Michael Richters --- .../basic-keypress/basic-keypress.ino | 555 +----------------- tests/examples/basic-keypress/test.ktest | 33 +- 2 files changed, 52 insertions(+), 536 deletions(-) diff --git a/tests/examples/basic-keypress/basic-keypress.ino b/tests/examples/basic-keypress/basic-keypress.ino index 21b6f3cf..4f256516 100644 --- a/tests/examples/basic-keypress/basic-keypress.ino +++ b/tests/examples/basic-keypress/basic-keypress.ino @@ -1,537 +1,46 @@ -// -*- mode: c++ -*- -// Copyright 2016 Keyboardio, inc. -// See "LICENSE" for license details - -#ifndef BUILD_INFORMATION -#define BUILD_INFORMATION "locally built" -#endif - - -/** - * These #include directives pull in the Kaleidoscope firmware core, - * as well as the Kaleidoscope plugins we use in the Model 01's firmware +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 -// The Kaleidoscope core -#include "Kaleidoscope.h" - -// Support for storing the keymap in EEPROM -#include "Kaleidoscope-EEPROM-Settings.h" -#include "Kaleidoscope-EEPROM-Keymap.h" - -// Support for communicating with the host via a simple Serial protocol -#include "Kaleidoscope-FocusSerial.h" - -// Support for keys that move the mouse -#include "Kaleidoscope-MouseKeys.h" - -// Support for macros -#include "Kaleidoscope-Macros.h" - -// Support for controlling the keyboard's LEDs -#include "Kaleidoscope-LEDControl.h" - -// Support for "Numpad" mode, which is mostly just the Numpad specific LED mode -#include "Kaleidoscope-NumPad.h" - -// Support for the "Boot greeting" effect, which pulses the 'LED' button for 10s -// when the keyboard is connected to a computer (or that computer is powered on) -#include "Kaleidoscope-LEDEffect-BootGreeting.h" - -// Support for LED modes that set all LEDs to a single color -#include "Kaleidoscope-LEDEffect-SolidColor.h" - -// Support for an LED mode that makes all the LEDs 'breathe' -#include "Kaleidoscope-LEDEffect-Breathe.h" - -// Support for an LED mode that makes a red pixel chase a blue pixel across the keyboard -#include "Kaleidoscope-LEDEffect-Chase.h" - -// Support for LED modes that pulse the keyboard's LED in a rainbow pattern -#include "Kaleidoscope-LEDEffect-Rainbow.h" - -// Support for an LED mode that lights up the keys as you press them -#include "Kaleidoscope-LED-Stalker.h" - -// Support for an LED mode that prints the keys you press in letters 4px high -#include "Kaleidoscope-LED-AlphaSquare.h" - -// Support for an LED mode that lets one configure per-layer color maps -#include "Kaleidoscope-Colormap.h" - -// Support for Keyboardio's internal keyboard testing mode -#include "Kaleidoscope-HardwareTestMode.h" - -// Support for host power management (suspend & wakeup) -#include "Kaleidoscope-HostPowerManagement.h" - -// Support for magic combos (key chords that trigger an action) -#include "Kaleidoscope-MagicCombo.h" - -// Support for USB quirks, like changing the key state report protocol -#include "Kaleidoscope-USB-Quirks.h" - -/** This 'enum' is a list of all the macros used by the Model 01's firmware - * The names aren't particularly important. What is important is that each - * is unique. - * - * These are the names of your macros. They'll be used in two places. - * The first is in your keymap definitions. There, you'll use the syntax - * `M(MACRO_NAME)` to mark a specific keymap position as triggering `MACRO_NAME` - * - * The second usage is in the 'switch' statement in the `macroAction` function. - * That switch statement actually runs the code associated with a macro when - * a macro key is pressed. - */ - -enum { MACRO_VERSION_INFO, - MACRO_ANY - }; - - - -/** The Model 01's key layouts are defined as 'keymaps'. By default, there are three - * keymaps: The standard QWERTY keymap, the "Function layer" keymap and the "Numpad" - * keymap. - * - * Each keymap is defined as a list using the 'KEYMAP_STACKED' macro, built - * of first the left hand's layout, followed by the right hand's layout. - * - * Keymaps typically consist mostly of `Key_` definitions. There are many, many keys - * defined as part of the USB HID Keyboard specification. You can find the names - * (if not yet the explanations) for all the standard `Key_` defintions offered by - * Kaleidoscope in these files: - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keyboard.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/consumerctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/sysctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keymaps.h - * - * Additional things that should be documented here include - * using ___ to let keypresses fall through to the previously active layer - * using XXX to mark a keyswitch as 'blocked' on this layer - * using ShiftToLayer() and LockLayer() keys to change the active keymap. - * keeping NUM and FN consistent and accessible on all layers - * - * The PROG key is special, since it is how you indicate to the board that you - * want to flash the firmware. However, it can be remapped to a regular key. - * When the keyboard boots, it first looks to see whether the PROG key is held - * down; if it is, it simply awaits further flashing instructions. If it is - * not, it continues loading the rest of the firmware and the keyboard - * functions normally, with whatever binding you have set to PROG. More detail - * here: https://community.keyboard.io/t/how-the-prog-key-gets-you-into-the-bootloader/506/8 - * - * The "keymaps" data structure is a list of the keymaps compiled into the firmware. - * The order of keymaps in the list is important, as the ShiftToLayer(#) and LockLayer(#) - * macros switch to key layers based on this list. - * - * - - * A key defined as 'ShiftToLayer(FUNCTION)' will switch to FUNCTION while held. - * Similarly, a key defined as 'LockLayer(NUMPAD)' will switch to NUMPAD when tapped. - */ - -/** - * Layers are "0-indexed" -- That is the first one is layer 0. The second one is layer 1. - * The third one is layer 2. - * This 'enum' lets us use names like QWERTY, FUNCTION, and NUMPAD in place of - * the numbers 0, 1 and 2. - * - */ - -enum { PRIMARY, NUMPAD, FUNCTION }; // layers - - -/** - * To change your keyboard's layout from QWERTY to DVORAK or COLEMAK, comment out the line - * - * #define PRIMARY_KEYMAP_QWERTY - * - * by changing it to - * - * // #define PRIMARY_KEYMAP_QWERTY - * - * Then uncomment the line corresponding to the layout you want to use. - * - */ - -#define PRIMARY_KEYMAP_QWERTY -// #define PRIMARY_KEYMAP_COLEMAK -// #define PRIMARY_KEYMAP_DVORAK -// #define PRIMARY_KEYMAP_CUSTOM - - - -/* This comment temporarily turns off astyle's indent enforcement - * so we can make the keymaps actually resemble the physical key layout better - */ // *INDENT-OFF* - KEYMAPS( - -#if defined (PRIMARY_KEYMAP_QWERTY) - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_DVORAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Quote, Key_Comma, Key_Period, Key_P, Key_Y, Key_Tab, - Key_PageUp, Key_A, Key_O, Key_E, Key_U, Key_I, - Key_PageDown, Key_Semicolon, Key_Q, Key_J, Key_K, Key_X, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_F, Key_G, Key_C, Key_R, Key_L, Key_Slash, - Key_D, Key_H, Key_T, Key_N, Key_S, Key_Minus, - Key_RightAlt, Key_B, Key_M, Key_W, Key_V, Key_Z, Key_Equals, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_COLEMAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_F, Key_P, Key_G, Key_Tab, - Key_PageUp, Key_A, Key_R, Key_S, Key_T, Key_D, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_J, Key_L, Key_U, Key_Y, Key_Semicolon, Key_Equals, - Key_H, Key_N, Key_E, Key_I, Key_O, Key_Quote, - Key_RightAlt, Key_K, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_CUSTOM) - // Edit this keymap to make a custom layout - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#else - -#error "No default keymap defined. You should make sure that you have a line like '#define PRIMARY_KEYMAP_QWERTY' in your sketch" - -#endif - - - - [NUMPAD] = KEYMAP_STACKED - (___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, - ___, - - M(MACRO_VERSION_INFO), ___, Key_7, Key_8, Key_9, Key_KeypadSubtract, ___, - ___, ___, Key_4, Key_5, Key_6, Key_KeypadAdd, ___, - ___, Key_1, Key_2, Key_3, Key_Equals, ___, - ___, ___, Key_0, Key_Period, Key_KeypadMultiply, Key_KeypadDivide, Key_Enter, - ___, ___, ___, ___, - ___), - - [FUNCTION] = KEYMAP_STACKED - (___, Key_F1, Key_F2, Key_F3, Key_F4, Key_F5, Key_CapsLock, - Key_Tab, ___, Key_mouseUp, ___, Key_mouseBtnR, Key_mouseWarpEnd, Key_mouseWarpNE, - Key_Home, Key_mouseL, Key_mouseDn, Key_mouseR, Key_mouseBtnL, Key_mouseWarpNW, - Key_End, Key_PrintScreen, Key_Insert, ___, Key_mouseBtnM, Key_mouseWarpSW, Key_mouseWarpSE, - ___, Key_Delete, ___, ___, - ___, - - Consumer_ScanPreviousTrack, Key_F6, Key_F7, Key_F8, Key_F9, Key_F10, Key_F11, - Consumer_PlaySlashPause, Consumer_ScanNextTrack, Key_LeftCurlyBracket, Key_RightCurlyBracket, Key_LeftBracket, Key_RightBracket, Key_F12, - Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___, - Key_PcApplication, Consumer_Mute, Consumer_VolumeDecrement, Consumer_VolumeIncrement, ___, Key_Backslash, Key_Pipe, - ___, ___, Key_Enter, ___, - ___) -) // KEYMAPS( - -/* Re-enable astyle's indent enforcement */ + [0] = KEYMAP_STACKED + ( + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, Key_A, Key_S, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) // *INDENT-ON* -/** versionInfoMacro handles the 'firmware version info' macro - * When a key bound to the macro is pressed, this macro - * prints out the firmware build information as virtual keystrokes - */ - -static void versionInfoMacro(uint8_t keyState) { - if (keyToggledOn(keyState)) { - Macros.type(PSTR("Keyboardio Model 01 - Kaleidoscope ")); - Macros.type(PSTR(BUILD_INFORMATION)); - } -} - -/** anyKeyMacro is used to provide the functionality of the 'Any' key. - * - * When the 'any key' macro is toggled on, a random alphanumeric key is - * selected. While the key is held, the function generates a synthetic - * keypress event repeating that randomly selected key. - * - */ - -static void anyKeyMacro(uint8_t keyState) { - static Key lastKey; - bool toggledOn = false; - if (keyToggledOn(keyState)) { - lastKey.setKeyCode(Key_A.getKeyCode() + (uint8_t)(millis() % 36)); - toggledOn = true; - } - - if (keyIsPressed(keyState)) - Kaleidoscope.hid().keyboard().pressKey(lastKey, toggledOn); -} - - -/** macroAction dispatches keymap events that are tied to a macro - to that macro. It takes two uint8_t parameters. - - The first is the macro being called (the entry in the 'enum' earlier in this file). - The second is the state of the keyswitch. You can use the keyswitch state to figure out - if the key has just been toggled on, is currently pressed or if it's just been released. - - The 'switch' statement should have a 'case' for each entry of the macro enum. - Each 'case' statement should call out to a function to handle the macro in question. - - */ - -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; - } - return MACRO_NONE; -} - - - -// These 'solid' color effect definitions define a rainbow of -// LED color modes calibrated to draw 500mA or less on the -// Keyboardio Model 01. - -static constexpr uint8_t solid_red_level = 160; -static kaleidoscope::plugin::LEDSolidColor solidRed(solid_red_level, 0, 0); -static kaleidoscope::plugin::LEDSolidColor solidOrange(140, 70, 0); -static kaleidoscope::plugin::LEDSolidColor solidYellow(130, 100, 0); -static kaleidoscope::plugin::LEDSolidColor solidGreen(0, 160, 0); -static kaleidoscope::plugin::LEDSolidColor solidBlue(0, 70, 130); -static kaleidoscope::plugin::LEDSolidColor solidIndigo(0, 0, 170); -static kaleidoscope::plugin::LEDSolidColor solidViolet(130, 0, 120); - -/** toggleLedsOnSuspendResume toggles the LEDs off when the host goes to sleep, - * and turns them back on when it wakes up. - */ -void toggleLedsOnSuspendResume(kaleidoscope::plugin::HostPowerManagement::Event event) { - switch (event) { - case kaleidoscope::plugin::HostPowerManagement::Suspend: - LEDControl.disable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Resume: - LEDControl.enable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Sleep: - break; - } -} - -/** hostPowerManagementEventHandler dispatches power management events (suspend, - * resume, and sleep) to other functions that perform action based on these - * events. - */ -void hostPowerManagementEventHandler(kaleidoscope::plugin::HostPowerManagement::Event event) { - toggleLedsOnSuspendResume(event); -} - -/** A tiny wrapper, to be used by MagicCombo. - * This simply toggles the keyboard protocol via USBQuirks, and wraps it within - * a function with an unused argument, to match what MagicCombo expects. - */ -static void toggleKeyboardProtocol(uint8_t combo_index) { - USBQuirks.toggleKeyboardProtocol(); -} - -/** Magic combo list, a list of key combo and action pairs the firmware should - * recognise. - */ -USE_MAGIC_COMBOS({.action = toggleKeyboardProtocol, - // Left Fn + Esc + Shift - .keys = { R3C6, R2C6, R3C7 } - }); - -// First, tell Kaleidoscope which plugins you want to use. -// The order can be important. For example, LED effects are -// added in the order they're listed here. -KALEIDOSCOPE_INIT_PLUGINS( - // The EEPROMSettings & EEPROMKeymap plugins make it possible to have an - // editable keymap in EEPROM. - EEPROMSettings, - EEPROMKeymap, - - // Focus allows bi-directional communication with the host, and is the - // interface through which the keymap in EEPROM can be edited. - Focus, - - // FocusSettingsCommand adds a few Focus commands, intended to aid in - // changing some settings of the keyboard, such as the default layer (via the - // `settings.defaultLayer` command) - FocusSettingsCommand, - - // FocusEEPROMCommand adds a set of Focus commands, which are very helpful in - // both debugging, and in backing up one's EEPROM contents. - FocusEEPROMCommand, - - // The boot greeting effect pulses the LED button for 10 seconds after the - // keyboard is first connected - BootGreetingEffect, - - // The hardware test mode, which can be invoked by tapping Prog, LED and the - // left Fn button at the same time. - HardwareTestMode, - - // LEDControl provides support for other LED modes - LEDControl, - - // We start with the LED effect that turns off all the LEDs. - LEDOff, - - // The rainbow effect changes the color of all of the keyboard's keys at the same time - // running through all the colors of the rainbow. - LEDRainbowEffect, - - // The rainbow wave effect lights up your keyboard with all the colors of a rainbow - // and slowly moves the rainbow across your keyboard - LEDRainbowWaveEffect, - - // The chase effect follows the adventure of a blue pixel which chases a red pixel across - // your keyboard. Spoiler: the blue pixel never catches the red pixel - LEDChaseEffect, - - // These static effects turn your keyboard's LEDs a variety of colors - solidRed, solidOrange, solidYellow, solidGreen, solidBlue, solidIndigo, solidViolet, - - // The breathe effect slowly pulses all of the LEDs on your keyboard - LEDBreatheEffect, - - // The AlphaSquare effect prints each character you type, using your - // keyboard's LEDs as a display - AlphaSquareEffect, - - // The stalker effect lights up the keys you've pressed recently - StalkerEffect, - - // The Colormap effect makes it possible to set up per-layer colormaps - ColormapEffect, - - // The numpad plugin is responsible for lighting up the 'numpad' mode - // with a custom LED effect - NumPad, - - // The macros plugin adds support for macros - Macros, - - // The MouseKeys plugin lets you add keys to your keymap which move the mouse. - MouseKeys, - - // The HostPowerManagement plugin allows us to turn LEDs off when then host - // goes to sleep, and resume them when it wakes up. - HostPowerManagement, - - // The MagicCombo plugin lets you use key combinations to trigger custom - // actions - a bit like Macros, but triggered by pressing multiple keys at the - // same time. - MagicCombo, - - // The USBQuirks plugin lets you do some things with USB that we aren't - // comfortable - or able - to do automatically, but can be useful - // nevertheless. Such as toggling the key report protocol between Boot (used - // by BIOSes) and Report (NKRO). - USBQuirks -); - -/** The 'setup' function is one of the two standard Arduino sketch functions. - * It's called when your keyboard first powers up. This is where you set up - * Kaleidoscope and any plugins. - */ void setup() { - // First, call Kaleidoscope's internal setup function Kaleidoscope.setup(); - - // While we hope to improve this in the future, the NumPad plugin - // needs to be explicitly told which keymap layer is your numpad layer - NumPad.numPadLayer = NUMPAD; - - // We configure the AlphaSquare effect to use RED letters - AlphaSquare.color = CRGB(255, 0, 0); - - // We set the brightness of the rainbow effects to 150 (on a scale of 0-255) - // This draws more than 500mA, but looks much nicer than a dimmer effect - LEDRainbowEffect.brightness(150); - LEDRainbowWaveEffect.brightness(150); - - // The LED Stalker mode has a few effects. The one we like is called - // 'BlazingTrail'. For details on other options, see - // https://github.com/keyboardio/Kaleidoscope/blob/master/doc/plugin/LED-Stalker.md - StalkerEffect.variant = STALKER(BlazingTrail); - - // We want to make sure that the firmware starts with LED effects off - // This avoids over-taxing devices that don't have a lot of power to share - // with USB devices - LEDOff.activate(); - - // To make the keymap editable without flashing new firmware, we store - // additional layers in EEPROM. For now, we reserve space for five layers. If - // one wants to use these layers, just set the default layer to one in EEPROM, - // by using the `settings.defaultLayer` Focus command, or by using the - // `keymap.onlyCustom` command to use EEPROM layers only. - EEPROMKeymap.setup(5); - - // We need to tell the Colormap plugin how many layers we want to have custom - // maps for. To make things simple, we set it to five layers, which is how - // many editable layers we have (see above). - ColormapEffect.max_layers(5); } -/** loop is the second of the standard Arduino sketch functions. - * As you might expect, it runs in a loop, never exiting. - * - * For Kaleidoscope-based keyboard firmware, you usually just want to - * call Kaleidoscope.loop(); and not do anything custom here. - */ - void loop() { Kaleidoscope.loop(); } diff --git a/tests/examples/basic-keypress/test.ktest b/tests/examples/basic-keypress/test.ktest index 8f40a91c..eb774156 100644 --- a/tests/examples/basic-keypress/test.ktest +++ b/tests/examples/basic-keypress/test.ktest @@ -1,27 +1,34 @@ VERSION 1 -NAME Keys Active When pressed -# Comment lines end up as comments in the generated source -KEYSWITCH switchA 2 1 -KEYSWITCH switchS 2 2 +KEYSWITCH A 2 1 +KEYSWITCH S 2 2 -PRESS switchA -RUN 1 cycles +# ============================================================================== +NAME Keys active when pressed + +RUN 4 ms +PRESS A +RUN 1 cycle EXPECT keyboard-report Key_A # Key A should be pressed -RELEASE switchA +RUN 4 ms +RELEASE A RUN 1 cycle EXPECT keyboard-report empty # No keys should be pressed - -PRESS switchA -PRESS switchS +RUN 4 ms +PRESS A +PRESS S RUN 1 ms -EXPECT keyboard-report Key_A # TODO modflag rollover prevention inapropriately sends two reports here. It's not harmful, but is annoying +EXPECT keyboard-report Key_A # Key A should be pressed EXPECT keyboard-report Key_A, Key_S # A and S should be pressed -RELEASE switchA -RELEASE switchS +RUN 4 ms +RELEASE A +RELEASE S RUN 1 cycle +# A is released first because of scan order +EXPECT keyboard-report Key_S # Key S should be pressed EXPECT keyboard-report empty # No keys should be pressed +RUN 5 ms From 78ec964b09513971c5e1a24ad779472d7139bdd5 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:06:32 -0500 Subject: [PATCH 090/108] Update testcases: events/keyboard-state/macros Signed-off-by: Michael Richters --- .../events/keyboard-state/macros/macros.ino | 13 +++----- .../events/keyboard-state/macros/test.ktest | 32 ++++++++----------- 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/tests/features/events/keyboard-state/macros/macros.ino b/tests/features/events/keyboard-state/macros/macros.ino index 5f98bf6b..ca67cb94 100644 --- a/tests/features/events/keyboard-state/macros/macros.ino +++ b/tests/features/events/keyboard-state/macros/macros.ino @@ -19,10 +19,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED @@ -60,12 +56,13 @@ KEYMAPS( ) // *INDENT-ON* -const macro_t *macroAction(uint8_t index, uint8_t key_state) { - if (keyToggledOn(key_state)) { +const macro_t *macroAction(uint8_t index, KeyEvent &event) { + if (keyToggledOn(event.state)) { switch (index) { case 0: - Kaleidoscope.hid().keyboard().pressKey(Key_Y); - break; + return MACRO(D(Y)); + //event.key = Key_Y; + //break; } } return MACRO_NONE; diff --git a/tests/features/events/keyboard-state/macros/test.ktest b/tests/features/events/keyboard-state/macros/test.ktest index 2a8754e7..30a190b1 100644 --- a/tests/features/events/keyboard-state/macros/test.ktest +++ b/tests/features/events/keyboard-state/macros/test.ktest @@ -1,51 +1,47 @@ VERSION 1 -KEYSWITCH A 0 0 -KEYSWITCH LAYER_SHIFT 1 0 +KEYSWITCH A 0 0 +KEYSWITCH L_1 1 0 # ============================================================================== # Keyboard state array NAME Keyboard state array cleared -RUN 10 ms - +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` -RUN 5 ms - +RUN 4 ms RELEASE A RUN 1 cycle EXPECT keyboard-report empty # Report should be empty RUN 5 ms - # Press and hold `ShiftToLayer(1)`, changing the `A` key to `X` -PRESS LAYER_SHIFT -RUN 5 ms +PRESS L_1 +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_Y # Report should contain only `Y` -RUN 1 cycle -EXPECT keyboard-report empty # Report should be empty RUN 5 ms +RELEASE L_1 +RUN 4 ms RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty -RUN 5 ms - -RELEASE LAYER_SHIFT -RUN 5 ms - +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` -RUN 5 ms - +RUN 4 ms RELEASE A RUN 1 cycle EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms From 836e4af8aeb2510829fe1bc0300f73888c9412c2 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:08:55 -0500 Subject: [PATCH 091/108] Update testcases for Macros plugin Signed-off-by: Michael Richters --- .../activation-order/activation-order.ino | 6 +- .../layers/activation-order/test.ktest | 145 ++++++++++++ .../layers/activation-order/test/testcase.cpp | 206 ------------------ tests/plugins/Macros/basic/basic.ino | 18 +- tests/plugins/Macros/basic/test.ktest | 97 +++++++++ tests/plugins/Macros/basic/test/testcase.cpp | 136 ------------ 6 files changed, 256 insertions(+), 352 deletions(-) create mode 100644 tests/features/layers/activation-order/test.ktest delete mode 100644 tests/features/layers/activation-order/test/testcase.cpp create mode 100644 tests/plugins/Macros/basic/test.ktest delete mode 100644 tests/plugins/Macros/basic/test/testcase.cpp diff --git a/tests/features/layers/activation-order/activation-order.ino b/tests/features/layers/activation-order/activation-order.ino index 60f39a09..7e9d491a 100644 --- a/tests/features/layers/activation-order/activation-order.ino +++ b/tests/features/layers/activation-order/activation-order.ino @@ -77,10 +77,10 @@ KEYMAPS( KALEIDOSCOPE_INIT_PLUGINS(Macros); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case 0: - if (keyToggledOn(keyState)) + if (keyToggledOn(event.state)) Layer.deactivate(0); else Layer.activate(0); diff --git a/tests/features/layers/activation-order/test.ktest b/tests/features/layers/activation-order/test.ktest new file mode 100644 index 00000000..a9ab0a09 --- /dev/null +++ b/tests/features/layers/activation-order/test.ktest @@ -0,0 +1,145 @@ +VERSION 1 + +KEYSWITCH TOP_LEFT 0 0 +KEYSWITCH TOP_RIGHT 0 15 +KEYSWITCH PALM_LEFT 3 6 +KEYSWITCH PALM_RIGHT 3 9 +KEYSWITCH LEFT_THUMB 3 7 + +# ============================================================================== +NAME Layer Activation Order base layer has not regressed + +RUN 4 ms +PRESS TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_0 + +RUN 4 ms +RELEASE TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME Layer Activation Order shift to layer 1 + +RUN 4 ms +PRESS PALM_LEFT +RUN 1 cycle + +RUN 4 ms +PRESS TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_1 + +RUN 4 ms +RELEASE TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 4 ms +RELEASE PALM_LEFT +RUN 1 cycle + +RUN 5 ms + +# ============================================================================== +NAME Layer Activation Order shifting with caching + +RUN 4 ms +PRESS TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_0 + +# activate layer 1 +RUN 4 ms +PRESS PALM_LEFT +RUN 1 cycle + +RUN 4 ms +PRESS TOP_RIGHT +RUN 1 cycle +EXPECT keyboard-report Key_0 Key_1 + +RUN 4 ms +RELEASE TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_1 + +RUN 4 ms +RELEASE TOP_RIGHT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 4 ms +PRESS TOP_RIGHT +RUN 1 cycle +EXPECT keyboard-report Key_1 + +RUN 4 ms +RELEASE TOP_RIGHT +RUN 1 cycle +EXPECT keyboard-report empty + +# deactivate layer 1 +RUN 4 ms +RELEASE PALM_LEFT +RUN 1 cycle + +RUN 5 ms + +# ============================================================================== +NAME Layer Activation Order ordering + +# activate layer 2 +RUN 4 ms +PRESS PALM_RIGHT +RUN 1 cycle + +RUN 4 ms +PRESS TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_2 + +# activate layer 1 (on top of layer 2) +RUN 4 ms +PRESS PALM_LEFT +RUN 1 cycle + +RUN 4 ms +PRESS TOP_RIGHT +RUN 1 cycle +EXPECT keyboard-report Key_1 Key_2 + +RUN 4 ms +RELEASE PALM_RIGHT +RELEASE PALM_LEFT +RELEASE TOP_RIGHT +RELEASE TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_1 +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME Layer Activation Order layer 0 fallback + +# Use a Macro to deactivate layer 0 +RUN 4 ms +PRESS LEFT_THUMB +RUN 1 cycle + +RUN 4 ms +PRESS TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report Key_0 + +RUN 4 ms +RELEASE LEFT_THUMB +RELEASE TOP_LEFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms diff --git a/tests/features/layers/activation-order/test/testcase.cpp b/tests/features/layers/activation-order/test/testcase.cpp deleted file mode 100644 index d1e39e25..00000000 --- a/tests/features/layers/activation-order/test/testcase.cpp +++ /dev/null @@ -1,206 +0,0 @@ -/* -*- mode: c++ -*- - * Copyright (C) 2020 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 "testing/setup-googletest.h" - -SETUP_GOOGLETEST(); - -namespace kaleidoscope { -namespace testing { -namespace { - -using ::testing::IsEmpty; - -class LayerActivationOrder : public VirtualDeviceTest { - public: - const KeyAddr KEYSWITCH_TOP_LEFT = KeyAddr{0, 0}; // layer-dependent key - const KeyAddr KEYSWITCH_TOP_RIGHT = KeyAddr{0, 15}; // layer-dependent key - const KeyAddr KEYSWITCH_LEFT_PALM = KeyAddr{3, 6}; // ShiftToLayer(1) - const KeyAddr KEYSWITCH_RIGHT_PALM = KeyAddr{3, 9}; // ShiftToLayer(2) - const KeyAddr KEYSWITCH_LEFT_THUMB_RIGHTMOST = KeyAddr{3, 7}; // L0 deactivate macro - - const Key LAYER0_KEY = Key_0; - const Key LAYER1_KEY = Key_1; - const Key LAYER2_KEY = Key_2; - - void pressKeyswitch(const KeyAddr& addr) { - sim_.Press(addr.row(), addr.col()); - } - - void releaseKeyswitch(const KeyAddr& addr) { - sim_.Release(addr.row(), addr.col()); - } - - auto pressKeyswitchAndRunCycle(const KeyAddr& addr) { - pressKeyswitch(addr); - return RunCycle(); - } - - auto releaseKeyswitchAndRunCycle(const KeyAddr& addr) { - releaseKeyswitch(addr); - return RunCycle(); - } - - void assertSingleKeyboardReportContaining(std::unique_ptr &state, Key k) { - ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1); - EXPECT_THAT( - state->HIDReports()->Keyboard(0).ActiveKeycodes(), - Contains(k)); - } - - void assertSingleKeyboardReportNotContaining(std::unique_ptr &state, Key k) { - ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1); - EXPECT_THAT( - state->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::Not(Contains(k))); - } - - void assertSingleEmptyReport(std::unique_ptr &state) { - ASSERT_EQ(state->HIDReports()->Keyboard().size(), 1); - EXPECT_THAT( - state->HIDReports()->Keyboard(0).ActiveKeycodes(), - IsEmpty()); - } - - void assertNoReport(std::unique_ptr &state) { - ASSERT_EQ(state->HIDReports()->Keyboard().size(), 0); - } - - void assertNoReportAfterCycle() { - auto state = RunCycle(); - assertNoReport(state); - } - - void TestPressAndRelease(const KeyAddr& addr, Key k) { - auto state = pressKeyswitchAndRunCycle(addr); - assertSingleKeyboardReportContaining(state, k); - - state = releaseKeyswitchAndRunCycle(addr); - assertSingleEmptyReport(state); - - assertNoReportAfterCycle(); - } -}; - -TEST_F(LayerActivationOrder, BaseLayerHasNotRegressed) { - TestPressAndRelease(KEYSWITCH_TOP_LEFT, LAYER0_KEY); -} - -TEST_F(LayerActivationOrder, ShifToLayerOne) { - // Pressing (KEYSWITCH_LEFT_PALM) shifts to Layer 1, and we stay there until release. - auto state = pressKeyswitchAndRunCycle(KEYSWITCH_LEFT_PALM); - TestPressAndRelease(KEYSWITCH_TOP_LEFT, LAYER1_KEY); - - // Releasing (KEYSWITCH_LEFT_PALM) gets us back to the base layer - state = releaseKeyswitchAndRunCycle(KEYSWITCH_LEFT_PALM); - TestPressAndRelease(KEYSWITCH_TOP_LEFT, LAYER0_KEY); -} - -TEST_F(LayerActivationOrder, ShiftingWithCaching) { - // Pressing (KEYSWITCH_TOP_LEFT) will activate the key on layer 0 - auto state = pressKeyswitchAndRunCycle(KEYSWITCH_TOP_LEFT); - assertSingleKeyboardReportContaining(state, LAYER0_KEY); - - // Pressing (KEYSWITCH_LEFT_PALM) will switch to Layer 1 - state = pressKeyswitchAndRunCycle(KEYSWITCH_LEFT_PALM); - - // ...since we're still pressing (KEYSWITCH_TOP_LEFT), and there was no change - // in the HID states, we shouldn't emit a report. - assertNoReport(state); - - // Pressing (KEYSWITCH_TOP_RIGHT), the report shall contain keys from both - // layer 0 and layer1, because we started holding the layer 0 key prior to - // switching layers, so it's code should remain cached. - state = pressKeyswitchAndRunCycle(KEYSWITCH_TOP_RIGHT); - assertSingleKeyboardReportContaining(state, LAYER0_KEY); - assertSingleKeyboardReportContaining(state, LAYER1_KEY); - - // Releasing (KEYSWITCH_TOP_LEFT), the report should now contain the key from - // layer1 only, and should not contain the layer0 key anymore. - state = releaseKeyswitchAndRunCycle(KEYSWITCH_TOP_LEFT); - assertSingleKeyboardReportContaining(state, LAYER1_KEY); - assertSingleKeyboardReportNotContaining(state, LAYER0_KEY); - - // Release (KEYSWITCH_TOP_RIGHT) - state = releaseKeyswitchAndRunCycle(KEYSWITCH_TOP_RIGHT); - - // Test the layer 1 key in isolation again - TestPressAndRelease(KEYSWITCH_TOP_LEFT, LAYER1_KEY); - - // Release the layer key as well. - state = releaseKeyswitchAndRunCycle(KEYSWITCH_LEFT_PALM); - - // Since the layer key release is internal to us, we shouldn't send a report. - assertNoReport(state); -} - -TEST_F(LayerActivationOrder, Ordering) { - // Pressing (KEYSWITCH_RIGHT_PALM) will switch to Layer 2 - auto state = pressKeyswitchAndRunCycle(KEYSWITCH_RIGHT_PALM); - - // Pressing (KEYSWITCH_TOP_LEFT) will activate a key on layer 2 - state = pressKeyswitchAndRunCycle(KEYSWITCH_TOP_LEFT); - assertSingleKeyboardReportContaining(state, LAYER2_KEY); - - // Pressing (KEYSWITCH_LEFT_PALM) will activate Layer 1 - state = pressKeyswitchAndRunCycle(KEYSWITCH_LEFT_PALM); - - // Pressing (KEYSWITCH_TOP_RIGHT) will activate the layer 1 key now, due to - // activation ordering. - state = pressKeyswitchAndRunCycle(KEYSWITCH_TOP_RIGHT); - - // We should have both the layer 1 and the layer 2 key active, because we're - // holding both. - assertSingleKeyboardReportContaining(state, LAYER1_KEY); - assertSingleKeyboardReportContaining(state, LAYER2_KEY); - - // Releaseing all held keys, we should get an empty report. - releaseKeyswitch(KEYSWITCH_TOP_LEFT); - releaseKeyswitch(KEYSWITCH_TOP_RIGHT); - releaseKeyswitch(KEYSWITCH_LEFT_PALM); - releaseKeyswitch(KEYSWITCH_RIGHT_PALM); - state = RunCycle(); - - assertSingleEmptyReport(state); - - // One more cycle, and we should generate no report at all - state = RunCycle(); - assertNoReport(state); -} - -TEST_F(LayerActivationOrder, LayerZero) { - // Pressing the rightmost of the left thumb keys should deactivate layer 0 - auto state = pressKeyswitchAndRunCycle(KEYSWITCH_LEFT_THUMB_RIGHTMOST); - - // Pressing KEYSWITCH_TOP_LEFT should fall back to activating the key on layer 0 - state = pressKeyswitchAndRunCycle(KEYSWITCH_TOP_LEFT); - assertSingleKeyboardReportContaining(state, LAYER0_KEY); - - // Releasing all keys should generate a single empty report - releaseKeyswitch(KEYSWITCH_TOP_LEFT); - releaseKeyswitch(KEYSWITCH_LEFT_THUMB_RIGHTMOST); - state = RunCycle(); - - assertSingleEmptyReport(state); - - // Afterwards, we should generate no more reports. - state = RunCycle(); - assertNoReport(state); -} - -} // namespace -} // namespace testing -} // namespace kaleidoscope diff --git a/tests/plugins/Macros/basic/basic.ino b/tests/plugins/Macros/basic/basic.ino index 59c552ff..d69bd1db 100644 --- a/tests/plugins/Macros/basic/basic.ino +++ b/tests/plugins/Macros/basic/basic.ino @@ -21,7 +21,7 @@ KEYMAPS( [0] = KEYMAP_STACKED ( - M(0), M(1), M(255), ___, ___, ___, ___, + M(0), M(1), M(255), M(2), M(3), ___, ___, Key_X, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, @@ -40,17 +40,21 @@ KEYMAPS( ) // *INDENT-ON* -const macro_t *macroAction(uint8_t index, uint8_t key_state) { - if (keyToggledOn(key_state)) { - switch (index) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (keyToggledOn(event.state)) { + switch (macro_id) { case 0: - Kaleidoscope.hid().keyboard().pressKey(Key_A); + Macros.type(PSTR("a")); break; case 1: - Kaleidoscope.hid().keyboard().pressKey(Key_B); + Macros.type(PSTR("abc")); break; + case 2: + return MACRO(D(A), T(C), U(A), T(B)); + case 3: + return MACRO(D(A), D(B)); case 255: - Kaleidoscope.hid().keyboard().pressKey(Key_C); + Macros.type(PSTR("c")); break; } } diff --git a/tests/plugins/Macros/basic/test.ktest b/tests/plugins/Macros/basic/test.ktest new file mode 100644 index 00000000..07d735ce --- /dev/null +++ b/tests/plugins/Macros/basic/test.ktest @@ -0,0 +1,97 @@ +VERSION 1 + +KEYSWITCH M_0 0 0 +KEYSWITCH M_1 0 1 +KEYSWITCH M_2 0 2 +KEYSWITCH M_3 0 3 +KEYSWITCH M_4 0 4 +KEYSWITCH X 1 0 + +# ============================================================================== +NAME Macro index 0 + +RUN 5 ms +PRESS M_0 +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms +RELEASE M_0 +RUN 1 cycle + +# ============================================================================== +NAME Macro index 1 + +RUN 5 ms +PRESS M_1 +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_B # Report should contain only `B` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_C # Report should contain only `C` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms +RELEASE M_1 +RUN 1 cycle + +# ============================================================================== +NAME Macro index 2 + +RUN 5 ms +PRESS M_3 +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` +EXPECT keyboard-report Key_A Key_C # Report should contain `A` & `C` +EXPECT keyboard-report Key_A # Report should contain only `A` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_B # Report should contain only `B` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms +RELEASE M_3 +RUN 1 cycle + +# ============================================================================== +NAME Macro index 3 + +RUN 5 ms +PRESS M_4 +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` +EXPECT keyboard-report Key_A Key_B # Report should contain `A` & `B` + +RUN 5 ms +RELEASE M_4 +RUN 1 cycle +EXPECT keyboard-report Key_B # Report should contain only `B` +EXPECT keyboard-report empty # Report should be empty +RUN 1 cycle + +# ============================================================================== +NAME Macro index 255 + +RUN 5 ms +PRESS M_2 +RUN 1 cycle +EXPECT keyboard-report Key_C # Report should contain only `C` +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms +RELEASE M_2 +RUN 1 cycle + +# ============================================================================== +NAME Macros other key + +RUN 5 ms +PRESS X +RUN 1 cycle +EXPECT keyboard-report Key_X # Report should contain only `X` + +RUN 5 ms +RELEASE X +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty diff --git a/tests/plugins/Macros/basic/test/testcase.cpp b/tests/plugins/Macros/basic/test/testcase.cpp deleted file mode 100644 index e23c5d52..00000000 --- a/tests/plugins/Macros/basic/test/testcase.cpp +++ /dev/null @@ -1,136 +0,0 @@ -/* -*- mode: c++ -*- - * Copyright (C) 2020 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 "testing/setup-googletest.h" - -SETUP_GOOGLETEST(); - -namespace kaleidoscope { -namespace testing { -namespace { - -constexpr KeyAddr addr_macro_A{0, 0}; -constexpr KeyAddr addr_macro_B{0, 1}; -constexpr KeyAddr addr_macro_C{0, 2}; -constexpr KeyAddr addr_macro_X{0, 3}; -constexpr KeyAddr addr_X{1, 0}; - -class MacrosBasic : public VirtualDeviceTest { - protected: - std::set expected_keycodes_ = {}; - std::unique_ptr state_ = nullptr; -}; - -TEST_F(MacrosBasic, MacroIndex_0) { - - sim_.Press(addr_macro_A); - state_ = RunCycle(); - expected_keycodes_.insert(Key_A.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one HID report"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should include only `A`"; - - sim_.Release(addr_macro_A); - state_ = RunCycle(); - expected_keycodes_.erase(Key_A.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one report after letter key release"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should be empty"; -} - -TEST_F(MacrosBasic, MacroIndex_1) { - - sim_.Press(addr_macro_B); - state_ = RunCycle(); - expected_keycodes_.insert(Key_B.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one HID report"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should include only `B`"; - - sim_.Release(addr_macro_B); - state_ = RunCycle(); - expected_keycodes_.erase(Key_B.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one report after letter key release"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should be empty"; -} - -TEST_F(MacrosBasic, MacroIndex_255) { - - sim_.Press(addr_macro_C); - state_ = RunCycle(); - expected_keycodes_.insert(Key_C.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one HID report"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should include only `C`"; - - state_ = RunCycle(); - expected_keycodes_.erase(Key_C.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one report in the next cycle"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should be empty"; - - sim_.Release(addr_macro_C); - state_ = RunCycle(); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 0) - << "There should be no report after release"; -} - -TEST_F(MacrosBasic, NonMacrosKey) { - - sim_.Press(addr_X); - state_ = RunCycle(); - expected_keycodes_.insert(Key_X.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one HID report"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should include only `X`"; - - sim_.Release(addr_X); - state_ = RunCycle(); - expected_keycodes_.erase(Key_X.getKeyCode()); - - ASSERT_EQ(state_->HIDReports()->Keyboard().size(), 1) - << "There should be one report after letter key release"; - EXPECT_THAT(state_->HIDReports()->Keyboard(0).ActiveKeycodes(), - ::testing::ElementsAreArray(expected_keycodes_)) - << "The report should be empty"; -} - -} // namespace -} // namespace testing -} // namespace kaleidoscope From ba65bf46d24ca29046a8459fdf30ec986da9acbe Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:10:01 -0500 Subject: [PATCH 092/108] Update testcases for TopsyTurvy plugin Signed-off-by: Michael Richters --- tests/plugins/TopsyTurvy/basic/basic.ino | 4 +- tests/plugins/TopsyTurvy/basic/test.ktest | 284 ++++++++++++++++++++++ 2 files changed, 286 insertions(+), 2 deletions(-) diff --git a/tests/plugins/TopsyTurvy/basic/basic.ino b/tests/plugins/TopsyTurvy/basic/basic.ino index ad1cb607..22af9114 100644 --- a/tests/plugins/TopsyTurvy/basic/basic.ino +++ b/tests/plugins/TopsyTurvy/basic/basic.ino @@ -25,8 +25,8 @@ KEYMAPS( ( TOPSY(1), TOPSY(2), ___, ___, ___, ___, ___, Key_A, Key_B, ___, ___, ___, ___, ___, - Key_LeftShift, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, + Key_LeftShift, Key_RightShift, Key_LeftAlt, Key_Meh, ___, ___, + LSHIFT(Key_X), LCTRL(Key_Y), ___, ___, ___, ___, ___, ___, ___, ___, ___, ___, diff --git a/tests/plugins/TopsyTurvy/basic/test.ktest b/tests/plugins/TopsyTurvy/basic/test.ktest index a3ec293e..62780fc0 100644 --- a/tests/plugins/TopsyTurvy/basic/test.ktest +++ b/tests/plugins/TopsyTurvy/basic/test.ktest @@ -5,6 +5,11 @@ KEYSWITCH TOPSY_2 0 1 KEYSWITCH A 1 0 KEYSWITCH B 1 1 KEYSWITCH LSHIFT 2 0 +KEYSWITCH RSHIFT 2 1 +KEYSWITCH LALT 2 2 +KEYSWITCH MEH 2 3 +KEYSWITCH SHIFT_X 3 0 +KEYSWITCH CTRL_Y 3 1 # ============================================================================== NAME TopsyTurvy without shift @@ -41,3 +46,282 @@ RUN 5 ms RELEASE LSHIFT RUN 1 cycle EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover from shift + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_1 # The report should contain only `1` +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover to shift + +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_1 # The report should contain only `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy encompassing shift + +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_1 # The report should contain only `1` +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover from other + +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # The report should contain `A` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +RELEASE A +RUN 1 cycle +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover to other + +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_A # The report should contain only `A` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy encompassing other + +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_A # The report should contain only `A` +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle + +# ============================================================================== +NAME TopsyTurvy encompassed by other + +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # The report should contain `A` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty +RUN 5 ms +RELEASE A +RUN 1 cycle + +# ============================================================================== +NAME TopsyTurvy rollover from other with shift + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # The report should contain `shift` + `A` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_1 # The report should contain only `1` +RUN 5 ms +RELEASE A +RUN 1 cycle +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover to other with shift + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_1 # The report should contain only `1` +RUN 5 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_A # The report should contain `shift` + `A` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +RUN 5 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover to TopsyTurvy + +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +PRESS TOPSY_2 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_2 # The report should contain `shift` + `2` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +RUN 5 ms +RELEASE TOPSY_2 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy rollover from other modifier + +RUN 5 ms +PRESS LALT +RUN 1 cycle +EXPECT keyboard-report Key_LeftAlt # The report should contain `alt` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftAlt Key_LeftShift # The report should contain `alt` + `shift` +EXPECT keyboard-report Key_LeftAlt Key_LeftShift Key_1 # The report should contain `alt` + `shift` + `1` +RUN 5 ms +RELEASE LALT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_1 # The report should contain `shift` + `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # The report should contain `shift` +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME TopsyTurvy with other modifier + +RUN 5 ms +PRESS LALT +RUN 1 cycle +EXPECT keyboard-report Key_LeftAlt # The report should contain `alt` +RUN 5 ms +PRESS TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftAlt Key_LeftShift # The report should contain `alt` + `shift` +EXPECT keyboard-report Key_LeftAlt Key_LeftShift Key_1 # The report should contain `alt` + `shift` + `1` +RUN 5 ms +RELEASE TOPSY_1 +RUN 1 cycle +EXPECT keyboard-report Key_LeftAlt Key_LeftShift # The report should contain `alt` + `shift` +EXPECT keyboard-report Key_LeftAlt # The report should contain `alt` +RUN 5 ms +RELEASE LALT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty From fb9576925066bb9db553fa06ffb6298b4f908438 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:10:21 -0500 Subject: [PATCH 093/108] Update testcases for Qukeys plugin Signed-off-by: Michael Richters --- tests/issues/970/test.ktest | 14 ++++++++------ tests/plugins/Qukeys/TapRepeat/test.ktest | 15 ++++++--------- tests/plugins/Qukeys/basic/basic.ino | 6 +++--- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/tests/issues/970/test.ktest b/tests/issues/970/test.ktest index 977c5b36..ad8b728d 100644 --- a/tests/issues/970/test.ktest +++ b/tests/issues/970/test.ktest @@ -1,25 +1,27 @@ -NAME Issue 970 Qukeys min prior interval overflow +VERSION 1 KEYSWITCH A 2 1 +# ============================================================================== +NAME Issue 970 Qukeys min prior interval overflow + PRESS A RUN 10 ms RELEASE A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` -RUN 2 cycles EXPECT keyboard-report empty # Report should be empty RUN 65536 ms PRESS A -RUN 202 ms +RUN 1 cycle +RUN 200 ms # hold timeout is 200 ms +RUN 1 ms EXPECT keyboard-report Key_LeftGui # Report should contain only `LeftGui` RUN 10 ms - RELEASE A -# I'm not sure why it takes 2 cycles before the report is sent -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty diff --git a/tests/plugins/Qukeys/TapRepeat/test.ktest b/tests/plugins/Qukeys/TapRepeat/test.ktest index 0cb8af66..97a793c5 100644 --- a/tests/plugins/Qukeys/TapRepeat/test.ktest +++ b/tests/plugins/Qukeys/TapRepeat/test.ktest @@ -22,7 +22,7 @@ PRESS A RUN 5 ms RELEASE A -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty EXPECT keyboard-report Key_A # Report should contain only A @@ -43,8 +43,7 @@ PRESS A RUN 50 ms RELEASE A -# I'm not sure why this takes 2 cycles instead of just one -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty # ============================================================================== @@ -65,7 +64,7 @@ PRESS J RUN 5 ms RELEASE J -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty EXPECT keyboard-report Key_J # Report should contain only J @@ -86,8 +85,7 @@ PRESS J RUN 50 ms RELEASE J -# I'm not sure why this takes 2 cycles instead of just one -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty # ============================================================================== @@ -108,7 +106,7 @@ PRESS B RUN 5 ms RELEASE B -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty EXPECT keyboard-report Key_B # Report should contain only B @@ -129,6 +127,5 @@ PRESS B RUN 50 ms RELEASE B -# I'm not sure why this takes 2 cycles instead of just one -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty diff --git a/tests/plugins/Qukeys/basic/basic.ino b/tests/plugins/Qukeys/basic/basic.ino index 74d062d5..769fed17 100644 --- a/tests/plugins/Qukeys/basic/basic.ino +++ b/tests/plugins/Qukeys/basic/basic.ino @@ -65,10 +65,10 @@ KEYMAPS( // *INDENT-ON* // Defining a macro (on the "any" key: see above) to toggle Qukeys on and off -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - switch (macro_index) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case MACRO_TOGGLE_QUKEYS: - if (keyToggledOn(key_state)) + if (keyToggledOn(event.state)) Qukeys.toggle(); break; } From d2cb0786046497221b5d017d376a71b613ac3fdf Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:11:16 -0500 Subject: [PATCH 094/108] Update testcases for OneShot plugin Signed-off-by: Michael Richters --- tests/issues/896/sketch.json | 6 ++++++ tests/plugins/OneShot/basic/test.ktest | 3 +-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/issues/896/sketch.json diff --git a/tests/issues/896/sketch.json b/tests/issues/896/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/issues/896/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/plugins/OneShot/basic/test.ktest b/tests/plugins/OneShot/basic/test.ktest index fdb05b11..454a1303 100644 --- a/tests/plugins/OneShot/basic/test.ktest +++ b/tests/plugins/OneShot/basic/test.ktest @@ -15,7 +15,7 @@ EXPECT keyboard-report Key_LeftShift # The report should contain `shift` RUN 5 ms RELEASE OS_shift RUN 45 ms -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report empty # Report should be empty # ============================================================================== @@ -32,7 +32,6 @@ RUN 10 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_LeftShift Key_A # There should be `shift`+`A` -RUN 2 cycle EXPECT keyboard-report Key_A # There should be only `A` RUN 5 ms RELEASE A From f7b7799756598f77de6cc0ae7de515942636747b Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:12:33 -0500 Subject: [PATCH 095/108] Update testcases for TapDance plugin Signed-off-by: Michael Richters --- tests/issues/806/806.ino | 4 -- tests/issues/806/test.ktest | 4 +- tests/issues/922/922.ino | 4 -- tests/issues/922/test.ktest | 72 ++++++++++++------------- tests/issues/980/980.ino | 4 -- tests/issues/980/sketch.json | 6 --- tests/issues/980/test.ktest | 8 +-- tests/plugins/TapDance/basic/test.ktest | 14 +++-- 8 files changed, 42 insertions(+), 74 deletions(-) delete mode 100644 tests/issues/980/sketch.json diff --git a/tests/issues/806/806.ino b/tests/issues/806/806.ino index 2d51e094..f9049b99 100644 --- a/tests/issues/806/806.ino +++ b/tests/issues/806/806.ino @@ -19,10 +19,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED diff --git a/tests/issues/806/test.ktest b/tests/issues/806/test.ktest index 8beda100..25d39665 100644 --- a/tests/issues/806/test.ktest +++ b/tests/issues/806/test.ktest @@ -13,12 +13,10 @@ PRESS TD_0 RUN 1 cycle RUN 25 ms # timeout is 25 ms -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report Key_A # The report should contain only `A` RUN 20 ms - RELEASE TD_0 RUN 1 cycle - EXPECT keyboard-report empty # The report should be empty diff --git a/tests/issues/922/922.ino b/tests/issues/922/922.ino index bb3eaaa4..93a7326c 100644 --- a/tests/issues/922/922.ino +++ b/tests/issues/922/922.ino @@ -19,10 +19,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED diff --git a/tests/issues/922/test.ktest b/tests/issues/922/test.ktest index ce7c683c..ccc50f5b 100644 --- a/tests/issues/922/test.ktest +++ b/tests/issues/922/test.ktest @@ -14,15 +14,14 @@ RUN 1 cycle EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` RUN 4 ms RELEASE TD_0 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_0 release -RUN 4 ms +RUN 5 ms RELEASE TD_1 -RUN 18 ms -EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 16 ms # timeout = 25ms RUN 1 cycle +EXPECT keyboard-report Key_A, Key_B # TD_1 should time out, yielding `B` +EXPECT keyboard-report Key_B # TD_0 should be released EXPECT keyboard-report empty # Empty report after TD_1 timeout -RUN 11 ms +RUN 13 ms # t = 50ms RUN 5 ms PRESS TD_0 @@ -32,15 +31,14 @@ RUN 1 cycle EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` RUN 4 ms RELEASE TD_0 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_0 release -RUN 4 ms +RUN 5 ms RELEASE TD_1 -RUN 18 ms -EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 16 ms # timeout = 25ms RUN 1 cycle +EXPECT keyboard-report Key_A, Key_B # TD_1 should time out, yielding `B` +EXPECT keyboard-report Key_B # TD_0 should be released EXPECT keyboard-report empty # Empty report after TD_1 timeout -RUN 11 ms +RUN 13 ms # t = 100ms # ============================================================================== NAME TapDance to TapDance rollover right to left @@ -53,15 +51,14 @@ RUN 1 cycle EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` RUN 4 ms RELEASE TD_1 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_1 release -RUN 4 ms +RUN 5 ms RELEASE TD_0 -RUN 18 ms -EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 16 ms # timeout = 25ms RUN 1 cycle -EXPECT keyboard-report empty # Empty report after TD_0 timeout -RUN 11 ms +EXPECT keyboard-report Key_B, Key_A # TD_0 should time out, yielding `A` +EXPECT keyboard-report Key_A # TD_1 should be released +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 13 ms # t = 150ms RUN 5 ms PRESS TD_1 @@ -71,15 +68,14 @@ RUN 1 cycle EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` RUN 4 ms RELEASE TD_1 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_1 release -RUN 4 ms +RUN 5 ms RELEASE TD_0 -RUN 18 ms -EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 16 ms # timeout = 25ms RUN 1 cycle -EXPECT keyboard-report empty # Empty report after TD_0 timeout -RUN 11 ms +EXPECT keyboard-report Key_B, Key_A # TD_0 should time out, yielding `A` +EXPECT keyboard-report Key_A # TD_1 should be released +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 13 ms # t = 200ms # ============================================================================== NAME TapDance to TapDance rollover back and forth @@ -92,15 +88,14 @@ RUN 1 cycle EXPECT keyboard-report Key_A # TD_0 should be interrupted, yielding `A` RUN 4 ms RELEASE TD_0 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_0 release -RUN 4 ms +RUN 5 ms RELEASE TD_1 -RUN 18 ms -EXPECT keyboard-report Key_B # TD_1 should time out, yielding `B` +RUN 16 ms # timeout = 25ms RUN 1 cycle +EXPECT keyboard-report Key_A, Key_B # TD_1 should time out, yielding `B` +EXPECT keyboard-report Key_B # TD_0 should be released EXPECT keyboard-report empty # Empty report after TD_1 timeout -RUN 11 ms +RUN 13 ms # t = 250ms RUN 5 ms PRESS TD_1 @@ -110,12 +105,11 @@ RUN 1 cycle EXPECT keyboard-report Key_B # TD_1 should be interrupted, yielding `B` RUN 4 ms RELEASE TD_1 -RUN 1 cycle -EXPECT keyboard-report empty # Empty report on TD_1 release -RUN 4 ms +RUN 5 ms RELEASE TD_0 -RUN 18 ms -EXPECT keyboard-report Key_A # TD_0 should time out, yielding `A` +RUN 16 ms # timeout = 25ms RUN 1 cycle -EXPECT keyboard-report empty # Empty report after TD_0 timeout -RUN 11 ms +EXPECT keyboard-report Key_B, Key_A # TD_0 should time out, yielding `A` +EXPECT keyboard-report Key_A # TD_1 should be released +EXPECT keyboard-report empty # Empty report after TD_1 timeout +RUN 13 ms # t = 300ms diff --git a/tests/issues/980/980.ino b/tests/issues/980/980.ino index 2d51e094..f9049b99 100644 --- a/tests/issues/980/980.ino +++ b/tests/issues/980/980.ino @@ -19,10 +19,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED diff --git a/tests/issues/980/sketch.json b/tests/issues/980/sketch.json deleted file mode 100644 index 43dc4c7e..00000000 --- a/tests/issues/980/sketch.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "cpu": { - "fqbn": "keyboardio:virtual:model01", - "port": "" - } -} \ No newline at end of file diff --git a/tests/issues/980/test.ktest b/tests/issues/980/test.ktest index d695aa5a..8a9b0799 100644 --- a/tests/issues/980/test.ktest +++ b/tests/issues/980/test.ktest @@ -8,7 +8,6 @@ KEYSWITCH LL_1 2 0 NAME TapDance issue 980 no overlap RUN 5 ms - PRESS TD_0 RUN 5 ms RELEASE TD_0 @@ -16,12 +15,11 @@ RUN 10 ms PRESS TD_0 RUN 5 ms - RELEASE TD_0 RUN 5 ms PRESS X -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X) RUN 5 ms @@ -32,7 +30,6 @@ RUN 5 ms PRESS LL_1 RUN 1 cycle - RELEASE LL_1 RUN 1 cycle @@ -50,7 +47,7 @@ PRESS TD_0 RUN 5 ms PRESS X -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report Key_Y # The key should be mapped from layer 1 (Y), not layer 0 (X) RUN 5 ms @@ -64,6 +61,5 @@ RUN 5 ms PRESS LL_1 RUN 1 cycle - RELEASE LL_1 RUN 1 cycle diff --git a/tests/plugins/TapDance/basic/test.ktest b/tests/plugins/TapDance/basic/test.ktest index 24d41faa..a6017217 100644 --- a/tests/plugins/TapDance/basic/test.ktest +++ b/tests/plugins/TapDance/basic/test.ktest @@ -19,11 +19,12 @@ RELEASE TD_0 RUN 5 ms PRESS X RUN 1 cycle -EXPECT keyboard-report Key_B # The report should contain `B` -RUN 1 cycle +EXPECT keyboard-report Key_B # Report should contain `B` EXPECT keyboard-report empty # Report should be empty EXPECT keyboard-report Key_X # Report should contain `X` +RUN 10 ms + RUN 5 ms RELEASE X RUN 1 cycle @@ -43,11 +44,11 @@ PRESS TD_0 RUN 1 cycle RUN 5 ms RELEASE TD_0 + RUN 20 ms # Timeout = 25ms -RUN 2 ms # Extra 2 cycles for some reason -EXPECT keyboard-report Key_B # The report should contain `B` RUN 1 cycle +EXPECT keyboard-report Key_B # The report should contain `B` EXPECT keyboard-report empty # Report should be empty # ============================================================================== @@ -65,7 +66,6 @@ RUN 5 ms PRESS X RUN 1 cycle EXPECT keyboard-report Key_B # The report should contain `B` -RUN 1 cycle EXPECT keyboard-report Key_B Key_X # Report should contain `B` & `X` RUN 5 ms @@ -73,7 +73,6 @@ RELEASE TD_0 RUN 1 cycle EXPECT keyboard-report Key_X # Report should contain `X` - RUN 5 ms RELEASE X RUN 1 cycle @@ -93,11 +92,10 @@ PRESS TD_0 RUN 1 cycle RUN 25 ms -RUN 2 cycles +RUN 1 cycle EXPECT keyboard-report Key_B # The report should contain `B` RUN 10 ms RELEASE TD_0 RUN 1 cycle - EXPECT keyboard-report empty # Report should be empty From b0478b43d801e30fc37dd69012224152713d24a7 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:13:55 -0500 Subject: [PATCH 096/108] Update testcases for MagicCombo plugin Signed-off-by: Michael Richters --- tests/plugins/MagicCombo/basic/basic.ino | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/tests/plugins/MagicCombo/basic/basic.ino b/tests/plugins/MagicCombo/basic/basic.ino index f5bdc3ed..7f773b39 100644 --- a/tests/plugins/MagicCombo/basic/basic.ino +++ b/tests/plugins/MagicCombo/basic/basic.ino @@ -19,10 +19,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED @@ -45,11 +41,9 @@ KEYMAPS( // *INDENT-ON* void tapKeyA(uint8_t magic_combo_index) { - std::cerr << "tapKeyA" << std::endl; - handleKeyswitchEvent(Key_A, KeyAddr{1, 0}, IS_PRESSED | INJECTED); - Kaleidoscope.hid().keyboard().sendReport(); - handleKeyswitchEvent(Key_NoKey, KeyAddr{1, 0}, WAS_PRESSED | INJECTED); - Kaleidoscope.hid().keyboard().sendReport(); + KeyAddr k{1, 0}; + Kaleidoscope.handleKeyEvent(KeyEvent{k, IS_PRESSED | INJECTED, Key_A}); + Kaleidoscope.handleKeyEvent(KeyEvent{k, WAS_PRESSED | INJECTED}); } USE_MAGIC_COMBOS({.action = tapKeyA, .keys = {R0C0, R0C1, R0C2}}); From 5d69eca65d668f969fee92c13402c1857d3d42da Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:15:20 -0500 Subject: [PATCH 097/108] Update testcase for issue 978 Signed-off-by: Michael Richters --- tests/issues/978/978.ino | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/tests/issues/978/978.ino b/tests/issues/978/978.ino index a9e0b0e7..c2401b72 100644 --- a/tests/issues/978/978.ino +++ b/tests/issues/978/978.ino @@ -18,10 +18,6 @@ #include "./common.h" -#undef min -#undef max -#include - // *INDENT-OFF* KEYMAPS( [0] = KEYMAP_STACKED @@ -48,9 +44,12 @@ namespace plugin { class IdleKeyDetector : public kaleidoscope::Plugin { public: - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state) { - if (key_addr == KeyAddr{0, 0} && key_state == 0) { - handleKeyswitchEvent(Key_X, key_addr, IS_PRESSED | WAS_PRESSED); + // handleKeyswitchEvent() is going to mask the underlying issue if it recurs, + // but leaving this here is better than nothing. + EventHandlerResult onKeyswitchEvent(KeyEvent &event) { + if (event.addr == KeyAddr{0, 0} && event.state == 0) { + event.key = Key_X; + event.state = IS_PRESSED; } return EventHandlerResult::OK; } From e101121eecfea17626e7514e5311212e27248ca4 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:18:21 -0500 Subject: [PATCH 098/108] Add testcases for SpaceCadet plugin Signed-off-by: Michael Richters --- tests/plugins/SpaceCadet/basic/basic.ino | 68 +++++++++++ tests/plugins/SpaceCadet/basic/common.h | 27 +++++ tests/plugins/SpaceCadet/basic/sketch.json | 6 + tests/plugins/SpaceCadet/basic/test.ktest | 125 +++++++++++++++++++++ 4 files changed, 226 insertions(+) create mode 100644 tests/plugins/SpaceCadet/basic/basic.ino create mode 100644 tests/plugins/SpaceCadet/basic/common.h create mode 100644 tests/plugins/SpaceCadet/basic/sketch.json create mode 100644 tests/plugins/SpaceCadet/basic/test.ktest diff --git a/tests/plugins/SpaceCadet/basic/basic.ino b/tests/plugins/SpaceCadet/basic/basic.ino new file mode 100644 index 00000000..aba9a916 --- /dev/null +++ b/tests/plugins/SpaceCadet/basic/basic.ino @@ -0,0 +1,68 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 "./common.h" + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_LeftShift, Key_RightShift, ___, ___, ___, ___, ___, + Key_A, Key_B, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(SpaceCadet); + +void setup() { + Kaleidoscope.setup(); + + //Set the SpaceCadet map + //Setting is {KeyThatWasPressed, AlternativeKeyToSend, TimeoutInMS} + //Note: must end with the SPACECADET_MAP_END delimiter + static kaleidoscope::plugin::SpaceCadet::KeyBinding spacecadetmap[] = { + {Key_LeftShift, Key_X, 10}, + {Key_RightShift, Key_Y, 0}, + {Key_LeftGui, Key_LeftCurlyBracket, 10}, + {Key_RightAlt, Key_RightCurlyBracket, 10}, + {Key_LeftAlt, Key_RightCurlyBracket, 10}, + {Key_LeftControl, Key_LeftBracket, 10}, + {Key_RightControl, Key_RightBracket, 10}, + SPACECADET_MAP_END + }; + //Set the map. + SpaceCadet.map = spacecadetmap; + SpaceCadet.time_out = 20; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/SpaceCadet/basic/common.h b/tests/plugins/SpaceCadet/basic/common.h new file mode 100644 index 00000000..dcfcc35b --- /dev/null +++ b/tests/plugins/SpaceCadet/basic/common.h @@ -0,0 +1,27 @@ +// -*- mode: c++ -*- + +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 . + */ + +#pragma once + +#include + +namespace kaleidoscope { +namespace testing { + +} // namespace testing +} // namespace kaleidoscope diff --git a/tests/plugins/SpaceCadet/basic/sketch.json b/tests/plugins/SpaceCadet/basic/sketch.json new file mode 100644 index 00000000..43dc4c7e --- /dev/null +++ b/tests/plugins/SpaceCadet/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} \ No newline at end of file diff --git a/tests/plugins/SpaceCadet/basic/test.ktest b/tests/plugins/SpaceCadet/basic/test.ktest new file mode 100644 index 00000000..f3edaae8 --- /dev/null +++ b/tests/plugins/SpaceCadet/basic/test.ktest @@ -0,0 +1,125 @@ +VERSION 1 + +KEYSWITCH LSHIFT 0 0 +KEYSWITCH RSHIFT 0 1 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 + +# ============================================================================== +NAME SpaceCadet tap + +RUN 5 ms + +PRESS LSHIFT +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_X # Report should contain `X` (0x1B) +EXPECT keyboard-report empty # Report should be empty +RUN 5 ms + +# ============================================================================== +NAME SpaceCadet hold + +RUN 5 ms + +PRESS LSHIFT +RUN 1 cycle + +RUN 10 ms +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME SpaceCadet hold with global timeout + +RUN 5 ms + +PRESS RSHIFT +RUN 1 cycle + +RUN 20 ms +RUN 1 cycle +EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5) + +RUN 5 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME SpaceCadet interrupt + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle + +RUN 3 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) +EXPECT keyboard-report Key_LeftShift Key_A # Report should add `A` (0x04, 0xE1) + +RUN 3 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` (0x04) + +RUN 3 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME SpaceCadet interrupt SpaceCadet with tap + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle + +RUN 3 ms +PRESS RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 3 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_Y # Report should add `Y` (0x1C) +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 3 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +# ============================================================================== +NAME SpaceCadet interrupt SpaceCadet with hold + +RUN 5 ms +PRESS LSHIFT +RUN 1 cycle + +RUN 3 ms +PRESS RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 20 ms +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_RightShift # Report should contain both `shift` keycodes (0xE1, 0xE5) + +RUN 5 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5) + +RUN 3 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty From 206d0681c820ecd6802ef8d6adfff2d724e1f568 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sun, 11 Apr 2021 11:06:30 -0500 Subject: [PATCH 099/108] Add testcases for Leader plugin Signed-off-by: Michael Richters --- tests/plugins/Leader/basic/basic.ino | 66 +++++++++++++++ tests/plugins/Leader/basic/sketch.json | 6 ++ tests/plugins/Leader/basic/test.ktest | 107 +++++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 tests/plugins/Leader/basic/basic.ino create mode 100644 tests/plugins/Leader/basic/sketch.json create mode 100644 tests/plugins/Leader/basic/test.ktest diff --git a/tests/plugins/Leader/basic/basic.ino b/tests/plugins/Leader/basic/basic.ino new file mode 100644 index 00000000..febdf47e --- /dev/null +++ b/tests/plugins/Leader/basic/basic.ino @@ -0,0 +1,66 @@ +/* -*- 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 + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + LEAD(0), ___, ___, ___, ___, ___, ___, + Key_A, Key_B, Key_C, Key_D, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(Leader); + +static void leaderBC(uint8_t id) { + Macros.type(PSTR("x")); +} + +static void leaderAC(uint8_t id) { + Macros.type(PSTR("xyz")); +} + +// *INDENT-OFF* +static const kaleidoscope::plugin::Leader::dictionary_t leader_dictionary[] PROGMEM = + LEADER_DICT( {LEADER_SEQ(LEAD(0), Key_B, Key_C), leaderBC}, + {LEADER_SEQ(LEAD(0), Key_A, Key_C), leaderAC} ); +// *INDENT-ON* + +void setup() { + Kaleidoscope.setup(); + Leader.time_out = 20; + Leader.dictionary = leader_dictionary; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/Leader/basic/sketch.json b/tests/plugins/Leader/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/Leader/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/Leader/basic/test.ktest b/tests/plugins/Leader/basic/test.ktest new file mode 100644 index 00000000..62cfa2f6 --- /dev/null +++ b/tests/plugins/Leader/basic/test.ktest @@ -0,0 +1,107 @@ +VERSION 1 + +KEYSWITCH LEAD_0 0 0 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 +KEYSWITCH C 1 2 +KEYSWITCH D 1 3 + +# ============================================================================== +NAME Leader sequence AC + +RUN 4 ms +PRESS LEAD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE LEAD_0 +RUN 1 cycle + +RUN 4 ms +PRESS A +RUN 1 cycle + +RUN 4 ms +RELEASE A +RUN 1 cycle + +RUN 4 ms +PRESS C +RUN 1 cycle +EXPECT keyboard-report Key_X # report should contain `X` (0x1b) +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_Y # report should contain `Y` (0x1c) +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_Z # report should contain `Z` (0x1d) +EXPECT keyboard-report empty # report should be empty + +RUN 4 ms +RELEASE C +RUN 1 cycle + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME Leader sequence BC + +RUN 4 ms +PRESS LEAD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE LEAD_0 +RUN 1 cycle + +RUN 4 ms +PRESS B +RUN 1 cycle + +RUN 4 ms +RELEASE B +RUN 1 cycle + +RUN 4 ms +PRESS C +RUN 1 cycle +EXPECT keyboard-report Key_X # report should contain `X` (0x1b) +EXPECT keyboard-report empty # report should be empty + +RUN 4 ms +RELEASE C +RUN 1 cycle + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME Leader sequence timeout + +RUN 4 ms +PRESS LEAD_0 +RUN 1 cycle + +RUN 4 ms +RELEASE LEAD_0 +RUN 1 cycle + +RUN 4 ms +PRESS B +RUN 1 cycle + +RUN 24 ms +RELEASE B +RUN 1 cycle + +RUN 4 ms +PRESS C +RUN 1 cycle +EXPECT keyboard-report Key_C # report should contain `C` (0x06) + +RUN 4 ms +RELEASE C +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports From f1369120154e928c0d9f60ad93d47228c05d8045 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Thu, 15 Apr 2021 13:22:00 -0500 Subject: [PATCH 100/108] Add testcases for Turbo plugin Signed-off-by: Michael Richters --- tests/plugins/Turbo/basic/basic.ino | 49 +++++++++++ tests/plugins/Turbo/basic/sketch.json | 6 ++ tests/plugins/Turbo/basic/test.ktest | 113 ++++++++++++++++++++++++++ 3 files changed, 168 insertions(+) create mode 100644 tests/plugins/Turbo/basic/basic.ino create mode 100644 tests/plugins/Turbo/basic/sketch.json create mode 100644 tests/plugins/Turbo/basic/test.ktest diff --git a/tests/plugins/Turbo/basic/basic.ino b/tests/plugins/Turbo/basic/basic.ino new file mode 100644 index 00000000..799484e0 --- /dev/null +++ b/tests/plugins/Turbo/basic/basic.ino @@ -0,0 +1,49 @@ +/* -*- 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 + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_Turbo, Key_RightGui, Key_LeftShift, ___, ___, ___, ___, + Key_A, Key_B, Key_C, Key_D, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(Turbo); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/Turbo/basic/sketch.json b/tests/plugins/Turbo/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/Turbo/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/Turbo/basic/test.ktest b/tests/plugins/Turbo/basic/test.ktest new file mode 100644 index 00000000..e1342728 --- /dev/null +++ b/tests/plugins/Turbo/basic/test.ktest @@ -0,0 +1,113 @@ +VERSION 1 + +KEYSWITCH TURBO 0 0 +KEYSWITCH R_GUI 0 1 +KEYSWITCH L_SHIFT 0 2 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 +KEYSWITCH C 1 2 +KEYSWITCH D 1 3 + +# ============================================================================== +NAME Turbo no regression + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME Turbo second + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 4 ms +PRESS TURBO +RUN 1 cycle + +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 10 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 10 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 10 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 10 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 4 ms +RELEASE TURBO +RUN 1 cycle + +RUN 20 ms + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME Turbo first + +RUN 4 ms +PRESS TURBO +RUN 1 cycle + +RUN 10 ms + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 7 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 10 ms +RUN 1 ms +EXPECT keyboard-report empty # report should be empty +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 20 ms + +RUN 4 ms +RELEASE TURBO +RUN 1 cycle + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports From cc8bd39c43fe7ac0b59bda2dc01e0ffd09f4bda9 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Mon, 12 Apr 2021 23:29:49 -0500 Subject: [PATCH 101/108] Add testcases for WinKeyToggle Signed-off-by: Michael Richters --- tests/plugins/WinKeyToggle/basic/basic.ino | 57 +++++++++++++ tests/plugins/WinKeyToggle/basic/sketch.json | 6 ++ tests/plugins/WinKeyToggle/basic/test.ktest | 89 ++++++++++++++++++++ 3 files changed, 152 insertions(+) create mode 100644 tests/plugins/WinKeyToggle/basic/basic.ino create mode 100644 tests/plugins/WinKeyToggle/basic/sketch.json create mode 100644 tests/plugins/WinKeyToggle/basic/test.ktest diff --git a/tests/plugins/WinKeyToggle/basic/basic.ino b/tests/plugins/WinKeyToggle/basic/basic.ino new file mode 100644 index 00000000..76f06d80 --- /dev/null +++ b/tests/plugins/WinKeyToggle/basic/basic.ino @@ -0,0 +1,57 @@ +/* -*- 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 + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + Key_LeftGui, Key_RightGui, Key_LeftShift, ___, ___, ___, ___, + Key_A, Key_B, Key_C, Key_D, ___, ___, ___, + M(0), ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (keyToggledOn(event.state) && macro_id == 0) { + WinKeyToggle.toggle(); + } + return MACRO_NONE; +} + +KALEIDOSCOPE_INIT_PLUGINS(WinKeyToggle, Macros); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/WinKeyToggle/basic/sketch.json b/tests/plugins/WinKeyToggle/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/WinKeyToggle/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/WinKeyToggle/basic/test.ktest b/tests/plugins/WinKeyToggle/basic/test.ktest new file mode 100644 index 00000000..9b412af4 --- /dev/null +++ b/tests/plugins/WinKeyToggle/basic/test.ktest @@ -0,0 +1,89 @@ +VERSION 1 + +KEYSWITCH L_GUI 0 0 +KEYSWITCH R_GUI 0 0 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 +KEYSWITCH C 1 2 +KEYSWITCH D 1 3 +KEYSWITCH TOGGLE 2 0 + +# ============================================================================== +NAME Win key enabled on start + +RUN 4 ms +PRESS L_GUI +RUN 1 cycle +EXPECT keyboard-report Key_LeftGui # report should contain `gui` (0xe3) + +RUN 4 ms +RELEASE L_GUI +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME Win key disabled after toggle + +RUN 4 ms +PRESS TOGGLE +RUN 1 cycle + +RUN 4 ms +RELEASE TOGGLE +RUN 1 cycle + +RUN 4 ms +PRESS L_GUI +RUN 1 cycle +EXPECT no keyboard-report + +RUN 4 ms +RELEASE L_GUI +RUN 1 cycle +EXPECT no keyboard-report + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME WinKeyToggle other keys not disabled + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_A # report should contain `A` (0x04) + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports + +# ============================================================================== +NAME WinKeyToggle turns back on + +RUN 4 ms +PRESS TOGGLE +RUN 1 cycle + +RUN 4 ms +RELEASE TOGGLE +RUN 1 cycle + +RUN 4 ms +PRESS L_GUI +RUN 1 cycle +EXPECT keyboard-report Key_LeftGui # report should contain `gui` (0xe3) + +RUN 4 ms +RELEASE L_GUI +RUN 1 cycle +EXPECT keyboard-report empty # report should be empty + +RUN 5 ms +EXPECT no keyboard-report # expect no more reports From 67bf76a99ae1a299b999ab894d9f49970ef9e594 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:13:37 -0500 Subject: [PATCH 102/108] Simplify and standardize some testcases Signed-off-by: Michael Richters --- tests/features/keycodes/test.ktest | 32 +- .../hid-v1.2-consumer-keys.ino | 555 +----------------- .../hid-v1.2-consumer-keys/test/testcase.cpp | 4 +- tests/issues/840/840.ino | 555 +----------------- tests/issues/941/test.ktest | 36 +- tests/issues/951/951.ino | 1 + 6 files changed, 97 insertions(+), 1086 deletions(-) diff --git a/tests/features/keycodes/test.ktest b/tests/features/keycodes/test.ktest index 66b7b037..d41de2ef 100644 --- a/tests/features/keycodes/test.ktest +++ b/tests/features/keycodes/test.ktest @@ -1,34 +1,36 @@ VERSION 1 -NAME Keyboard Non Modifiers -KEYSWITCH A 2 1 -KEYSWITCH S 2 2 -KEYSWITCH LeftShift 3 7 +KEYSWITCH A 2 1 +KEYSWITCH S 2 2 +KEYSWITCH LSHIFT 3 7 -RUN 10 ms +# ============================================================================== +NAME Keyboard non modifier + +RUN 4 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only 'a' -RUN 25 ms +RUN 4 ms RELEASE A - RUN 1 cycle EXPECT keyboard-report empty # Report should be empty -NAME Keyboard Modifiers +RUN 5 ms -RUN 10 ms -PRESS LeftShift +# ============================================================================== +NAME Keyboard modifier +RUN 4 ms +PRESS LSHIFT RUN 1 cycle EXPECT keyboard-report Key_LeftShift # Report should contain only `shift` - -RUN 25 ms -RELEASE LeftShift - +RUN 4 ms +RELEASE LSHIFT RUN 1 cycle - EXPECT keyboard-report empty # No keys should be pressed +RUN 5 ms + diff --git a/tests/hid/hid-v1.2-consumer-keys/hid-v1.2-consumer-keys.ino b/tests/hid/hid-v1.2-consumer-keys/hid-v1.2-consumer-keys.ino index 2fae10ad..0c9791b4 100644 --- a/tests/hid/hid-v1.2-consumer-keys/hid-v1.2-consumer-keys.ino +++ b/tests/hid/hid-v1.2-consumer-keys/hid-v1.2-consumer-keys.ino @@ -1,537 +1,46 @@ -// -*- mode: c++ -*- -// Copyright 2016 Keyboardio, inc. -// See "LICENSE" for license details - -#ifndef BUILD_INFORMATION -#define BUILD_INFORMATION "locally built" -#endif - - -/** - * These #include directives pull in the Kaleidoscope firmware core, - * as well as the Kaleidoscope plugins we use in the Model 01's firmware +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 -// The Kaleidoscope core -#include "Kaleidoscope.h" - -// Support for storing the keymap in EEPROM -#include "Kaleidoscope-EEPROM-Settings.h" -#include "Kaleidoscope-EEPROM-Keymap.h" - -// Support for communicating with the host via a simple Serial protocol -#include "Kaleidoscope-FocusSerial.h" - -// Support for keys that move the mouse -#include "Kaleidoscope-MouseKeys.h" - -// Support for macros -#include "Kaleidoscope-Macros.h" - -// Support for controlling the keyboard's LEDs -#include "Kaleidoscope-LEDControl.h" - -// Support for "Numpad" mode, which is mostly just the Numpad specific LED mode -#include "Kaleidoscope-NumPad.h" - -// Support for the "Boot greeting" effect, which pulses the 'LED' button for 10s -// when the keyboard is connected to a computer (or that computer is powered on) -#include "Kaleidoscope-LEDEffect-BootGreeting.h" - -// Support for LED modes that set all LEDs to a single color -#include "Kaleidoscope-LEDEffect-SolidColor.h" - -// Support for an LED mode that makes all the LEDs 'breathe' -#include "Kaleidoscope-LEDEffect-Breathe.h" - -// Support for an LED mode that makes a red pixel chase a blue pixel across the keyboard -#include "Kaleidoscope-LEDEffect-Chase.h" - -// Support for LED modes that pulse the keyboard's LED in a rainbow pattern -#include "Kaleidoscope-LEDEffect-Rainbow.h" - -// Support for an LED mode that lights up the keys as you press them -#include "Kaleidoscope-LED-Stalker.h" - -// Support for an LED mode that prints the keys you press in letters 4px high -#include "Kaleidoscope-LED-AlphaSquare.h" - -// Support for an LED mode that lets one configure per-layer color maps -#include "Kaleidoscope-Colormap.h" - -// Support for Keyboardio's internal keyboard testing mode -#include "Kaleidoscope-HardwareTestMode.h" - -// Support for host power management (suspend & wakeup) -#include "Kaleidoscope-HostPowerManagement.h" - -// Support for magic combos (key chords that trigger an action) -#include "Kaleidoscope-MagicCombo.h" - -// Support for USB quirks, like changing the key state report protocol -#include "Kaleidoscope-USB-Quirks.h" - -/** This 'enum' is a list of all the macros used by the Model 01's firmware - * The names aren't particularly important. What is important is that each - * is unique. - * - * These are the names of your macros. They'll be used in two places. - * The first is in your keymap definitions. There, you'll use the syntax - * `M(MACRO_NAME)` to mark a specific keymap position as triggering `MACRO_NAME` - * - * The second usage is in the 'switch' statement in the `macroAction` function. - * That switch statement actually runs the code associated with a macro when - * a macro key is pressed. - */ - -enum { MACRO_VERSION_INFO, - MACRO_ANY - }; - - - -/** The Model 01's key layouts are defined as 'keymaps'. By default, there are three - * keymaps: The standard QWERTY keymap, the "Function layer" keymap and the "Numpad" - * keymap. - * - * Each keymap is defined as a list using the 'KEYMAP_STACKED' macro, built - * of first the left hand's layout, followed by the right hand's layout. - * - * Keymaps typically consist mostly of `Key_` definitions. There are many, many keys - * defined as part of the USB HID Keyboard specification. You can find the names - * (if not yet the explanations) for all the standard `Key_` defintions offered by - * Kaleidoscope in these files: - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keyboard.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/consumerctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/sysctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keymaps.h - * - * Additional things that should be documented here include - * using ___ to let keypresses fall through to the previously active layer - * using XXX to mark a keyswitch as 'blocked' on this layer - * using ShiftToLayer() and LockLayer() keys to change the active keymap. - * keeping NUM and FN consistent and accessible on all layers - * - * The PROG key is special, since it is how you indicate to the board that you - * want to flash the firmware. However, it can be remapped to a regular key. - * When the keyboard boots, it first looks to see whether the PROG key is held - * down; if it is, it simply awaits further flashing instructions. If it is - * not, it continues loading the rest of the firmware and the keyboard - * functions normally, with whatever binding you have set to PROG. More detail - * here: https://community.keyboard.io/t/how-the-prog-key-gets-you-into-the-bootloader/506/8 - * - * The "keymaps" data structure is a list of the keymaps compiled into the firmware. - * The order of keymaps in the list is important, as the ShiftToLayer(#) and LockLayer(#) - * macros switch to key layers based on this list. - * - * - - * A key defined as 'ShiftToLayer(FUNCTION)' will switch to FUNCTION while held. - * Similarly, a key defined as 'LockLayer(NUMPAD)' will switch to NUMPAD when tapped. - */ - -/** - * Layers are "0-indexed" -- That is the first one is layer 0. The second one is layer 1. - * The third one is layer 2. - * This 'enum' lets us use names like QWERTY, FUNCTION, and NUMPAD in place of - * the numbers 0, 1 and 2. - * - */ - -enum { PRIMARY, NUMPAD, FUNCTION }; // layers - - -/** - * To change your keyboard's layout from QWERTY to DVORAK or COLEMAK, comment out the line - * - * #define PRIMARY_KEYMAP_QWERTY - * - * by changing it to - * - * // #define PRIMARY_KEYMAP_QWERTY - * - * Then uncomment the line corresponding to the layout you want to use. - * - */ - -#define PRIMARY_KEYMAP_QWERTY -// #define PRIMARY_KEYMAP_COLEMAK -// #define PRIMARY_KEYMAP_DVORAK -// #define PRIMARY_KEYMAP_CUSTOM - - - -/* This comment temporarily turns off astyle's indent enforcement - * so we can make the keymaps actually resemble the physical key layout better - */ // *INDENT-OFF* - KEYMAPS( - -#if defined (PRIMARY_KEYMAP_QWERTY) - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Consumer_VoiceCommand, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - ___, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_DVORAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Quote, Key_Comma, Key_Period, Key_P, Key_Y, Key_Tab, - Key_PageUp, Key_A, Key_O, Key_E, Key_U, Key_I, - Key_PageDown, Key_Semicolon, Key_Q, Key_J, Key_K, Key_X, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_F, Key_G, Key_C, Key_R, Key_L, Key_Slash, - Key_D, Key_H, Key_T, Key_N, Key_S, Key_Minus, - Key_RightAlt, Key_B, Key_M, Key_W, Key_V, Key_Z, Key_Equals, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_COLEMAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_F, Key_P, Key_G, Key_Tab, - Key_PageUp, Key_A, Key_R, Key_S, Key_T, Key_D, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_J, Key_L, Key_U, Key_Y, Key_Semicolon, Key_Equals, - Key_H, Key_N, Key_E, Key_I, Key_O, Key_Quote, - Key_RightAlt, Key_K, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_CUSTOM) - // Edit this keymap to make a custom layout - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#else - -#error "No default keymap defined. You should make sure that you have a line like '#define PRIMARY_KEYMAP_QWERTY' in your sketch" - -#endif - - - - [NUMPAD] = KEYMAP_STACKED - (___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, - ___, - - M(MACRO_VERSION_INFO), ___, Key_7, Key_8, Key_9, Key_KeypadSubtract, ___, - ___, ___, Key_4, Key_5, Key_6, Key_KeypadAdd, ___, - ___, Key_1, Key_2, Key_3, Key_Equals, ___, - ___, ___, Key_0, Key_Period, Key_KeypadMultiply, Key_KeypadDivide, Key_Enter, - ___, ___, ___, ___, - ___), - - [FUNCTION] = KEYMAP_STACKED - (___, Key_F1, Key_F2, Key_F3, Key_F4, Key_F5, Key_CapsLock, - Key_Tab, ___, Key_mouseUp, ___, Key_mouseBtnR, Key_mouseWarpEnd, Key_mouseWarpNE, - Key_Home, Key_mouseL, Key_mouseDn, Key_mouseR, Key_mouseBtnL, Key_mouseWarpNW, - Key_End, Key_PrintScreen, Key_Insert, ___, Key_mouseBtnM, Key_mouseWarpSW, Key_mouseWarpSE, - ___, Key_Delete, ___, ___, - ___, - - Consumer_ScanPreviousTrack, Key_F6, Key_F7, Key_F8, Key_F9, Key_F10, Key_F11, - Consumer_PlaySlashPause, Consumer_ScanNextTrack, Key_LeftCurlyBracket, Key_RightCurlyBracket, Key_LeftBracket, Key_RightBracket, Key_F12, - Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___, - Key_PcApplication, Consumer_Mute, Consumer_VolumeDecrement, Consumer_VolumeIncrement, ___, Key_Backslash, Key_Pipe, - ___, ___, Key_Enter, ___, - ___) -) // KEYMAPS( - -/* Re-enable astyle's indent enforcement */ + [0] = KEYMAP_STACKED + ( + Consumer_VoiceCommand, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) // *INDENT-ON* -/** versionInfoMacro handles the 'firmware version info' macro - * When a key bound to the macro is pressed, this macro - * prints out the firmware build information as virtual keystrokes - */ - -static void versionInfoMacro(uint8_t keyState) { - if (keyToggledOn(keyState)) { - Macros.type(PSTR("Keyboardio Model 01 - Kaleidoscope ")); - Macros.type(PSTR(BUILD_INFORMATION)); - } -} - -/** anyKeyMacro is used to provide the functionality of the 'Any' key. - * - * When the 'any key' macro is toggled on, a random alphanumeric key is - * selected. While the key is held, the function generates a synthetic - * keypress event repeating that randomly selected key. - * - */ - -static void anyKeyMacro(uint8_t keyState) { - static Key lastKey; - bool toggledOn = false; - if (keyToggledOn(keyState)) { - lastKey.setKeyCode(Key_A.getKeyCode() + (uint8_t)(millis() % 36)); - toggledOn = true; - } - - if (keyIsPressed(keyState)) - Kaleidoscope.hid().keyboard().pressKey(lastKey, toggledOn); -} - - -/** macroAction dispatches keymap events that are tied to a macro - to that macro. It takes two uint8_t parameters. - - The first is the macro being called (the entry in the 'enum' earlier in this file). - The second is the state of the keyswitch. You can use the keyswitch state to figure out - if the key has just been toggled on, is currently pressed or if it's just been released. - - The 'switch' statement should have a 'case' for each entry of the macro enum. - Each 'case' statement should call out to a function to handle the macro in question. - - */ - -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; - } - return MACRO_NONE; -} - - - -// These 'solid' color effect definitions define a rainbow of -// LED color modes calibrated to draw 500mA or less on the -// Keyboardio Model 01. - -static constexpr uint8_t solid_red_level = 160; -static kaleidoscope::plugin::LEDSolidColor solidRed(solid_red_level, 0, 0); -static kaleidoscope::plugin::LEDSolidColor solidOrange(140, 70, 0); -static kaleidoscope::plugin::LEDSolidColor solidYellow(130, 100, 0); -static kaleidoscope::plugin::LEDSolidColor solidGreen(0, 160, 0); -static kaleidoscope::plugin::LEDSolidColor solidBlue(0, 70, 130); -static kaleidoscope::plugin::LEDSolidColor solidIndigo(0, 0, 170); -static kaleidoscope::plugin::LEDSolidColor solidViolet(130, 0, 120); - -/** toggleLedsOnSuspendResume toggles the LEDs off when the host goes to sleep, - * and turns them back on when it wakes up. - */ -void toggleLedsOnSuspendResume(kaleidoscope::plugin::HostPowerManagement::Event event) { - switch (event) { - case kaleidoscope::plugin::HostPowerManagement::Suspend: - LEDControl.disable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Resume: - LEDControl.enable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Sleep: - break; - } -} - -/** hostPowerManagementEventHandler dispatches power management events (suspend, - * resume, and sleep) to other functions that perform action based on these - * events. - */ -void hostPowerManagementEventHandler(kaleidoscope::plugin::HostPowerManagement::Event event) { - toggleLedsOnSuspendResume(event); -} - -/** A tiny wrapper, to be used by MagicCombo. - * This simply toggles the keyboard protocol via USBQuirks, and wraps it within - * a function with an unused argument, to match what MagicCombo expects. - */ -static void toggleKeyboardProtocol(uint8_t combo_index) { - USBQuirks.toggleKeyboardProtocol(); -} - -/** Magic combo list, a list of key combo and action pairs the firmware should - * recognise. - */ -USE_MAGIC_COMBOS({.action = toggleKeyboardProtocol, - // Left Fn + Esc + Shift - .keys = { R3C6, R2C6, R3C7 } - }); - -// First, tell Kaleidoscope which plugins you want to use. -// The order can be important. For example, LED effects are -// added in the order they're listed here. -KALEIDOSCOPE_INIT_PLUGINS( - // The EEPROMSettings & EEPROMKeymap plugins make it possible to have an - // editable keymap in EEPROM. - EEPROMSettings, - EEPROMKeymap, - - // Focus allows bi-directional communication with the host, and is the - // interface through which the keymap in EEPROM can be edited. - Focus, - - // FocusSettingsCommand adds a few Focus commands, intended to aid in - // changing some settings of the keyboard, such as the default layer (via the - // `settings.defaultLayer` command) - FocusSettingsCommand, - - // FocusEEPROMCommand adds a set of Focus commands, which are very helpful in - // both debugging, and in backing up one's EEPROM contents. - FocusEEPROMCommand, - - // The boot greeting effect pulses the LED button for 10 seconds after the - // keyboard is first connected - BootGreetingEffect, - - // The hardware test mode, which can be invoked by tapping Prog, LED and the - // left Fn button at the same time. - HardwareTestMode, - - // LEDControl provides support for other LED modes - LEDControl, - - // We start with the LED effect that turns off all the LEDs. - LEDOff, - - // The rainbow effect changes the color of all of the keyboard's keys at the same time - // running through all the colors of the rainbow. - LEDRainbowEffect, - - // The rainbow wave effect lights up your keyboard with all the colors of a rainbow - // and slowly moves the rainbow across your keyboard - LEDRainbowWaveEffect, - - // The chase effect follows the adventure of a blue pixel which chases a red pixel across - // your keyboard. Spoiler: the blue pixel never catches the red pixel - LEDChaseEffect, - - // These static effects turn your keyboard's LEDs a variety of colors - solidRed, solidOrange, solidYellow, solidGreen, solidBlue, solidIndigo, solidViolet, - - // The breathe effect slowly pulses all of the LEDs on your keyboard - LEDBreatheEffect, - - // The AlphaSquare effect prints each character you type, using your - // keyboard's LEDs as a display - AlphaSquareEffect, - - // The stalker effect lights up the keys you've pressed recently - StalkerEffect, - - // The Colormap effect makes it possible to set up per-layer colormaps - ColormapEffect, - - // The numpad plugin is responsible for lighting up the 'numpad' mode - // with a custom LED effect - NumPad, - - // The macros plugin adds support for macros - Macros, - - // The MouseKeys plugin lets you add keys to your keymap which move the mouse. - MouseKeys, - - // The HostPowerManagement plugin allows us to turn LEDs off when then host - // goes to sleep, and resume them when it wakes up. - HostPowerManagement, - - // The MagicCombo plugin lets you use key combinations to trigger custom - // actions - a bit like Macros, but triggered by pressing multiple keys at the - // same time. - MagicCombo, - - // The USBQuirks plugin lets you do some things with USB that we aren't - // comfortable - or able - to do automatically, but can be useful - // nevertheless. Such as toggling the key report protocol between Boot (used - // by BIOSes) and Report (NKRO). - USBQuirks -); - -/** The 'setup' function is one of the two standard Arduino sketch functions. - * It's called when your keyboard first powers up. This is where you set up - * Kaleidoscope and any plugins. - */ void setup() { - // First, call Kaleidoscope's internal setup function Kaleidoscope.setup(); - - // While we hope to improve this in the future, the NumPad plugin - // needs to be explicitly told which keymap layer is your numpad layer - NumPad.numPadLayer = NUMPAD; - - // We configure the AlphaSquare effect to use RED letters - AlphaSquare.color = CRGB(255, 0, 0); - - // We set the brightness of the rainbow effects to 150 (on a scale of 0-255) - // This draws more than 500mA, but looks much nicer than a dimmer effect - LEDRainbowEffect.brightness(150); - LEDRainbowWaveEffect.brightness(150); - - // The LED Stalker mode has a few effects. The one we like is called - // 'BlazingTrail'. For details on other options, see - // https://github.com/keyboardio/Kaleidoscope/blob/master/doc/plugin/LED-Stalker.md - StalkerEffect.variant = STALKER(BlazingTrail); - - // We want to make sure that the firmware starts with LED effects off - // This avoids over-taxing devices that don't have a lot of power to share - // with USB devices - LEDOff.activate(); - - // To make the keymap editable without flashing new firmware, we store - // additional layers in EEPROM. For now, we reserve space for five layers. If - // one wants to use these layers, just set the default layer to one in EEPROM, - // by using the `settings.defaultLayer` Focus command, or by using the - // `keymap.onlyCustom` command to use EEPROM layers only. - EEPROMKeymap.setup(5); - - // We need to tell the Colormap plugin how many layers we want to have custom - // maps for. To make things simple, we set it to five layers, which is how - // many editable layers we have (see above). - ColormapEffect.max_layers(5); } -/** loop is the second of the standard Arduino sketch functions. - * As you might expect, it runs in a loop, never exiting. - * - * For Kaleidoscope-based keyboard firmware, you usually just want to - * call Kaleidoscope.loop(); and not do anything custom here. - */ - void loop() { Kaleidoscope.loop(); } diff --git a/tests/hid/hid-v1.2-consumer-keys/test/testcase.cpp b/tests/hid/hid-v1.2-consumer-keys/test/testcase.cpp index d7d2d2d5..eb2c110a 100644 --- a/tests/hid/hid-v1.2-consumer-keys/test/testcase.cpp +++ b/tests/hid/hid-v1.2-consumer-keys/test/testcase.cpp @@ -27,7 +27,7 @@ using ::testing::IsEmpty; class KeyboardReports : public VirtualDeviceTest {}; TEST_F(KeyboardReports, HIDUsageTablev12KeycodesAdded) { - sim_.Press(1, 1); // VoiceCommand + sim_.Press(0, 0); // VoiceCommand auto state = RunCycle(); ASSERT_EQ(state->HIDReports()->ConsumerControl().size(), 1); @@ -35,7 +35,7 @@ TEST_F(KeyboardReports, HIDUsageTablev12KeycodesAdded) { state->HIDReports()->ConsumerControl(0).ActiveKeycodes(), Contains(Consumer_VoiceCommand)); - sim_.Release(1, 1); // VoiceCommand + sim_.Release(0, 0); // VoiceCommand state = RunCycle(); ASSERT_EQ(state->HIDReports()->ConsumerControl().size(), 1); diff --git a/tests/issues/840/840.ino b/tests/issues/840/840.ino index 9a8e8be1..379cbcf8 100644 --- a/tests/issues/840/840.ino +++ b/tests/issues/840/840.ino @@ -1,537 +1,46 @@ -// -*- mode: c++ -*- -// Copyright 2016 Keyboardio, inc. -// See "LICENSE" for license details - -#ifndef BUILD_INFORMATION -#define BUILD_INFORMATION "locally built" -#endif - - -/** - * These #include directives pull in the Kaleidoscope firmware core, - * as well as the Kaleidoscope plugins we use in the Model 01's firmware +/* -*- mode: c++ -*- + * Copyright (C) 2020 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 -// The Kaleidoscope core -#include "Kaleidoscope.h" - -// Support for storing the keymap in EEPROM -#include "Kaleidoscope-EEPROM-Settings.h" -#include "Kaleidoscope-EEPROM-Keymap.h" - -// Support for communicating with the host via a simple Serial protocol -#include "Kaleidoscope-FocusSerial.h" - -// Support for keys that move the mouse -#include "Kaleidoscope-MouseKeys.h" - -// Support for macros -#include "Kaleidoscope-Macros.h" - -// Support for controlling the keyboard's LEDs -#include "Kaleidoscope-LEDControl.h" - -// Support for "Numpad" mode, which is mostly just the Numpad specific LED mode -#include "Kaleidoscope-NumPad.h" - -// Support for the "Boot greeting" effect, which pulses the 'LED' button for 10s -// when the keyboard is connected to a computer (or that computer is powered on) -#include "Kaleidoscope-LEDEffect-BootGreeting.h" - -// Support for LED modes that set all LEDs to a single color -#include "Kaleidoscope-LEDEffect-SolidColor.h" - -// Support for an LED mode that makes all the LEDs 'breathe' -#include "Kaleidoscope-LEDEffect-Breathe.h" - -// Support for an LED mode that makes a red pixel chase a blue pixel across the keyboard -#include "Kaleidoscope-LEDEffect-Chase.h" - -// Support for LED modes that pulse the keyboard's LED in a rainbow pattern -#include "Kaleidoscope-LEDEffect-Rainbow.h" - -// Support for an LED mode that lights up the keys as you press them -#include "Kaleidoscope-LED-Stalker.h" - -// Support for an LED mode that prints the keys you press in letters 4px high -#include "Kaleidoscope-LED-AlphaSquare.h" - -// Support for an LED mode that lets one configure per-layer color maps -#include "Kaleidoscope-Colormap.h" - -// Support for Keyboardio's internal keyboard testing mode -#include "Kaleidoscope-HardwareTestMode.h" - -// Support for host power management (suspend & wakeup) -#include "Kaleidoscope-HostPowerManagement.h" - -// Support for magic combos (key chords that trigger an action) -#include "Kaleidoscope-MagicCombo.h" - -// Support for USB quirks, like changing the key state report protocol -#include "Kaleidoscope-USB-Quirks.h" - -/** This 'enum' is a list of all the macros used by the Model 01's firmware - * The names aren't particularly important. What is important is that each - * is unique. - * - * These are the names of your macros. They'll be used in two places. - * The first is in your keymap definitions. There, you'll use the syntax - * `M(MACRO_NAME)` to mark a specific keymap position as triggering `MACRO_NAME` - * - * The second usage is in the 'switch' statement in the `macroAction` function. - * That switch statement actually runs the code associated with a macro when - * a macro key is pressed. - */ - -enum { MACRO_VERSION_INFO, - MACRO_ANY - }; - - - -/** The Model 01's key layouts are defined as 'keymaps'. By default, there are three - * keymaps: The standard QWERTY keymap, the "Function layer" keymap and the "Numpad" - * keymap. - * - * Each keymap is defined as a list using the 'KEYMAP_STACKED' macro, built - * of first the left hand's layout, followed by the right hand's layout. - * - * Keymaps typically consist mostly of `Key_` definitions. There are many, many keys - * defined as part of the USB HID Keyboard specification. You can find the names - * (if not yet the explanations) for all the standard `Key_` defintions offered by - * Kaleidoscope in these files: - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keyboard.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/consumerctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/sysctl.h - * https://github.com/keyboardio/Kaleidoscope/blob/master/src/key_defs/keymaps.h - * - * Additional things that should be documented here include - * using ___ to let keypresses fall through to the previously active layer - * using XXX to mark a keyswitch as 'blocked' on this layer - * using ShiftToLayer() and LockLayer() keys to change the active keymap. - * keeping NUM and FN consistent and accessible on all layers - * - * The PROG key is special, since it is how you indicate to the board that you - * want to flash the firmware. However, it can be remapped to a regular key. - * When the keyboard boots, it first looks to see whether the PROG key is held - * down; if it is, it simply awaits further flashing instructions. If it is - * not, it continues loading the rest of the firmware and the keyboard - * functions normally, with whatever binding you have set to PROG. More detail - * here: https://community.keyboard.io/t/how-the-prog-key-gets-you-into-the-bootloader/506/8 - * - * The "keymaps" data structure is a list of the keymaps compiled into the firmware. - * The order of keymaps in the list is important, as the ShiftToLayer(#) and LockLayer(#) - * macros switch to key layers based on this list. - * - * - - * A key defined as 'ShiftToLayer(FUNCTION)' will switch to FUNCTION while held. - * Similarly, a key defined as 'LockLayer(NUMPAD)' will switch to NUMPAD when tapped. - */ - -/** - * Layers are "0-indexed" -- That is the first one is layer 0. The second one is layer 1. - * The third one is layer 2. - * This 'enum' lets us use names like QWERTY, FUNCTION, and NUMPAD in place of - * the numbers 0, 1 and 2. - * - */ - -enum { PRIMARY, NUMPAD, FUNCTION }; // layers - - -/** - * To change your keyboard's layout from QWERTY to DVORAK or COLEMAK, comment out the line - * - * #define PRIMARY_KEYMAP_QWERTY - * - * by changing it to - * - * // #define PRIMARY_KEYMAP_QWERTY - * - * Then uncomment the line corresponding to the layout you want to use. - * - */ - -#define PRIMARY_KEYMAP_QWERTY -// #define PRIMARY_KEYMAP_COLEMAK -// #define PRIMARY_KEYMAP_DVORAK -// #define PRIMARY_KEYMAP_CUSTOM - - - -/* This comment temporarily turns off astyle's indent enforcement - * so we can make the keymaps actually resemble the physical key layout better - */ // *INDENT-OFF* - KEYMAPS( - -#if defined (PRIMARY_KEYMAP_QWERTY) - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, System_PowerDown, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, System_Sleep, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_DVORAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Quote, Key_Comma, Key_Period, Key_P, Key_Y, Key_Tab, - Key_PageUp, Key_A, Key_O, Key_E, Key_U, Key_I, - Key_PageDown, Key_Semicolon, Key_Q, Key_J, Key_K, Key_X, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_F, Key_G, Key_C, Key_R, Key_L, Key_Slash, - Key_D, Key_H, Key_T, Key_N, Key_S, Key_Minus, - Key_RightAlt, Key_B, Key_M, Key_W, Key_V, Key_Z, Key_Equals, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_COLEMAK) - - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_F, Key_P, Key_G, Key_Tab, - Key_PageUp, Key_A, Key_R, Key_S, Key_T, Key_D, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_J, Key_L, Key_U, Key_Y, Key_Semicolon, Key_Equals, - Key_H, Key_N, Key_E, Key_I, Key_O, Key_Quote, - Key_RightAlt, Key_K, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#elif defined (PRIMARY_KEYMAP_CUSTOM) - // Edit this keymap to make a custom layout - [PRIMARY] = KEYMAP_STACKED - (___, Key_1, Key_2, Key_3, Key_4, Key_5, Key_LEDEffectNext, - Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, - Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, - Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, - Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, - ShiftToLayer(FUNCTION), - - M(MACRO_ANY), Key_6, Key_7, Key_8, Key_9, Key_0, LockLayer(NUMPAD), - Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, - Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, - Key_RightAlt, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, - Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, - ShiftToLayer(FUNCTION)), - -#else - -#error "No default keymap defined. You should make sure that you have a line like '#define PRIMARY_KEYMAP_QWERTY' in your sketch" - -#endif - - - - [NUMPAD] = KEYMAP_STACKED - (___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, ___, ___, ___, - ___, ___, ___, ___, - ___, - - M(MACRO_VERSION_INFO), ___, Key_7, Key_8, Key_9, Key_KeypadSubtract, ___, - ___, ___, Key_4, Key_5, Key_6, Key_KeypadAdd, ___, - ___, Key_1, Key_2, Key_3, Key_Equals, ___, - ___, ___, Key_0, Key_Period, Key_KeypadMultiply, Key_KeypadDivide, Key_Enter, - ___, ___, ___, ___, - ___), - - [FUNCTION] = KEYMAP_STACKED - (___, Key_F1, Key_F2, Key_F3, Key_F4, Key_F5, Key_CapsLock, - Key_Tab, ___, Key_mouseUp, ___, Key_mouseBtnR, Key_mouseWarpEnd, Key_mouseWarpNE, - Key_Home, Key_mouseL, Key_mouseDn, Key_mouseR, Key_mouseBtnL, Key_mouseWarpNW, - Key_End, Key_PrintScreen, Key_Insert, ___, Key_mouseBtnM, Key_mouseWarpSW, Key_mouseWarpSE, - ___, Key_Delete, ___, ___, - ___, - - Consumer_ScanPreviousTrack, Key_F6, Key_F7, Key_F8, Key_F9, Key_F10, Key_F11, - Consumer_PlaySlashPause, Consumer_ScanNextTrack, Key_LeftCurlyBracket, Key_RightCurlyBracket, Key_LeftBracket, Key_RightBracket, Key_F12, - Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___, - Key_PcApplication, Consumer_Mute, Consumer_VolumeDecrement, Consumer_VolumeIncrement, ___, Key_Backslash, Key_Pipe, - ___, ___, Key_Enter, ___, - ___) -) // KEYMAPS( - -/* Re-enable astyle's indent enforcement */ + [0] = KEYMAP_STACKED + ( + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, System_PowerDown, ___, ___, ___, ___, + ___, ___, ___, ___, ___, System_Sleep, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) // *INDENT-ON* -/** versionInfoMacro handles the 'firmware version info' macro - * When a key bound to the macro is pressed, this macro - * prints out the firmware build information as virtual keystrokes - */ - -static void versionInfoMacro(uint8_t keyState) { - if (keyToggledOn(keyState)) { - Macros.type(PSTR("Keyboardio Model 01 - Kaleidoscope ")); - Macros.type(PSTR(BUILD_INFORMATION)); - } -} - -/** anyKeyMacro is used to provide the functionality of the 'Any' key. - * - * When the 'any key' macro is toggled on, a random alphanumeric key is - * selected. While the key is held, the function generates a synthetic - * keypress event repeating that randomly selected key. - * - */ - -static void anyKeyMacro(uint8_t keyState) { - static Key lastKey; - bool toggledOn = false; - if (keyToggledOn(keyState)) { - lastKey.setKeyCode(Key_A.getKeyCode() + (uint8_t)(millis() % 36)); - toggledOn = true; - } - - if (keyIsPressed(keyState)) - Kaleidoscope.hid().keyboard().pressKey(lastKey, toggledOn); -} - - -/** macroAction dispatches keymap events that are tied to a macro - to that macro. It takes two uint8_t parameters. - - The first is the macro being called (the entry in the 'enum' earlier in this file). - The second is the state of the keyswitch. You can use the keyswitch state to figure out - if the key has just been toggled on, is currently pressed or if it's just been released. - - The 'switch' statement should have a 'case' for each entry of the macro enum. - Each 'case' statement should call out to a function to handle the macro in question. - - */ - -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; - } - return MACRO_NONE; -} - - - -// These 'solid' color effect definitions define a rainbow of -// LED color modes calibrated to draw 500mA or less on the -// Keyboardio Model 01. - -static constexpr uint8_t solid_red_level = 160; -static kaleidoscope::plugin::LEDSolidColor solidRed(solid_red_level, 0, 0); -static kaleidoscope::plugin::LEDSolidColor solidOrange(140, 70, 0); -static kaleidoscope::plugin::LEDSolidColor solidYellow(130, 100, 0); -static kaleidoscope::plugin::LEDSolidColor solidGreen(0, 160, 0); -static kaleidoscope::plugin::LEDSolidColor solidBlue(0, 70, 130); -static kaleidoscope::plugin::LEDSolidColor solidIndigo(0, 0, 170); -static kaleidoscope::plugin::LEDSolidColor solidViolet(130, 0, 120); - -/** toggleLedsOnSuspendResume toggles the LEDs off when the host goes to sleep, - * and turns them back on when it wakes up. - */ -void toggleLedsOnSuspendResume(kaleidoscope::plugin::HostPowerManagement::Event event) { - switch (event) { - case kaleidoscope::plugin::HostPowerManagement::Suspend: - LEDControl.disable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Resume: - LEDControl.enable(); - break; - case kaleidoscope::plugin::HostPowerManagement::Sleep: - break; - } -} - -/** hostPowerManagementEventHandler dispatches power management events (suspend, - * resume, and sleep) to other functions that perform action based on these - * events. - */ -void hostPowerManagementEventHandler(kaleidoscope::plugin::HostPowerManagement::Event event) { - toggleLedsOnSuspendResume(event); -} - -/** A tiny wrapper, to be used by MagicCombo. - * This simply toggles the keyboard protocol via USBQuirks, and wraps it within - * a function with an unused argument, to match what MagicCombo expects. - */ -static void toggleKeyboardProtocol(uint8_t combo_index) { - USBQuirks.toggleKeyboardProtocol(); -} - -/** Magic combo list, a list of key combo and action pairs the firmware should - * recognise. - */ -USE_MAGIC_COMBOS({.action = toggleKeyboardProtocol, - // Left Fn + Esc + Shift - .keys = { R3C6, R2C6, R3C7 } - }); - -// First, tell Kaleidoscope which plugins you want to use. -// The order can be important. For example, LED effects are -// added in the order they're listed here. -KALEIDOSCOPE_INIT_PLUGINS( - // The EEPROMSettings & EEPROMKeymap plugins make it possible to have an - // editable keymap in EEPROM. - EEPROMSettings, - EEPROMKeymap, - - // Focus allows bi-directional communication with the host, and is the - // interface through which the keymap in EEPROM can be edited. - Focus, - - // FocusSettingsCommand adds a few Focus commands, intended to aid in - // changing some settings of the keyboard, such as the default layer (via the - // `settings.defaultLayer` command) - FocusSettingsCommand, - - // FocusEEPROMCommand adds a set of Focus commands, which are very helpful in - // both debugging, and in backing up one's EEPROM contents. - FocusEEPROMCommand, - - // The boot greeting effect pulses the LED button for 10 seconds after the - // keyboard is first connected - BootGreetingEffect, - - // The hardware test mode, which can be invoked by tapping Prog, LED and the - // left Fn button at the same time. - HardwareTestMode, - - // LEDControl provides support for other LED modes - LEDControl, - - // We start with the LED effect that turns off all the LEDs. - LEDOff, - - // The rainbow effect changes the color of all of the keyboard's keys at the same time - // running through all the colors of the rainbow. - LEDRainbowEffect, - - // The rainbow wave effect lights up your keyboard with all the colors of a rainbow - // and slowly moves the rainbow across your keyboard - LEDRainbowWaveEffect, - - // The chase effect follows the adventure of a blue pixel which chases a red pixel across - // your keyboard. Spoiler: the blue pixel never catches the red pixel - LEDChaseEffect, - - // These static effects turn your keyboard's LEDs a variety of colors - solidRed, solidOrange, solidYellow, solidGreen, solidBlue, solidIndigo, solidViolet, - - // The breathe effect slowly pulses all of the LEDs on your keyboard - LEDBreatheEffect, - - // The AlphaSquare effect prints each character you type, using your - // keyboard's LEDs as a display - AlphaSquareEffect, - - // The stalker effect lights up the keys you've pressed recently - StalkerEffect, - - // The Colormap effect makes it possible to set up per-layer colormaps - ColormapEffect, - - // The numpad plugin is responsible for lighting up the 'numpad' mode - // with a custom LED effect - NumPad, - - // The macros plugin adds support for macros - Macros, - - // The MouseKeys plugin lets you add keys to your keymap which move the mouse. - MouseKeys, - - // The HostPowerManagement plugin allows us to turn LEDs off when then host - // goes to sleep, and resume them when it wakes up. - HostPowerManagement, - - // The MagicCombo plugin lets you use key combinations to trigger custom - // actions - a bit like Macros, but triggered by pressing multiple keys at the - // same time. - MagicCombo, - - // The USBQuirks plugin lets you do some things with USB that we aren't - // comfortable - or able - to do automatically, but can be useful - // nevertheless. Such as toggling the key report protocol between Boot (used - // by BIOSes) and Report (NKRO). - USBQuirks -); - -/** The 'setup' function is one of the two standard Arduino sketch functions. - * It's called when your keyboard first powers up. This is where you set up - * Kaleidoscope and any plugins. - */ void setup() { - // First, call Kaleidoscope's internal setup function Kaleidoscope.setup(); - - // While we hope to improve this in the future, the NumPad plugin - // needs to be explicitly told which keymap layer is your numpad layer - NumPad.numPadLayer = NUMPAD; - - // We configure the AlphaSquare effect to use RED letters - AlphaSquare.color = CRGB(255, 0, 0); - - // We set the brightness of the rainbow effects to 150 (on a scale of 0-255) - // This draws more than 500mA, but looks much nicer than a dimmer effect - LEDRainbowEffect.brightness(150); - LEDRainbowWaveEffect.brightness(150); - - // The LED Stalker mode has a few effects. The one we like is called - // 'BlazingTrail'. For details on other options, see - // https://github.com/keyboardio/Kaleidoscope/blob/master/doc/plugin/LED-Stalker.md - StalkerEffect.variant = STALKER(BlazingTrail); - - // We want to make sure that the firmware starts with LED effects off - // This avoids over-taxing devices that don't have a lot of power to share - // with USB devices - LEDOff.activate(); - - // To make the keymap editable without flashing new firmware, we store - // additional layers in EEPROM. For now, we reserve space for five layers. If - // one wants to use these layers, just set the default layer to one in EEPROM, - // by using the `settings.defaultLayer` Focus command, or by using the - // `keymap.onlyCustom` command to use EEPROM layers only. - EEPROMKeymap.setup(5); - - // We need to tell the Colormap plugin how many layers we want to have custom - // maps for. To make things simple, we set it to five layers, which is how - // many editable layers we have (see above). - ColormapEffect.max_layers(5); } -/** loop is the second of the standard Arduino sketch functions. - * As you might expect, it runs in a loop, never exiting. - * - * For Kaleidoscope-based keyboard firmware, you usually just want to - * call Kaleidoscope.loop(); and not do anything custom here. - */ - void loop() { Kaleidoscope.loop(); } diff --git a/tests/issues/941/test.ktest b/tests/issues/941/test.ktest index eb208ba0..635a9585 100644 --- a/tests/issues/941/test.ktest +++ b/tests/issues/941/test.ktest @@ -1,27 +1,26 @@ -NAME Issue 941 one keypress per cycle +VERSION 1 KEYSWITCH A 2 1 KEYSWITCH S 2 2 KEYSWITCH D 2 3 -RUN 10 ms +# ============================================================================== +NAME Issue 941 one keypress per cycle +RUN 10 ms PRESS A RUN 1 cycle EXPECT keyboard-report Key_A # Report should contain only `A` RUN 10 ms - PRESS S RUN 1 cycle -EXPECT keyboard-report Key_A, Key_S # Report should contain 'A' and 'S' +EXPECT keyboard-report Key_A, Key_S # Report should contain `A` and `S` RUN 25 ms - RELEASE A - RUN 1 cycle -EXPECT keyboard-report Key_S # Report should contain only 'S' +EXPECT keyboard-report Key_S # Report should contain only `S` RELEASE S RUN 1 cycle @@ -29,34 +28,25 @@ EXPECT keyboard-report empty # Report should be empty RUN 10 ms -# TODO : this should be another test cycle "Simultaneous keypresses" -# NAME Simultaneous keypresses - +# ============================================================================== +NAME Issue 941 simultaneous keypresses # Press three keys in one scan cycle: - -RUN 10 ms - +RUN 5 ms PRESS A PRESS S PRESS D - -# This test is expected to fail if Kaleidoscope becomes event-driven; -# instead, there will be three reports here: the first will contain `D`, the -# second will add `S`, and the third will add `A` (I could have that wrong; -# it should be in keyscan order). - RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` EXPECT keyboard-report Key_A, Key_S # Report should contain `A` and `S` EXPECT keyboard-report Key_A, Key_S, Key_D # Report should contain `A` , `S`, and `D` # Release all three in one scan cycle: - -RUN 25 ms - +RUN 5 ms RELEASE A RELEASE S RELEASE D - RUN 1 cycles +EXPECT keyboard-report Key_D, Key_S # Report should contain `A` and `S` +EXPECT keyboard-report Key_D # Report should contain only `A` EXPECT keyboard-report empty # Report should be empty diff --git a/tests/issues/951/951.ino b/tests/issues/951/951.ino index 2c6c5a76..6e6222b5 100644 --- a/tests/issues/951/951.ino +++ b/tests/issues/951/951.ino @@ -1,3 +1,4 @@ +// -*- mode: c++ -*- /* Kaleidoscope - Firmware for computer input devices * Copyright (C) 2020 Keyboard.io, Inc. * From c81fd4a584c94b4c237de5442ff0ad9c2435baa5 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Thu, 29 Apr 2021 15:18:31 -0500 Subject: [PATCH 103/108] Add Macros plugin example sketch Signed-off-by: Michael Richters --- examples/Keystrokes/Macros/Macros.ino | 154 +++++++++++++++++++++++++ examples/Keystrokes/Macros/sketch.json | 6 + 2 files changed, 160 insertions(+) create mode 100644 examples/Keystrokes/Macros/Macros.ino create mode 100644 examples/Keystrokes/Macros/sketch.json diff --git a/examples/Keystrokes/Macros/Macros.ino b/examples/Keystrokes/Macros/Macros.ino new file mode 100644 index 00000000..5e0badb4 --- /dev/null +++ b/examples/Keystrokes/Macros/Macros.ino @@ -0,0 +1,154 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-Macros Examples + * 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 + +// Macros +enum { + TOGGLE_ONESHOT, +}; + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + (___, M(1), M(2), M(3), M(4), M(5), ___, + Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, + Key_PageUp, Key_A, Key_S, Key_D, Key_F, Key_G, + Key_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape, + + Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, + ShiftToLayer(1), + + ___, M(6), M(7), M(8), M(9), M(0), ___, + Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, + Key_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, + ___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + + Key_RightShift, Key_LeftAlt, Key_Spacebar, Key_RightControl, + ShiftToLayer(1)), + + [1] = KEYMAP_STACKED + ( + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + Key_UpArrow, Key_DownArrow, Key_LeftArrow, Key_RightArrow,___, ___, + ___, ___, ___, ___, ___, ___, ___, + + ___, ___, ___, ___, + ___), +) +// *INDENT-ON* + +// Example macro for typing a string of characters. +void macroTypeString(KeyEvent &event) { + if (keyToggledOn(event.state)) { + Macros.type(PSTR("Hello, world!")); + } +} + +// Example macro for macro step sequence. +const macro_t* macroSteps(KeyEvent &event) { + if (keyToggledOn(event.state)) { + // Note that the following sequence leaves two keys down (`Key_RightAlt` and + // `Key_C`). These virtual keys will remain in effect until the Macros key + // is released. + return MACRO(I(200), D(LeftShift), T(A), D(RightAlt), T(B), U(LeftShift), D(C)); + } + return MACRO_NONE; +} + +// Example macro that sets `event.key`. +const macro_t* macroNewSentence1(KeyEvent &event) { + if (keyToggledOn(event.state)) { + event.key = OSM(LeftShift); + return MACRO(Tc(Period), Tc(Spacebar), Tc(Spacebar)); + } + return MACRO_NONE; +} + +// Alternate example for above. +void macroNewSentence2(KeyEvent &event) { + if (keyToggledOn(event.state)) { + Macros.type(PSTR(". ")); + event.key = OSM(LeftShift); + } +} + +// Macro that calls `handleKeyEvent()`. This version works even if the OneShot +// plugin is registered before Macros in `KALEIDOSCOPE_INIT_PLUGINS()`. +void macroNewSentence3(KeyEvent &event) { + Macros.tap(Key_Period); + Macros.tap(Key_Spacebar); + Macros.tap(Key_Spacebar); + // Change the event into a OneShot key event. + event.key = OSM(LeftShift); + kaleidoscope::Runtime.handleKeyEvent(event); + // We can effectively erase the Macros key event, effectively aborting it. + event.key = Key_NoKey; + event.addr.clear(); +} + +// Macro that auto-repeats? + +const macro_t* macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { + + case 0: + macroTypeString(event); + break; + + case 1: + return macroNewSentence1(event); + + case 2: + macroNewSentence2(event); + break; + + case 3: + macroNewSentence3(event); + break; + + case 4: + return macroSteps(event); + + default: + break; + } + return MACRO_NONE; +} + +// For some of the above examples, it's important that Macros is registered +// before OneShot here. +KALEIDOSCOPE_INIT_PLUGINS(Macros, OneShot); + +void setup() { + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Keystrokes/Macros/sketch.json b/examples/Keystrokes/Macros/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/Macros/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} From 0a5fff500522f577b4b5bd7d02a014fd6140de6c Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sun, 11 Apr 2021 19:15:59 -0500 Subject: [PATCH 104/108] Update example sketches' `macroAction()` functions to KeyEvent Signed-off-by: Michael Richters --- examples/Devices/KBDFans/KBD4x/KBD4x.ino | 7 +++-- examples/Devices/Keyboardio/Atreus/Atreus.ino | 29 +++++++++---------- .../Devices/Keyboardio/Model01/Model01.ino | 4 +-- .../Devices/Technomancy/Atreus/Atreus.ino | 7 +++-- .../EEPROM-Keymap-Programmer.ino | 4 +-- examples/Keystrokes/OneShot/OneShot.ino | 4 +-- examples/Keystrokes/Qukeys/Qukeys.ino | 6 ++-- examples/Keystrokes/Unicode/Unicode.ino | 6 ++-- .../LEDs/LED-AlphaSquare/LED-AlphaSquare.ino | 6 ++-- .../LEDs/LED-Brightness/LED-Brightness.ino | 8 ++--- 10 files changed, 41 insertions(+), 40 deletions(-) diff --git a/examples/Devices/KBDFans/KBD4x/KBD4x.ino b/examples/Devices/KBDFans/KBD4x/KBD4x.ino index 91af5f75..3dddaa1e 100644 --- a/examples/Devices/KBDFans/KBD4x/KBD4x.ino +++ b/examples/Devices/KBDFans/KBD4x/KBD4x.ino @@ -75,10 +75,11 @@ KEYMAPS( KALEIDOSCOPE_INIT_PLUGINS(Macros); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case RESET: - Kaleidoscope.rebootBootloader(); + if (keyToggledOn(event.state)) + Kaleidoscope.rebootBootloader(); break; default: break; diff --git a/examples/Devices/Keyboardio/Atreus/Atreus.ino b/examples/Devices/Keyboardio/Atreus/Atreus.ino index f0f00296..fbdb2fc9 100644 --- a/examples/Devices/Keyboardio/Atreus/Atreus.ino +++ b/examples/Devices/Keyboardio/Atreus/Atreus.ino @@ -113,25 +113,24 @@ KALEIDOSCOPE_INIT_PLUGINS( MouseKeys ); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { - case MACRO_QWERTY: - // This macro is currently unused, but is kept around for compatibility - // reasons. We used to use it in place of `MoveToLayer(QWERTY)`, but no - // longer do. We keep it so that if someone still has the old layout with - // the macro in EEPROM, it will keep working after a firmware update. - Layer.move(QWERTY); - break; - case MACRO_VERSION_INFO: - if (keyToggledOn(keyState)) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (keyToggledOn(event.state)) { + switch (macro_id) { + case MACRO_QWERTY: + // This macro is currently unused, but is kept around for compatibility + // reasons. We used to use it in place of `MoveToLayer(QWERTY)`, but no + // longer do. We keep it so that if someone still has the old layout with + // the macro in EEPROM, it will keep working after a firmware update. + Layer.move(QWERTY); + break; + case MACRO_VERSION_INFO: Macros.type(PSTR("Keyboardio Atreus - Kaleidoscope ")); Macros.type(PSTR(BUILD_INFORMATION)); + break; + default: + break; } - break; - default: - break; } - return MACRO_NONE; } diff --git a/examples/Devices/Keyboardio/Model01/Model01.ino b/examples/Devices/Keyboardio/Model01/Model01.ino index 28625508..823f04d0 100644 --- a/examples/Devices/Keyboardio/Model01/Model01.ino +++ b/examples/Devices/Keyboardio/Model01/Model01.ino @@ -85,8 +85,8 @@ static kaleidoscope::plugin::LEDSolidColor solidBlue(0, 15, 100); static kaleidoscope::plugin::LEDSolidColor solidIndigo(0, 0, 100); static kaleidoscope::plugin::LEDSolidColor solidViolet(70, 0, 60); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - if (macroIndex == 1 && keyToggledOn(keyState)) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (macro_id == 1 && keyToggledOn(event.state)) { Kaleidoscope.serialPort().print("Keyboard.IO keyboard driver v0.00"); return MACRO(I(25), D(LeftShift), T(M), U(LeftShift), T(O), T(D), T(E), T(L), diff --git a/examples/Devices/Technomancy/Atreus/Atreus.ino b/examples/Devices/Technomancy/Atreus/Atreus.ino index 3ee131d1..b5760783 100644 --- a/examples/Devices/Technomancy/Atreus/Atreus.ino +++ b/examples/Devices/Technomancy/Atreus/Atreus.ino @@ -82,10 +82,11 @@ KEYMAPS( KALEIDOSCOPE_INIT_PLUGINS(Macros); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case QWERTY: - Layer.move(QWERTY); + if (keyToggledOn(event.state)) + Layer.move(QWERTY); break; default: break; diff --git a/examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino b/examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino index 10345d12..a3ddd53d 100644 --- a/examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino +++ b/examples/Features/EEPROM/EEPROM-Keymap-Programmer/EEPROM-Keymap-Programmer.ino @@ -42,8 +42,8 @@ KEYMAPS( ) // *INDENT-ON* -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - if (macroIndex == 0 && keyToggledOff(keyState)) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (macro_id == 0 && keyToggledOff(event.state)) { EEPROMKeymapProgrammer.activate(); } diff --git a/examples/Keystrokes/OneShot/OneShot.ino b/examples/Keystrokes/OneShot/OneShot.ino index 483d2c46..82ef7092 100644 --- a/examples/Keystrokes/OneShot/OneShot.ino +++ b/examples/Keystrokes/OneShot/OneShot.ino @@ -68,8 +68,8 @@ void macroToggleOneShot() { OneShot.toggleAutoOneShot(); } -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - if (macroIndex == TOGGLE_ONESHOT) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (macro_id == TOGGLE_ONESHOT) { macroToggleOneShot(); } diff --git a/examples/Keystrokes/Qukeys/Qukeys.ino b/examples/Keystrokes/Qukeys/Qukeys.ino index c2cad0d3..da74acdd 100644 --- a/examples/Keystrokes/Qukeys/Qukeys.ino +++ b/examples/Keystrokes/Qukeys/Qukeys.ino @@ -49,10 +49,10 @@ KEYMAPS( // *INDENT-ON* // Defining a macro (on the "any" key: see above) to toggle Qukeys on and off -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - switch (macro_index) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case MACRO_TOGGLE_QUKEYS: - if (keyToggledOn(key_state)) + if (keyToggledOn(event.state)) Qukeys.toggle(); break; } diff --git a/examples/Keystrokes/Unicode/Unicode.ino b/examples/Keystrokes/Unicode/Unicode.ino index a47aea6a..34c00e7c 100644 --- a/examples/Keystrokes/Unicode/Unicode.ino +++ b/examples/Keystrokes/Unicode/Unicode.ino @@ -52,10 +52,10 @@ static void unicode(uint32_t character, uint8_t keyState) { } } -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - switch (macroIndex) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + switch (macro_id) { case MACRO_KEYBOARD_EMOJI: - unicode(0x2328, keyState); + unicode(0x2328, event.state); break; } return MACRO_NONE; diff --git a/examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino b/examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino index 0f8e6615..f3ef2d7a 100644 --- a/examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino +++ b/examples/LEDs/LED-AlphaSquare/LED-AlphaSquare.ino @@ -42,11 +42,11 @@ KEYMAPS( ) // *INDENT-ON* -const macro_t *macroAction(uint8_t macro_index, uint8_t key_state) { - if (!keyToggledOn(key_state)) +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (!keyToggledOn(event.state)) return MACRO_NONE; - if (macro_index == 0) { + if (macro_id == 0) { for (uint8_t i = Key_A.getKeyCode(); i <= Key_0.getKeyCode(); i++) { LEDControl.set_all_leds_to(0, 0, 0); LEDControl.syncLeds(); diff --git a/examples/LEDs/LED-Brightness/LED-Brightness.ino b/examples/LEDs/LED-Brightness/LED-Brightness.ino index 5f184150..194358c0 100644 --- a/examples/LEDs/LED-Brightness/LED-Brightness.ino +++ b/examples/LEDs/LED-Brightness/LED-Brightness.ino @@ -45,16 +45,16 @@ KALEIDOSCOPE_INIT_PLUGINS(LEDControl, Macros, LEDRainbowWaveEffect); -const macro_t *macroAction(uint8_t macroIndex, uint8_t keyState) { - if (keyToggledOn(keyState)) { +const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) { + if (keyToggledOn(event.state)) { uint8_t brightness = LEDControl.getBrightness(); - if (macroIndex == 0) { + if (macro_id == 0) { if (brightness > 10) brightness -= 10; else brightness = 0; - } else if (macroIndex == 1) { + } else if (macro_id == 1) { if (brightness < 245) brightness += 10; else From 2f7fbaa3fe9047c72743c4c50627ab6c001349f4 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 14:09:55 -0500 Subject: [PATCH 105/108] Add overview documentation of event handler hook functions Signed-off-by: Michael Richters --- docs/api-reference/event-handler-hooks.md | 249 ++++++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 docs/api-reference/event-handler-hooks.md diff --git a/docs/api-reference/event-handler-hooks.md b/docs/api-reference/event-handler-hooks.md new file mode 100644 index 00000000..b86f0837 --- /dev/null +++ b/docs/api-reference/event-handler-hooks.md @@ -0,0 +1,249 @@ +# Kaleidoscope's Plugin Event Handlers + +Kaleidoscope provides a set of hook functions that plugins can define in order +to do their work. If one or more of the functions listed here are defined as +methods in a plugin class, that plugin can act on the input events that drive +Kaleidoscope. + +In response to input events (plus a few other places), Kaleidoscope calls the +event handlers for each plugin that defines them, in sequence. + +## Return values + +Every Kaleidoscope event handler function returns a value of type +`EventHandlerResult`, an enum with several variants. In some handlers, +Kaleidoscope ignores the return value, but for others, the result is used as a +signal to control Kaleidoscope's behavior. In particular, some event handler +hooks are "abortable". For those hooks, the return value of the plugin handlers +are used to control what Kaleidoscope does after each plugin's event handler +returns. + +- `EventHandlerResult::OK` is used to signal that Kaleidoscope should continue + on to the next handler in the sequence. + +- `EventHandlerResult::ABORT` is used to signal that Kaleidoscope should not + continue to call the other plugin handlers in the sequence, and stop + processing the event entirely. This is used by some plugins to cancel events + and/or delay them so that they occur at a later time, possibly with different + values. + +- `EventHandlerResult::EVENT_CONSUMED` is used to signal that the plugin has + successfully handled the event, and that there is nothing further to be done, + so there is no point in continuing to call further plugin event handlers for + the event. + +## Non-event "event" handlers + +There are three special "event" handlers that are not called in response to +input events, but are instead called at fixed points during Kaleidoscope's run +time. + +### `onSetup()` + +This handler is called when Kaleidoscope first starts. If a plugin needs to do +some work after its constructor is called, but before Kaleidoscope enters its +main loop and starts scanning for keyswitch events, it can do it in this +function. + +### `beforeEachCycle()` + +This handler gets called at the beginning of every keyswitch scan cycle, before +the scan. It can be used by plugins to do things that need to be done +repeatedly, regardless of any input from the user. Typically, this involves +things like checking for timeouts. + +### `afterEachCycle()` + +This is just like `beforeEachCycle()`, but gets called after the keyswitches +have been scanned (and any input events handled). + +## Keyswitch input event handlers + +This group of event handlers is triggered when keys on the keyboard are pressed +and released. With one exception, they use a `KeyEvent` object as their one +parameter. The `KeyEvent` class encapsulates the essential data about a key +press (or release): + +- `event.addr` contains the `KeyAddr` of the key that toggled on or off. + +- `event.state` contains information about the current and former state of the + key in the form of a `uint8_t` bitfield. + +- `event.key` contains the `Key` value of the event. For key presses, this is + generally determined by means of a keymap lookup. For releases, the value is + taken from the `live_keys` structure. Because the `event` is passed by + reference, changing this value in a plugin handler will affect which value + ends up in the `live_keys` array, and thus, the output of the keyboard. + +- `event.id` contains a `KeyEventId` value: an integer, usually monotonically + increasing. This is useful as a tool to allow plugins to avoid re-processing + the same event, thus avoiding infinite loops without resorting to an + `INJECTED` key state flag which would cause other plugins to ignore events + that they might otherwise be interested in. + +### `onKeyswitchEvent(KeyEvent &event)` + +This handler is called in response to changes detected in the state of +keyswitches, via the `Runtime.handleKeyswitchEvent()` function. After the +keyswitches are scanned in each cycle, Kaleidoscope goes through them all and +compares the state of each one to its previous state. For any of them that have +either toggled on or off, plugins that define this function get called (until +one of them returns either `ABORT` or `EVENT_CONSUMED`). + +This handler should be defined by any plugin that is concerned only with +physical keyswitch events, where the user has pressed or released a physical +key. For example, plugins that determine key values based on the timing of these +physical events should define this handler (for example, Qukeys and +TapDance). Plugins that don't explicitly need to use this handler should define +`onKeyEvent()` instead. + +Plugins that use this handler should abide by certain rules in order to interact +with each other to avoid infinite loops. A plugin might return `ABORT` to delay +an event (until some other event or a timeout occurs), then later re-start +processing of the same event by calling `Runtime.handleKeyswitchEvent()`. When +it does this, it must take care to use the same `KeyEventId` value as that +event's `id` parameter, and it should also take care to preserve the order of +any such events. This way, plugins implementing `onKeyswitchEvent()` are able +to keep track of event id numbers that they have already processed fully, and +ignore those events when plugins later in the sequence re-start them. + +In more specific detail, plugins that implement `onKeyswitchEvent()` must +guarantee that the `event.id` values they emit when returning `OK` are +monotonically increasing, and should only include `id` values that the plugin +has already received as input. Additionally, such plugins must ignore any event +with an `id` value that it has recently received and finished processing. The +class `KeyEventTracker` can help simplify following these rules. + +### `onKeyEvent(KeyEvent &event)` + +After a physical keyswitch event is processed by all of the plugins with +`onKeyswitchEvent()` handlers (and they all return `OK`), Kaleidoscope passes +that event on to the `Runtime.handleKeyEvent()` function, which calls plugins' +`onKeyEvent()` handlers. This is also the starting point for events which do not +correspond to physical key events, and can have an invalid `event.addr` value. + +Plugins that need to respond to keyboard input, but which do not need to be +closely tied to physical key events (and only those events) should use +`onKeyEvent()` to do their work. + +After all `onKeyEvent()` handlers have returned `OK` for an event, the +`live_keys` state array gets updated. For a key press event, the final +`event.key` value gets inserted into `live_keys[event.addr]`. From that point +on, the keyboard will behave as though a key with that value is being held until +that entry in `live_keys` is cleared (most likely as a result of a key release +event's `onKeyEvent()` handlers returning `OK`). Thus, if an `onKeyEvent()` +handler returns `ABORT` for a key release event, the keyboard will behave as +though that key is still held after it has been released. This is what enables +plugins like OneShot to function, but it also means that plugin authors need to +take care about returning `ABORT` (but not `EVENT_CONSUMED`) from an +`onKeyEvent()` handler, because it could result in "stuck" keys. + +`onKeyEvent()` handlers should not store events and release them later (by +calling `Runtime.handleKeyEvent()`), and must never call +`Runtime.handleKeyswitchEvent()`. + +### `onAddToReport(Key key)` + +After the `onKeyEvent()` handlers have all returned `OK`, Kaleidoscope moves on +to sending Keyboard HID reports. It clears the current report, and iterates +through the `live_keys` array, looking for non-empty values, and adding them to +the report. For System Control, Consumer Control, and Keyboard HID type `Key` +values, Kaleidoscope handles adding the keycodes to the correct report, but it +also calls this handler, in case a plugin needs to alter that report. + +A return value of `OK` allows Kaleidoscope to proceed with adding the +corresponding keycode(s) to the HID report, and `ABORT` causes it to leave and +keycodes from `key` out of the report. + +Note that this only applies to the Keyboard and Consumer Control HID reports, +not the System Control report, which has different semantics, and only supports +a single keycode at a time. + +### `beforeReportingState(const KeyEvent &event)` + +This gets called right before a set of HID reports is sent. At this point, +plugins have access to a (tentative) complete HID report, as well as the full +state of all live keys on the keyboard. This is especially useful for plugins +that might need to do things like remove keycodes (such as keyboard modifiers) +from the forthcoming report just before it gets sent. + +This event handler still has access to the event information for the event that +triggered the report, but because it is passed as a `const` reference, it is no +longer possible to change any of its values. + +[Note: The older version of `beforeReportingState()` got called once per cycle, +regardless of the pattern of keyswitches toggling on and off, and many plugins +used it as a place to do things like check for timeouts. This new version does +not get called every cycle, so when porting old code to the newer handlers, it's +important to move any code that must be called every cycle to either +`beforeEachCycle()` or `afterEachCycle()`.] + +[Also note: Unlike the deprecated `beforeReportingState()`, this one is +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.] + +## Other events + +### `onLayerChange()` + +Called whenever one or more keymap layers are activated or deactivated (just +after the change takes place). + +### `onLEDModeChange()` + +Called by `LEDControl` whenever the active LED mode changes. + +### `beforeSyncingLeds()` + +Called immediately before Kaleidoscope sends updated color values to the +LEDs. This event handler is particularly useful to plugins that need to override +the active LED mode (e.g. LED-ActiveModColor). + +### `onFocusEvent()` + +### `onNameQuery()` + +### `exploreSketch()` + +## Deprecated + +Two existing "event" handlers have been deprecated. In the old version of +Kaleidoscope's main loop, the keyboard's state information was stored in the +keyscanner (which physical switches were on in the current and former scans), +and in the HID reports. The Keyboard HID report would be cleared at the start of +every cycle, and re-populated, on key at a time, calling every +`onKeyswitchEvent()` handler for every active key. Then, once the tentative HID +report was complete, the `beforeReportingState()` handlers would be called, and +the complete report would be sent to the host. In most cycles, that report would +be identical to the previous report, and would be suppressed. + +The new system stores the keyboard's current state in the `live_keys` array +instead, and only calls event handlers in response to keyswitch state changes +(and artificially generated events), ultimately sending HID reports in response +to events, rather than at the end of every cycle. + +### `onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state)` + +This handler was called in every cycle, for every non-idle key. Its concept of +an "event" included held keys that did not have a state change. These deprecated +handlers are still called, in response to events and also when preparing the HID +reports, but there is no longer a reasonable mechanism to call them in every +cycle, for every active key, so some functionality could be lost. + +It is strongly recommended to switch to using one of the two `KeyEvent` +functions instead, depending on the needs of the plugin (either `onKeyEvent()` +if it is fit for the purpose, or `onKeyswitchEvent()` if necessary). The +`onAddToReport()` function might also be useful, particularly if the plugin in +question uses special `Key` values not recognized by Kaleidoscope itself, but +which should result in keycodes being added to HID reports. + +### `beforeReportingState()` + +The old version of this handler has been deprecated, but it will still be called +both before HID reports are sent and also once per cycle. It is likely that +these handlers will continue to function, but the code therein should be moved +either to the new `KeyEvent` version of `beforeReportingState()` and/or +`afterEachCycle()` (or `beforeEachCycle()`), depending on whether it needs to be +run only in response to input events or if it must execute every cycle, +respectively. From 60567d3d502146ed6b055439660739f33a4504f9 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Tue, 6 Apr 2021 19:17:40 -0500 Subject: [PATCH 106/108] Begin updates to UPGRADING document for KeyEvent API changes Signed-off-by: Michael Richters --- docs/UPGRADING.md | 44 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 6203eb3f..ae3963a7 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -7,6 +7,8 @@ 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) + - [Event-driven main loop](#event-driven-main-loop) + - [Keyboard state array](#keyboard-state-array) - [New build system](#new-build-system) - [New device API](#new-device-api) - [New plugin API](#new-plugin-api) @@ -35,6 +37,40 @@ any API we've included in a release. Typically, this means that any code that us ## New features +### 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. + +Furthermore, there are now two functions for initiating the processing of key events: +- `Runtime.handleKeyswitchEvent()` is the starting point for events that represent physical keyswitches toggling on or off. +- `Runtime.handleKeyEvent()` is the starting point for "artificial" key events. It is also called at the end of `handleKeyswitchEvent()`. +In general, if a plugin needs to generate a key event, it should call `handleKeyEvent()`, not `handleKeyswitchEvent()`. + +Each of the above functions calls its own set of plugin event handlers. When those event handlers are all done, event processing continues as `handleKeyEvent()` prepares a new keyboard HID report, then sends it: +- `Runtime.prepareKeyboardReport()` first clears the HID report, then populates it based on the contents of the `live_keys[]` array. Note that the HID report is not cleared until _after_ the new plugin event handlers have been called. +- `Runtime.sendKeyboardReport()` handles generating extra HID reports required for keys with keyboard modifier flags to avoid certain bugs, then calls a new plugin event handler before finally sending the new HID report. +These functions should rarely, if ever, need to be called by plugins. + +#### The `KeyEvent` data type + +There is a new `KeyEvent` type that encapsulates all the data relevant to a new key event, and it is used as the parameter for the new event-handling functions. +- `event.addr` contains the `KeyAddr` associated with the event. +- `event.state` contains the state bitfield (`uint8_t`), which can be tested with `keyToggledOn()`/`keyToggledOff()`. +- `event.key` contains a `Key` value, usually looked up from the keymap. +- `event.id` contains a pseudo-unique ID number of type `KeyEventId` (an 8-bit integer), used by certain plugins (see `onKeyswitchEvent()` below). + +#### New plugin event handlers +##### `onKeyswitchEvent(KeyEvent &event)` +##### `onKeyEvent(KeyEvent &event)` +##### `onAddToReport(Key key)` +##### `beforeReportingState(const KeyEvent &event)` + +#### For end-users + +Existing sketches should be mostly backwards-compatible, but some updates will be needed for sketches that use custom code. In particular, users of the Macros plugin are likely to need to make adjustments to the code in the user-defined `macroAction()` function, including that function's signature, the new version of which takes a `KeyEvent` parameter instead of just an event state value. In most cases, this will make the resulting code more straightforward without any loss of functionality. + +In addition to Macros, these changes might also affect user-defined code executed by the TapDance, Leader, and Syster plugins. Please see the documentation and examples for the affected plugins for details. + ### Keyboard State array The keymap cache (`Layer_::live_composite_keymap_[]`) has been replaced by a keyboard state array (`kaleidoscope::live_keys[]`). The top-level functions that handle keyswitch events have been updated to treat this new array as a representation of the current state of the keyboard, with corresponding `Key` values for any keys that are active (physically held or activated by a plugin). @@ -86,10 +122,10 @@ It also comes with several convenience functions which can be used to make the i // Set a value in the keyboard state array to a specified Key value: live_keys.activate(key_addr, Key_X); -// Set a value to Key_Unbound, deactivating the key: +// Set a value to Key_Inactive, deactivating the key: live_keys.clear(key_addr); -// Set all values in the array to Key_Unbound: +// Set all values in the array to Key_Inactive: live_keys.clear();) // Set a value to Key_Masked, masking the key until its next release event: @@ -688,9 +724,9 @@ The following headers and names have changed: ### Live Composite Keymap Cache -The live composite keymap, which contained a lazily-updated version of the current keymap, has been replaced. The `Layer.updateLiveCompositeKeymap()` functions have been deprecated, and depending on the purpose of the caller, it might be appropriate to use `Runtime.updateActiveKey()` instead. +The live composite keymap, which contained a lazily-updated version of the current keymap, has been replaced. The `Layer.updateLiveCompositeKeymap()` functions have been deprecated, and depending on the purpose of the caller, it might be appropriate to use `live_keys.activate()` instead. -When `handleKeyswitchEvent()` is looking up a `Key` value for an event, it first checks the value in the active keys cache before calling `Layer.lookup()` to get the value from the keymap. In the vast majority of cases, it won't be necessary to call `Runtime.updateActiveKey()` manually, however, because simply changing the value of the `Key` parameter of an `onKeyswitchEvent()` handler will have the same effect. +When `handleKeyswitchEvent()` is looking up a `Key` value for an event, it first checks the value in the active keys cache before calling `Layer.lookup()` to get the value from the keymap. In the vast majority of cases, it won't be necessary to call `live_keys.activate()` manually, however, because simply changing the value of the `Key` parameter of an `onKeyswitchEvent()` handler will have the same effect. Second, the `Layer.eventHandler()` function has been deprecated. There wasn't much need for this to be available to plugins, and it's possible to call `Layer.handleKeymapKeyswitchEvent()` directly instead. From 17045d92945aaf0827f56927029e9b2bb4028b50 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sun, 16 May 2021 13:17:27 -0500 Subject: [PATCH 107/108] Make ActiveModColor work if registered ahead of OneShot If `ActiveModColorEffect` was registered ahead of `OneShot` in `KALEIDOSCOPE_INIT_PLUGINS()`, `OSM()` and `OSL()` keys would light up in the OneShot "sticky" state, not in the "held" or "one-shot" states. This happened because OneShot changes the `event.key` value to the corresponding base key (modifier or layer shift), but if ActiveModColor had already processed that key event, it wouldn't recognize the key as a modifier/layer shift key, and would therefore ignore it. This change makes ActiveModColor also recognize OneShot keys as modifier/layer shift keys. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/LED-ActiveModColor.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 069a2208..ab127bfd 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -45,6 +45,7 @@ EventHandlerResult ActiveModColorEffect::onKeyEvent(KeyEvent &event) { // we also highlight modifier and layer-shift keys. if (event.key.isKeyboardModifier() || event.key.isLayerShift() || + ::OneShot.isOneShotKey(event.key) || ::OneShot.isActive(event.addr)) { mod_key_bits_.set(event.addr); } From 6bdcd01080a5ce7bd31f40cb5289c4f116cf5528 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Fri, 28 May 2021 15:05:54 -0500 Subject: [PATCH 108/108] Add glossary entries for LiveKeys special values Signed-off-by: Michael Richters --- docs/codebase/glossary.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/codebase/glossary.md b/docs/codebase/glossary.md index 317d63e4..446b6a6c 100644 --- a/docs/codebase/glossary.md +++ b/docs/codebase/glossary.md @@ -51,6 +51,13 @@ An ordered list of all the currently-active layers, in the order they should be A representation of the current state of the keyboard's keys, where non-transparent entries indicate keys that are active (logically—usually, but not necessarily, physically held). Represented in the code by the `LiveKeys` type (and the `live_keys` object). +#### Active/inactive keys + +In the `live_keys[]` array, an _active_ key usually corresponds to a keyswitch that is physically pressed. In the common case of HID Keyboard keys, an active key will result in one or more keycodes being inserted in any new HID report. In some cases, an key can be active when its physical keyswitch is not pressed (e.g. OneShot keys that have been tapped), and in other cases a key might be _inactive_ even though its keyswitch is pressed (e.g. a Qukeys key whose value has not yet been resolved). Inactive keys are represented in the `live_keys[]` array by the special value `Key_Inactive`. + +#### Masked keys + +In the `live_keys[]` array, a _masked_ key is one whose next key press (either physical or logical) will be ignored. A masked key is automatically unmasked the next time it toggles off. Masked keys are represented by the special value `Key_Masked`. ## Keyswitch state