Merge pull request #1037 from gedankenexperimenter/spacecadet-no-delay

Add SpaceCadet "no-delay" mode
pull/1045/head
Gergely Nagy 3 years ago committed by GitHub
commit a1abdf0b83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,13 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading
## New features ## 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 ### New Qukeys features
#### Tap-repeat #### Tap-repeat

@ -133,6 +133,13 @@ properties:
> is disabled. This is useful for interfacing with other plugins or macros, > is disabled. This is useful for interfacing with other plugins or macros,
> especially where SpaceCadet functionality isn't always desired. > 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` ### `Key_SpaceCadetEnable`
> This provides a key for placing on a keymap for enabling the SpaceCadet > This provides a key for placing on a keymap for enabling the SpaceCadet

@ -46,7 +46,7 @@ uint16_t SpaceCadet::time_out = 200;
// ----------------------------------------------------------------------------- // -----------------------------------------------------------------------------
// State variables // 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 // 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 // key that has been pressed. If `pending_map_index_` is negative, it means
@ -78,23 +78,6 @@ SpaceCadet::SpaceCadet() {
map = initialmap; map = initialmap;
} }
// -----------------------------------------------------------------------------
// 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
void SpaceCadet::disable() {
disabled = true;
}
// ============================================================================= // =============================================================================
// Event handler hook functions // Event handler hook functions
@ -134,7 +117,7 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
} }
// Do nothing if disabled, but keep the event tracker current. // Do nothing if disabled, but keep the event tracker current.
if (disabled) if (mode_ == Mode::OFF)
return EventHandlerResult::OK; return EventHandlerResult::OK;
if (!event_queue_.isEmpty()) { if (!event_queue_.isEmpty()) {
@ -162,7 +145,13 @@ EventHandlerResult SpaceCadet::onKeyswitchEvent(KeyEvent &event) {
// Check for a SpaceCadet key // Check for a SpaceCadet key
pending_map_index_ = getSpaceCadetKeyIndex(event.key); pending_map_index_ = getSpaceCadetKeyIndex(event.key);
if (pending_map_index_ >= 0) { 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); event_queue_.append(event);
return EventHandlerResult::ABORT; return EventHandlerResult::ABORT;
} }
@ -211,6 +200,11 @@ void SpaceCadet::flushQueue() {
void SpaceCadet::flushEvent(bool is_tap) { void SpaceCadet::flushEvent(bool is_tap) {
KeyEvent event = event_queue_.event(0); KeyEvent event = event_queue_.event(0);
if (is_tap && pending_map_index_ >= 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.key = map[pending_map_index_].output;
} }
event_queue_.shift(); event_queue_.shift();

@ -60,9 +60,18 @@ class SpaceCadet : public kaleidoscope::Plugin {
SpaceCadet(void); SpaceCadet(void);
// Methods // Methods
static void enable(void); static void enable() {
static void disable(void); mode_ = Mode::ON;
static bool active(void); }
static void disable() {
mode_ = Mode::OFF;
}
static void enableWithoutDelay() {
mode_ = Mode::NO_DELAY;
}
static bool active() {
return (mode_ == Mode::ON || mode_ == Mode::NO_DELAY);
}
// Publically accessible variables // Publically accessible variables
static uint16_t time_out; // The global timeout in milliseconds static uint16_t time_out; // The global timeout in milliseconds
@ -73,7 +82,12 @@ class SpaceCadet : public kaleidoscope::Plugin {
EventHandlerResult afterEachCycle(); EventHandlerResult afterEachCycle();
private: private:
static bool disabled; enum Mode : uint8_t {
ON,
OFF,
NO_DELAY,
};
static uint8_t mode_;
static KeyEventTracker event_tracker_; static KeyEventTracker event_tracker_;

@ -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 <http://www.gnu.org/licenses/>.
*/
#include <Kaleidoscope.h>
#include <Kaleidoscope-SpaceCadet.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;
SpaceCadet.enableWithoutDelay();
}
void loop() {
Kaleidoscope.loop();
}

@ -0,0 +1,6 @@
{
"cpu": {
"fqbn": "keyboardio:virtual:model01",
"port": ""
}
}

@ -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
Loading…
Cancel
Save