Replace the keymap cache with a keyboard state array

This replaces the `Layer.live_composite_keymap_[]` cache with a representation
of the keyboard's current state as an array of `Key` objects, one per key on the
keyboard. In this new array, an idle key will have the value `Key_Transparent`,
and a pressed key will have the value of whatever key it's currently mapped to
in the keymap (or whatever value the active set of plugins has assigned to
it). A value of `Key_NoKey` will mask that key until it is released.

If a plugin returns `ABORT` from its `onKeyswitchEvent()` handler, that means
that the keymap cache should not be updated. It's especially important to have
this occur after plugins like OneShot and Qukeys, where the key can stay
active (or become active) after the physical keyswitch has been released.

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

@ -0,0 +1,98 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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_internal/device.h"
#include "kaleidoscope/KeyAddr.h"
#include "kaleidoscope/key_defs.h"
namespace kaleidoscope {
// A `KeyAddrMap` is a collection of objects, indexed by `KeyAddr`, with one
// entry per key on the keyboard.
template<typename _ContentType, uint8_t _size>
class KeyAddrMap {
private:
_ContentType values_[_size]; // NOLINT(runtime/arrays)
public:
typedef KeyAddrMap<_ContentType, _size> ThisType;
// Return the number of `Key` entries in the array
static constexpr uint8_t size() {
return _size;
}
// To set the value of an entry:
// key_array[key_addr] = Key_X;
_ContentType& operator[](KeyAddr key_addr) {
return values_[key_addr.toInt()];
}
// To get the value of an entry:
// Key key = key_array[key_addr];
const _ContentType& operator[](KeyAddr key_addr) const {
return values_[key_addr.toInt()];
}
// ---------------------------------------------------------------------------
// The following code defines an iterator class for a class `KeyMap`, such
// that we can write the following code to get each entry in the array:
//
// typedef KeyAddrMap<Key> KeyMap;
//
// for (Key key : key_map) {...}
//
// Or, if we need write access to the entries in the array:
//
// for (Key &key : key_map) {...}
private:
class Iterator;
friend class ThisType::Iterator;
public:
Iterator begin() {
return {*this, KeyAddr(uint8_t(0))};
}
Iterator end() {
return {*this, KeyAddr(_size)};
}
private:
class Iterator {
public:
Iterator(ThisType &map, KeyAddr key_addr)
: map_(map), key_addr_(key_addr) {}
bool operator!=(const Iterator &other) const {
return key_addr_ != other.key_addr_;
}
_ContentType& operator*() const {
return map_[key_addr_];
}
Iterator& operator++() {
++key_addr_;
return *this;
}
private:
ThisType &map_;
KeyAddr key_addr_;
};
};
} // namespace kaleidoscope

@ -0,0 +1,25 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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/KeyAddrMap.h"
namespace kaleidoscope {
typedef KeyAddrMap<Key, KeyAddr::upper_limit> KeyMap;
} // namespace kaleidoscope

@ -0,0 +1,23 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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/LiveKeys.h"
namespace kaleidoscope {
LiveKeys live_keys;
} // namespace kaleidoscope

@ -0,0 +1,105 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-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/key_defs.h" // for Key, Key_NoKey, Key_Transparent
#include "kaleidoscope/KeyAddr.h" // for KeyAddr
#include "kaleidoscope/KeyMap.h" // for KeyMap
namespace kaleidoscope {
/// A representation of the "live" state of the keys on the keyboard
///
/// This is structure of `Key` values, indexed by `KeyAddr` values, with one
/// entry per key on the keyboard. These entries are meant to represent the
/// current state of the keys, with two special values:
///
/// - `Key_Inactive` indicates that the given key is not active. Usually this
/// will correspond to the keyswitch being off (not pressed).
///
/// - `Key_Masked` indicates that the given key has been masked. When a key
/// release event is processed for that key, it will be reset to
/// `Key_Inactive`.
///
/// Any other value indicates that the key is active (i.e. pressed, though
/// plugins can set entries to active even when the physical keyswitches are not
/// engaged), and the `Key` value is what the that key is "sending" at the
/// time. At the end of its processing of a `KeyEvent`, Kaleidoscope will use
/// the contents of this array to populate the Keyboard HID reports.
class LiveKeys {
public:
// For array-style subscript addressing of entries in a read-only context:
const Key& operator[](KeyAddr key_addr) const {
if (key_addr.isValid()) {
return key_map_[key_addr];
}
dummy_ = Key_Masked;
return dummy_;
}
// For array-style subscript addressing of entries by reference. The client
// code can alter values in the array this way.
Key& operator[](KeyAddr key_addr) {
if (key_addr.isValid()) {
return key_map_[key_addr];
}
dummy_ = Key_Masked;
return dummy_;
}
/// Set an entry to "active" with a specified `Key` value.
void activate(KeyAddr key_addr, Key key) {
if (key_addr.isValid())
key_map_[key_addr] = key;
}
/// Deactivate an entry by setting its value to `Key_Inactive`.
void clear(KeyAddr key_addr) {
if (key_addr.isValid())
key_map_[key_addr] = Key_Inactive;
}
/// Mask a key by setting its entry to `Key_Masked`. The key will become
/// unmasked by Kaleidoscope on release (but not on a key press event).
void mask(KeyAddr key_addr) {
if (key_addr.isValid())
key_map_[key_addr] = Key_Masked;
}
/// Clear the entire array by setting all values to `Key_Inactive`.
void clear() {
for (Key &key : key_map_) {
key = Key_Inactive;
}
}
/// Returns an iterator for use in range-based for loops:
///
/// for (Key key : live_keys.all()) {...}
KeyMap& all() {
return key_map_;
}
private:
KeyMap key_map_;
mutable Key dummy_{0, 0};
};
extern LiveKeys live_keys;
} // namespace kaleidoscope

@ -15,6 +15,7 @@
*/
#include "kaleidoscope/Runtime.h"
#include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope/layers.h"
#include "kaleidoscope/keyswitch_state.h"
@ -42,6 +43,9 @@ Runtime_::setup(void) {
device().setup();
// Clear the keyboard state array (all keys idle at start)
live_keys.clear();
Layer.setup();
}

@ -16,13 +16,15 @@
#pragma once
#include <stdint.h> // for uint8_t, uint16_t
#include "kaleidoscope/HIDTables.h"
#include <Arduino.h> // for pgm_read_byte, INPUT
#include "kaleidoscope/HIDTables.h" // for HID_KEYBOARD_LEFT_CONTROL
#include "kaleidoscope/key_defs/keyboard.h"
#include "kaleidoscope/key_defs/sysctl.h"
#include "kaleidoscope/key_defs/consumerctl.h"
#include "kaleidoscope/key_defs/keymaps.h"
#include "kaleidoscope/key_defs/keymaps.h" // for LAYER_SHIFT_OFFSET
#include "kaleidoscope/key_defs/aliases.h"
// -----------------------------------------------------------------------------
@ -34,21 +36,21 @@
// -----------------------------------------------------------------------------
// Constant flags values
#define KEY_FLAGS B00000000
#define CTRL_HELD B00000001
#define LALT_HELD B00000010
#define RALT_HELD B00000100
#define SHIFT_HELD B00001000
#define GUI_HELD B00010000
#define KEY_FLAGS 0b00000000
#define CTRL_HELD 0b00000001
#define LALT_HELD 0b00000010
#define RALT_HELD 0b00000100
#define SHIFT_HELD 0b00001000
#define GUI_HELD 0b00010000
#define SYNTHETIC B01000000
#define RESERVED B10000000
#define SYNTHETIC 0b01000000
#define RESERVED 0b10000000
// we assert that synthetic keys can never have keys held, so we reuse the _HELD bits
#define IS_SYSCTL B00000001
#define IS_INTERNAL B00000010
#define SWITCH_TO_KEYMAP B00000100
#define IS_CONSUMER B00001000
#define IS_SYSCTL 0b00000001
#define IS_INTERNAL 0b00000010
#define SWITCH_TO_KEYMAP 0b00000100
#define IS_CONSUMER 0b00001000
// HID Usage Types: Because these constants, like the ones above, are
// used in the flags byte of the Key class, they can't overlap any of
@ -56,18 +58,18 @@
// the HID usage type of a keycode, which leaves us with only two
// bits. Since we don't currently do anything different based on HID
// usage type, these are currently all set to zeroes.
#define HID_TYPE_CA B00000000
#define HID_TYPE_CL B00000000
#define HID_TYPE_LC B00000000
#define HID_TYPE_MC B00000000
#define HID_TYPE_NARY B00000000
#define HID_TYPE_OOC B00000000
#define HID_TYPE_OSC B00000000
#define HID_TYPE_RTC B00000000
#define HID_TYPE_SEL B00000000
#define HID_TYPE_SV B00000000
#define HID_TYPE_CA 0b00000000
#define HID_TYPE_CL 0b00000000
#define HID_TYPE_LC 0b00000000
#define HID_TYPE_MC 0b00000000
#define HID_TYPE_NARY 0b00000000
#define HID_TYPE_OOC 0b00000000
#define HID_TYPE_OSC 0b00000000
#define HID_TYPE_RTC 0b00000000
#define HID_TYPE_SEL 0b00000000
#define HID_TYPE_SV 0b00000000
// Mask defining the allowed usage type flag bits:
#define HID_TYPE_MASK B00110000
#define HID_TYPE_MASK 0b00110000
// =============================================================================

@ -15,6 +15,7 @@
*/
#include "kaleidoscope/Runtime.h"
#include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope/hooks.h"
#include "kaleidoscope/keyswitch_state.h"
#include "kaleidoscope/layers.h"
@ -85,35 +86,44 @@ void handleKeyswitchEvent(Key mappedKey, KeyAddr key_addr, uint8_t keyState) {
* them.
*/
if (key_addr.isValid()) {
/* If a key had an on event, we update the live composite keymap. See
* layers.h for an explanation about the different caches we have. */
if (keyToggledOn(keyState)) {
if (mappedKey == Key_NoKey || keyState & EPHEMERAL) {
Layer.updateLiveCompositeKeymap(key_addr);
} else {
Layer.updateLiveCompositeKeymap(key_addr, mappedKey);
}
}
/* Convert key_addr to the correct mappedKey
* The condition here means that if mappedKey and key_addr are both valid,
* the mappedKey wins - we don't re-look-up the mappedKey
*/
// If the caller did not supply a `Key` value, get it from the keymap
// cache. If that value is transparent, look it up from the active layer for
// that key address.
if (mappedKey == Key_NoKey) {
// Note: If the next line returns `Key_NoKey`, that will effectively mask
// the key.
mappedKey = Layer.lookup(key_addr);
}
} // key_addr valid
// Keypresses with out-of-bounds key_addr start here in the processing chain
auto result = kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState);
// If any plugin returns `ABORT`, stop here and don't update the active keys
// cache entry.
if (result == kaleidoscope::EventHandlerResult::ABORT)
return;
if (kaleidoscope::Hooks::onKeyswitchEvent(mappedKey, key_addr, keyState) != kaleidoscope::EventHandlerResult::OK)
// Update the keyboard state array
if (key_addr.isValid()) {
if (keyToggledOn(keyState)) {
kaleidoscope::live_keys.activate(key_addr, mappedKey);
} else if (keyToggledOff(keyState)) {
kaleidoscope::live_keys.clear(key_addr);
}
}
// Only continue if all plugin handlers returned `OK`.
if (result != kaleidoscope::EventHandlerResult::OK)
return;
mappedKey = Layer.eventHandler(mappedKey, key_addr, keyState);
if (mappedKey == Key_NoKey)
// If the key has been masked (i.e. `Key_NoKey`), or it's a plugin-specific
// key (`RESERVED`), don't bother continuing.
if (mappedKey == Key_NoKey || (mappedKey.getFlags() & RESERVED) != 0)
return;
// Handle all built-in key types.
Layer.eventHandler(mappedKey, key_addr, keyState);
handleKeyswitchEventDefault(mappedKey, key_addr, keyState);
}

@ -1,5 +1,5 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-2018 Keyboard.io, Inc.
* Copyright (C) 2013-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
@ -14,7 +14,8 @@
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "kaleidoscope/Runtime.h"
#include "kaleidoscope_internal/device.h"
#include "kaleidoscope/hooks.h"
#include "kaleidoscope/layers.h"
#include "kaleidoscope/keyswitch_state.h"
@ -42,19 +43,15 @@ uint32_t Layer_::layer_state_;
uint8_t Layer_::active_layer_count_ = 1;
int8_t Layer_::active_layers_[31];
Key Layer_::live_composite_keymap_[Runtime.device().numKeys()];
uint8_t Layer_::active_layer_keymap_[Runtime.device().numKeys()];
uint8_t Layer_::active_layer_keymap_[kaleidoscope_internal::device.numKeys()];
Layer_::GetKeyFunction Layer_::getKey = &Layer_::getKeyFromPROGMEM;
void Layer_::setup() {
// Explicitly set layer 0's state to 1
bitSet(layer_state_, 0);
// Update the keymap cache, so we start with a non-empty state.
// Update the active layer cache (every entry will be `0` to start)
Layer.updateActiveLayers();
for (auto key_addr : KeyAddr::all()) {
Layer.updateLiveCompositeKeymap(key_addr);
}
}
void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) {
@ -120,26 +117,31 @@ void Layer_::handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState) {
}
Key Layer_::eventHandler(Key mappedKey, KeyAddr key_addr, uint8_t keyState) {
if (mappedKey.getFlags() != (SYNTHETIC | SWITCH_TO_KEYMAP))
return mappedKey;
handleKeymapKeyswitchEvent(mappedKey, keyState);
return Key_NoKey;
if (mappedKey.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))
handleKeymapKeyswitchEvent(mappedKey, keyState);
return mappedKey;
}
Key Layer_::getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr) {
return keyFromKeymap(layer, key_addr);
}
// Deprecated
void Layer_::updateLiveCompositeKeymap(KeyAddr key_addr) {
int8_t layer = active_layer_keymap_[key_addr.toInt()];
live_composite_keymap_[key_addr.toInt()] = (*getKey)(layer, key_addr);
// We could update the active keys cache here (as commented below), but I
// think that's unlikely to serve whatever purpose the caller had in
// mind. `Layer.lookup()` will still give the correct result, and without a
// `Key` value is specified, this function no longer serves a purpose.
// #include "kaleidoscope/LiveKeys.h"
// int8_t layer = active_layer_keymap_[key_addr.toInt()];
// live_keys.activate(key_addr, (*getKey)(layer, key_addr));
}
void Layer_::updateActiveLayers(void) {
// First, set every entry in the active layer keymap to point to the default
// layer (layer 0).
memset(active_layer_keymap_, 0, Runtime.device().numKeys());
memset(active_layer_keymap_, 0, kaleidoscope_internal::device.numKeys());
// For each key address, set its entry in the active layer keymap to the value
// of the top active layer that has a non-transparent entry for that address.

@ -1,5 +1,5 @@
/* Kaleidoscope - Firmware for computer input devices
* Copyright (C) 2013-2018 Keyboard.io, Inc.
* Copyright (C) 2013-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
@ -20,6 +20,7 @@
#include "kaleidoscope/key_defs.h"
#include "kaleidoscope/keymaps.h"
#include "kaleidoscope/device/device.h"
#include "kaleidoscope/LiveKeys.h"
#include "kaleidoscope_internal/device.h"
#include "kaleidoscope_internal/sketch_exploration/sketch_exploration.h"
#include "kaleidoscope_internal/shortname.h"
@ -83,7 +84,13 @@ class Layer_ {
* `lookupOnActiveLayer`.
*/
static Key lookup(KeyAddr key_addr) {
return live_composite_keymap_[key_addr.toInt()];
// First check the keyboard state array
Key key = live_keys[key_addr];
// If that entry is clear, look up the entry from the active keymap layers
if (key == Key_Transparent) {
key = lookupOnActiveLayer(key_addr);
}
return key;
}
static Key lookupOnActiveLayer(KeyAddr key_addr) {
uint8_t layer = active_layer_keymap_[key_addr.toInt()];
@ -111,9 +118,11 @@ class Layer_ {
static Key getKeyFromPROGMEM(uint8_t layer, KeyAddr key_addr);
DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP)
static void updateLiveCompositeKeymap(KeyAddr key_addr, Key mappedKey) {
live_composite_keymap_[key_addr.toInt()] = mappedKey;
live_keys.activate(key_addr, mappedKey);
}
DEPRECATED(LAYER_UPDATELIVECOMPOSITEKEYMAP)
static void updateLiveCompositeKeymap(KeyAddr key_addr);
static void updateActiveLayers(void);
@ -127,7 +136,6 @@ class Layer_ {
static uint32_t layer_state_;
static uint8_t active_layer_count_;
static int8_t active_layers_[31];
static Key live_composite_keymap_[kaleidoscope_internal::device.numKeys()];
static uint8_t active_layer_keymap_[kaleidoscope_internal::device.numKeys()];
static void handleKeymapKeyswitchEvent(Key keymapEntry, uint8_t keyState);

@ -27,3 +27,10 @@
/* Messages */
#define _DEPRECATED_MESSAGE_LAYER_UPDATELIVECOMPOSITEKEYMAP __NL__ \
"`Layer.updateLiveCompositeKeymap()` is deprecated.\n" __NL__ \
"The 'live composite keymap' cache has been replaced with the\n" __NL__ \
"'active keys' cache, which now represents the state of the active\n" __NL__ \
"keys at any given time. It is probably not necessary to directly\n" __NL__ \
"update that cache from a plugin, but if you need to, please use\n" __NL__ \
"the `live_keys.activate(key_addr, key)` function instead."

Loading…
Cancel
Save