diff --git a/docs/NEWS.md b/docs/NEWS.md index 858b02a8..3326b470 100644 --- a/docs/NEWS.md +++ b/docs/NEWS.md @@ -12,6 +12,17 @@ See [UPGRADING.md](UPGRADING.md) for more detailed instructions about upgrading ## New features +### Better protection against unintended modifiers from Qukeys + +Qukeys has two new configuration options for preventing unintended modifiers in +the output, particularly when typing fast: + +- `Qukeys.setMinimumHoldTime(ms)` sets the minimum duration of a qukey press + required for it to be eligible to take on its alternate (modifier) value. +- `Qukeys.setMinimumPriorInterval(ms)` sets the minimum interval between the + previous non-modifier key press and the press of the qukey required to make + the qukey eligible to take on its alternate (modifier) value. + ### KALEIDOSCOPE_API_VERSION bump `KALEIDOSCOPE_API_VERSION` has been bumped to **2** due to the plugin API diff --git a/docs/plugins/Qukeys.md b/docs/plugins/Qukeys.md index ca9721f7..706b6b1e 100644 --- a/docs/plugins/Qukeys.md +++ b/docs/plugins/Qukeys.md @@ -113,6 +113,15 @@ likely to generate errors and out-of-order events. > > Defaults to `50` (milliseconds). +### `.setMinimumPriorInterval(min_interval)` + +> Sets the minimum amount of time (in milliseconds) that must pass between the +> press event of a prior (non-modifier) key and the press of a qukey required to +> make that qukey eligible to take on it's alternate state. This is another +> measure that can be taken to prevent unintended modifiers while typing fast. +> +> Defaults to `75` (milliseconds). + ### `.activate()` ### `.deactivate()` ### `.toggle()` diff --git a/examples/Keystrokes/Qukeys/Qukeys.ino b/examples/Keystrokes/Qukeys/Qukeys.ino index 030aff66..5cc8c8cc 100644 --- a/examples/Keystrokes/Qukeys/Qukeys.ino +++ b/examples/Keystrokes/Qukeys/Qukeys.ino @@ -73,6 +73,7 @@ void setup() { Qukeys.setHoldTimeout(1000); Qukeys.setOverlapThreshold(50); Qukeys.setMinimumHoldTime(100); + Qukeys.setMinimumPriorInterval(80); Kaleidoscope.setup(); } diff --git a/src/kaleidoscope/plugin/Qukeys.cpp b/src/kaleidoscope/plugin/Qukeys.cpp index ffa9df41..f6ec29af 100644 --- a/src/kaleidoscope/plugin/Qukeys.cpp +++ b/src/kaleidoscope/plugin/Qukeys.cpp @@ -126,6 +126,14 @@ EventHandlerResult Qukeys::beforeReportingState() { queue_head_.primary_key : queue_head_.alternate_key; flushEvent(event_key); } + + // Last, if there hasn't been a keypress in a while, update the prior keypress + // timestamp to avoid integer overflow issues: + if (Runtime.hasTimeExpired(prior_keypress_timestamp_, + minimum_prior_interval_)) { + prior_keypress_timestamp_ = + Runtime.millisAtCycleStart() - (minimum_prior_interval_ + 1); + } return EventHandlerResult::OK; } @@ -177,6 +185,14 @@ bool Qukeys::processQueue() { // key, so we don't need to do it repeatedly later. bool qukey_is_spacecadet = isModifierKey(queue_head_.primary_key); + // If the qukey press followed a non-modifier key too closely, it's not + // eligible to take on its alternate value unless it's a SpaceCadet-type key. + if (!Runtime.hasTimeExpired(prior_keypress_timestamp_, + minimum_prior_interval_) && + !qukey_is_spacecadet) { + flushEvent(queue_head_.primary_key); + } + // Now we search the queue for events that will let us decide if the qukey // should be flushed (and if so, in which of its two states). We start with // the second event in the queue (index 1). @@ -275,6 +291,11 @@ void Qukeys::flushEvent(Key event_key) { KeyAddr queue_head_addr = event_queue_.addr(0); uint8_t keyswitch_state = event_queue_.isRelease(0) ? WAS_PRESSED : IS_PRESSED; + // If the flushed event is a keypress of a non-modifier, record its timestamp: + if (!event_queue_.isRelease(0) && !isModifierKey(event_key)) { + prior_keypress_timestamp_ = event_queue_.timestamp(0); + } + // Remove the head event from the queue: event_queue_.shift(); // This ensures that the flushed event will be ignored by the event handler hook: diff --git a/src/kaleidoscope/plugin/Qukeys.h b/src/kaleidoscope/plugin/Qukeys.h index 30995912..e4b5d320 100644 --- a/src/kaleidoscope/plugin/Qukeys.h +++ b/src/kaleidoscope/plugin/Qukeys.h @@ -130,6 +130,12 @@ class Qukeys : public kaleidoscope::Plugin { minimum_hold_time_ = min_hold_time; } + // Set the minimum interval between the previous keypress and the qukey press + // to make the qukey eligible to become its alternate keycode. + void setMinimumPriorInterval(uint8_t min_interval) { + minimum_prior_interval_ = min_interval; + } + // Function for defining the array of qukeys data (in PROGMEM). It's a // template function that takes as its sole argument an array reference of // size `_qukeys_count`, so there's no need to use `sizeof` to calculate the @@ -190,6 +196,14 @@ class Qukeys : public kaleidoscope::Plugin { // for very fast typists). uint8_t minimum_hold_time_{50}; + // The minimum interval in milliseconds between the previous keypress and the + // press of a qukey required to make the qukey eligible to take on its + // alternate value. + uint8_t minimum_prior_interval_{75}; + + // Timestamp of the keypress event immediately prior to the queue head event. + uint16_t prior_keypress_timestamp_{0}; + // This is a guard against re-processing events when qukeys flushes them from // its event queue. We can't just use an "injected" key state flag, because // that would cause other plugins to also ignore the event.