Merge pull request #1062 from gedankenexperimenter/plugin/oneshot-meta-keys

Move handling of OneShot "meta" keys to a new `OneShotMetaKeys` plugin
pull/1046/merge
Gergely Nagy 3 years ago committed by GitHub
commit eaa0999c28
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -12,6 +12,29 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading
## New features ## New features
### OneShot public functions
The OneShot plugin now allows other plugins to control the OneShot state of
individual keys, by calling one of the following:
- `OneShot.setPending(key_addr)`: Put the key at `key_addr` in the "pending"
OneShot state. This will make that key act like any other OneShot key until
it is cancelled by a subsequent keypress. Once a key is in this state,
OneShot will manage it from that point on, including making the key "sticky"
if it is double-tapped.
- `OneShot.setSticky(key_addr)`: Put the key at `key_addr` in the "sticky"
OneShot state. The key will be released by OneShot when it is tapped again.
- `OneShot.setOneShot(key_addr)`: Put the key at `key_addr` in the "one-shot"
state. This is normally the state OneShot key will be in after it has been
tapped. Calling `setPending()` is more likely to be useful.
- `OneShot.clear(key_addr)`: Clear the OneShot state of the key at `key_addr`.
Note: Any plugin that calls one of these OneShot methods must either be
registered in `KALEIDOSCOPE_INIT_PLUGINS()` after OneShot, or it must add the
`INJECTED` bit to the keyswitch state of the event (i.e. `event.state |=
INJECTED`) to prevent OneShot from prematurely advancing keys to the next
OneShot state.
### SpaceCadet "no-delay" mode ### SpaceCadet "no-delay" mode
SpaceCadet can now be enabled in "no-delay" mode, wherein the primary (modifier) SpaceCadet can now be enabled in "no-delay" mode, wherein the primary (modifier)

@ -16,6 +16,7 @@ If any of this does not make sense to you, or you have trouble updating your .in
- [Bidirectional communication for plugins](#bidirectional-communication-for-plugins) - [Bidirectional communication for plugins](#bidirectional-communication-for-plugins)
- [Consistent timing](#consistent-timing) - [Consistent timing](#consistent-timing)
+ [Breaking changes](#breaking-changes) + [Breaking changes](#breaking-changes)
- [OneShot meta keys](#oneshot-meta-keys)
- [git checkouts aren't compatible with Arduino IDE (GUI)]([#repository-rearchitecture) - [git checkouts aren't compatible with Arduino IDE (GUI)]([#repository-rearchitecture)
- [Layer system switched to activation-order](#layer-system-switched-to-activation-order) - [Layer system switched to activation-order](#layer-system-switched-to-activation-order)
- [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state) - [The `RxCy` macros and peeking into the keyswitch state](#the-rxcy-macros-and-peeking-into-the-keyswitch-state)
@ -434,6 +435,10 @@ As a developer, one can continue using `millis()`, but migrating to `Kaleidoscop
## Breaking changes ## Breaking changes
### OneShot meta keys
The special OneShot keys `OneShot_MetaStickyKey` & `OneShot_ActiveStickyKey` are no longer handled by the OneShot plugin directly, but instead by a separate OneShotMetaKeys plugin. If you use these keys in your sketch, you will need to add the new plugin, and register it after OneShot in `KALEIDOSCOPE_INIT_PLUGINS()` for those keys to work properly.
### Repository rearchitecture ### Repository rearchitecture
To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory. To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory.

@ -28,7 +28,7 @@ enum {
KEYMAPS( KEYMAPS(
[0] = KEYMAP_STACKED [0] = KEYMAP_STACKED
( (
M(TOGGLE_ONESHOT), Key_1, Key_2, Key_3, Key_4, Key_5, OneShot_MetaStickyKey, M(TOGGLE_ONESHOT), Key_1, Key_2, Key_3, Key_4, Key_5, ___,
Key_Backtick, Key_Q, Key_W, Key_E, Key_R, Key_T, Key_Tab, 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_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_PageDown, Key_Z, Key_X, Key_C, Key_V, Key_B, Key_Escape,
@ -36,7 +36,7 @@ KEYMAPS(
OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift), OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift),
Key_Meh, Key_Meh,
OneShot_ActiveStickyKey, Key_6, Key_7, Key_8, Key_9, Key_0, ___, ___, 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_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_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote,
___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, ___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,

@ -0,0 +1,88 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShotMetaKeys -- Special OneShot keys
* 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-Macros.h>
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-OneShotMetaKeys.h>
// Macros
enum {
TOGGLE_ONESHOT,
};
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
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),
Key_Meh,
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_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus,
Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl,
OSL(1)),
[1] = KEYMAP_STACKED
(
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
Key_UpArrow, Key_DownArrow, Key_LeftArrow, Key_RightArrow,___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___),
)
// *INDENT-ON*
void macroToggleOneShot() {
OneShot.toggleAutoOneShot();
}
const macro_t *macroAction(uint8_t macro_id, KeyEvent &event) {
if (macro_id == TOGGLE_ONESHOT) {
macroToggleOneShot();
}
return MACRO_NONE;
}
KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotMetaKeys, Macros);
void setup() {
Kaleidoscope.setup();
}
void loop() {
Kaleidoscope.loop();
}

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

@ -63,6 +63,11 @@ The `ActiveModColorEffect` object provides the following methods:
* [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md) * [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.md)
* [Kaleidoscope-OneShot](Kaleidoscope-OneShot.md) * [Kaleidoscope-OneShot](Kaleidoscope-OneShot.md)
* [Kaleidoscope-OneShotMetaKeys](Kaleidoscope-OneShotMetaKeys.md)
The `ActiveModColorEffect` plugin doesn't require that either OneShot or
OneShotMetaKeys plugins are registered with `KALEIDOSCOPE_INIT_PLUGINS()` in
order to work, but it does depend on their header files.
## Further reading ## Further reading

@ -17,6 +17,7 @@
#include <Kaleidoscope-LED-ActiveModColor.h> #include <Kaleidoscope-LED-ActiveModColor.h>
#include <Kaleidoscope-OneShot.h> #include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-OneShotMetaKeys.h>
#include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope/layers.h" #include "kaleidoscope/layers.h"
#include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/keyswitch_state.h"
@ -54,7 +55,7 @@ EventHandlerResult ActiveModColorEffect::onKeyEvent(KeyEvent &event) {
// Get the entry from the live keys array // Get the entry from the live keys array
Key entry_key = live_keys[entry_addr]; Key entry_key = live_keys[entry_addr];
// Skip empty entries // Skip empty entries
if (entry_key == Key_Transparent || entry_key == Key_NoKey) { if (entry_key == Key_Inactive || entry_key == Key_Masked) {
continue; continue;
} }
// Highlight everything else // Highlight everything else

@ -46,25 +46,6 @@ 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, "held" state, but will be deactivated when released like any non-one-shot key,
it will have a white highlight. (These colors are configurable.) 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 ## Using the plugin
After adding one-shot keys to the keymap, all one needs to do, is enable the After adding one-shot keys to the keymap, all one needs to do, is enable the

@ -52,10 +52,6 @@ KeyAddrBitfield OneShot::glue_addrs_;
uint16_t OneShot::start_time_ = 0; uint16_t OneShot::start_time_ = 0;
KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr; KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr;
#ifndef ONESHOT_WITHOUT_METASTICKY
KeyAddr OneShot::meta_sticky_key_addr_ {KeyAddr::invalid_state};
#endif
// ============================================================================ // ============================================================================
// Public interface // Public interface
@ -118,28 +114,40 @@ bool OneShot::isSticky() {
// could potentially use three different color values for the three // could potentially use three different color values for the three
// states (sticky | active && !sticky | pressed && !active). // states (sticky | active && !sticky | pressed && !active).
__attribute__((weak))
bool OneShot::isStickable(Key key) { bool OneShot::isStickable(Key key) {
return isStickableDefault(key);
}
bool OneShot::isStickableDefault(Key key) {
int8_t n; int8_t n;
// If the key is either a keyboard modifier or a layer shift, we check to see
// if it has been set to be non-stickable.
if (key.isKeyboardModifier()) { if (key.isKeyboardModifier()) {
n = key.getKeyCode() - Key_LeftControl.getKeyCode(); n = key.getKeyCode() - Key_LeftControl.getKeyCode();
return bitRead(stickable_keys_, n); return bitRead(stickable_keys_, n);
} else if (key.isLayerShift()) { } else if (key.isLayerShift()) {
n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET;
// We only keep track of the stickability of the first 8 layers.
if (n < oneshot_key_count) { if (n < oneshot_key_count) {
return bitRead(stickable_keys_, n); return bitRead(stickable_keys_, n);
} }
#ifndef ONESHOT_WITHOUT_METASTICKY
} else if (key == OneShot_MetaStickyKey) {
return true;
#endif
} }
return false; // The default is for all keys to be "stickable"; if the default was false,
// any user code or other plugin that uses `setPending()` to turn a key into a
// OneShot would need to override `isStickable()` in order to make that key
// stickable (the default `OSM()` behaviour).
return true;
} }
bool OneShot::isTemporary(KeyAddr key_addr) { bool OneShot::isTemporary(KeyAddr key_addr) {
return temp_addrs_.read(key_addr); return temp_addrs_.read(key_addr);
} }
bool OneShot::isPending(KeyAddr key_addr) {
return (glue_addrs_.read(key_addr) && temp_addrs_.read(key_addr));
}
bool OneShot::isSticky(KeyAddr key_addr) { bool OneShot::isSticky(KeyAddr key_addr) {
return (glue_addrs_.read(key_addr) && !temp_addrs_.read(key_addr)); return (glue_addrs_.read(key_addr) && !temp_addrs_.read(key_addr));
} }
@ -148,6 +156,31 @@ bool OneShot::isActive(KeyAddr key_addr) {
return (isTemporary(key_addr) || glue_addrs_.read(key_addr)); return (isTemporary(key_addr) || glue_addrs_.read(key_addr));
} }
// ----------------------------------------------------------------------------
// Public state-setting functions
void OneShot::setPending(KeyAddr key_addr) {
temp_addrs_.set(key_addr);
glue_addrs_.clear(key_addr);
start_time_ = Runtime.millisAtCycleStart();
}
void OneShot::setOneShot(KeyAddr key_addr) {
temp_addrs_.set(key_addr);
glue_addrs_.set(key_addr);
start_time_ = Runtime.millisAtCycleStart();
}
void OneShot::setSticky(KeyAddr key_addr) {
temp_addrs_.clear(key_addr);
glue_addrs_.set(key_addr);
}
void OneShot::clear(KeyAddr key_addr) {
temp_addrs_.clear(key_addr);
glue_addrs_.clear(key_addr);
}
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
// Other functions // Other functions
@ -190,27 +223,6 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) {
if (keyToggledOn(event.state)) { if (keyToggledOn(event.state)) {
// Make all held keys sticky if `OneShot_ActiveStickyKey` toggles on.
if (event.key == OneShot_ActiveStickyKey) {
// Skip the stickify key itself
for (KeyAddr entry_addr : KeyAddr::all()) {
if (entry_addr == event.addr) {
continue;
}
// 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) {
continue;
}
// Make everything else sticky
temp_addrs_.clear(entry_addr);
glue_addrs_.set(entry_addr);
}
prev_key_addr_ = event.addr;
return EventHandlerResult::OK;
}
if (!temp && !glue) { if (!temp && !glue) {
// The key is in the "normal" state. The first thing we need to do is // 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 // convert OneShot keys to their equivalent values, and record the fact
@ -222,34 +234,6 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) {
is_oneshot = true; is_oneshot = true;
} }
#ifndef ONESHOT_WITHOUT_METASTICKY
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) {
// The meta key is in the "one-shot" state; release it immediately.
releaseKey(meta_sticky_key_addr_);
} else {
// The meta key is in the "pending" state; cancel that, and let it
// deactivate on release.
temp_addrs_.clear(meta_sticky_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();
return EventHandlerResult::OK;
}
#endif
if (is_oneshot || if (is_oneshot ||
(auto_modifiers_ && event.key.isKeyboardModifier()) || (auto_modifiers_ && event.key.isKeyboardModifier()) ||
(auto_layers_ && event.key.isLayerShift())) { (auto_layers_ && event.key.isLayerShift())) {
@ -309,11 +293,6 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) {
// stop that event from sending a report, and instead send a "hold" // stop that event from sending a report, and instead send a "hold"
// event. This is handled in the `beforeReportingState()` hook below. // event. This is handled in the `beforeReportingState()` hook below.
return EventHandlerResult::ABORT; return EventHandlerResult::ABORT;
#ifndef ONESHOT_WITHOUT_METASTICKY
} 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
} }
} }
@ -434,11 +413,6 @@ void OneShot::releaseKey(KeyAddr key_addr) {
glue_addrs_.clear(key_addr); glue_addrs_.clear(key_addr);
temp_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::none();
#endif
KeyEvent event{key_addr, WAS_PRESSED | INJECTED}; KeyEvent event{key_addr, WAS_PRESSED | INJECTED};
Runtime.handleKeyEvent(event); Runtime.handleKeyEvent(event);
} }

@ -68,11 +68,6 @@
#define OSM(kc) Key(kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).getKeyCode() - Key_LeftControl.getKeyCode()) #define OSM(kc) Key(kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).getKeyCode() - Key_LeftControl.getKeyCode())
#define OSL(n) Key(kaleidoscope::ranges::OSL_FIRST + n) #define OSL(n) Key(kaleidoscope::ranges::OSL_FIRST + n)
// ----------------------------------------------------------------------------
// Key constants
constexpr Key OneShot_MetaStickyKey {kaleidoscope::ranges::OS_META_STICKY};
constexpr Key OneShot_ActiveStickyKey {kaleidoscope::ranges::OS_ACTIVE_STICKY};
namespace kaleidoscope { namespace kaleidoscope {
namespace plugin { namespace plugin {
@ -153,12 +148,49 @@ class OneShot : public kaleidoscope::Plugin {
key.getRaw() <= kaleidoscope::ranges::OS_LAST); key.getRaw() <= kaleidoscope::ranges::OS_LAST);
} }
static bool isStickable(Key key); // inline? /// Determine if the given `key` is allowed to become sticky.
static bool isStickable(Key key);
static bool isStickableDefault(Key key);
static bool isTemporary(KeyAddr key_addr); // inline? static bool isTemporary(KeyAddr key_addr); // inline?
static bool isPending(KeyAddr key_addr);
static bool isSticky(KeyAddr key_addr); // inline? static bool isSticky(KeyAddr key_addr); // inline?
static bool isActive(KeyAddr key_addr); // inline? static bool isActive(KeyAddr key_addr); // inline?
// --------------------------------------------------------------------------
// Public OneShot state control
/// Put a key in the "pending" OneShot state.
///
/// This function puts the key at `key_addr` in the "pending" OneShot state.
/// This is appropriate to use when a key toggles on and you want it to behave
/// like a OneShot key starting with the current event, and lasting until the
/// key becomes inactive (cancelled by a subsequent keypress).
static void setPending(KeyAddr key_addr);
/// Put a key directly in the "one-shot" state.
///
/// This function puts the key at `key_addr` in the "one-shot" state. This is
/// usually the state of a OneShot key after it is released, but before it is
/// cancelled by a subsequent keypress. In most cases, you probably want to
/// use `setPending()` instead, rather than calling this function explicitly,
/// as OneShot will automatically cause any key in the "pending" state to
/// progress to this state when it is (physically) released.
static void setOneShot(KeyAddr key_addr);
/// Put a key in the "sticky" OneShot state.
///
/// This function puts the key at `key_addr` in the "sticky" OneShot state.
/// It will remain active until it is pressed again.
static void setSticky(KeyAddr key_addr);
/// Clear any OneShot state for a key.
///
/// This function clears any OneShot state of the key at `key_addr`. It does
/// not, however, release the key if it is held.
static void clear(KeyAddr key_addr);
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Utility function for other plugins to cancel OneShot keys // Utility function for other plugins to cancel OneShot keys
static void cancel(bool with_stickies = false); static void cancel(bool with_stickies = false);
@ -262,10 +294,6 @@ class OneShot : public kaleidoscope::Plugin {
static uint16_t start_time_; static uint16_t start_time_;
static KeyAddr prev_key_addr_; static KeyAddr prev_key_addr_;
#ifndef ONESHOT_WITHOUT_METASTICKY
static KeyAddr meta_sticky_key_addr_;
#endif
// -------------------------------------------------------------------------- // --------------------------------------------------------------------------
// Internal utility functions // Internal utility functions
static bool hasTimedOut(uint16_t ttl) { static bool hasTimedOut(uint16_t ttl) {

@ -0,0 +1,56 @@
# OneShot Meta Keys
This plugin provides support for two special OneShot keys:
`OneShot_MetaStickyKey` & `OneShot_ActiveStickyKey`, each of which can be used
to make any key on the keyboard (not just modifiers and layer shift keys)
"sticky", so that they remain active even after the key has been released.
These are both `Key` values that can be used as entries in your sketch's keymap.
Any keys made sticky in this way can be released just like OneShot modifier
keys, by tapping them again to cancel the effect.
## The `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.
Double-tapping `OneShot_MetaStickyKey` will make it sticky, just like any other
OneShot key. A third tap will release the key.
## The `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
To use the plugin, just include one of the two special OneShot keys somewhere in
your keymap, and add both OneShot and OneShotMetaKeys to your sketch:
```c++
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-OneShotMetaKeys.h>
// somewhere in the keymap...
OneShot_MetaStickyKey, OneShot_ActiveStickyKey
KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotMetaKeys);
```
Important note: OneShotMetaKeys _must_ be registered after OneShot in
`KALEIDOSCOPE_INIT_PLUGINS()` in order to function properly.
## Dependencies
* [Kaleidoscope-OneShot](Kaleidoscope-OneShot.md)
* [Kaleidoscope-Ranges](Kaleidoscope-Ranges.md)
## Further reading
Starting from the [example][plugin:example] is the recommended way of getting
started with the plugin.
[plugin:example]: /examples/Keystrokes/OneShotMetaKeys/OneShotMetaKeys.ino

@ -0,0 +1,7 @@
name=Kaleidoscope-OneShotMetaKeys
version=0.0.0
sentence=OneShot_ActiveStickyKey & OneShot_MetaStickyKey
maintainer=Kaleidoscope's Developers <jesse@keyboard.io>
url=https://github.com/keyboardio/Kaleidoscope
author=Keyboardio
paragraph=

@ -0,0 +1,20 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* 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/>.
*/
#pragma once
#include <kaleidoscope/plugin/OneShotMetaKeys.h>

@ -0,0 +1,94 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* 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-OneShotMetaKeys.h>
#include <Kaleidoscope-FocusSerial.h>
#include <Kaleidoscope-OneShot.h>
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/LiveKeys.h"
namespace kaleidoscope {
namespace plugin {
// ============================================================================
// Public interface
// ----------------------------------------------------------------------------
// Plugin hook functions
EventHandlerResult OneShotMetaKeys::onNameQuery() {
return ::Focus.sendName(F("OneShotMetaKeys"));
}
EventHandlerResult OneShotMetaKeys::onKeyEvent(KeyEvent &event) {
// Ignore key release and injected events.
if (keyToggledOff(event.state) || event.state & INJECTED)
return EventHandlerResult::OK;
// Make all held keys sticky if `OneShot_ActiveStickyKey` toggles on.
if (event.key == OneShot_ActiveStickyKey) {
// Note: we don't need to explicitly skip the key the active sticky key
// itself (i.e. `event.addr`), because its entry in `live_keys[]` has not
// yet been inserted at this point.
for (KeyAddr addr : KeyAddr::all()) {
// Get the entry from the keyboard state array.
Key key = live_keys[addr];
// Skip idle and masked entries.
if (key == Key_Inactive || key == Key_Masked) {
continue;
}
// Make everything else sticky.
::OneShot.setSticky(addr);
}
return EventHandlerResult::OK;
}
// If there's an active meta-sticky key, we need to make newly-pressed keys
// sticky, unless they were already active, in which case we let OneShot
// release them from the "sticky" state.
if (isMetaStickyActive() &&
live_keys[event.addr] != event.key) {
::OneShot.setSticky(event.addr);
return EventHandlerResult::OK;
}
// If a previously-inactive meta-sticky key was just pressed, we have OneShot
// put it in the "pending" state so it will act like a OneShot key.
if (event.key == OneShot_MetaStickyKey &&
live_keys[event.addr] != OneShot_MetaStickyKey) {
::OneShot.setPending(event.addr);
}
return EventHandlerResult::OK;
}
bool OneShotMetaKeys::isMetaStickyActive() {
for (Key key : live_keys.all()) {
if (key == OneShot_MetaStickyKey)
return true;
}
return false;
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::OneShotMetaKeys OneShotMetaKeys;

@ -0,0 +1,52 @@
/* -*- mode: c++ -*-
* Kaleidoscope-OneShot -- One-shot modifiers and layers
* 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/>.
*/
#pragma once
#include <Kaleidoscope-OneShot.h>
#include <Kaleidoscope-Ranges.h>
#include "kaleidoscope/event_handler_result.h"
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/KeyEvent.h"
#include "kaleidoscope/plugin.h"
// ----------------------------------------------------------------------------
// Key constants
constexpr Key OneShot_MetaStickyKey {kaleidoscope::ranges::OS_META_STICKY};
constexpr Key OneShot_ActiveStickyKey {kaleidoscope::ranges::OS_ACTIVE_STICKY};
namespace kaleidoscope {
namespace plugin {
class OneShotMetaKeys : public kaleidoscope::Plugin {
public:
// --------------------------------------------------------------------------
// Plugin hook functions
EventHandlerResult onNameQuery();
EventHandlerResult onKeyEvent(KeyEvent &event);
private:
static bool isMetaStickyActive();
};
} // namespace plugin
} // namespace kaleidoscope
extern kaleidoscope::plugin::OneShotMetaKeys OneShotMetaKeys;

@ -0,0 +1,76 @@
/* -*- 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-OneShot.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
Key_Insert, OSM(LeftAlt), OSM(RightAlt), ___, ___, ___, ___,
Key_A, Key_B, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
namespace kaleidoscope {
namespace plugin {
class OneShotInsert : public Plugin {
public:
EventHandlerResult onKeyEvent(KeyEvent &event) {
if (keyToggledOn(event.state) && (event.state & INJECTED) == 0 &&
event.key == Key_Insert && live_keys[event.addr] != event.key)
::OneShot.setPending(event.addr);
return EventHandlerResult::OK;
}
};
bool OneShot::isStickable(Key key) {
if (key == Key_LeftAlt)
return false;
return OneShot::isStickableDefault(key);
}
} // namespace plugin
} // namespace kaleidoscope
kaleidoscope::plugin::OneShotInsert OneShotInsert;
KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotInsert);
void setup() {
Kaleidoscope.setup();
OneShot.setTimeout(50);
OneShot.setHoldTimeout(20);
OneShot.setDoubleTapTimeout(20);
}
void loop() {
Kaleidoscope.loop();
}

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

@ -0,0 +1,168 @@
VERSION 1
KEYSWITCH INSERT 0 0
KEYSWITCH LALT 0 1
KEYSWITCH RALT 0 2
KEYSWITCH A 1 0
KEYSWITCH B 1 1
# ==============================================================================
NAME OneShot insert timeout
RUN 4 ms
PRESS INSERT
RUN 1 cycle
EXPECT keyboard-report Key_Insert
RUN 4 ms
RELEASE INSERT
RUN 1 cycle
RUN 45 ms
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot insert interrupt
RUN 4 ms
PRESS INSERT
RUN 1 cycle
EXPECT keyboard-report Key_Insert
RUN 4 ms
RELEASE INSERT
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_Insert Key_A
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot insert sticky
RUN 4 ms
PRESS INSERT
RUN 1 cycle
EXPECT keyboard-report Key_Insert
RUN 4 ms
RELEASE INSERT
RUN 1 cycle
RUN 4 ms
PRESS INSERT
RUN 1 cycle
RUN 4 ms
RELEASE INSERT
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_Insert Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_Insert
RUN 100 ms
RUN 4 ms
PRESS INSERT
RUN 1 cycle
RUN 4 ms
RELEASE INSERT
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot left alt not sticky
RUN 4 ms
PRESS LALT
RUN 1 cycle
EXPECT keyboard-report Key_LeftAlt
RUN 4 ms
RELEASE LALT
RUN 1 cycle
RUN 4 ms
PRESS LALT
RUN 1 cycle
RUN 4 ms
RELEASE LALT
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot right alt sticky
RUN 4 ms
PRESS RALT
RUN 1 cycle
EXPECT keyboard-report Key_RightAlt
RUN 4 ms
RELEASE RALT
RUN 1 cycle
RUN 4 ms
PRESS RALT
RUN 1 cycle
RUN 4 ms
RELEASE RALT
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_RightAlt Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_RightAlt
RUN 100 ms
RUN 4 ms
PRESS RALT
RUN 1 cycle
RUN 4 ms
RELEASE RALT
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms

@ -0,0 +1,53 @@
/* -*- 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-OneShot.h>
#include <Kaleidoscope-OneShotMetaKeys.h>
// *INDENT-OFF*
KEYMAPS(
[0] = KEYMAP_STACKED
(
OneShot_MetaStickyKey, OneShot_ActiveStickyKey, ___, ___, ___, ___, ___,
Key_A, Key_B, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___,
___, ___, ___, ___, ___, ___, ___,
___, ___, ___, ___,
___
),
)
// *INDENT-ON*
KALEIDOSCOPE_INIT_PLUGINS(OneShot, OneShotMetaKeys);
void setup() {
Kaleidoscope.setup();
OneShot.setTimeout(50);
OneShot.setHoldTimeout(20);
OneShot.setDoubleTapTimeout(20);
}
void loop() {
Kaleidoscope.loop();
}

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

@ -0,0 +1,325 @@
VERSION 1
KEYSWITCH OS_META 0 0
KEYSWITCH OS_ACTIVE 0 1
KEYSWITCH A 1 0
KEYSWITCH B 1 1
# ==============================================================================
NAME OneShot meta sticky
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot meta sticky rollover
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot meta sticky overlap
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot meta sticky overlap to rollover
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot meta sticky sticky
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
PRESS B
RUN 1 cycle
EXPECT keyboard-report Key_A Key_B
RUN 4 ms
RELEASE B
RUN 1 cycle
RUN 4 ms
PRESS OS_META
RUN 1 cycle
RUN 4 ms
RELEASE OS_META
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_B
RUN 4 ms
PRESS B
RUN 1 cycle
RUN 4 ms
RELEASE B
RUN 1 cycle
EXPECT keyboard-report empty
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot active sticky
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
PRESS OS_ACTIVE
RUN 1 cycle
RUN 4 ms
RELEASE OS_ACTIVE
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
# ==============================================================================
NAME OneShot active sticky two keys
RUN 4 ms
PRESS A
RUN 1 cycle
EXPECT keyboard-report Key_A
RUN 4 ms
PRESS B
RUN 1 cycle
EXPECT keyboard-report Key_A Key_B
RUN 4 ms
PRESS OS_ACTIVE
RUN 1 cycle
RUN 4 ms
RELEASE OS_ACTIVE
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
RUN 4 ms
RELEASE B
RUN 1 cycle
RUN 4 ms
PRESS A
RUN 1 cycle
RUN 4 ms
RELEASE A
RUN 1 cycle
EXPECT keyboard-report Key_B
RUN 4 ms
PRESS B
RUN 1 cycle
RUN 4 ms
RELEASE B
RUN 1 cycle
EXPECT keyboard-report empty
RUN 5 ms
Loading…
Cancel
Save