Add CharShift plugin

Signed-off-by: Michael Richters <gedankenexperimenter@gmail.com>
pull/1040/head
Michael Richters 3 years ago
parent c4e6da0e40
commit f7264f9f4d
No known key found for this signature in database
GPG Key ID: 1288FD13E4EEF0C0

@ -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 <Kaleidoscope.h>
#include <Kaleidoscope-CharShift.h>
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

@ -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 <jesse@keyboard.io>
url=https://github.com/keyboardio/Kaleidoscope
author=Michael Richters <gedankenexperimenter@gmail.com>
paragraph=

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include <kaleidoscope/plugin/CharShift.h>

@ -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 <http://www.gnu.org/licenses/>.
*/
#include "kaleidoscope/plugin/CharShift.h"
#include <Kaleidoscope-Ranges.h>
#include <Kaleidoscope-FocusSerial.h>
#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;

@ -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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "kaleidoscope/Runtime.h"
#include <Kaleidoscope-Ranges.h>
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 <uint8_t _num_keypairs>
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);
}

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

Loading…
Cancel
Save