diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp index 3caeb077..26367a94 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Escape-OneShot -- Turn ESC into a key that cancels OneShots, if active. - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -19,24 +19,31 @@ #include #include #include "kaleidoscope/keyswitch_state.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { namespace plugin { -EventHandlerResult EscapeOneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - if (mapped_key != Key_Escape || (keyState & INJECTED)) - return EventHandlerResult::OK; - - if (!keyToggledOn(keyState)) - return EventHandlerResult::OK; - - if ((!::OneShot.isActive() || ::OneShot.isPressed()) && !::OneShot.isSticky()) { - return EventHandlerResult::OK; +EventHandlerResult EscapeOneShot::onKeyswitchEvent( + Key &key, KeyAddr key_addr, uint8_t key_state) { + // We only act on an escape key that has just been pressed, and not + // generated by some other plugin. Also, only if at least one + // OneShot key is active and/or sticky. Last, only if there are no + // OneShot keys currently being held. + if (key == Key_Escape && + keyToggledOn(key_state) && + !(key_state & INJECTED) && + ::OneShot.isActive()) { + // Cancel all OneShot keys + ::OneShot.cancel(true); + // Change the escape key to a blank key, and signal that event processing is + // complete. + key = Key_NoKey; + return EventHandlerResult::EVENT_CONSUMED; } - ::OneShot.cancel(true); - mapped_key = Key_NoKey; - return EventHandlerResult::EVENT_CONSUMED; + // Otherwise, do nothing + return EventHandlerResult::OK; } } diff --git a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h index af214228..bc52a8ad 100644 --- a/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h +++ b/plugins/Kaleidoscope-Escape-OneShot/src/kaleidoscope/plugin/Escape-OneShot.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-Escape-OneShot -- Turn ESC into a key that cancels OneShots, if active. - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -25,8 +25,7 @@ class EscapeOneShot : public kaleidoscope::Plugin { public: EscapeOneShot(void) {} - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); - + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); }; } } diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp index 5e4004e5..1b293cdc 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.cpp @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-LED-ActiveModColor -- Light up the LEDs under the active modifiers - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -18,34 +18,44 @@ #include #include #include "kaleidoscope/layers.h" +#include "kaleidoscope/keyswitch_state.h" namespace kaleidoscope { namespace plugin { -KeyAddr ActiveModColorEffect::mod_keys_[MAX_MODS_PER_LAYER]; -uint8_t ActiveModColorEffect::mod_key_count_; +KeyAddrBitfield ActiveModColorEffect::mod_key_bits_; bool ActiveModColorEffect::highlight_normal_modifiers_ = true; -cRGB ActiveModColorEffect::highlight_color = (cRGB) { - 160, 160, 160 -}; - +cRGB ActiveModColorEffect::highlight_color = CRGB(160, 160, 160); +cRGB ActiveModColorEffect::oneshot_color = CRGB(160, 160, 0); cRGB ActiveModColorEffect::sticky_color = CRGB(160, 0, 0); -EventHandlerResult ActiveModColorEffect::onLayerChange() { - if (!Runtime.has_leds) - return EventHandlerResult::OK; - - mod_key_count_ = 0; +EventHandlerResult ActiveModColorEffect::onKeyswitchEvent( + Key &key, + KeyAddr key_addr, + uint8_t key_state) { - for (auto key_addr : KeyAddr::all()) { - Key k = Layer.lookupOnActiveLayer(key_addr); + // If `key_addr` is not a physical key address, ignore it: + if (! key_addr.isValid()) { + return EventHandlerResult::OK; + } - if (::OneShot.isOneShotKey(k) || - (highlight_normal_modifiers_ && ( - (k >= Key_LeftControl && k <= Key_RightGui) || - (k.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))))) { - mod_keys_[mod_key_count_++] = key_addr; + if (keyToggledOn(key_state)) { + // If a key toggles on, we check its value. If it's a OneShot key, + // it will get highlighted. Conditionally (if + // `highlight_normal_modifiers_` is set), we also highlight + // modifier and layer-shift keys. + if ((key >= Key_LeftControl && key <= Key_RightGui) || + (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP))) { + mod_key_bits_.set(key_addr); + } + } else if (keyToggledOff(key_state)) { + // Things get a bit ugly here because this plugin might come + // before OneShot in the order, so we can't just count on OneShot + // stopping the suppressed release event before we see it here. + if (mod_key_bits_.read(key_addr) && !::OneShot.isActive(key_addr)) { + mod_key_bits_.clear(key_addr); + ::LEDControl.refreshAt(key_addr); } } @@ -53,36 +63,20 @@ EventHandlerResult ActiveModColorEffect::onLayerChange() { } EventHandlerResult ActiveModColorEffect::beforeReportingState() { - if (mod_key_count_ == 0) { - onLayerChange(); - } - - for (uint8_t i = 0; i < mod_key_count_; i++) { - const KeyAddr &key_addr = mod_keys_[i]; - - Key k = Layer.lookupOnActiveLayer(key_addr); - - if (::OneShot.isOneShotKey(k)) { - if (::OneShot.isSticky(k)) - ::LEDControl.setCrgbAt(key_addr, sticky_color); - else if (::OneShot.isActive(k)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); - } else if (k >= Key_LeftControl && k <= Key_RightGui) { - if (kaleidoscope::Runtime.hid().keyboard().isModifierKeyActive(k)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); - } else if (k.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP)) { - uint8_t layer = k.getKeyCode(); - if (layer >= LAYER_SHIFT_OFFSET) - layer -= LAYER_SHIFT_OFFSET; - if (Layer.isActive(layer)) - ::LEDControl.setCrgbAt(key_addr, highlight_color); - else - ::LEDControl.refreshAt(key_addr); + // This loop iterates through only the `key_addr`s that have their + // bits in the `mod_key_bits_` bitfield set. + for (KeyAddr key_addr : mod_key_bits_) { + + if (::OneShot.isTemporary(key_addr)) { + // Temporary OneShot keys get one color: + ::LEDControl.setCrgbAt(key_addr, oneshot_color); + } else if (::OneShot.isSticky(key_addr)) { + // Sticky OneShot keys get another color: + ::LEDControl.setCrgbAt(key_addr, sticky_color); + } else if (highlight_normal_modifiers_) { + // Normal modifiers get a third color: + ::LEDControl.setCrgbAt(key_addr, highlight_color); } } diff --git a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h index 7b554f42..bdbc3bdd 100644 --- a/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h +++ b/plugins/Kaleidoscope-LED-ActiveModColor/src/kaleidoscope/plugin/LED-ActiveModColor.h @@ -1,6 +1,6 @@ /* -*- mode: c++ -*- * Kaleidoscope-LED-ActiveModColor -- Light up the LEDs under the active modifiers - * Copyright (C) 2016, 2017, 2018 Keyboard.io, Inc + * Copyright (C) 2016-2020 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 @@ -18,6 +18,7 @@ #pragma once #include "kaleidoscope/Runtime.h" +#include "kaleidoscope/KeyAddrBitfield.h" #include #define MAX_MODS_PER_LAYER 16 @@ -29,22 +30,21 @@ class ActiveModColorEffect : public kaleidoscope::Plugin { ActiveModColorEffect(void) {} static cRGB highlight_color; + static cRGB oneshot_color; static cRGB sticky_color; static void highlightNormalModifiers(bool value) { highlight_normal_modifiers_ = value; } + EventHandlerResult onKeyswitchEvent(Key &key, + KeyAddr key_addr, + uint8_t key_state); EventHandlerResult beforeReportingState(); - EventHandlerResult onLayerChange(); - EventHandlerResult onSetup() { - return onLayerChange(); - } private: static bool highlight_normal_modifiers_; - static KeyAddr mod_keys_[MAX_MODS_PER_LAYER]; - static uint8_t mod_key_count_; + static KeyAddrBitfield mod_key_bits_; }; } } diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index c7a07aa0..99a78850 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -19,274 +19,375 @@ #include #include "kaleidoscope/keyswitch_state.h" #include "kaleidoscope/key_events.h" +#include "kaleidoscope/layers.h" namespace kaleidoscope { namespace plugin { -// ---- state --------- +// ---------------------------------------------------------------------------- +// Configuration variables -uint16_t OneShot::start_time_ = 0; uint16_t OneShot::time_out = 2500; uint16_t OneShot::hold_time_out = 250; int16_t OneShot::double_tap_time_out = -1; -OneShot::key_state_t OneShot::state_[OneShot::ONESHOT_KEY_COUNT]; -Key OneShot::prev_key_; -bool OneShot::should_cancel_ = false; -bool OneShot::should_cancel_stickies_ = false; - -bool OneShot::isPressed() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].pressed) - return true; + +// ---------------------------------------------------------------------------- +// State variables + +uint16_t OneShot::stickable_keys_ = -1; + +KeyAddrBitfield OneShot::temp_addrs_; +KeyAddrBitfield OneShot::glue_addrs_; + +uint16_t OneShot::start_time_ = 0; +KeyAddr OneShot::prev_key_addr_ = OneShot::invalid_key_addr; +uint8_t OneShot::release_countdown_ = 0; + + +// ============================================================================ +// Public interface + +// ---------------------------------------------------------------------------- +// Configuration functions + +void OneShot::enableStickability(Key key) { + uint8_t n = getKeyIndex(key); + stickable_keys_ |= (1 << n); +} + +void OneShot::disableStickability(Key key) { + uint8_t n = getKeyIndex(key); + stickable_keys_ &= ~(1 << n); +} + +void OneShot::enableStickabilityForModifiers() { + stickable_keys_ |= stickable_modifiers_mask; +} + +void OneShot::disableStickabilityForModifiers() { + stickable_keys_ &= ~stickable_modifiers_mask; +} + +void OneShot::enableStickabilityForLayers() { + stickable_keys_ |= stickable_layers_mask; +} + +void OneShot::disableStickabilityForLayers() { + stickable_keys_ &= ~stickable_layers_mask; +} + +// ---------------------------------------------------------------------------- +// Global tests for any OneShot key + +bool OneShot::isActive() { + for (KeyAddr key_addr __attribute__((unused)) : temp_addrs_) { + return true; + } + for (KeyAddr key_addr __attribute__((unused)) : glue_addrs_) { + return true; } return false; } bool OneShot::isSticky() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].sticky) + for (KeyAddr key_addr : glue_addrs_) { + if (! temp_addrs_.read(key_addr)) { return true; + } } return false; } -bool OneShot::isStickable(Key key) { - return state_[key.getRaw() - ranges::OS_FIRST].stickable; -} +// ---------------------------------------------------------------------------- +// Key-specific OneShot key tests -// ---- OneShot stuff ---- -void OneShot::injectNormalKey(uint8_t idx, uint8_t key_state) { - Key key; +// These functions are particularly useful for ActiveModColor, which +// could potentially use three different color values for the three +// states (sticky | active && !sticky | pressed && !active). - if (idx < 8) { - key = Key(Key_LeftControl.getKeyCode() + idx, - Key_LeftControl.getFlags()); - } else { - key = Key(LAYER_SHIFT_OFFSET + idx - 8, - KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP); +bool OneShot::isModifier(Key key) { + // Returns `true` if `key` is a modifier key, including modifiers + // with extra mod flags applied (e.g. `Key_Meh`). + if ((key.getFlags() & (SYNTHETIC | RESERVED)) != 0) { + return false; } + return (key.getKeyCode() >= Key_LeftControl.getKeyCode() && + key.getKeyCode() <= Key_RightGui.getKeyCode()); +} - handleKeyswitchEvent(key, UnknownKeyswitchLocation, key_state | INJECTED); +bool OneShot::isLayerShift(Key key) { + // Returns `true` if `key` is a layer-shift key. + return (key.getFlags() == (SYNTHETIC | SWITCH_TO_KEYMAP) && + key.getKeyCode() >= LAYER_SHIFT_OFFSET && + key.getKeyCode() < LAYER_MOVE_OFFSET); } -void OneShot::activateOneShot(uint8_t idx) { - injectNormalKey(idx, IS_PRESSED); +bool OneShot::isStickable(Key key) { + int8_t n; + if (isModifier(key)) { + n = key.getKeyCode() - Key_LeftControl.getKeyCode(); + return bitRead(stickable_keys_, n); + } else if (isLayerShift(key)) { + n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; + if (n < oneshot_key_count) { + return bitRead(stickable_keys_, n); + } + } + return false; } -void OneShot::cancelOneShot(uint8_t idx) { - state_[idx].active = false; - injectNormalKey(idx, WAS_PRESSED); +bool OneShot::isTemporary(KeyAddr key_addr) { + return temp_addrs_.read(key_addr); } -EventHandlerResult OneShot::onNameQuery() { - return ::Focus.sendName(F("OneShot")); +bool OneShot::isSticky(KeyAddr key_addr) { + return (glue_addrs_.read(key_addr) && !temp_addrs_.read(key_addr)); } -EventHandlerResult OneShot::onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState) { - uint8_t idx = mapped_key.getRaw() - ranges::OS_FIRST; +bool OneShot::isActive(KeyAddr key_addr) { + return (isTemporary(key_addr) || glue_addrs_.read(key_addr)); +} - if (keyState & INJECTED) - return EventHandlerResult::OK; +// ---------------------------------------------------------------------------- +// Other functions - if (!isActive()) { - if (!isOneShotKey_(mapped_key)) { - return EventHandlerResult::OK; +// Cancel all active OneShot keys (if `cancel_stickies` is true) or +// just non-sticky active OneShot keys. This function is called by +// Escape-OneShot to release active OneShot keys. +void OneShot::cancel(bool cancel_stickies) { + if (cancel_stickies) { + for (KeyAddr key_addr : glue_addrs_) { + releaseKey(key_addr); } - - if (keyToggledOff(keyState)) { - state_[idx].pressed = false; - } else if (keyToggledOn(keyState)) { - start_time_ = Runtime.millisAtCycleStart(); - state_[idx].position = key_addr.toInt(); - state_[idx].pressed = true; - state_[idx].active = true; - prev_key_ = mapped_key; - - activateOneShot(idx); + } + for (KeyAddr key_addr : temp_addrs_) { + if (glue_addrs_.read(key_addr)) { + releaseKey(key_addr); + } else { + temp_addrs_.clear(key_addr); } - - return EventHandlerResult::EVENT_CONSUMED; } +} - if (isOneShotKey_(mapped_key)) { - if (state_[idx].sticky) { - if (keyToggledOn(keyState)) { // maybe on _off instead? - prev_key_ = mapped_key; - state_[idx].sticky = false; - cancelOneShot(idx); - should_cancel_ = false; - } - } else { - if (keyToggledOff(keyState)) { - state_[idx].pressed = false; - if (Runtime.hasTimeExpired(start_time_, hold_time_out)) { - cancelOneShot(idx); - should_cancel_ = false; - } - } +// ---------------------------------------------------------------------------- +// Plugin hook functions - if (keyToggledOn(keyState)) { - state_[idx].pressed = true; +EventHandlerResult OneShot::onNameQuery() { + return ::Focus.sendName(F("OneShot")); +} - if (prev_key_ == mapped_key && isStickable(mapped_key)) { - uint16_t dtto = (double_tap_time_out == -1) ? time_out : double_tap_time_out; - if (!Runtime.hasTimeExpired(start_time_, dtto)) { - state_[idx].sticky = true; - prev_key_ = mapped_key; - } - } else { - start_time_ = Runtime.millisAtCycleStart(); +EventHandlerResult OneShot::onKeyswitchEvent( + Key &key, KeyAddr key_addr, uint8_t key_state) { - state_[idx].position = key_addr.toInt(); - state_[idx].active = true; - prev_key_ = mapped_key; + // Ignore injected key events. This prevents re-processing events + // that the hook functions generate (by calling `injectNormalKey()` + // via one of the `*OneShot()` functions). There are more robust + // ways to do this, but since OneShot is intended to react to only + // physical keypresses, this is adequate. + if (key_state & INJECTED) + return EventHandlerResult::OK; - activateOneShot(idx); + bool temp = temp_addrs_.read(key_addr); + bool glue = glue_addrs_.read(key_addr); + + if (keyToggledOn(key_state)) { + + if (!temp && !glue) { + // This key_addr is not in a OneShot state. + if (isOneShotKey(key)) { + // Replace the OneShot key with its corresponding normal key. + pressKey(key_addr, key); + return EventHandlerResult::ABORT; + } else if (!isModifier(key) && !isLayerShift(key)) { + // Only trigger release of temporary OneShot keys if the + // pressed key is neither a modifier nor a layer shift. + release_countdown_ = (1 << 1); + } + // return EventHandlerResult::OK; + + } else if (temp && glue) { + // This key_addr is in the temporary OneShot state. + if (key_addr == prev_key_addr_) { + // The same OneShot key has been pressed twice in a row. It + // will either become sticky (if it has been double-tapped), + // or it will be become a normal key. Either way, its `temp` + // state will be cleared. + temp_addrs_.clear(key_addr); + + // Derive the true double-tap timeout value if we're using the default. + uint16_t dtto = (double_tap_time_out < 0) ? time_out : double_tap_time_out; + + // If the key is not stickable, or the double-tap timeout has + // expired, clear the `glue` state, as well; this OneShot key + // has been cancelled, and will become a normal key. + if (!isStickable(key) || hasTimedOut(dtto)) { + glue_addrs_.clear(key_addr); + } else { + return EventHandlerResult::ABORT; } + } else { + // This is a temporary OneShot key, but has not been pressed + // twice in a row, so we need to clear its state. + temp_addrs_.clear(key_addr); + glue_addrs_.clear(key_addr); } - } - return EventHandlerResult::EVENT_CONSUMED; - } + } else if (!temp && glue) { + // This is a sticky OneShot key that has been pressed. Clear + // state now, so it will become a normal key. + glue_addrs_.clear(key_addr); + + } else { // (temp && !glue) + // A key has been pressed that is in the "pending" OneShot + // state. Since this key should have entered the "temporary" + // OneShot state as soon as it was released (from its first + // press), it should only be possible to release a key that's in + // this state. + } + // Always record the address of a keypress. It might be useful for + // other plugins, so this could perhaps be tracked in the + // Kaleidoscope core. + prev_key_addr_ = key_addr; + + } else if (keyToggledOff(key_state)) { + + if (temp || glue) { + // Any key in the "pending" OneShot state needs its `glue` state + // bit set to make it "temporary". If it's in the "sticky" + // OneShot state, this is redundant, but we're trading time + // efficiency to get smaller binary size. + glue_addrs_.set(key_addr); + // This is an active OneShot key that has just been released. We + // need to stop that event from sending a report, and instead + // send a "hold" event. This is handled in the + // `beforeReportingState()` hook below. + //Layer.updateLiveCompositeKeymap(key_addr, key); + return EventHandlerResult::ABORT; + } - // ordinary key here, with some event + } else { + // This key is being held. + if (temp && !glue) { + // This key is in the "pending" OneShot state. We need to check + // its hold timeout, and turn it back into a normal key if it + // has timed out. + if (hasTimedOut(hold_time_out)) { + temp_addrs_.clear(key_addr); + } + } - if (keyIsPressed(keyState)) { - prev_key_ = mapped_key; - if (!(mapped_key >= Key_LeftControl && mapped_key <= Key_RightGui) && - !(mapped_key.getFlags() == (KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP))) { - should_cancel_ = true; + if (isOneShotKey(key)) { + // whoops! someone cancelled a oneshot key while it was being + // held; reactivate it, but set it as a normal modifier + // instead. Or better yet, mask it, in case of a layer change. } } return EventHandlerResult::OK; } + +// For any active OneShot modifier keys, keep those modifiers active +// in the keyboard HID report. EventHandlerResult OneShot::beforeReportingState() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - if (state_[i].active) { - activateOneShot(i); - } + for (KeyAddr key_addr : glue_addrs_) { + holdKey(key_addr); } - return EventHandlerResult::OK; } + EventHandlerResult OneShot::afterEachCycle() { - bool oneshot_active = false; - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (state_[i].active) { - oneshot_active = true; - break; - } - } - if (oneshot_active && hasTimedOut()) - cancel(); - - bool is_cancelled = false; - - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if (should_cancel_) { - if (state_[i].sticky) { - if (should_cancel_stickies_) { - is_cancelled = true; - state_[i].sticky = false; - cancelOneShot(i); - state_[i].pressed = false; - } - } else if (state_[i].active && !state_[i].pressed) { - is_cancelled = true; - cancelOneShot(i); + // If a normal, non-modifier key has been pressed, or if active, + // non-sticky OneShot keys have timed out, this is where they get + // released. Release is triggered when `release_countdown_` gets to + // 1, not 0, because most of the time it will be 0 (see below). It + // gets set to 2 on the press of a normal key when there are any + // active OneShot keys; that way, the OneShot keys will stay active + // long enough to apply to the newly-pressed key. + if ((release_countdown_ == 1) || hasTimedOut(time_out)) { + for (KeyAddr key_addr : temp_addrs_) { + if (glue_addrs_.read(key_addr)) { + releaseKey(key_addr); } + temp_addrs_.clear(key_addr); } } - - if (is_cancelled) { - should_cancel_ = false; - should_cancel_stickies_ = false; - } + // Also, advance the counter for OneShot keys that have been + // cancelled by the press of a non-OneShot, non-modifier key. An + // unconditional bit shift should be more efficient than checking + // for zero to avoid underflow. + release_countdown_ >>= 1; return EventHandlerResult::OK; } -void OneShot::inject(Key mapped_key, uint8_t key_state) { - onKeyswitchEvent(mapped_key, UnknownKeyswitchLocation, key_state); -} +// ============================================================================ +// Private functions, not exposed to other plugins -// --- glue code --- +// ---------------------------------------------------------------------------- +// Helper functions for acting on OneShot key events -bool OneShot::isActive(void) { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - if ((state_[i].active && !hasTimedOut()) || - state_[i].pressed || - state_[i].sticky) - return true; - } - return false; +uint8_t OneShot::getOneShotKeyIndex(Key oneshot_key) { + // The calling function is responsible for verifying that + // `oneshot_key` is an actual OneShot key (i.e. call + // `isOneShotKey(oneshot_key)` first). + uint8_t index = oneshot_key.getRaw() - ranges::OS_FIRST; + return index; } -bool OneShot::isActive(Key key) { - uint8_t idx = key.getRaw() - ranges::OS_FIRST; - - return (state_[idx].active && !hasTimedOut()) || - state_[idx].pressed || - state_[idx].sticky; -} - -bool OneShot::isSticky(Key key) { - uint8_t idx = key.getRaw() - ranges::OS_FIRST; - - return state_[idx].sticky; -} - -bool OneShot::isModifierActive(Key key) { - if (key < Key_LeftControl || key > Key_RightGui) - return false; - - uint8_t idx = key.getKeyCode() - Key_LeftControl.getKeyCode(); - return state_[idx].active; -} - -void OneShot::cancel(bool with_stickies) { - should_cancel_ = true; - should_cancel_stickies_ = with_stickies; -} - -void OneShot::enableStickability(Key key) { - if (key >= ranges::OS_FIRST && key <= ranges::OS_LAST) - state_[key.getRaw() - ranges::OS_FIRST].stickable = true; -} - -void OneShot::disableStickability(Key key) { - if (key >= ranges::OS_FIRST && key <= ranges::OS_LAST) - state_[key.getRaw() - ranges::OS_FIRST].stickable = false; -} - -void OneShot::enableStickabilityForModifiers() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - state_[i].stickable = true; +uint8_t OneShot::getKeyIndex(Key key) { + // Default to returning a value that's out of range. This should be + // harmless because we only use the returned index to reference a + // bit in a bitfield, not as a memory address. + uint8_t n{oneshot_key_count}; + + if (isOneShotKey(key)) { + n = getOneShotKeyIndex(key); + } else if (isModifier(key)) { + n = key.getKeyCode() - Key_LeftControl.getKeyCode(); + } else if (isLayerShift(key)) { + n = oneshot_mod_count + key.getKeyCode() - LAYER_SHIFT_OFFSET; } + return n; } -void OneShot::enableStickabilityForLayers() { - for (uint8_t i = ONESHOT_KEY_COUNT / 2; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = true; +Key OneShot::decodeOneShotKey(Key oneshot_key) { + // The calling function is responsible for verifying that + // `oneshot_key` is an actual OneShot key (i.e. call + // `isOneShotKey(oneshot_key)` first). + uint8_t n = getOneShotKeyIndex(oneshot_key); + if (n < oneshot_mod_count) { + return Key(Key_LeftControl.getKeyCode() + n, + Key_LeftControl.getFlags()); + } else { + return Key(LAYER_SHIFT_OFFSET + n - oneshot_mod_count, + KEY_FLAGS | SYNTHETIC | SWITCH_TO_KEYMAP); } } -void OneShot::disableStickabilityForModifiers() { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT / 2; i++) { - state_[i].stickable = false; - } -} +// ------------------------------------------------------------------------------ +// Helper functions for sending key events for keys in OneShot states -void OneShot::disableStickabilityForLayers() { - for (uint8_t i = ONESHOT_KEY_COUNT / 2; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = false; - } +void OneShot::pressKey(KeyAddr key_addr, Key oneshot_key) { + Key key = decodeOneShotKey(oneshot_key); + prev_key_addr_ = key_addr; + start_time_ = Runtime.millisAtCycleStart(); + temp_addrs_.set(key_addr); + handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED); } +void OneShot::holdKey(KeyAddr key_addr) { + handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | IS_PRESSED | INJECTED); } + +void OneShot::releaseKey(KeyAddr key_addr) { + glue_addrs_.clear(key_addr); + temp_addrs_.clear(key_addr); + handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); } +} // namespace plugin +} // namespace kaleidoscope + kaleidoscope::plugin::OneShot OneShot; diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index 30c14167..033c5e87 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -19,6 +19,11 @@ #include "kaleidoscope/Runtime.h" #include +#include "kaleidoscope/key_events.h" +#include "kaleidoscope/KeyAddrBitfield.h" + +// ---------------------------------------------------------------------------- +// Keymap macros #define OSM(kc) Key(kaleidoscope::ranges::OSM_FIRST + (Key_ ## kc).getKeyCode() - Key_LeftControl.getKeyCode()) #define OSL(n) Key(kaleidoscope::ranges::OSL_FIRST + n) @@ -28,25 +33,11 @@ namespace plugin { class OneShot : public kaleidoscope::Plugin { public: - OneShot(void) { - for (uint8_t i = 0; i < ONESHOT_KEY_COUNT; i++) { - state_[i].stickable = true; - } - } - - static bool isOneShotKey(Key key) { - return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && key.getRaw() <= kaleidoscope::ranges::OS_LAST); - } - static bool isActive(void); - static bool isActive(Key key); - static bool isPressed(); - static bool isSticky(); - static bool isSticky(Key key); - static void cancel(bool with_stickies = false); + // Constructor + OneShot() {} - static uint16_t time_out; - static int16_t double_tap_time_out; - static uint16_t hold_time_out; + // -------------------------------------------------------------------------- + // Configuration functions static inline void enableStickablity() {} static void enableStickability(Key key); @@ -68,46 +59,86 @@ class OneShot : public kaleidoscope::Plugin { static void disableStickabilityForModifiers(); static void disableStickabilityForLayers(); - static bool isStickable(Key key); + // -------------------------------------------------------------------------- + // Global test functions + + static bool isActive(); + static bool isSticky(); + + // -------------------------------------------------------------------------- + // Single-key test functions + static bool isOneShotKey(Key key) { + return (key.getRaw() >= kaleidoscope::ranges::OS_FIRST && + key.getRaw() <= kaleidoscope::ranges::OS_LAST); + } + static bool isStickable(Key key); // inline? + + static bool isTemporary(KeyAddr key_addr); // inline? + static bool isSticky(KeyAddr key_addr); // inline? + static bool isActive(KeyAddr key_addr); // inline? - static bool isModifierActive(Key key); + // -------------------------------------------------------------------------- + // Utility function for other plugins to cancel OneShot keys + static void cancel(bool with_stickies = false); + + // -------------------------------------------------------------------------- + // Vestigial functions? + void inject(Key key, uint8_t key_state) {} + static bool isModifierActive(Key key) { + return false; + } + + // -------------------------------------------------------------------------- + // Configuration variables (should probably be private) + static uint16_t time_out; + static uint16_t hold_time_out; + static int16_t double_tap_time_out; + + // -------------------------------------------------------------------------- + // Plugin hook functions EventHandlerResult onNameQuery(); + EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); EventHandlerResult beforeReportingState(); EventHandlerResult afterEachCycle(); - EventHandlerResult onKeyswitchEvent(Key &mapped_key, KeyAddr key_addr, uint8_t keyState); - - void inject(Key mapped_key, uint8_t key_state); private: - static constexpr uint8_t ONESHOT_KEY_COUNT = 16; - typedef struct { - bool active: 1; - bool pressed: 1; - bool stickable: 1; - bool sticky: 1; - uint8_t __reserved: 4; - uint8_t position; - } key_state_t; - static key_state_t state_[ONESHOT_KEY_COUNT]; - static uint16_t start_time_; - static Key prev_key_; - static bool should_cancel_; - static bool should_cancel_stickies_; + // -------------------------------------------------------------------------- + // Constants + static constexpr uint8_t oneshot_key_count = 16; + static constexpr uint8_t oneshot_mod_count = 8; + static constexpr uint8_t oneshot_layer_count = oneshot_key_count - oneshot_mod_count; + static constexpr uint16_t stickable_modifiers_mask = uint16_t(uint16_t(-1) >> oneshot_layer_count); + static constexpr uint16_t stickable_layers_mask = uint16_t(uint16_t(-1) << oneshot_mod_count); + static constexpr KeyAddr invalid_key_addr = KeyAddr(KeyAddr::invalid_state); - static void injectNormalKey(uint8_t idx, uint8_t key_state); - static void activateOneShot(uint8_t idx); - static void cancelOneShot(uint8_t idx); + // -------------------------------------------------------------------------- + // State variables + static uint16_t stickable_keys_; - static bool isOneShotKey_(Key key) { - return key.getRaw() >= ranges::OS_FIRST && key.getRaw() <= ranges::OS_LAST; - } - static bool hasTimedOut() { - return Runtime.hasTimeExpired(start_time_, time_out); + static KeyAddrBitfield temp_addrs_; + static KeyAddrBitfield glue_addrs_; + + static uint16_t start_time_; + static KeyAddr prev_key_addr_; + static uint8_t release_countdown_; + + // -------------------------------------------------------------------------- + // Internal utility functions + static bool hasTimedOut(uint16_t ttl) { + return Runtime.hasTimeExpired(start_time_, ttl); } + static uint8_t getOneShotKeyIndex(Key oneshot_key); + static uint8_t getKeyIndex(Key key); + static Key decodeOneShotKey(Key oneshot_key); + + static void pressKey(KeyAddr key_addr, Key oneshot_key); + static void holdKey(KeyAddr key_addr); + static void releaseKey(KeyAddr key_addr); }; -} -} + +} // namespace plugin +} // namespace kaleidoscope extern kaleidoscope::plugin::OneShot OneShot; diff --git a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp index 14ce696d..732c461f 100644 --- a/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp +++ b/plugins/Kaleidoscope-Qukeys/src/kaleidoscope/plugin/Qukeys.cpp @@ -327,11 +327,10 @@ void Qukeys::flushEvent(Key event_key) { // that qukey's primary and alternate `Key` values for use later. We do this // because it's much more efficient than doing that as a separate step. bool Qukeys::isQukey(KeyAddr k) { - // First, look up the value from the keymap. We need to do a full lookup, not - // just looking up the cached value (i.e. `Layer.lookup(k)`), because the - // cached value will be out of date if a layer change happened since the - // keyswitch toggled on. - Key key = Layer.lookupOnActiveLayer(k); + // First, look up the value from the keymap. This value should be + // correct in the cache, even if there's been a layer change since + // the key was pressed. + Key key = Layer.lookup(k); // Next, we check to see if this is a DualUse-type qukey (defined in the keymap) if (isDualUseKey(key)) { diff --git a/src/kaleidoscope/KeyAddrBitfield.h b/src/kaleidoscope/KeyAddrBitfield.h new file mode 100644 index 00000000..cf4cc66c --- /dev/null +++ b/src/kaleidoscope/KeyAddrBitfield.h @@ -0,0 +1,184 @@ +/* Kaleidoscope - Firmware for computer input devices + * Copyright (C) 2020 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 + +#include "kaleidoscope/KeyAddr.h" + + +namespace kaleidoscope { + +// Return the number of `UnitType` units required to store `n` bits. Both `UnitType` & +// `WidthType` should be integer types. `WidthType` is whatever type the parameter `n` is +// stored as, and can be deduced by the compiler, so it's not necessary to declare it +// when calling this function (e.g. `bitfieldSize(n)`). The default `UnitType` +// is `byte` (i.e. `uint8_t`, which is almost always what we want, so most of the time we +// can also drop that template parameter (e.g. `bitfieldSize(n)`). +template +constexpr _WidthType bitfieldSize(_WidthType n) { + return ((n - 1) / (8 * sizeof(_UnitType))) + 1; +} + +// ================================================================================ +// Generic Bitfield class, useful for defining KeyAddrBitfield, and others. +class KeyAddrBitfield { + + public: + + static constexpr uint8_t size = KeyAddr::upper_limit; + static constexpr uint8_t block_size = 8 * sizeof(uint8_t); + static constexpr uint8_t total_blocks = bitfieldSize(size); + + static constexpr uint8_t blockIndex(KeyAddr k) { + return k.toInt() / block_size; + } + static constexpr uint8_t bitIndex(KeyAddr k) { + return k.toInt() % block_size; + } + static constexpr KeyAddr index(uint8_t block_index, uint8_t bit_index) { + uint8_t offset = (block_index * block_size) + bit_index; + return KeyAddr(offset); + } + bool read(KeyAddr k) const { + // assert(k.toInt() < size); + return bitRead(data_[blockIndex(k)], bitIndex(k)); + } + void set(KeyAddr k) { + // assert(k.toInt() < size); + bitSet(data_[blockIndex(k)], bitIndex(k)); + } + void clear(KeyAddr k) { + // assert(k.toInt() < size); + bitClear(data_[blockIndex(k)], bitIndex(k)); + } + void write(KeyAddr k, bool value) { + // assert(k.toInt() < size); + bitWrite(data_[blockIndex(k)], bitIndex(k), value); + } + + // This function returns the number of set bits in the bitfield up to and + // including the bit at index `k`. Two important things to note: it doesn't + // verify that the bit for index `k` is set (the caller must do so first, + // using `read()`), and what is returned is 1-indexed, so the caller will need + // to subtract 1 before using it as an array index (e.g. when doing a `Key` + // lookup for a sparse keymap layer). + uint8_t ordinal(KeyAddr k) const { + // assert(k.toInt() < size); + uint8_t block_index = blockIndex(k); + uint8_t count{0}; + for (uint8_t b{0}; b < block_index; ++b) { + count += __builtin_popcount(data_[b]); + } + uint8_t last_data_unit = data_[block_index]; + last_data_unit &= ~(0xFF << bitIndex(k)); + count += __builtin_popcount(last_data_unit); + return count; + } + + uint8_t &block(uint8_t block_index) { + // assert(block_index < total_blocks); + return data_[block_index]; + } + + private: + + uint8_t data_[total_blocks] = {}; + + + // ---------------------------------------------------------------------------- + // Iterator! + public: + class Iterator; + friend class KeyAddrBitfield::Iterator; + + Iterator begin() { + return Iterator{*this, 0}; + } + Iterator end() { + return Iterator{*this, total_blocks}; + } + + class Iterator { + public: + Iterator(KeyAddrBitfield &bitfield, uint8_t x) + : bitfield_(bitfield), block_index_(x) {} + + bool operator!=(const Iterator &other) { + // First, the test for the end condition (return false when all the blocks have been + // tested): + while (block_index_ < other.block_index_) { + // Get the data for the block at `block_index_` from the bitfield, then shift it + // by the number of bits we've already checked (`bit_index_`): + block_ = bitfield_.data_[block_index_]; + block_ >>= bit_index_; + + // Now we iterate through that block until we either find a bit that is set, or we + // find that there are no more bits set. If (as expected most of the time) no bits + // are set, we do nothing: + while (block_ != 0) { + // If the low (remaining) bit is set, generate an `KeyAddr` object from the + // bitfield coordinates and store it for the dereference operator to return: + if (block_ & 1) { + index_ = KeyAddrBitfield::index(block_index_, bit_index_); + return true; + } + // The low bit wasn't set, so we shift the data block by one and track that + // shift with the bit coordinate (`bit_index_`): + block_ >>= 1; + bit_index_ += 1; + } + + // When we're done checking a block, move on to the next one: + block_index_ += 1; + bit_index_ = 0; + } + return false; + } + + KeyAddr operator*() { + // assert(index_ < size); + return index_; + } + + void operator++() { + ++bit_index_; + } + + private: + KeyAddrBitfield &bitfield_; + uint8_t block_index_; // index of the block + uint8_t bit_index_{0}; // bit index in the block + uint8_t block_; + KeyAddr index_; + + }; // class Iterator { + +} __attribute__((packed)); // class KeyAddrBitfield { + +} // namespace kaleidoscope { + + +// ================================================================================ +// How to use the iterator above: +#if 0 +// To use the KeyAddrBitfield::Iterator, write a loop like the following: +KeyAddrBitfield bitfield; +for (KeyAddr k : bitfield) { + // Here, you'll get a `KeyAddr` object for each bit that is set in `bitfield`. +} +#endif