aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--quantum/action.c3
-rw-r--r--quantum/action_tapping.c105
-rw-r--r--quantum/action_tapping.h3
-rw-r--r--tests/tap_hold_configurations/flow_tap/config.h1
-rw-r--r--tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp331
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();