diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp index 9b5c7c5f..46234785 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.cpp @@ -51,7 +51,6 @@ 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; #ifndef ONESHOT_WITHOUT_METASTICKY KeyAddr OneShot::meta_sticky_key_addr_ {KeyAddr::invalid_state}; @@ -177,30 +176,28 @@ EventHandlerResult OneShot::onNameQuery() { return ::Focus.sendName(F("OneShot")); } -EventHandlerResult OneShot::onKeyswitchEvent( - Key &key, KeyAddr key_addr, uint8_t key_state) { +EventHandlerResult OneShot::onKeyEvent(KeyEvent& event) { - // 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) + // 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 (event.state & INJECTED) return EventHandlerResult::OK; - bool temp = temp_addrs_.read(key_addr); - bool glue = glue_addrs_.read(key_addr); + bool temp = temp_addrs_.read(event.addr); + bool glue = glue_addrs_.read(event.addr); - if (keyToggledOn(key_state)) { + if (keyToggledOn(event.state)) { // Make all held keys sticky if `OneShot_ActiveStickyKey` toggles on. - if (key == OneShot_ActiveStickyKey) { + if (event.key == OneShot_ActiveStickyKey) { // Skip the stickify key itself for (KeyAddr entry_addr : KeyAddr::all()) { - if (entry_addr == key_addr) { + if (entry_addr == event.addr) { continue; } - // Get the entry from the keymap cache + // Get the entry from the keyboard state array Key entry_key = live_keys[entry_addr]; // Skip empty entries if (entry_key == Key_Transparent || entry_key == Key_NoKey) { @@ -210,177 +207,147 @@ EventHandlerResult OneShot::onKeyswitchEvent( temp_addrs_.clear(entry_addr); glue_addrs_.set(entry_addr); } + prev_key_addr_ = event.addr; return EventHandlerResult::OK; } if (!temp && !glue) { - // This key_addr is not in a OneShot state. + // The key is in the "normal" state. The first thing we need to do is + // convert OneShot keys to their equivalent values, and record the fact + // that the key that just toggled on should transition to the "pending" + // state. + bool is_oneshot = false; + if (isOneShotKey(event.key)) { + event.key = decodeOneShotKey(event.key); + is_oneshot = true; + } + #ifndef ONESHOT_WITHOUT_METASTICKY - if (meta_sticky_key_addr_.isValid()) { + bool is_meta_sticky_key_active = meta_sticky_key_addr_.isValid(); + if (is_meta_sticky_key_active) { // If the meta key isn't sticky, release it bool ms_temp = temp_addrs_.read(meta_sticky_key_addr_); bool ms_glue = glue_addrs_.read(meta_sticky_key_addr_); if (ms_temp) { if (ms_glue) { - // ms key is temp one-shot + // The meta key is in the "one-shot" state; release it immediately. releaseKey(meta_sticky_key_addr_); - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; } else { - // ms key is held + // The meta key is in the "pending" state; cancel that, and let it + // deactivate on release. temp_addrs_.clear(meta_sticky_key_addr_); } - } else { - // ms key is sticky } - glue_addrs_.set(key_addr); - //prev_key_addr_ = key_addr; - start_time_ = Runtime.millisAtCycleStart(); - //return EventHandlerResult::OK; - - } else if (key == OneShot_MetaStickyKey) { - meta_sticky_key_addr_ = key_addr; - temp_addrs_.set(key_addr); + glue_addrs_.set(event.addr); + } else if (event.key == OneShot_MetaStickyKey) { + meta_sticky_key_addr_ = event.addr; + temp_addrs_.set(event.addr); + } + if (is_meta_sticky_key_active || (event.key == OneShot_MetaStickyKey)) { + prev_key_addr_ = event.addr; start_time_ = Runtime.millisAtCycleStart(); - } else // NOLINT + return EventHandlerResult::OK; + } #endif - // *INDENT-OFF* - // Because of the preceding #ifdef, indentation gets thrown off for astyle here. - // This is only an independent `if` block if `ONESHOT_WITHOUT_METASTICKY` - // is set (see above); otherwise it's an `else if`. - if (isOneShotKey(key) || - (auto_modifiers_ && isModifier(key)) || - (auto_layers_ && isLayerShift(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); + if (is_oneshot || + (auto_modifiers_ && event.key.isKeyboardModifier()) || + (auto_layers_ && event.key.isLayerShift())) { + temp_addrs_.set(event.addr); + start_time_ = Runtime.millisAtCycleStart(); + } else if (!event.key.isKeyboardModifier() && + !event.key.isLayerShift()) { + // Only trigger release of temporary one-shot keys if the pressed key is + // neither a modifier nor a layer shift. We need the actual release of + // those keys to happen after the current event is finished, however, so + // we trigger it by back-dating the start time, so that the timeout + // check will trigger in the afterEachCycle() hook. + start_time_ -= timeout_; } - // *INDENT-ON* } 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_timeout_ < 0) ? timeout_ : double_tap_timeout_; - - // 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; - } + // temporary one-shot + temp_addrs_.clear(event.addr); + + if (event.addr == prev_key_addr_ && + isStickable(event.key) && + !hasDoubleTapTimedOut()) { + // The same stickable key has been double-tapped within the double-tap + // timeout window. We cancel the second press event, emulating a single + // press-and-hold. This doesn't interfere with `prev_key_addr_`, since + // it's the same key again. + 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); + // A second tap that's not a double-tap cancels the one-shot state + glue_addrs_.clear(event.addr); } } 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); - // Then replace the key toggled on event with a key held event. - holdKey(key_addr); - return EventHandlerResult::EVENT_CONSUMED; - - } 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. + // sticky state + temp_addrs_.clear(event.addr); + glue_addrs_.clear(event.addr); + + } else { /* if (temp && !glue) */ + // A key has been pressed that is in the "pending" one-shot state. Since + // this key should have entered the "temporary" one-shot 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; + // 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_ = event.addr; - } else if (keyToggledOff(key_state)) { + } else { + // Key toggled off 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); + // Any key in the "pending" one-shot 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 execution speed to get a smaller binary. + glue_addrs_.set(event.addr); + // This is an active one-shot 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. return EventHandlerResult::ABORT; #ifndef ONESHOT_WITHOUT_METASTICKY - } else if (key == OneShot_MetaStickyKey) { - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + } else if (event.key == OneShot_MetaStickyKey) { + // Turn off the meta key if it's released in its "normal" state. + meta_sticky_key_addr_ = KeyAddr::none(); #endif } - } 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_timeout_)) { - temp_addrs_.clear(key_addr); - } - } - - 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; } +// ---------------------------------------------------------------------------- +EventHandlerResult OneShot::afterEachCycle() { -// For any active OneShot modifier keys, keep those modifiers active -// in the keyboard HID report. -EventHandlerResult OneShot::beforeReportingState() { - for (KeyAddr key_addr : glue_addrs_) { - holdKey(key_addr); - } - return EventHandlerResult::OK; -} + bool oneshot_expired = hasTimedOut(timeout_); + bool hold_expired = hasTimedOut(hold_timeout_); + bool any_temp_keys = false; + for (KeyAddr key_addr : temp_addrs_) { + any_temp_keys = true; -EventHandlerResult OneShot::afterEachCycle() { - // 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(timeout_)) { - for (KeyAddr key_addr : temp_addrs_) { - if (glue_addrs_.read(key_addr)) { + if (glue_addrs_.read(key_addr)) { + // Release keys in "one-shot" state that have timed out or been cancelled + // by another key press. + if (oneshot_expired) releaseKey(key_addr); - } - temp_addrs_.clear(key_addr); + } else { + // Cancel "pending" state of keys held longer than the hold timeout. + if (hold_expired) + temp_addrs_.clear(key_addr); } } - // 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; + + // Keep the start time from getting stale; if there are no keys waiting for a + // timeout, it's safe to advance the timer to the current time. + if (!any_temp_keys) { + start_time_ = Runtime.millisAtCycleStart(); + } // Temporary fix for deprecated variables #ifndef NDEPRECATED @@ -449,21 +416,26 @@ void OneShot::pressKey(KeyAddr key_addr, Key key) { prev_key_addr_ = key_addr; start_time_ = Runtime.millisAtCycleStart(); temp_addrs_.set(key_addr); - handleKeyswitchEvent(key, key_addr, IS_PRESSED | INJECTED); + KeyEvent event{key_addr, IS_PRESSED | INJECTED, key}; + Runtime.handleKeyEvent(event); } void OneShot::holdKey(KeyAddr key_addr) { - handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | IS_PRESSED | INJECTED); + KeyEvent event{key_addr, WAS_PRESSED | IS_PRESSED | INJECTED}; + Runtime.handleKeyEvent(event); } void OneShot::releaseKey(KeyAddr key_addr) { glue_addrs_.clear(key_addr); temp_addrs_.clear(key_addr); +#ifndef ONESHOT_WITHOUT_METASTICKY if (live_keys[key_addr] == OneShot_MetaStickyKey) - meta_sticky_key_addr_ = KeyAddr{KeyAddr::invalid_state}; + meta_sticky_key_addr_ = KeyAddr::none(); +#endif - handleKeyswitchEvent(Key_NoKey, key_addr, WAS_PRESSED | INJECTED); + KeyEvent event{key_addr, WAS_PRESSED | INJECTED}; + Runtime.handleKeyEvent(event); } // ------------------------------------------------------------------------------ diff --git a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h index afad5c3a..5ad18b1d 100644 --- a/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h +++ b/plugins/Kaleidoscope-OneShot/src/kaleidoscope/plugin/OneShot.h @@ -208,19 +208,20 @@ class OneShot : public kaleidoscope::Plugin { // -------------------------------------------------------------------------- // Configuration variables (should probably be private) +#ifndef NDEPRECATED DEPRECATED(ONESHOT_TIMEOUT) static uint16_t time_out; DEPRECATED(ONESHOT_HOLD_TIMEOUT) static uint16_t hold_time_out; DEPRECATED(ONESHOT_DOUBLE_TAP_TIMEOUT) static int16_t double_tap_time_out; +#endif // -------------------------------------------------------------------------- // Plugin hook functions EventHandlerResult onNameQuery(); - EventHandlerResult onKeyswitchEvent(Key &key, KeyAddr key_addr, uint8_t key_state); - EventHandlerResult beforeReportingState(); + EventHandlerResult onKeyEvent(KeyEvent &event); EventHandlerResult afterEachCycle(); private: @@ -251,7 +252,6 @@ class OneShot : public kaleidoscope::Plugin { static uint16_t start_time_; static KeyAddr prev_key_addr_; - static uint8_t release_countdown_; #ifndef ONESHOT_WITHOUT_METASTICKY static KeyAddr meta_sticky_key_addr_; @@ -262,6 +262,11 @@ class OneShot : public kaleidoscope::Plugin { static bool hasTimedOut(uint16_t ttl) { return Runtime.hasTimeExpired(start_time_, ttl); } + static bool hasDoubleTapTimedOut() { + // Derive the true double-tap timeout value if we're using the default. + uint16_t dtto = (double_tap_timeout_ < 0) ? timeout_ : double_tap_timeout_; + return hasTimedOut(dtto); + } static uint8_t getOneShotKeyIndex(Key oneshot_key); static uint8_t getKeyIndex(Key key); static Key decodeOneShotKey(Key oneshot_key);