diff --git a/docs/NEWS.md b/docs/NEWS.md index c28fefdd..5efee04f 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,29 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## 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 can now be enabled in "no-delay" mode, wherein the primary (modifier) diff --git a/docs/UPGRADING.md b/docs/UPGRADING.md index 49b21c38..25b98358 100644 --- a/docs/UPGRADING.md +++ b/docs/UPGRADING.md @@ -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) - [Consistent timing](#consistent-timing) + [Breaking changes](#breaking-changes) + - [OneShot meta keys](#oneshot-meta-keys) - [git checkouts aren't compatible with Arduino IDE (GUI)]([#repository-rearchitecture) - [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) @@ -434,6 +435,10 @@ As a developer, one can continue using `millis()`, but migrating to `Kaleidoscop ## 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 To improve build times and to better highlight Kaleidoscope's many plugins, plugins have been move into directories inside the Kaleidoscope directory. diff --git a/examples/Keystrokes/OneShot/OneShot.ino b/examples/Keystrokes/OneShot/OneShot.ino index 82ef7092..96a249ca 100644 --- a/examples/Keystrokes/OneShot/OneShot.ino +++ b/examples/Keystrokes/OneShot/OneShot.ino @@ -28,7 +28,7 @@ enum { KEYMAPS( [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_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, @@ -36,7 +36,7 @@ KEYMAPS( OSM(LeftControl), Key_Backspace, OSM(LeftGui), OSM(LeftShift), 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_H, Key_J, Key_K, Key_L, Key_Semicolon, Key_Quote, ___, Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, diff --git a/examples/Keystrokes/OneShotMetaKeys/OneShotMetaKeys.ino b/examples/Keystrokes/OneShotMetaKeys/OneShotMetaKeys.ino new file mode 100644 index 00000000..1a155c4a --- /dev/null +++ b/examples/Keystrokes/OneShotMetaKeys/OneShotMetaKeys.ino @@ -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 . + */ + +#include +#include +#include +#include + +// 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(); +} diff --git a/examples/Keystrokes/OneShotMetaKeys/sketch.json b/examples/Keystrokes/OneShotMetaKeys/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/OneShotMetaKeys/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/README.md b/plugins/Kaleidoscope-LED-ActiveModColor/README.md index e2a2c709..c5e9fb56 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/README.md +++ b/plugins/Kaleidoscope-LED-ActiveModColor/README.md @@ -63,6 +63,11 @@ The `ActiveModColorEffect` object provides the following methods: * [Kaleidoscope-LEDControl](Kaleidoscope-LEDControl.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 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 ab127bfd..ac1e55a8 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 #include "kaleidoscope/LiveKeys.h" #include "kaleidoscope/layers.h" #include "kaleidoscope/keyswitch_state.h" @@ -54,7 +55,7 @@ EventHandlerResult ActiveModColorEffect::onKeyEvent(KeyEvent &event) { // 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) { + if (entry_key == Key_Inactive || entry_key == Key_Masked) { continue; } // Highlight everything else diff --git a/plugins/Kaleidoscope-OneShot/README.md b/plugins/Kaleidoscope-OneShot/README.md index f0e8699d..0b3e2725 100644 --- a/plugins/Kaleidoscope-OneShot/README.md +++ b/plugins/Kaleidoscope-OneShot/README.md @@ -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, 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 After adding one-shot keys to the keymap, all one needs to do, is enable the diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 63b6b851..1ab16ba7 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -52,10 +52,6 @@ KeyAddrBitfield OneShot::glue_addrs_; uint16_t OneShot::start_time_ = 0; 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 @@ -118,28 +114,40 @@ bool OneShot::isSticky() { // could potentially use three different color values for the three // states (sticky | active && !sticky | pressed && !active). +__attribute__((weak)) bool OneShot::isStickable(Key key) { + return isStickableDefault(key); +} + +bool OneShot::isStickableDefault(Key key) { 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()) { n = key.getKeyCode() - Key_LeftControl.getKeyCode(); return bitRead(stickable_keys_, n); } else if (key.isLayerShift()) { 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) { 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) { 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) { 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)); } +// ---------------------------------------------------------------------------- +// 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 @@ -190,27 +223,6 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { 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) { // 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 @@ -222,34 +234,6 @@ EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { 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 || (auto_modifiers_ && event.key.isKeyboardModifier()) || (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" // event. This is handled in the `beforeReportingState()` hook below. 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); 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}; Runtime.handleKeyEvent(event); } diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 6072a1ed..034dec5b 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -68,11 +68,6 @@ #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}; -constexpr Key OneShot_ActiveStickyKey {kaleidoscope::ranges::OS_ACTIVE_STICKY}; - namespace kaleidoscope { namespace plugin { @@ -153,12 +148,49 @@ class OneShot : public kaleidoscope::Plugin { 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 isPending(KeyAddr key_addr); static bool isSticky(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 static void cancel(bool with_stickies = false); @@ -262,10 +294,6 @@ class OneShot : public kaleidoscope::Plugin { static uint16_t start_time_; static KeyAddr prev_key_addr_; -#ifndef ONESHOT_WITHOUT_METASTICKY - static KeyAddr meta_sticky_key_addr_; -#endif - // -------------------------------------------------------------------------- // Internal utility functions static bool hasTimedOut(uint16_t ttl) { diff --git a/plugins/Kaleidoscope-OneShotMetaKeys/README.md b/plugins/Kaleidoscope-OneShotMetaKeys/README.md new file mode 100644 index 00000000..9b192e8e --- /dev/null +++ b/plugins/Kaleidoscope-OneShotMetaKeys/README.md @@ -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 +#include + +// 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 diff --git a/plugins/Kaleidoscope-OneShotMetaKeys/library.properties b/plugins/Kaleidoscope-OneShotMetaKeys/library.properties new file mode 100644 index 00000000..28d5cb9d --- /dev/null +++ b/plugins/Kaleidoscope-OneShotMetaKeys/library.properties @@ -0,0 +1,7 @@ +name=Kaleidoscope-OneShotMetaKeys +version=0.0.0 +sentence=OneShot_ActiveStickyKey & OneShot_MetaStickyKey +maintainer=Kaleidoscope's Developers +url=https://github.com/keyboardio/Kaleidoscope +author=Keyboardio +paragraph= diff --git a/plugins/Kaleidoscope-OneShotMetaKeys/src/Kaleidoscope-OneShotMetaKeys.h b/plugins/Kaleidoscope-OneShotMetaKeys/src/Kaleidoscope-OneShotMetaKeys.h new file mode 100644 index 00000000..5266fc62 --- /dev/null +++ b/plugins/Kaleidoscope-OneShotMetaKeys/src/Kaleidoscope-OneShotMetaKeys.h @@ -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 . + */ + +#pragma once + +#include diff --git a/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.cpp b/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.cpp new file mode 100644 index 00000000..e8765933 --- /dev/null +++ b/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.cpp @@ -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 . + */ + +#include + +#include +#include + +#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; diff --git a/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.h b/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.h new file mode 100644 index 00000000..5f5a4f3a --- /dev/null +++ b/plugins/Kaleidoscope-OneShotMetaKeys/src/kaleidoscope/plugin/OneShotMetaKeys.h @@ -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 . + */ + +#pragma once + +#include +#include + +#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; diff --git a/tests/issues/1061/1061.ino b/tests/issues/1061/1061.ino new file mode 100644 index 00000000..df2c20cd --- /dev/null +++ b/tests/issues/1061/1061.ino @@ -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 . + */ + +#include +#include + +// *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(); +} diff --git a/tests/issues/1061/sketch.json b/tests/issues/1061/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/issues/1061/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/issues/1061/test.ktest b/tests/issues/1061/test.ktest new file mode 100644 index 00000000..ee4aecd9 --- /dev/null +++ b/tests/issues/1061/test.ktest @@ -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 diff --git a/tests/plugins/OneShot/meta-keys/meta-keys.ino b/tests/plugins/OneShot/meta-keys/meta-keys.ino new file mode 100644 index 00000000..56b47042 --- /dev/null +++ b/tests/plugins/OneShot/meta-keys/meta-keys.ino @@ -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 . + */ + +#include +#include +#include + +// *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(); +} diff --git a/tests/plugins/OneShot/meta-keys/sketch.json b/tests/plugins/OneShot/meta-keys/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/OneShot/meta-keys/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/OneShot/meta-keys/test.ktest b/tests/plugins/OneShot/meta-keys/test.ktest new file mode 100644 index 00000000..d8b883d8 --- /dev/null +++ b/tests/plugins/OneShot/meta-keys/test.ktest @@ -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