diff --git a/docs/NEWS.md b/docs/NEWS.md index 5efee04f..eb0a0eb8 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -238,6 +238,10 @@ To make it easier to port Kaleidoscope, we introduced the `ATMegaKeyboard` base ## New plugins +### CharShift + +The [CharShift](plugins/Kaleidoscope-CharShift.md) plugin allows independent assignment of symbols to keys depending on whether or not a `shift` key is held. + ### AutoShift The [AutoShift](plugins/Kaleidoscope-AutoShift.md) plugin provides an alternative way to get shifted symbols, by long-pressing keys instead of using a separate `shift` key. diff --git a/examples/Keystrokes/CharShift/CharShift.ino b/examples/Keystrokes/CharShift/CharShift.ino new file mode 100644 index 00000000..b1aa83ec --- /dev/null +++ b/examples/Keystrokes/CharShift/CharShift.ino @@ -0,0 +1,43 @@ +// -*- mode: c++ -*- + +#include + +#include + +// *INDENT-OFF* +KEYMAPS( + [0] = KEYMAP_STACKED + ( + XXX, Key_1, Key_2, Key_3, Key_4, Key_5, Key_NoKey, + 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, Key_skip, + Key_Enter, Key_Y, Key_U, Key_I, Key_O, Key_P, Key_Equals, + Key_H, Key_J, Key_K, Key_L, CS(2), Key_Quote, + Key_skip, Key_N, Key_M, CS(0), CS(1), Key_Slash, Key_Minus, + + Key_RightShift, Key_RightAlt, Key_Spacebar, Key_RightControl, + XXX + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(CharShift); + +void setup() { + CS_KEYS( + kaleidoscope::plugin::CharShift::KeyPair(Key_Comma, Key_Semicolon), // CS(0) + kaleidoscope::plugin::CharShift::KeyPair(Key_Period, LSHIFT(Key_Semicolon)), // CS(1) + kaleidoscope::plugin::CharShift::KeyPair(LSHIFT(Key_Comma), LSHIFT(Key_Period)), // CS(2) + ); + Kaleidoscope.setup(); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/examples/Keystrokes/CharShift/sketch.json b/examples/Keystrokes/CharShift/sketch.json new file mode 100644 index 00000000..884ed009 --- /dev/null +++ b/examples/Keystrokes/CharShift/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:avr:model01", + "port": "" + } +} diff --git a/plugins/Kaleidoscope-CharShift/README.md b/plugins/Kaleidoscope-CharShift/README.md new file mode 100644 index 00000000..c147f22d --- /dev/null +++ b/plugins/Kaleidoscope-CharShift/README.md @@ -0,0 +1,66 @@ +# CharShift + +CharShift allows you to independently assign symbols to shifted and unshifted +positions of keymap entries. Either or both symbols can be ones that normally +requires the `shift` modifier, and either or both symbols can be ones normally +produced without it. + +For example you can configure your keyboard so that a single key produces `,` +when pressed unshifted, but `;` when pressed with `shift` held. Or `(` +unshifted, and `[` shifted. Or `+`/`*` — all without changing your OS keyboard +layout. + +## Using the plugin + +Using the plugin with its defaults is as simple as including the header, and +enabling the plugin: + +```c++ +#include +#include + +KALEIDOSCOPE_INIT_PLUGINS(CharShift); +``` + +Further configuration is required, of course; see below. + +Note: CharShift should be registered in `KALEIDOSCOPE_INIT_PLUGINS()` after any +plugin that changes the event's `Key` value to that of an CharShift key. + +## Configuring CharShift keys + +To use CharShift, we must first define `KeyPair` objects, which can then be +referenced by entries in the keymap. This is easiest to do by using the +`UNKEYS()` preprocessor macro in the sketch's `setup()` function, as follows: + +```c++ +void setup() { + Kaleidoscope.setup(); + UNKEYS( + kaleidoscope::plugin::KeyPair(Key_Comma, Key_Semicolon), // `,`/`;` + kaleidoscope::plugin::KeyPair(Key_Period, LSHIFT(Key_Semicolon)), // `.`/`:` + kaleidoscope::plugin::KeyPair(LSHIFT(Key_9), Key_LeftBracket), // `(`/`[` + kaleidoscope::plugin::KeyPair(LSHIFT(Key_Comma), LSHIFT(Key_LeftBracket)), // `<`/`{` + ); +} +``` + +The first argument to the `KeyPair()` constructor is the value for when the key is +pressed without `shift` held, the second is what you'll get if a `shift` +modifier is being held when the key toggles on. If that second ("upper") value +doesn't have the `shift` modifier flag (i.e. `LSHIFT()`) applied to it, the held +`shift` modifier will be suppressed when the key is pressed, allowing the +"unshifted" symbol to be produced. + +These `KeyPair`s can be referred to in the sketch's keymap by using the `UK()` +preprocessor macro, which takes an integer argument, referring to items in the +`UNKEYS()` array, starting with zero. With the example above, an entry of +`UK(2)` will output `(` when pressed without `shift`, and `[` if `shift` is +being held. + +## Further reading + +Starting from the [example][plugin:example] is the recommended way of getting +started with the plugin. + + [plugin:example]: /examples/Keystrokes/CharShift/CharShift.ino diff --git a/plugins/Kaleidoscope-CharShift/library.properties b/plugins/Kaleidoscope-CharShift/library.properties new file mode 100644 index 00000000..3c852806 --- /dev/null +++ b/plugins/Kaleidoscope-CharShift/library.properties @@ -0,0 +1,7 @@ +name=Kaleidoscope-CharShift +version=0.0.0 +sentence=Independent assignment of symbols to keys based on shift state +maintainer=Kaleidoscope's Developers +url=https://github.com/keyboardio/Kaleidoscope +author=Michael Richters +paragraph= diff --git a/plugins/Kaleidoscope-CharShift/src/Kaleidoscope-CharShift.h b/plugins/Kaleidoscope-CharShift/src/Kaleidoscope-CharShift.h new file mode 100644 index 00000000..37b8587c --- /dev/null +++ b/plugins/Kaleidoscope-CharShift/src/Kaleidoscope-CharShift.h @@ -0,0 +1,20 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-CharShift -- Independently assign shifted and unshifted symbols + * 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-CharShift/src/kaleidoscope/plugin/CharShift.cpp b/plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.cpp new file mode 100644 index 00000000..dd453991 --- /dev/null +++ b/plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.cpp @@ -0,0 +1,149 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-CharShift -- Independently assign shifted and unshifted symbols + * 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 "kaleidoscope/plugin/CharShift.h" + +#include +#include + +#include "kaleidoscope/KeyAddr.h" +#include "kaleidoscope/key_defs.h" +#include "kaleidoscope/KeyEvent.h" +#include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/progmem_helpers.h" +#include "kaleidoscope/Runtime.h" + +namespace kaleidoscope { +namespace plugin { + +// ============================================================================= +// CharShift class variables +CharShift::KeyPair const * CharShift::progmem_keypairs_{nullptr}; +uint8_t CharShift::num_keypairs_{0}; + +bool CharShift::reverse_shift_state_{false}; + + +// ============================================================================= +// Event handlers + +// ----------------------------------------------------------------------------- +EventHandlerResult CharShift::onNameQuery() { + return ::Focus.sendName(F("CharShift")); +} + +// ----------------------------------------------------------------------------- +EventHandlerResult CharShift::onKeyEvent(KeyEvent &event) { + // If the event is for anything other than an CharShift key, we ignore it. + if (!isCharShiftKey(event.key)) { + // If this event is for a Keyboard key, we need to stop + // `beforeReportingState()` from suppressing `shift` keys. + if (event.key.isKeyboardKey()) + reverse_shift_state_ = false; + return EventHandlerResult::OK; + } + + // Default to not suppressing `shift` modifiers. + reverse_shift_state_ = false; + + // It shouldn't be possible for an CharShift key to toggle off, because it + // will get replaced by on of its `KeyPair` values when it toggles on, but just + // in case, we exit early if that happens. + if (keyToggledOff(event.state)) + return EventHandlerResult::OK; + + // Next, we get the `KeyPair` values corresponding to the event key. + KeyPair keypair = decodeCharShiftKey(event.key); + + // Determine if a shift key is being held. + bool shift_held = false; + for (Key key : live_keys.all()) { + if (key.isKeyboardShift()) { + shift_held = true; + break; + } + } + + if (!shift_held) { + // No shift key is held; just use the base value of the `KeyPair`. + event.key = keypair.lower; + } else { + // At least one shift key is held; use the shifted value. + event.key = keypair.upper; + // Check for a shift modifier flag. + if (event.key.isKeyboardKey() && + (event.key.getFlags() & SHIFT_HELD) == 0) { + // The upper key doesn't have the `shift` modifier flag; we need to + // suppress `shift` in `beforeReportingState()`. + reverse_shift_state_ = true; + } + } + + return EventHandlerResult::OK; +} + +// ----------------------------------------------------------------------------- +EventHandlerResult CharShift::beforeReportingState(const KeyEvent &event) { + // If `onKeyEvent()` has signalled that `shift` should be suppressed, this is + // the time to do it. We can't do it in `onKeyEvent()`, because the new + // Keyboard HID report hasn't been prepared yet there. + if (reverse_shift_state_) { + Runtime.hid().keyboard().releaseKey(Key_LeftShift); + Runtime.hid().keyboard().releaseKey(Key_RightShift); + } + return EventHandlerResult::OK; +} + +// ============================================================================= +// Support functions + +bool CharShift::isCharShiftKey(Key key) { + return (key >= ranges::CS_FIRST && key <= ranges::CS_LAST); +} + +CharShift::KeyPair CharShift::decodeCharShiftKey(Key key) { + uint8_t i = key.getRaw() - ranges::CS_FIRST; + if (i < numKeyPairs()) { + return readKeyPair(i); + } + return {Key_NoKey, Key_NoKey}; +} + +// This should be overridden if the KeyPairs array is stored in EEPROM +__attribute__((weak)) +uint8_t CharShift::numKeyPairs() { + return numProgmemKeyPairs(); +} + +// This should be overridden if the KeyPairs array is stored in EEPROM +__attribute__((weak)) +CharShift::KeyPair CharShift::readKeyPair(uint8_t n) { + return readKeyPairFromProgmem(n); +} + +uint8_t CharShift::numProgmemKeyPairs() { + return num_keypairs_; +} + +CharShift::KeyPair CharShift::readKeyPairFromProgmem(uint8_t n) { + return cloneFromProgmem(progmem_keypairs_[n]); +} + +} // namespace plugin +} // namespace kaleidoscope + +kaleidoscope::plugin::CharShift CharShift; diff --git a/plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.h b/plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.h new file mode 100644 index 00000000..f2ace27a --- /dev/null +++ b/plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.h @@ -0,0 +1,137 @@ +/* -*- mode: c++ -*- + * Kaleidoscope-CharShift -- Independently assign shifted and unshifted symbols + * 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 "kaleidoscope/Runtime.h" + +#include + +namespace kaleidoscope { +namespace plugin { + +// ============================================================================= +/// Kaleidoscope plugin for independently assigning shifted symbols +/// +/// This plugin provides a way to define keys that, when `shift` is held, will +/// produce different symbols than they normally would. Either, both, or +/// neither of the symbols assigned to a key in this way can be ones that are +/// normally produced with the `shift` modifier. Crucially, when `shift` is +/// held, and the `Key` to be output does not have the `shift` modifier flag +/// applied to it, the held `shift` modifier will be suppressed. +class CharShift : public Plugin { + + public: + EventHandlerResult onNameQuery(); + EventHandlerResult onKeyEvent(KeyEvent &event); + EventHandlerResult beforeReportingState(const KeyEvent &event); + + // --------------------------------------------------------------------------- + /// A structure that stores CharShift key pair values + /// + /// This structure stores two separate `Key` values: `lower` (to be used when + /// a key is pressed without `shift` held), and `upper` (to be used when + /// `shift` is held). + struct KeyPair { + Key lower; + Key upper; + + /// Constructor for use in PROGMEM + /// + /// This constructor is used when defining an array of `KeyPair` objects in + /// PROGMEM (though it can also be used elsewhere). + constexpr KeyPair(Key lower, Key upper) : lower(lower), upper(upper) {} + + /// Constructor for reading from PROGMEM + /// + /// This constructor is used by the helper function that copies values from + /// PROGMEM so that they can be used normally. + KeyPair() = default; + }; + + /// Configure the KeyPairs array in PROGMEM + /// + /// This function configures the PROGMEM array of `KeyPair` objects, + /// automatically setting the internal count variable from the size of the + /// `keypairs` array given, which must be a fixed-sized array, not a pointer. + /// Generally, it will be called via the `KEYPAIRS()` preprocessor macro, not + /// directly by user code. + template + static void setProgmemKeyPairs(KeyPair const(&keypairs)[_num_keypairs]) { + progmem_keypairs_ = keypairs; + num_keypairs_ = _num_keypairs; + } + + private: + // A pointer to an array of `KeyPair` objects in PROGMEM + static KeyPair const * progmem_keypairs_; + // The size of the PROGMEM array of `KeyPair` objects + static uint8_t num_keypairs_; + + // If a `shift` key needs to be suppressed in `beforeReportingState()` + static bool reverse_shift_state_; + + /// Test for keys that should be handled by CharShift + static bool isCharShiftKey(Key key); + + /// Look up the `KeyPair` specified by the given keymap entry + static KeyPair decodeCharShiftKey(Key key); + + /// Get the total number of KeyPairs defined + /// + /// This function can be overridden in order to store the `KeyPair` array in + /// EEPROM instead of PROGMEM. + static uint8_t numKeyPairs(); + + /// Get the `KeyPair` at the specified index from the defined `KeyPair` array + /// + /// This function can be overridden in order to store the `KeyPair` array in + /// EEPROM instead of PROGMEM. + static KeyPair readKeyPair(uint8_t n); + + // Default for `keypairsCount()`: size of the PROGMEM array + static uint8_t numProgmemKeyPairs(); + // Default for `readKeypair(i)`: fetch the value from PROGMEM + static KeyPair readKeyPairFromProgmem(uint8_t n); +}; + +} // namespace plugin +} // namespace kaleidoscope + +extern kaleidoscope::plugin::CharShift CharShift; + +/// Define an array of `KeyPair` objects in PROGMEM +/// +/// This preprocessor macro takes a list of `KeyPair` objects as arguments, and +/// defines them as an array in PROGMEM, and configures `CharShift` to use that +/// array, automatically setting the count correctly to prevent out-of-bounds +/// lookups. +#define CS_KEYS(keypairs...) { \ + static kaleidoscope::plugin::CharShift::KeyPair const kp_table[] PROGMEM = { \ + keypairs \ + }; \ + CharShift.setProgmemKeyPairs(kp_table); \ +} + +/// Define an `KeyPair` entry in a keymap +/// +/// This defines a `Key` object that will be handled by the CharShift plugin. +/// The argument `n` is the index number of the `KeyPair` in the array (starting +/// at zero). +constexpr kaleidoscope::Key CS(uint8_t n) { + return kaleidoscope::Key(kaleidoscope::ranges::CS_FIRST + n); +} diff --git a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h index 0a3630f2..e4387268 100644 --- a/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h +++ b/plugins/Kaleidoscope-Ranges/src/Kaleidoscope-Ranges.h @@ -41,6 +41,8 @@ namespace ranges { // important for compatibility with existing Chrysalis keymaps, despite the fact // that it makes the code more obtuse here. +constexpr uint8_t MAX_CS_KEYS = 64; + enum : uint16_t { // Macro ranges pre-date Kaleidoscope-Ranges, so they're coming before // ranges::FIRST, because we want to keep the keycodes backwards compatible. @@ -83,6 +85,8 @@ enum : uint16_t { OS_META_STICKY, OS_ACTIVE_STICKY, OS_CANCEL, + CS_FIRST, + CS_LAST = CS_FIRST + MAX_CS_KEYS, SAFE_START, KALEIDOSCOPE_SAFE_START = SAFE_START diff --git a/tests/issues/1010/test/testcase.cpp b/tests/issues/1010/test/testcase.cpp index a4874eda..6a8eeee7 100644 --- a/tests/issues/1010/test/testcase.cpp +++ b/tests/issues/1010/test/testcase.cpp @@ -25,6 +25,9 @@ namespace testing { class Issue1010 : public ::testing::Test { public: + + static constexpr uint8_t MAX_CS_KEYS = 64; + enum : uint16_t { MACRO_FIRST = (SYNTHETIC | 0b00100000) << 8, MACRO_LAST = MACRO_FIRST + 255, @@ -62,6 +65,8 @@ class Issue1010 : public ::testing::Test { OS_META_STICKY, OS_ACTIVE_STICKY, OS_CANCEL, + CS_FIRST, + CS_LAST = CS_FIRST + MAX_CS_KEYS, SAFE_START, KALEIDOSCOPE_SAFE_START = SAFE_START @@ -140,6 +145,11 @@ TEST_F(Issue1010, RangesHaveNotChanged) { uint16_t(kaleidoscope::ranges::OS_ACTIVE_STICKY)); ASSERT_EQ(uint16_t(Issue1010::OS_CANCEL), uint16_t(kaleidoscope::ranges::OS_CANCEL)); + ASSERT_EQ(uint16_t(Issue1010::CS_FIRST), + uint16_t(kaleidoscope::ranges::CS_FIRST)); + ASSERT_EQ(uint16_t(Issue1010::CS_LAST), + uint16_t(kaleidoscope::ranges::CS_LAST)); + ASSERT_EQ(uint16_t(Issue1010::SAFE_START), uint16_t(kaleidoscope::ranges::SAFE_START)); ASSERT_EQ(uint16_t(Issue1010::KALEIDOSCOPE_SAFE_START), diff --git a/tests/plugins/CharShift/basic/basic.ino b/tests/plugins/CharShift/basic/basic.ino new file mode 100644 index 00000000..05929f4e --- /dev/null +++ b/tests/plugins/CharShift/basic/basic.ino @@ -0,0 +1,55 @@ +/* -*- 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_LeftShift, Key_RightShift, ___, ___, ___, ___, ___, + Key_X, LSHIFT(Key_Y), ___, ___, ___, ___, ___, + CS(0), CS(1), CS(2), CS(3), ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___, + + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, ___, ___, ___, + ___, ___, ___, ___, + ___ + ), +) +// *INDENT-ON* + +KALEIDOSCOPE_INIT_PLUGINS(CharShift); + +void setup() { + Kaleidoscope.setup(); + CS_KEYS( + kaleidoscope::plugin::CharShift::KeyPair(Key_A, Key_B), + kaleidoscope::plugin::CharShift::KeyPair(Key_C, LSHIFT(Key_D)), + kaleidoscope::plugin::CharShift::KeyPair(LSHIFT(Key_E), Key_F), + kaleidoscope::plugin::CharShift::KeyPair(LSHIFT(Key_G), LSHIFT(Key_H)), + ); +} + +void loop() { + Kaleidoscope.loop(); +} diff --git a/tests/plugins/CharShift/basic/sketch.json b/tests/plugins/CharShift/basic/sketch.json new file mode 100644 index 00000000..8cc86922 --- /dev/null +++ b/tests/plugins/CharShift/basic/sketch.json @@ -0,0 +1,6 @@ +{ + "cpu": { + "fqbn": "keyboardio:virtual:model01", + "port": "" + } +} diff --git a/tests/plugins/CharShift/basic/test.ktest b/tests/plugins/CharShift/basic/test.ktest new file mode 100644 index 00000000..d8088620 --- /dev/null +++ b/tests/plugins/CharShift/basic/test.ktest @@ -0,0 +1,212 @@ +VERSION 1 + +KEYSWITCH LSHIFT 0 0 +KEYSWITCH RSHIFT 0 1 +KEYSWITCH x 1 0 +KEYSWITCH Y 1 1 +KEYSWITCH CS_ab 2 0 +KEYSWITCH CS_cD 2 1 +KEYSWITCH CS_Ef 2 2 +KEYSWITCH CS_GH 2 3 + +# ============================================================================== +NAME CharShift lower lower + +RUN 4 ms +PRESS CS_ab +RUN 1 cycle +EXPECT keyboard-report Key_A # report: { 4 } + +RUN 4 ms +RELEASE CS_ab +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS CS_ab +RUN 1 cycle +EXPECT keyboard-report empty +EXPECT keyboard-report Key_B # report: { 5 } + +RUN 4 ms +RELEASE CS_ab +RUN 1 cycle +EXPECT keyboard-report empty +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME CharShift lower upper + +RUN 4 ms +PRESS CS_cD +RUN 1 cycle +EXPECT keyboard-report Key_C # report: { 6 } + +RUN 4 ms +RELEASE CS_cD +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS CS_cD +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_D # report: { e1 7 } + +RUN 4 ms +RELEASE CS_cD +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME CharShift upper lower + +RUN 4 ms +PRESS CS_Ef +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_E # report: { e1 8 } + +RUN 4 ms +RELEASE CS_Ef +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report empty + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS CS_Ef +RUN 1 cycle +EXPECT keyboard-report empty +EXPECT keyboard-report Key_F # report: { 9 } + +RUN 4 ms +RELEASE CS_Ef +RUN 1 cycle +EXPECT keyboard-report empty +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME CharShift upper upper + +RUN 4 ms +PRESS CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_G # report: { e1 10 } + +RUN 4 ms +RELEASE CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report empty + +RUN 4 ms +PRESS LSHIFT +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +PRESS CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_H # report: { e1 11 } + +RUN 4 ms +RELEASE CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } + +RUN 4 ms +RELEASE LSHIFT +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME Rollover from normal to CharShift upper + +RUN 4 ms +PRESS x +RUN 1 cycle +EXPECT keyboard-report Key_X + +RUN 4 ms +PRESS CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_X Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_X Key_LeftShift Key_G # report: { e1 10 } + +RUN 4 ms +RELEASE x +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift Key_G # report: { e1 10 } + +RUN 4 ms +RELEASE CS_GH +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report empty + +RUN 5 ms + +# ============================================================================== +NAME Rollover from shifted to CharShift lower + +RUN 4 ms +PRESS Y +RUN 1 cycle +EXPECT keyboard-report Key_LeftShift # report: { e1 } +EXPECT keyboard-report Key_LeftShift Key_Y + +RUN 4 ms +PRESS CS_ab +RUN 1 cycle +EXPECT keyboard-report Key_Y +EXPECT keyboard-report Key_Y Key_A + +RUN 4 ms +RELEASE Y +RUN 1 cycle +EXPECT keyboard-report Key_A + +RUN 4 ms +RELEASE CS_ab +RUN 1 cycle +EXPECT keyboard-report empty + +RUN 5 ms