From f7264f9f4df0cea5f6e47803a5e21ee0cfe86d8f Mon Sep 17 00:00:00 2001 From: Michael Richters Date: Sun, 30 May 2021 21:29:41 -0500 Subject: [PATCH] Add CharShift plugin Signed-off-by: Michael Richters --- plugins/Kaleidoscope-CharShift/README.md | 66 ++++++++ .../Kaleidoscope-CharShift/library.properties | 7 + .../src/Kaleidoscope-CharShift.h | 20 +++ .../src/kaleidoscope/plugin/CharShift.cpp | 149 ++++++++++++++++++ .../src/kaleidoscope/plugin/CharShift.h | 137 ++++++++++++++++ .../src/Kaleidoscope-Ranges.h | 4 + 6 files changed, 383 insertions(+) create mode 100644 plugins/Kaleidoscope-CharShift/README.md create mode 100644 plugins/Kaleidoscope-CharShift/library.properties create mode 100644 plugins/Kaleidoscope-CharShift/src/Kaleidoscope-CharShift.h create mode 100644 plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.cpp create mode 100644 plugins/Kaleidoscope-CharShift/src/kaleidoscope/plugin/CharShift.h 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