mirror of
https://github.com/qmk/qmk_firmware.git
synced 2025-06-08 09:22:49 +00:00
[Bug][Core] Fix for Flow Tap: fix handling of distinct taps and timer updates. (#25175)
* Flow Tap bug fix. As reported by @amarz45 and @mwpardue, there is a bug where if two tap-hold keys are pressed in distinct taps back to back, then Flow Tap is not applied on the second tap-hold key, but it should be. In a related bug reported by @NikGovorov, if a tap-hold key is held followed by a tap of a tap-hold key, then Flow Tap updates its timer on the release of the held tap-hold key, but it should be ignored. The problem common to both these bugs is that I incorrectly assumed `tapping_key` is cleared to noevent once it is released, when actually `tapping_key` is still maintained for `TAPPING_TERM` ms after release (for Quick Tap). This commit fixes that. Thanks to @amarz45, @mwpardue, and @NikGovorov for reporting! Details: * Logic for converting the current tap-hold event to a tap is extracted to `flow_tap_key_if_within_term()`, which is now invoked also in the post-release "interfered with other tap key" case. This fixes the distinct taps bug. * The Flow Tap timer is now updated at the beginning of each call to `process_record()`, provided that there is no unsettled tap-hold key at that time and that the record is not for a mod or layer switch key. By moving this update logic to `process_record()`, it is conceptually simpler and more robust. * Unit tests extended to cover the reported scenarios. * Fix formatting. * Revision to fix @NikGovorov's scenario. The issue is that when another key is pressed while a layer-tap hasn't been settled yet, the `prev_keycode` remembers the keycode from before the layer switched. This can then enable Flow Tap for the following key when it shouldn't, or vice versa. Thanks to @NikGovorov for reporting! This commit revises Flow Tap in the following ways: * The previous key and timer are both updated from `process_record()`. This is slightly later in the sequence of processing than before, and by this point, a just-settled layer-tap should have taken effect so that the keycode from the correct layer is remembered. * The Flow Tap previous key and timer are updated now also on key release events, except for releases of modifiers and held layer switches. * The Flow Tap previous key and timer are now updated together, for simplicity. This makes the logic easier to think about. * A few additional unit tests, including @NikGovorov's scenario as "layer_tap_ignored_with_disabled_key_complex."
This commit is contained in:
parent
b5f8f4d6a2
commit
73e2ef486a
@ -281,6 +281,9 @@ void process_record(keyrecord_t *record) {
|
|||||||
if (IS_NOEVENT(record->event)) {
|
if (IS_NOEVENT(record->event)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
#ifdef FLOW_TAP_TERM
|
||||||
|
flow_tap_update_last_event(record);
|
||||||
|
#endif // FLOW_TAP_TERM
|
||||||
|
|
||||||
if (!process_record_quantum(record)) {
|
if (!process_record_quantum(record)) {
|
||||||
#ifndef NO_ACTION_ONESHOT
|
#ifndef NO_ACTION_ONESHOT
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "action_tapping.h"
|
#include "action_tapping.h"
|
||||||
#include "action_util.h"
|
#include "action_util.h"
|
||||||
#include "keycode.h"
|
#include "keycode.h"
|
||||||
|
#include "quantum_keycodes.h"
|
||||||
#include "timer.h"
|
#include "timer.h"
|
||||||
|
|
||||||
#ifndef NO_ACTION_TAPPING
|
#ifndef NO_ACTION_TAPPING
|
||||||
@ -102,10 +103,10 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco
|
|||||||
# endif
|
# endif
|
||||||
|
|
||||||
# if defined(FLOW_TAP_TERM)
|
# if defined(FLOW_TAP_TERM)
|
||||||
static uint32_t last_input = 0;
|
static uint32_t flow_tap_prev_time = 0;
|
||||||
static uint16_t prev_keycode = KC_NO;
|
static uint16_t flow_tap_prev_keycode = KC_NO;
|
||||||
|
|
||||||
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
|
static bool flow_tap_key_if_within_term(keyrecord_t *record);
|
||||||
# endif // defined(FLOW_TAP_TERM)
|
# endif // defined(FLOW_TAP_TERM)
|
||||||
|
|
||||||
static keyrecord_t tapping_key = {};
|
static keyrecord_t tapping_key = {};
|
||||||
@ -157,19 +158,6 @@ void action_tapping_process(keyrecord_t record) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (IS_EVENT(record.event)) {
|
if (IS_EVENT(record.event)) {
|
||||||
# if defined(FLOW_TAP_TERM)
|
|
||||||
const uint16_t keycode = get_record_keycode(&record, false);
|
|
||||||
// Track the previous key press.
|
|
||||||
if (record.event.pressed) {
|
|
||||||
prev_keycode = keycode;
|
|
||||||
}
|
|
||||||
// If there is no unsettled tap-hold key, update last input time. Ignore
|
|
||||||
// mod keys in this update to allow for chording multiple mods for
|
|
||||||
// hotkeys like "Ctrl+Shift+arrow".
|
|
||||||
if (IS_NOEVENT(tapping_key.event) && !IS_MODIFIER_KEYCODE(keycode)) {
|
|
||||||
last_input = timer_read32();
|
|
||||||
}
|
|
||||||
# endif // defined(FLOW_TAP_TERM)
|
|
||||||
ac_dprintf("\n");
|
ac_dprintf("\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -252,23 +240,9 @@ bool process_tapping(keyrecord_t *keyp) {
|
|||||||
// into the "pressed" tapping key state
|
// into the "pressed" tapping key state
|
||||||
|
|
||||||
# if defined(FLOW_TAP_TERM)
|
# if defined(FLOW_TAP_TERM)
|
||||||
const uint16_t keycode = get_record_keycode(keyp, false);
|
if (flow_tap_key_if_within_term(keyp)) {
|
||||||
if (is_mt_or_lt(keycode)) {
|
|
||||||
const uint32_t idle_time = timer_elapsed32(last_input);
|
|
||||||
uint16_t term = get_flow_tap_term(keycode, keyp, prev_keycode);
|
|
||||||
if (term > 500) {
|
|
||||||
term = 500;
|
|
||||||
}
|
|
||||||
if (idle_time < 500 && idle_time < term) {
|
|
||||||
debug_event(keyp->event);
|
|
||||||
ac_dprintf(" within flow tap term (%u < %u) considered a tap\n", (int16_t)idle_time, term);
|
|
||||||
keyp->tap.count = 1;
|
|
||||||
registered_taps_add(keyp->event.key);
|
|
||||||
debug_registered_taps();
|
|
||||||
process_record(keyp);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
# endif // defined(FLOW_TAP_TERM)
|
# endif // defined(FLOW_TAP_TERM)
|
||||||
|
|
||||||
ac_dprintf("Tapping: Start(Press tap key).\n");
|
ac_dprintf("Tapping: Start(Press tap key).\n");
|
||||||
@ -582,6 +556,13 @@ bool process_tapping(keyrecord_t *keyp) {
|
|||||||
return true;
|
return true;
|
||||||
} else if (is_tap_record(keyp)) {
|
} else if (is_tap_record(keyp)) {
|
||||||
// Sequential tap can be interfered with other tap key.
|
// Sequential tap can be interfered with other tap key.
|
||||||
|
# if defined(FLOW_TAP_TERM)
|
||||||
|
if (flow_tap_key_if_within_term(keyp)) {
|
||||||
|
tapping_key = (keyrecord_t){0};
|
||||||
|
debug_tapping_key();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
# endif // defined(FLOW_TAP_TERM)
|
||||||
ac_dprintf("Tapping: Start with interfering other tap.\n");
|
ac_dprintf("Tapping: Start with interfering other tap.\n");
|
||||||
tapping_key = *keyp;
|
tapping_key = *keyp;
|
||||||
waiting_buffer_scan_tap();
|
waiting_buffer_scan_tap();
|
||||||
@ -809,6 +790,66 @@ static void waiting_buffer_process_regular(void) {
|
|||||||
# endif // CHORDAL_HOLD
|
# endif // CHORDAL_HOLD
|
||||||
|
|
||||||
# ifdef FLOW_TAP_TERM
|
# ifdef FLOW_TAP_TERM
|
||||||
|
void flow_tap_update_last_event(keyrecord_t *record) {
|
||||||
|
// Don't update while a tap-hold key is unsettled.
|
||||||
|
if (waiting_buffer_tail != waiting_buffer_head || (tapping_key.event.pressed && tapping_key.tap.count == 0)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint16_t keycode = get_record_keycode(record, false);
|
||||||
|
// Ignore releases of modifiers and held layer switches.
|
||||||
|
if (!record->event.pressed) {
|
||||||
|
switch (keycode) {
|
||||||
|
case MODIFIER_KEYCODE_RANGE:
|
||||||
|
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
|
||||||
|
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
|
||||||
|
# ifndef NO_ACTION_ONESHOT // Ignore one-shot keys.
|
||||||
|
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
|
||||||
|
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
|
||||||
|
# endif // NO_ACTION_ONESHOT
|
||||||
|
# ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys.
|
||||||
|
case QK_TRI_LAYER_LOWER:
|
||||||
|
case QK_TRI_LAYER_UPPER:
|
||||||
|
# endif // TRI_LAYER_ENABLE
|
||||||
|
return;
|
||||||
|
case QK_MODS ... QK_MODS_MAX:
|
||||||
|
if (QK_MODS_GET_BASIC_KEYCODE(keycode) == KC_NO) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
|
||||||
|
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
|
||||||
|
if (record->tap.count == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flow_tap_prev_keycode = keycode;
|
||||||
|
flow_tap_prev_time = timer_read32();
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool flow_tap_key_if_within_term(keyrecord_t *record) {
|
||||||
|
const uint16_t keycode = get_record_keycode(record, false);
|
||||||
|
if (is_mt_or_lt(keycode)) {
|
||||||
|
const uint32_t idle_time = timer_elapsed32(flow_tap_prev_time);
|
||||||
|
uint16_t term = get_flow_tap_term(keycode, record, flow_tap_prev_keycode);
|
||||||
|
if (term > 500) {
|
||||||
|
term = 500;
|
||||||
|
}
|
||||||
|
if (idle_time < 500 && idle_time < term) {
|
||||||
|
debug_event(record->event);
|
||||||
|
ac_dprintf(" within flow tap term (%u < %u) considered a tap\n", (int16_t)idle_time, term);
|
||||||
|
record->tap.count = 1;
|
||||||
|
registered_taps_add(record->event.key);
|
||||||
|
debug_registered_taps();
|
||||||
|
process_record(record);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// By default, enable Flow Tap for the keys in the main alphas area and Space.
|
// By default, enable Flow Tap for the keys in the main alphas area and Space.
|
||||||
// This should work reasonably even if the layout is remapped on the host to an
|
// This should work reasonably even if the layout is remapped on the host to an
|
||||||
// alt layout or international layout (e.g. Dvorak or AZERTY), where these same
|
// alt layout or international layout (e.g. Dvorak or AZERTY), where these same
|
||||||
|
@ -166,6 +166,9 @@ bool is_flow_tap_key(uint16_t keycode);
|
|||||||
* @return Time in milliseconds.
|
* @return Time in milliseconds.
|
||||||
*/
|
*/
|
||||||
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
|
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
|
||||||
|
|
||||||
|
/** Updates the Flow Tap last key and timer. */
|
||||||
|
void flow_tap_update_last_event(keyrecord_t *record);
|
||||||
#endif // FLOW_TAP_TERM
|
#endif // FLOW_TAP_TERM
|
||||||
|
|
||||||
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
|
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
|
||||||
|
@ -20,3 +20,4 @@
|
|||||||
#include "test_common.h"
|
#include "test_common.h"
|
||||||
|
|
||||||
#define FLOW_TAP_TERM 150
|
#define FLOW_TAP_TERM 150
|
||||||
|
#define PERMISSIVE_HOLD
|
||||||
|
@ -25,7 +25,169 @@ using testing::InSequence;
|
|||||||
|
|
||||||
class FlowTapTest : public TestFixture {};
|
class FlowTapTest : public TestFixture {};
|
||||||
|
|
||||||
TEST_F(FlowTapTest, short_flow_tap_settled_as_tapped) {
|
// Test an input of quick distinct taps. All should be settled as tapped.
|
||||||
|
TEST_F(FlowTapTest, distinct_taps) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto regular_key = KeymapKey(0, 0, 0, KC_A);
|
||||||
|
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
|
||||||
|
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
|
||||||
|
auto mod_tap_key3 = KeymapKey(0, 3, 0, ALT_T(KC_D));
|
||||||
|
|
||||||
|
set_keymap({regular_key, mod_tap_key1, mod_tap_key2, mod_tap_key3});
|
||||||
|
|
||||||
|
// Tap regular key.
|
||||||
|
EXPECT_REPORT(driver, (KC_A));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
tap_key(regular_key, FLOW_TAP_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap mod-tap 1.
|
||||||
|
EXPECT_REPORT(driver, (KC_B));
|
||||||
|
mod_tap_key1.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
mod_tap_key1.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap mod-tap 2.
|
||||||
|
EXPECT_REPORT(driver, (KC_C));
|
||||||
|
mod_tap_key2.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
mod_tap_key2.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap mod-tap 3.
|
||||||
|
EXPECT_REPORT(driver, (KC_D));
|
||||||
|
mod_tap_key3.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
mod_tap_key3.release();
|
||||||
|
idle_for(FLOW_TAP_TERM + 1); // Pause between taps.
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap mod-tap 1.
|
||||||
|
EXPECT_NO_REPORT(driver);
|
||||||
|
mod_tap_key1.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_B));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
mod_tap_key1.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap mod-tap 2.
|
||||||
|
EXPECT_REPORT(driver, (KC_C));
|
||||||
|
mod_tap_key2.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
mod_tap_key2.release();
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// By default, Flow Tap is disabled when mods other than Shift and AltGr are on.
|
||||||
|
TEST_F(FlowTapTest, hotkey_taps) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto ctrl_key = KeymapKey(0, 0, 0, KC_LCTL);
|
||||||
|
auto shft_key = KeymapKey(0, 1, 0, KC_LSFT);
|
||||||
|
auto alt_key = KeymapKey(0, 2, 0, KC_LALT);
|
||||||
|
auto gui_key = KeymapKey(0, 3, 0, KC_LGUI);
|
||||||
|
auto regular_key = KeymapKey(0, 4, 0, KC_A);
|
||||||
|
auto mod_tap_key = KeymapKey(0, 5, 0, RCTL_T(KC_B));
|
||||||
|
|
||||||
|
set_keymap({ctrl_key, shft_key, alt_key, gui_key, regular_key, mod_tap_key});
|
||||||
|
|
||||||
|
for (KeymapKey* mod_key : {&ctrl_key, &alt_key, &gui_key}) {
|
||||||
|
// Hold mod key.
|
||||||
|
EXPECT_REPORT(driver, (mod_key->code));
|
||||||
|
mod_key->press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
// Tap regular key.
|
||||||
|
EXPECT_REPORT(driver, (mod_key->code, KC_A));
|
||||||
|
regular_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (mod_key->code));
|
||||||
|
regular_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Press mod-tap, where Flow Tap is disabled due to the held mod.
|
||||||
|
EXPECT_REPORT(driver, (mod_key->code, KC_RCTL));
|
||||||
|
mod_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Release mod-tap.
|
||||||
|
EXPECT_REPORT(driver, (mod_key->code));
|
||||||
|
mod_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
// Release mod key.
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
mod_key->release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hold Shift key.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT));
|
||||||
|
shft_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
// Tap regular key.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
|
||||||
|
regular_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT));
|
||||||
|
regular_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Press mod-tap, where Flow Tap applies to settle as tapped.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
|
||||||
|
mod_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Release mod-tap.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT));
|
||||||
|
mod_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
|
||||||
|
// Release Shift key.
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
shft_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test input with two mod-taps in a rolled press quickly after a regular key.
|
||||||
|
TEST_F(FlowTapTest, rolled_press) {
|
||||||
TestDriver driver;
|
TestDriver driver;
|
||||||
InSequence s;
|
InSequence s;
|
||||||
auto regular_key = KeymapKey(0, 0, 0, KC_A);
|
auto regular_key = KeymapKey(0, 0, 0, KC_A);
|
||||||
@ -152,6 +314,53 @@ TEST_F(FlowTapTest, holding_multiple_mod_taps) {
|
|||||||
VERIFY_AND_CLEAR(driver);
|
VERIFY_AND_CLEAR(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(FlowTapTest, holding_mod_tap_with_regular_mod) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto regular_key = KeymapKey(0, 0, 0, KC_A);
|
||||||
|
auto mod_key = KeymapKey(0, 1, 0, KC_LSFT);
|
||||||
|
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C));
|
||||||
|
|
||||||
|
set_keymap({regular_key, mod_key, mod_tap_key});
|
||||||
|
|
||||||
|
// Tap regular key.
|
||||||
|
EXPECT_REPORT(driver, (KC_A));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
tap_key(regular_key);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_NO_REPORT(driver);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Press mod and mod-tap keys.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT));
|
||||||
|
mod_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
mod_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM - 5); // Hold almost until tapping term.
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Press regular key.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL, KC_A));
|
||||||
|
regular_key.press();
|
||||||
|
idle_for(10);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Release keys.
|
||||||
|
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
|
||||||
|
EXPECT_REPORT(driver, (KC_LCTL));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
regular_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
mod_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
mod_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(FlowTapTest, layer_tap_key) {
|
TEST_F(FlowTapTest, layer_tap_key) {
|
||||||
TestDriver driver;
|
TestDriver driver;
|
||||||
InSequence s;
|
InSequence s;
|
||||||
@ -216,6 +425,125 @@ TEST_F(FlowTapTest, layer_tap_key) {
|
|||||||
VERIFY_AND_CLEAR(driver);
|
VERIFY_AND_CLEAR(driver);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(FlowTapTest, layer_tap_ignored_with_disabled_key) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto no_key = KeymapKey(0, 0, 0, KC_NO);
|
||||||
|
auto regular_key = KeymapKey(1, 0, 0, KC_ESC);
|
||||||
|
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
|
||||||
|
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
|
||||||
|
|
||||||
|
set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_ESC));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
layer_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
tap_key(regular_key);
|
||||||
|
layer_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_LCTL));
|
||||||
|
mod_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
mod_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FlowTapTest, layer_tap_ignored_with_disabled_key_complex) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto regular_key1 = KeymapKey(0, 0, 0, KC_Q);
|
||||||
|
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_SPC));
|
||||||
|
auto mod_tap_key1 = KeymapKey(0, 2, 0, CTL_T(KC_T));
|
||||||
|
// Place RALT_T(KC_I), where Flow Tap is enabled, in the same position on
|
||||||
|
// layer 0 as KC_RGHT, where Flow Tap is disabled. This tests that Flow Tap
|
||||||
|
// tracks the keycode from the correct layer.
|
||||||
|
auto mod_tap_key2 = KeymapKey(0, 3, 0, RALT_T(KC_I));
|
||||||
|
auto regular_key2 = KeymapKey(1, 3, 0, KC_RGHT);
|
||||||
|
|
||||||
|
set_keymap({regular_key1, layer_tap_key, mod_tap_key1, mod_tap_key2, regular_key2});
|
||||||
|
|
||||||
|
// Tap regular key 1.
|
||||||
|
EXPECT_REPORT(driver, (KC_Q));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
tap_key(regular_key1);
|
||||||
|
idle_for(FLOW_TAP_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Hold layer-tap key.
|
||||||
|
EXPECT_NO_REPORT(driver);
|
||||||
|
layer_tap_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
// idle_for(TAPPING_TERM + 1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Tap regular key 2.
|
||||||
|
EXPECT_REPORT(driver, (KC_RGHT));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
tap_key(regular_key2);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Release layer-tap key.
|
||||||
|
EXPECT_NO_REPORT(driver);
|
||||||
|
layer_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
// Quickly hold mod-tap key 1.
|
||||||
|
EXPECT_NO_REPORT(driver);
|
||||||
|
mod_tap_key1.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_LCTL));
|
||||||
|
EXPECT_REPORT(driver, (KC_LCTL, KC_Q));
|
||||||
|
EXPECT_REPORT(driver, (KC_LCTL));
|
||||||
|
tap_key(regular_key1);
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
mod_tap_key1.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(FlowTapTest, layer_tap_ignored_with_enabled_key) {
|
||||||
|
TestDriver driver;
|
||||||
|
InSequence s;
|
||||||
|
auto no_key = KeymapKey(0, 0, 0, KC_NO);
|
||||||
|
auto regular_key = KeymapKey(1, 0, 0, KC_C);
|
||||||
|
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
|
||||||
|
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
|
||||||
|
|
||||||
|
set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_C));
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
layer_tap_key.press();
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
tap_key(regular_key);
|
||||||
|
layer_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_REPORT(driver, (KC_B));
|
||||||
|
mod_tap_key.press();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
|
||||||
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
|
idle_for(TAPPING_TERM + 1);
|
||||||
|
mod_tap_key.release();
|
||||||
|
run_one_scan_loop();
|
||||||
|
VERIFY_AND_CLEAR(driver);
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(FlowTapTest, combo_key) {
|
TEST_F(FlowTapTest, combo_key) {
|
||||||
TestDriver driver;
|
TestDriver driver;
|
||||||
InSequence s;
|
InSequence s;
|
||||||
@ -275,6 +603,7 @@ TEST_F(FlowTapTest, oneshot_mod_key) {
|
|||||||
// Nested press of OSM and regular keys.
|
// Nested press of OSM and regular keys.
|
||||||
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
|
||||||
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
|
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
|
||||||
|
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
|
||||||
EXPECT_EMPTY_REPORT(driver);
|
EXPECT_EMPTY_REPORT(driver);
|
||||||
osm_key.press();
|
osm_key.press();
|
||||||
run_one_scan_loop();
|
run_one_scan_loop();
|
||||||
|
Loading…
Reference in New Issue
Block a user