diff options
-rw-r--r-- | quantum/action.c | 3 | ||||
-rw-r--r-- | quantum/action_tapping.c | 105 | ||||
-rw-r--r-- | quantum/action_tapping.h | 3 | ||||
-rw-r--r-- | tests/tap_hold_configurations/flow_tap/config.h | 1 | ||||
-rw-r--r-- | tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp | 331 |
5 files changed, 410 insertions, 33 deletions
diff --git a/quantum/action.c b/quantum/action.c index eb0dbc7022..dd82c9ec99 100644 --- a/quantum/action.c +++ b/quantum/action.c @@ -281,6 +281,9 @@ void process_record(keyrecord_t *record) { if (IS_NOEVENT(record->event)) { return; } +#ifdef FLOW_TAP_TERM + flow_tap_update_last_event(record); +#endif // FLOW_TAP_TERM if (!process_record_quantum(record)) { #ifndef NO_ACTION_ONESHOT diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 312c639169..3e391d1526 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -6,6 +6,7 @@ #include "action_tapping.h" #include "action_util.h" #include "keycode.h" +#include "quantum_keycodes.h" #include "timer.h" #ifndef NO_ACTION_TAPPING @@ -102,10 +103,10 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco # endif # if defined(FLOW_TAP_TERM) -static uint32_t last_input = 0; -static uint16_t prev_keycode = KC_NO; +static uint32_t flow_tap_prev_time = 0; +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) static keyrecord_t tapping_key = {}; @@ -157,19 +158,6 @@ void action_tapping_process(keyrecord_t record) { } } 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"); } } @@ -252,22 +240,8 @@ bool process_tapping(keyrecord_t *keyp) { // into the "pressed" tapping key state # if defined(FLOW_TAP_TERM) - const uint16_t keycode = get_record_keycode(keyp, false); - 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; - } + if (flow_tap_key_if_within_term(keyp)) { + return true; } # endif // defined(FLOW_TAP_TERM) @@ -582,6 +556,13 @@ bool process_tapping(keyrecord_t *keyp) { return true; } else if (is_tap_record(keyp)) { // 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"); tapping_key = *keyp; waiting_buffer_scan_tap(); @@ -809,6 +790,66 @@ static void waiting_buffer_process_regular(void) { # endif // CHORDAL_HOLD # 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. // 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 diff --git a/quantum/action_tapping.h b/quantum/action_tapping.h index 2af000ad73..0cf4aa1200 100644 --- a/quantum/action_tapping.h +++ b/quantum/action_tapping.h @@ -166,6 +166,9 @@ bool is_flow_tap_key(uint16_t keycode); * @return Time in milliseconds. */ 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 #ifdef DYNAMIC_TAPPING_TERM_ENABLE diff --git a/tests/tap_hold_configurations/flow_tap/config.h b/tests/tap_hold_configurations/flow_tap/config.h index a17d488214..d6f385d8d4 100644 --- a/tests/tap_hold_configurations/flow_tap/config.h +++ b/tests/tap_hold_configurations/flow_tap/config.h @@ -20,3 +20,4 @@ #include "test_common.h" #define FLOW_TAP_TERM 150 +#define PERMISSIVE_HOLD diff --git a/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp b/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp index 7816fcb6da..a4233f0d57 100644 --- a/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp +++ b/tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp @@ -25,7 +25,169 @@ using testing::InSequence; 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; InSequence s; auto regular_key = KeymapKey(0, 0, 0, KC_A); @@ -152,6 +314,53 @@ TEST_F(FlowTapTest, holding_multiple_mod_taps) { 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) { TestDriver driver; InSequence s; @@ -216,6 +425,125 @@ TEST_F(FlowTapTest, layer_tap_key) { 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) { TestDriver driver; InSequence s; @@ -275,6 +603,7 @@ TEST_F(FlowTapTest, oneshot_mod_key) { // Nested press of OSM and regular keys. EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); EXPECT_EMPTY_REPORT(driver); osm_key.press(); run_one_scan_loop(); |