diff --git a/examples/Keystrokes/PrefixLayer/PrefixLayer.ino b/examples/Keystrokes/PrefixLayer/PrefixLayer.ino new file mode 100644 index 00000000..f6a0ccac --- /dev/null +++ b/examples/Keystrokes/PrefixLayer/PrefixLayer.ino @@ -0,0 +1,70 @@ +// -*- mode: c++ -*- + +/* This example demonstrates the Model 01 / Model 100 butterfly logo key as a + * tmux prefix key. When the key is held, Ctrl-B is pressed prior to the key + * you pressed. + * + * This example also demonstrates the purpose of using an entire layer for this + * plugin: the h/j/k/l keys in the TMUX layer are swapped for arrow keys to + * make switching between panes easier. + */ + +#include +#include + +enum { + PRIMARY, + TMUX, +}; // layers + +/* Used in setup() below. */ +static const kaleidoscope::plugin::PrefixLayer::Entry prefix_layers[] PROGMEM = { + kaleidoscope::plugin::PrefixLayer::Entry(TMUX, LCTRL(Key_B)), +}; + +// clang-format off +KEYMAPS( + [PRIMARY] = KEYMAP_STACKED + (XXX, Key_1, Key_2, Key_3, Key_4, Key_5, XXX, + 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, + Key_LeftControl, Key_Backspace, Key_LeftGui, Key_LeftShift, + XXX, + + XXX, Key_6, Key_7, Key_8, Key_9, Key_0, XXX, + 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, + ShiftToLayer(TMUX), Key_N, Key_M, Key_Comma, Key_Period, Key_Slash, Key_Minus, + Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, + XXX), + + [TMUX] = KEYMAP_STACKED + (___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + Key_LeftArrow, Key_DownArrow, Key_UpArrow, Key_RightArrow, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___), +) +// clang-format on + +KALEIDOSCOPE_INIT_PLUGINS(PrefixLayer); + +void setup() { + Kaleidoscope.setup(); + /* Configure the previously-defined prefix layers. */ + PrefixLayer.prefix_layers = prefix_layers; + PrefixLayer.prefix_layers_length = 1; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Keystrokes/PrefixLayer/sketch.json b/examples/Keystrokes/PrefixLayer/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/PrefixLayer/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/plugins/Kaleidoscope-PrefixLayer/README.md b/plugins/Kaleidoscope-PrefixLayer/README.md new file mode 100644 index 00000000..c0d6f8bc --- /dev/null +++ b/plugins/Kaleidoscope-PrefixLayer/README.md @@ -0,0 +1,60 @@ +# PrefixLayer + +The `PrefixLayer` plugin allows you to easily create a keyboard layer designed +for use with programs that use a prefix key, such as tmux or screen. When a key +in a prefix layer is pressed, the prefix is injected first, then the key in +that layer is pressed. + +## Using the plugin + +You will need to define a keymap layer and configure the plugin to use that +layer with a prefix key. You can then include the plugin's header and set the +`.prefix_layers` property. + +```c++ +#include +#include + +enum { + PRIMARY, + TMUX, +}; // layers + +static const kaleidoscope::plugin::PrefixLayer::Entry prefix_layers[] PROGMEM = { + kaleidoscope::plugin::PrefixLayer::Entry(TMUX, LCTRL(Key_B)), +}; + +KALEIDOSCOPE_INIT_PLUGINS(LEDControl, PrefixLayer); + +void setup() { + Kaleidoscope.setup(); + PrefixLayer.prefix_layers = prefix_layers; + PrefixLayer.prefix_layers_length = 1; +} +``` + +## Plugin methods + +The plugin provides a `PrefixLayer` object, which has the following methods +and properties: + +### `.prefix_layers` + +> A `kaleidoscope::plugin::PrefixLayer::Entry` array that maps layers to prefix +> keys. The `Entry` constructor accepts `Entry(layer_number, prefix_key)`. This +> array must be stored in `PROGMEM` as shown above. +> +> Defaults to an empty array. + +### `.prefix_layers_length` + +> Length of the `prefix_layers` array. +> +> Defaults to *0* + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + + [plugin:example]: /examples/Keystrokes/PrefixLayer/PrefixLayer.ino diff --git a/plugins/Kaleidoscope-PrefixLayer/library.properties b/plugins/Kaleidoscope-PrefixLayer/library.properties new file mode 100644 index 00000000..7b38d9c0 --- /dev/null +++ b/plugins/Kaleidoscope-PrefixLayer/library.properties @@ -0,0 +1,7 @@ +name=Kaleidoscope-PrefixLayer +version=0.0.0 +sentence=Sends a prefix key for every key in a layer. +maintainer=Kaleidoscope's Developers +url=https://github.com/keyboardio/Kaleidoscope +author=iliana etaoin, James Cash +paragraph= diff --git a/plugins/Kaleidoscope-PrefixLayer/src/Kaleidoscope-PrefixLayer.h b/plugins/Kaleidoscope-PrefixLayer/src/Kaleidoscope-PrefixLayer.h new file mode 100644 index 00000000..0f9875cf --- /dev/null +++ b/plugins/Kaleidoscope-PrefixLayer/src/Kaleidoscope-PrefixLayer.h @@ -0,0 +1,21 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-PrefixLayer -- Sends a prefix key for every key in a layer. + * Copyright (C) 2017, 2022 iliana etaoin + * Copyright (C) 2017 James Cash + * + * 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 "kaleidoscope/plugin/PrefixLayer.h" // IWYU pragma: export diff --git a/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.cpp b/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.cpp new file mode 100644 index 00000000..18cc0c0b --- /dev/null +++ b/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.cpp @@ -0,0 +1,77 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-PrefixLayer -- Sends a prefix key for every key in a layer. + * Copyright (C) 2017, 2022 iliana etaoin + * Copyright (C) 2017 James Cash + * + * 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 "kaleidoscope/plugin/PrefixLayer.h" + +#include // for PROGMEM +#include // for uint8_t + +#include "kaleidoscope/KeyEvent.h" // for KeyEvent +#include "kaleidoscope/Runtime.h" // for Runtime +#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult, EventHandlerResult::OK +#include "kaleidoscope/key_defs.h" // for Key +#include "kaleidoscope/keyswitch_state.h" // for keyToggledOn +#include "kaleidoscope/layers.h" // for Layer + +namespace kaleidoscope { +namespace plugin { + +static const PrefixLayer::Entry prefix_layers_default_[] PROGMEM = {}; +const PrefixLayer::Entry *PrefixLayer::prefix_layers = prefix_layers_default_; +uint8_t PrefixLayer::prefix_layers_length = 0; +bool PrefixLayer::clear_modifiers_ = false; + +EventHandlerResult PrefixLayer::onKeyEvent(KeyEvent &event) { + if (event.state & INJECTED) + return EventHandlerResult::OK; + if (!keyToggledOn(event.state)) + return EventHandlerResult::OK; + if (event.key == Key_NoKey) + return EventHandlerResult::OK; + if (!event.key.isKeyboardKey()) + return EventHandlerResult::OK; + if (event.key.isKeyboardModifier()) + return EventHandlerResult::OK; + + for (uint8_t i = 0; i < prefix_layers_length; i++) { + if (Layer.isActive(prefix_layers[i].layer)) { + clear_modifiers_ = true; + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), IS_PRESSED | INJECTED, prefix_layers[i].prefix}); + Runtime.handleKeyEvent(KeyEvent{KeyAddr::none(), WAS_PRESSED | INJECTED, prefix_layers[i].prefix}); + clear_modifiers_ = false; + } + } + + return EventHandlerResult::OK; +} + +EventHandlerResult PrefixLayer::beforeReportingState(const KeyEvent &event) { + if (clear_modifiers_) { + for (uint8_t i = HID_KEYBOARD_FIRST_MODIFIER; i <= HID_KEYBOARD_LAST_MODIFIER; i++) { + Runtime.hid().keyboard().releaseKey(Key(i, KEY_FLAGS)); + } + Runtime.hid().keyboard().pressModifiers(event.key); + } + + return EventHandlerResult::OK; +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::PrefixLayer PrefixLayer; diff --git a/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.h b/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.h new file mode 100644 index 00000000..196e1a70 --- /dev/null +++ b/plugins/Kaleidoscope-PrefixLayer/src/kaleidoscope/plugin/PrefixLayer.h @@ -0,0 +1,54 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-PrefixLayer -- Sends a prefix key for every key in a layer. + * Copyright (C) 2017, 2022 iliana etaoin + * Copyright (C) 2017 James Cash + * + * 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 // for uint8_t + +#include "kaleidoscope/KeyEvent.h" // for KeyEvent +#include "kaleidoscope/event_handler_result.h" // for EventHandlerResult +#include "kaleidoscope/key_defs.h" // for Key +#include "kaleidoscope/plugin.h" // for Plugin + +namespace kaleidoscope { +namespace plugin { + +class PrefixLayer : public Plugin { + public: + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult beforeReportingState(const KeyEvent &event); + + struct Entry { + uint8_t layer; + Key prefix; + + constexpr Entry(uint8_t layer, Key prefix) + : layer(layer), prefix(prefix) {} + }; + + static const Entry *prefix_layers; + static uint8_t prefix_layers_length; + + private: + static bool clear_modifiers_; +}; + +} // namespace plugin +} // namespace kaleidoscope + +extern kaleidoscope::plugin::PrefixLayer PrefixLayer; diff --git a/tests/plugins/PrefixLayer/basic/basic.ino b/tests/plugins/PrefixLayer/basic/basic.ino new file mode 100644 index 00000000..76a067b9 --- /dev/null +++ b/tests/plugins/PrefixLayer/basic/basic.ino @@ -0,0 +1,68 @@ +/* -*- mode: c++ -*- + * Copyright (C) 2022 iliana etaoin + * + * 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 + +// clang-format off +KEYMAPS( + [0] = KEYMAP_STACKED + (ShiftToLayer(1), ___, ___, ___, ___, ___, ___, + Key_LeftControl, Key_LeftShift, ___, ___, ___, ___, ___, + Key_H, Key_J, Key_K, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___), + + [1] = KEYMAP_STACKED + (___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, Key_DownArrow, XXX, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___), +) +// clang-format on + +static const kaleidoscope::plugin::PrefixLayer::Entry prefix_layers[] PROGMEM = { + kaleidoscope::plugin::PrefixLayer::Entry(1, LCTRL(Key_B)), +}; + +KALEIDOSCOPE_INIT_PLUGINS(PrefixLayer); + +void setup() { + Kaleidoscope.setup(); + PrefixLayer.prefix_layers = prefix_layers; + PrefixLayer.prefix_layers_length = 1; +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/PrefixLayer/basic/sketch.json b/tests/plugins/PrefixLayer/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/PrefixLayer/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/PrefixLayer/basic/test.ktest b/tests/plugins/PrefixLayer/basic/test.ktest new file mode 100644 index 00000000..bb201048 --- /dev/null +++ b/tests/plugins/PrefixLayer/basic/test.ktest @@ -0,0 +1,231 @@ +VERSION 1 + +KEYSWITCH PREFIX_B 0 0 +KEYSWITCH CTRL 1 0 +KEYSWITCH SHIFT 1 1 +KEYSWITCH H 2 0 +KEYSWITCH J 2 1 +KEYSWITCH K 2 2 + +# ============================================================================== +NAME Prefix layer passthrough + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS H +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # press Ctrl +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report empty # release Ctrl +EXPECT keyboard-report Key_H # press H + +RUN 4 ms +RELEASE H +RUN 1 cycle +EXPECT keyboard-report empty # release H + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +# ============================================================================== +NAME Prefix layer explicit + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS J +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # press Ctrl +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report empty # release Ctrl +EXPECT keyboard-report Key_DownArrow # press J + +RUN 4 ms +RELEASE J +RUN 1 cycle +EXPECT keyboard-report empty # release J + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +# ============================================================================== +NAME Prefix layer masked + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS K +RUN 1 cycle +EXPECT no keyboard-report # press K (masked) + +RUN 4 ms +RELEASE K +RUN 1 cycle +EXPECT no keyboard-report # release K (masked) + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +# ============================================================================== +NAME Prefix layer same modifier first + +RUN 4 ms +PRESS CTRL +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # press Ctrl + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS H +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report Key_LCtrl Key_H # press H + +RUN 4 ms +RELEASE H +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # release H + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +RUN 4 ms +RELEASE CTRL +RUN 1 cycle +EXPECT keyboard-report empty # release Ctrl + +# ============================================================================== +NAME Prefix layer same modifier second + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS CTRL +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # press Ctrl + +RUN 4 ms +PRESS H +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report Key_LCtrl Key_H # press H + +RUN 4 ms +RELEASE H +RUN 1 cycle +EXPECT keyboard-report Key_LCtrl # release H + +RUN 4 ms +RELEASE CTRL +RUN 1 cycle +EXPECT keyboard-report empty # release Ctrl + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +# ============================================================================== +NAME Prefix layer different modifier first + +RUN 4 ms +PRESS SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LShift # press Shift + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS H +RUN 1 cycle +EXPECT keyboard-report Key_LShift Key_LCtrl # press Ctrl +EXPECT keyboard-report Key_LCtrl # release pressed shift +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report Key_LShift # restoring pressed Shift +EXPECT keyboard-report Key_LShift Key_H # press H + +RUN 4 ms +RELEASE H +RUN 1 cycle +EXPECT keyboard-report Key_LShift # release H + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B + +RUN 4 ms +RELEASE SHIFT +RUN 1 cycle +EXPECT keyboard-report empty # release Ctrl + +# ============================================================================== +NAME Prefix layer different modifier second + +RUN 4 ms +PRESS PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after pressing PREFIX_B + +RUN 4 ms +PRESS SHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LShift # press Shift + +RUN 4 ms +PRESS H +RUN 1 cycle +EXPECT keyboard-report Key_LShift Key_LCtrl # press Ctrl +EXPECT keyboard-report Key_LCtrl # release pressed shift +EXPECT keyboard-report Key_LCtrl Key_B # press B, Ctrl held +EXPECT keyboard-report Key_LCtrl # release B, Ctrl held +EXPECT keyboard-report Key_LShift # restoring pressed Shift +EXPECT keyboard-report Key_LShift Key_H # press H + +RUN 4 ms +RELEASE H +RUN 1 cycle +EXPECT keyboard-report Key_LShift # release H + +RUN 4 ms +RELEASE SHIFT +RUN 1 cycle +EXPECT keyboard-report empty # release Ctrl + +RUN 4 ms +RELEASE PREFIX_B +RUN 1 cycle +EXPECT no keyboard-report # no report after releasing PREFIX_B