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-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/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