From b0ea789f286ea3de30a075317498a47501c1fc29 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 29 May 2021 09:40:41 -0500 Subject: [PATCH 1/6] Change `SpaceCadet::disabled` to `SpaceCadet::mode_` Switch from `bool` to `enum` in preparation for a third mode of SpaceCadet functionality, where the modifier becomes active immediately when the key is pressed, rather than waiting for the key to resolve into the "hold" or "tap" state. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/SpaceCadet.cpp | 10 +++++----- .../src/kaleidoscope/plugin/SpaceCadet.h | 7 ++++++- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index 6135c320..dfa2eca8 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -46,7 +46,7 @@ uint16_t SpaceCadet::time_out = 200; // ----------------------------------------------------------------------------- // State variables -bool SpaceCadet::disabled = false; +uint8_t SpaceCadet::mode_ = SpaceCadet::Mode::ON; // 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 @@ -82,17 +82,17 @@ SpaceCadet::SpaceCadet() { // Function to determine whether SpaceCadet is active (useful for Macros and // other plugins). bool SpaceCadet::active() { - return !disabled; + return mode_ == Mode::ON; } // Function to enable SpaceCadet behavior void SpaceCadet::enable() { - disabled = false; + mode_ = Mode::ON; } // Function to disable SpaceCadet behavior void SpaceCadet::disable() { - disabled = true; + mode_ = Mode::OFF; } // ============================================================================= @@ -134,7 +134,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { } // Do nothing if disabled, but keep the event tracker current. - if (disabled) + if (mode_ != Mode::ON) return EventHandlerResult::OK; if (!event_queue_.isEmpty()) { diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 971bb540..4f71803a 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -73,7 +73,12 @@ class SpaceCadet : public kaleidoscope::Plugin { EventHandlerResult afterEachCycle(); private: - static bool disabled; + enum Mode : uint8_t { + ON, + OFF, + NO_DELAY, + }; + static uint8_t mode_; static KeyEventTracker event_tracker_; From 768c95ae46455feaabaa86a69467696c3b592a61 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 29 May 2021 09:52:58 -0500 Subject: [PATCH 2/6] Make SpaceCadet public mode functions inline Moving these one-liners to the header file lets them be inlined by client code. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/SpaceCadet.cpp | 17 ----------------- .../src/kaleidoscope/plugin/SpaceCadet.h | 12 +++++++++--- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index dfa2eca8..4e091993 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -78,23 +78,6 @@ SpaceCadet::SpaceCadet() { map = initialmap; } -// ----------------------------------------------------------------------------- -// Function to determine whether SpaceCadet is active (useful for Macros and -// other plugins). -bool SpaceCadet::active() { - return mode_ == Mode::ON; -} - -// Function to enable SpaceCadet behavior -void SpaceCadet::enable() { - mode_ = Mode::ON; -} - -// Function to disable SpaceCadet behavior -void SpaceCadet::disable() { - mode_ = Mode::OFF; -} - // ============================================================================= // Event handler hook functions diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 4f71803a..23453120 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -60,9 +60,15 @@ class SpaceCadet : public kaleidoscope::Plugin { SpaceCadet(void); // Methods - static void enable(void); - static void disable(void); - static bool active(void); + static void enable() { + mode_ = Mode::ON; + } + static void disable() { + mode_ = Mode::OFF; + } + static bool active() { + return (mode_ == Mode::ON); + } // Publically accessible variables static uint16_t time_out; // The global timeout in milliseconds From 2faa7c00a956cf2de370544a1557d129ba91140d Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 29 May 2021 10:31:10 -0500 Subject: [PATCH 3/6] Add a "no-delay" mode to SpaceCadet SpaceCadet now has three "modes": on, off, and on with no delay. In "no-delay" mode, when a SpaceCadet key is pressed, its primary (modifier) key value is sent immediately to the host, and if it is released before timing out, that value is then replaced by the configured "tap" value of the key. Signed-off-by: Michael Richters --- .../src/kaleidoscope/plugin/SpaceCadet.cpp | 15 +++++++++++++-- .../src/kaleidoscope/plugin/SpaceCadet.h | 5 ++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp index 4e091993..b3fd2edb 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.cpp @@ -117,7 +117,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { } // Do nothing if disabled, but keep the event tracker current. - if (mode_ != Mode::ON) + if (mode_ == Mode::OFF) return EventHandlerResult::OK; if (!event_queue_.isEmpty()) { @@ -145,7 +145,13 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) { // Check for a SpaceCadet key pending_map_index_ = getSpaceCadetKeyIndex(event.key); if (pending_map_index_ >= 0) { - // A SpaceCadet key was pressed + // A SpaceCadet key has just toggled on. First, if we're in no-delay mode, + // we need to send the event unchanged (with the primary `Key` value), + // bypassing other `onKeyswitchEvent()` handlers. + if (mode_ == Mode::NO_DELAY) + Runtime.handleKeyEvent(event); + // Queue the press event and abort; this press event will be resolved + // later. event_queue_.append(event); return EventHandlerResult::ABORT; } @@ -194,6 +200,11 @@ void SpaceCadet::flushQueue() { void SpaceCadet::flushEvent(bool is_tap) { KeyEvent event = event_queue_.event(0); if (is_tap && pending_map_index_ >= 0) { + // If we're in no-delay mode, we should first send the release of the + // modifier key as a courtesy before sending the tap event. + if (mode_ == Mode::NO_DELAY) { + Runtime.handleKeyEvent(KeyEvent(event.addr, WAS_PRESSED)); + } event.key = map[pending_map_index_].output; } event_queue_.shift(); diff --git a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h index 23453120..3365d755 100644 --- a/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h +++ b/plugins/Kaleidoscope-SpaceCadet/src/kaleidoscope/plugin/SpaceCadet.h @@ -66,8 +66,11 @@ class SpaceCadet : public kaleidoscope::Plugin { static void disable() { mode_ = Mode::OFF; } + static void enableWithoutDelay() { + mode_ = Mode::NO_DELAY; + } static bool active() { - return (mode_ == Mode::ON); + return (mode_ == Mode::ON || mode_ == Mode::NO_DELAY); } // Publically accessible variables From 68d5fa80d1e33c137fc867e169d883bf1e852aca Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 29 May 2021 11:04:43 -0500 Subject: [PATCH 4/6] Add testcases for SpaceCadet in "no-delay" mode Signed-off-by: Michael Richters --- .../plugins/SpaceCadet/no-delay/no-delay.ino | 68 +++++++++ tests/plugins/SpaceCadet/no-delay/sketch.json | 6 + tests/plugins/SpaceCadet/no-delay/test.ktest | 141 ++++++++++++++++++ 3 files changed, 215 insertions(+) create mode 100644 tests/plugins/SpaceCadet/no-delay/no-delay.ino create mode 100644 tests/plugins/SpaceCadet/no-delay/sketch.json create mode 100644 tests/plugins/SpaceCadet/no-delay/test.ktest diff --git a/tests/plugins/SpaceCadet/no-delay/no-delay.ino b/tests/plugins/SpaceCadet/no-delay/no-delay.ino new file mode 100644 index 00000000..7881a202 --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/no-delay.ino @@ -0,0 +1,68 @@ +/* -*- 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_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; + + SpaceCadet.enableWithoutDelay(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/SpaceCadet/no-delay/sketch.json b/tests/plugins/SpaceCadet/no-delay/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/SpaceCadet/no-delay/test.ktest b/tests/plugins/SpaceCadet/no-delay/test.ktest new file mode 100644 index 00000000..ba35c59a --- /dev/null +++ b/tests/plugins/SpaceCadet/no-delay/test.ktest @@ -0,0 +1,141 @@ +VERSION 1 + +KEYSWITCH LSHIFT 0 0 +KEYSWITCH RSHIFT 0 1 +KEYSWITCH A 1 0 +KEYSWITCH B 1 1 + +# ============================================================================== +NAME SpaceCadet tap + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty +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 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 10 ms # timeout = 10 ms (for this key) +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME SpaceCadet hold with global timeout + +RUN 4 ms +PRESS RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5) + +RUN 20 ms # timeout = 20 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME SpaceCadet interrupt + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 4 ms +PRESS A +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_A # Report should add `A` (0x04, 0xE1) + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_A # Report should contain only `A` (0x04) + +RUN 4 ms +RELEASE A +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME SpaceCadet interrupt SpaceCadet with tap + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 4 ms +PRESS RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_RightShift # Report should add `shift` (0xE5) + +RUN 4 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) +EXPECT keyboard-report Key_LeftShift Key_Y # Report should add `Y` (0x1C) +EXPECT keyboard-report Key_LeftShift # Report should contain `shift` (0xE1) + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms + +# ============================================================================== +NAME SpaceCadet interrupt SpaceCadet with hold + +# First, press left shift. It takes effect immediately, because SpaceCadet is in +# "no-delay" mode. +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +# Before left shift times out (timeout=10ms), press right shift, which also +# takes effect without delay. +RUN 4 ms +PRESS RSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_RightShift # report: { e1 e5 } + +# Next, release left shift after it times out (so it's not a "tap"), but before +# the right shift times out. This does not generate a report, because the right +# shift might still become a "tap" if it's released soon enough. +RUN 10 ms +RELEASE LSHIFT + +# Next, the right shift times out, resolving to its modifier state. This allows +# the left shift to be released, because the right shift can't be a "tap". +RUN 10 ms +EXPECT keyboard-report Key_RightShift # Report should contain `shift` (0xE5) + +# Last, release the right shift as normal. +RUN 4 ms +RELEASE RSHIFT +RUN 1 cycle +EXPECT keyboard-report empty # Report should be empty + +RUN 5 ms From 03ebc96348d2633a314e4bd114fd59500b0c8efa Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sat, 29 May 2021 11:15:26 -0500 Subject: [PATCH 5/6] Add documentation for SpaceCadet "no-delay" mode Signed-off-by: Michael Richters --- plugins/Kaleidoscope-SpaceCadet/README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/plugins/Kaleidoscope-SpaceCadet/README.md b/plugins/Kaleidoscope-SpaceCadet/README.md index 4b6a0574..bae8ea0c 100644 --- a/plugins/Kaleidoscope-SpaceCadet/README.md +++ b/plugins/Kaleidoscope-SpaceCadet/README.md @@ -133,6 +133,13 @@ properties: > is disabled. This is useful for interfacing with other plugins or macros, > especially where SpaceCadet functionality isn't always desired. +### `.enableWithoutDelay()` + +> This method enables the SpaceCadet plugin in "no-delay" mode. In this mode, +> SpaceCadet immediately sends the primary (modifier) value of the SpaceCadet +> key when it is pressed. If it is then released before timing out, it sends the +> alternate "tap" value, replacing the modifier. + ### `Key_SpaceCadetEnable` > This provides a key for placing on a keymap for enabling the SpaceCadet From f039b375de096f26fc4a7fb9a8c0ac4e1385ce65 Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sun, 30 May 2021 13:46:09 -0500 Subject: [PATCH 6/6] Add SpaceCadet "no-delay" mode to NEWS.md Signed-off-by: Michael Richters --- docs/NEWS.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/NEWS.md b/docs/NEWS.md index b00e3872..56841b76 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,13 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## New features +### SpaceCadet "no-delay" mode + +SpaceCadet can now be enabled in "no-delay" mode, wherein the primary (modifier) +value of the key will be sent to the host immediately when the key is pressed. +If the SpaceCadet key is released before the timeout, the modifier is released, +and then the alternate (symbol) value is sent. To activate "no-delay" mode, call `SpaceCadet.enableWithoutDelay()`. + ### New Qukeys features #### Tap-repeat