From ca0c12847a97a62f887fd4625673395104a7257b Mon Sep 17 00:00:00 2001 From: Sergey Vlasov Date: Mon, 3 Oct 2022 12:48:16 +0300 Subject: Fix layer switching from tap dances by redoing the keymap lookup (#17935) --- quantum/process_keycode/process_tap_dance.c | 12 +- quantum/process_keycode/process_tap_dance.h | 2 +- quantum/quantum.c | 6 +- tests/tap_dance/tap_dance_layers/config.h | 6 + tests/tap_dance/tap_dance_layers/tap_dance_defs.c | 97 +++ tests/tap_dance/tap_dance_layers/tap_dance_defs.h | 29 + tests/tap_dance/tap_dance_layers/test.mk | 10 + .../tap_dance_layers/test_tap_dance_layers.cpp | 717 +++++++++++++++++++++ 8 files changed, 874 insertions(+), 5 deletions(-) create mode 100644 tests/tap_dance/tap_dance_layers/config.h create mode 100644 tests/tap_dance/tap_dance_layers/tap_dance_defs.c create mode 100644 tests/tap_dance/tap_dance_layers/tap_dance_defs.h create mode 100644 tests/tap_dance/tap_dance_layers/test.mk create mode 100644 tests/tap_dance/tap_dance_layers/test_tap_dance_layers.cpp diff --git a/quantum/process_keycode/process_tap_dance.c b/quantum/process_keycode/process_tap_dance.c index 3270a1b000..6e8e596673 100644 --- a/quantum/process_keycode/process_tap_dance.c +++ b/quantum/process_keycode/process_tap_dance.c @@ -115,12 +115,12 @@ static inline void process_tap_dance_action_on_dance_finished(qk_tap_dance_actio } } -void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { +bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { qk_tap_dance_action_t *action; - if (!record->event.pressed) return; + if (!record->event.pressed) return false; - if (!active_td || keycode == active_td) return; + if (!active_td || keycode == active_td) return false; action = &tap_dance_actions[TD_INDEX(active_td)]; action->state.interrupted = true; @@ -130,6 +130,12 @@ void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record) { // Tap dance actions can leave some weak mods active (e.g., if the tap dance is mapped to a keycode with // modifiers), but these weak mods should not affect the keypress which interrupted the tap dance. clear_weak_mods(); + + // Signal that a tap dance has been finished due to being interrupted, + // therefore the keymap lookup for the currently processed event needs to + // be repeated with the current layer state that might have been updated by + // the finished tap dance. + return true; } bool process_tap_dance(uint16_t keycode, keyrecord_t *record) { diff --git a/quantum/process_keycode/process_tap_dance.h b/quantum/process_keycode/process_tap_dance.h index d97900d96b..d6d6c136dc 100644 --- a/quantum/process_keycode/process_tap_dance.h +++ b/quantum/process_keycode/process_tap_dance.h @@ -81,7 +81,7 @@ void reset_tap_dance(qk_tap_dance_state_t *state); /* To be used internally */ -void preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); +bool preprocess_tap_dance(uint16_t keycode, keyrecord_t *record); bool process_tap_dance(uint16_t keycode, keyrecord_t *record); void tap_dance_task(void); diff --git a/quantum/quantum.c b/quantum/quantum.c index ff36e14775..5b5364c8f1 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -251,7 +251,11 @@ bool process_record_quantum(keyrecord_t *record) { #endif #ifdef TAP_DANCE_ENABLE - preprocess_tap_dance(keycode, record); + if (preprocess_tap_dance(keycode, record)) { + // The tap dance might have updated the layer state, therefore the + // result of the keycode lookup might change. + keycode = get_record_keycode(record, true); + } #endif if (!( diff --git a/tests/tap_dance/tap_dance_layers/config.h b/tests/tap_dance/tap_dance_layers/config.h new file mode 100644 index 0000000000..32a19a8c68 --- /dev/null +++ b/tests/tap_dance/tap_dance_layers/config.h @@ -0,0 +1,6 @@ +// Copyright 2022 Sergey Vlasov (@sigprof) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "test_common.h" diff --git a/tests/tap_dance/tap_dance_layers/tap_dance_defs.c b/tests/tap_dance/tap_dance_layers/tap_dance_defs.c new file mode 100644 index 0000000000..5ec900c041 --- /dev/null +++ b/tests/tap_dance/tap_dance_layers/tap_dance_defs.c @@ -0,0 +1,97 @@ +// Copyright 2022 Sergey Vlasov (@sigprof) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "quantum.h" +#include "tap_dance_defs.h" + +// Implement custom keycodes which are used to check that the layer switching +// behaves properly. +bool process_record_user(uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case FAST_AB: + case SLOW_AB: + if (record->event.pressed) { + tap_code(KC_A); + } else { + tap_code(KC_B); + } + return keycode == SLOW_AB; + case FAST_CD: + case SLOW_CD: + if (record->event.pressed) { + tap_code(KC_C); + } else { + tap_code(KC_D); + } + return keycode == SLOW_CD; + } + return true; +} + +// Implement a custom tap dance with the following behavior: +// - single tap: KC_APP +// - single hold: MO(1) +// - double tap/hold: KC_RCTL +// (The single tap and hold actions are mostly equivalent to LT(1, KC_APP).) + +enum lt_app_state { + LTA_NONE, + LTA_SINGLE_TAP, + LTA_SINGLE_HOLD, + LTA_DOUBLE_HOLD, +}; + +static enum lt_app_state saved_lt_app_state; + +static enum lt_app_state get_lt_app_state(qk_tap_dance_state_t *state) { + if (state->count == 1) { + if (!state->pressed) { + return LTA_SINGLE_TAP; + } else { + return LTA_SINGLE_HOLD; + } + } else if (state->count == 2) { + return LTA_DOUBLE_HOLD; + } else { + return LTA_NONE; + } +} + +static void lt_app_finished(qk_tap_dance_state_t *state, void *user_data) { + saved_lt_app_state = get_lt_app_state(state); + switch (saved_lt_app_state) { + case LTA_NONE: + break; + case LTA_SINGLE_TAP: + register_code(KC_APP); + break; + case LTA_SINGLE_HOLD: + layer_on(1); + break; + case LTA_DOUBLE_HOLD: + register_code(KC_RCTL); + break; + } +} + +static void lt_app_reset(qk_tap_dance_state_t *state, void *user_data) { + switch (saved_lt_app_state) { + case LTA_NONE: + break; + case LTA_SINGLE_TAP: + unregister_code(KC_APP); + break; + case LTA_SINGLE_HOLD: + layer_off(1); + break; + case LTA_DOUBLE_HOLD: + unregister_code(KC_RCTL); + break; + } +} + +qk_tap_dance_action_t tap_dance_actions[] = { + [TD_L_MOVE] = ACTION_TAP_DANCE_LAYER_MOVE(KC_APP, 1), + [TD_L_TOGG] = ACTION_TAP_DANCE_LAYER_TOGGLE(KC_APP, 1), + [TD_LT_APP] = ACTION_TAP_DANCE_FN_ADVANCED(NULL, lt_app_finished, lt_app_reset), +}; diff --git a/tests/tap_dance/tap_dance_layers/tap_dance_defs.h b/tests/tap_dance/tap_dance_layers/tap_dance_defs.h new file mode 100644 index 0000000000..37cab0c2cb --- /dev/null +++ b/tests/tap_dance/tap_dance_layers/tap_dance_defs.h @@ -0,0 +1,29 @@ +// Copyright 2022 Sergey Vlasov (@sigprof) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +enum custom_keycodes { + // (FAST|SLOW)_xy = tap KC_x on press, tap KC_y on release. For FAST_xy + // process_record_user() returns false to stop processing early; for + // SLOW_xy process_record_user() returns true, therefore all other key + // handlers are invoked. + FAST_AB = SAFE_RANGE, + FAST_CD, + SLOW_AB, + SLOW_CD, +}; + +enum tap_dance_ids { + TD_L_MOVE, // ACTION_TAP_DANCE_LAYER_MOVE(KC_APP, 1) + TD_L_TOGG, // ACTION_TAP_DANCE_LAYER_TOGGLE(KC_APP, 1) + TD_LT_APP, // similar to LT(1, KC_APP) with KC_RCTL on tap+hold or double tap +}; + +#ifdef __cplusplus +} +#endif diff --git a/tests/tap_dance/tap_dance_layers/test.mk b/tests/tap_dance/tap_dance_layers/test.mk new file mode 100644 index 0000000000..b4cdc9b088 --- /dev/null +++ b/tests/tap_dance/tap_dance_layers/test.mk @@ -0,0 +1,10 @@ +# Copyright 2022 Sergey Vlasov (@sigprof) +# SPDX-License-Identifier: GPL-2.0-or-later + +# -------------------------------------------------------------------------------- +# Keep this file, even if it is empty, as a marker that this folder contains tests +# -------------------------------------------------------------------------------- + +TAP_DANCE_ENABLE = yes + +SRC += tap_dance_defs.c diff --git a/tests/tap_dance/tap_dance_layers/test_tap_dance_layers.cpp b/tests/tap_dance/tap_dance_layers/test_tap_dance_layers.cpp new file mode 100644 index 0000000000..8b736b19c6 --- /dev/null +++ b/tests/tap_dance/tap_dance_layers/test_tap_dance_layers.cpp @@ -0,0 +1,717 @@ +// Copyright 2022 Sergey Vlasov (@sigprof) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_keymap_key.hpp" +#include "tap_dance_defs.h" + +using testing::_; +using testing::InSequence; + +struct TapDanceKeyParams { + std::string name; // Tap dance name (part of test name) + uint16_t keycode; // Tap dance keycode (TD(n)) + uint16_t expect_on_tap; // Keycode for single tap + uint16_t expect_on_hold; // Keycode for single hold (may be MO(1)) + uint16_t expect_on_double_tap; // Keycode for double tap (may be MO(1)) + uint16_t expect_on_double_hold; // Keycode for double hold (may be MO(1)) +}; + +struct OtherKeyLayerParams { + uint16_t keycode; // Keycode in the keymap + uint16_t expect_on_press; // Keycode to expect on press + uint16_t expect_on_release; // Keycode to expect on release (may be KC_NO if none) +}; + +struct OtherKeyParams { + std::string name; // Other key name (part of test name) + OtherKeyLayerParams l0; // Keycodes for layer 0 + OtherKeyLayerParams l1; // Keycodes for layer 1 +}; + +typedef std::tuple TapDanceLayersParams; + +class TapDanceLayers : public ::testing::WithParamInterface, public TestFixture { + protected: + TapDanceKeyParams tap_dance; + OtherKeyParams other_key; + + std::unique_ptr key_td, key_td_l1, key_other, key_other_l1; + + void SetUp() override { + std::tie(tap_dance, other_key) = GetParam(); + + key_td = std::make_unique(0, 1, 0, tap_dance.keycode); + key_td_l1 = std::make_unique(1, 1, 0, KC_TRNS); + key_other = std::make_unique(0, 2, 0, other_key.l0.keycode); + key_other_l1 = std::make_unique(1, 2, 0, other_key.l1.keycode); + + set_keymap({*key_td, *key_td_l1, *key_other, *key_other_l1}); + } +}; + +static const TapDanceKeyParams tap_dance_keys[] = { + TapDanceKeyParams{ + "LayerMove", + TD(TD_L_MOVE), + KC_APP, + KC_APP, + MO(1), + MO(1), + }, + TapDanceKeyParams{ + "LayerToggle", + TD(TD_L_TOGG), + KC_APP, + KC_APP, + MO(1), + MO(1), + }, + TapDanceKeyParams{ + "CustomLT", + TD(TD_LT_APP), + KC_APP, + MO(1), + KC_RCTL, + KC_RCTL, + }, +}; + +static const OtherKeyParams other_keys[] = { + OtherKeyParams{ + "Builtin", + OtherKeyLayerParams{KC_A, KC_A, KC_NO}, + OtherKeyLayerParams{KC_B, KC_B, KC_NO}, + }, + OtherKeyParams{ + "CustomFast", + OtherKeyLayerParams{FAST_AB, KC_A, KC_B}, + OtherKeyLayerParams{FAST_CD, KC_C, KC_D}, + }, + OtherKeyParams{ + "CustomSlow", + OtherKeyLayerParams{SLOW_AB, KC_A, KC_B}, + OtherKeyLayerParams{SLOW_CD, KC_C, KC_D}, + }, +}; + +// clang-format off +INSTANTIATE_TEST_CASE_P( + Layers, + TapDanceLayers, + ::testing::Combine( + ::testing::ValuesIn(tap_dance_keys), + ::testing::ValuesIn(other_keys) + ), + [](const ::testing::TestParamInfo& info) { + return std::get<0>(info.param).name + std::get<1>(info.param).name; + } +); +// clang-format on + +// Test single tap of the tap dance key with tapping term delay after the tap. +TEST_P(TapDanceLayers, SingleTap) { + TestDriver driver; + InSequence s; + + // The tap of the tap dance key does not result in sending a report + // immediately. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + + // After the tapping term expires, a tap event for the single tap keycode + // is generated. + idle_for(TAPPING_TERM - 1); + EXPECT_REPORT(driver, (tap_dance.expect_on_tap)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 0 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test single tap of the tap dance key without a delay between the tap dance +// key and the other key. +TEST_P(TapDanceLayers, SingleTapFast) { + TestDriver driver; + InSequence s; + + // The tap of the tap dance key does not result in sending a report + // immediately. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + + // A quick press of the other key causes the tap event for the tap dance to + // be sent before the press event for the other key, and the layer 0 + // mapping is used for the other key. + EXPECT_REPORT(driver, (tap_dance.expect_on_tap)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test single hold of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which switch the layer on hold). +TEST_P(TapDanceLayers, SingleHoldLayer) { + if (tap_dance.expect_on_hold != MO(1)) { + // Do nothing - the SingleHoldKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the hold of the tap dance key. + EXPECT_NO_REPORT(driver); + key_td->press(); + run_one_scan_loop(); + + // After the tapping term expires, the tap dance finishes and switches the + // layer, but does not send a report. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key does not produce a report. + EXPECT_NO_REPORT(driver); + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test single hold of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which send a keycode on single hold). +TEST_P(TapDanceLayers, SingleHoldKeycode) { + if (tap_dance.expect_on_hold == MO(1)) { + // Do nothing - the SingleHoldLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the hold of the tap dance key. + EXPECT_NO_REPORT(driver); + key_td->press(); + run_one_scan_loop(); + + // After the tapping term expires, the tap dance sends the report with the + // hold keycode. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + EXPECT_REPORT(driver, (tap_dance.expect_on_hold)); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 0 mapping of + // that key. + EXPECT_REPORT(driver, (tap_dance.expect_on_hold, other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (tap_dance.expect_on_hold)); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key sends the release report for the + // corresponding hold keycode. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } else { + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + } + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test single hold of the tap dance key without tapping term delay after the +// hold (test variant for tap dances which switch the layer on hold). +TEST_P(TapDanceLayers, SingleHoldFastLayer) { + if (tap_dance.expect_on_hold != MO(1)) { + // Do nothing - the SingleHoldFastKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the hold of the tap dance key. + EXPECT_NO_REPORT(driver); + key_td->press(); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key does not produce a report. + EXPECT_NO_REPORT(driver); + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test single hold of the tap dance key without tapping term delay after the hold +// (test variant for tap dances which send a keycode on single hold). +TEST_P(TapDanceLayers, SingleHoldFastKeycode) { + if (tap_dance.expect_on_hold == MO(1)) { + // Do nothing - the SingleHoldFastLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the hold of the tap dance key. + EXPECT_NO_REPORT(driver); + key_td->press(); + run_one_scan_loop(); + + // Pressing the other key produces first the report for the tap dance hold + // keycode, and then the reports for the layer 0 mapping of the other key. + EXPECT_REPORT(driver, (tap_dance.expect_on_hold)); + EXPECT_REPORT(driver, (tap_dance.expect_on_hold, other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (tap_dance.expect_on_hold)); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key sends a release report for the corresponding + // hold keycode. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } else { + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + } + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double tap of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which switch the layer on double tap). +TEST_P(TapDanceLayers, DoubleTapLayer) { + if (tap_dance.expect_on_double_tap != MO(1)) { + // Do nothing - the DoubleTapKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double tap of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + tap_key(*key_td); + + // After the tapping term this tap dance does not send a report too. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double tap of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which send a keycode on double tap). +TEST_P(TapDanceLayers, DoubleTapKeycode) { + if (tap_dance.expect_on_double_tap == MO(1)) { + // Do nothing - the DoubleTapLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double tap of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + tap_key(*key_td); + + // After the tapping term this tap dance sends the double tap keycode. + idle_for(TAPPING_TERM - 1); + EXPECT_REPORT(driver, (tap_dance.expect_on_double_tap)); + EXPECT_EMPTY_REPORT(driver); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 0 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double tap of the tap dance key without tapping term delay after the +// hold (test variant for tap dances which switch the layer on double tap). +TEST_P(TapDanceLayers, DoubleTapFastLayer) { + if (tap_dance.expect_on_double_tap != MO(1)) { + // Do nothing - the DoubleTapFastKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double tap of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + tap_key(*key_td); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double tap of the tap dance key without tapping term delay after the +// hold (test variant for tap dances which send a keycode on double tap). +TEST_P(TapDanceLayers, DoubleTapFastKeycode) { + if (tap_dance.expect_on_double_tap == MO(1)) { + // Do nothing - the DoubleTapFastLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double tap of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + tap_key(*key_td); + + // Pressing the other key produces first the report for the tap dance + // double tap keycode, and then the reports for the layer 0 mapping of the + // other key. + EXPECT_REPORT(driver, (tap_dance.expect_on_double_tap)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double hold of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which switch the layer on double hold). +TEST_P(TapDanceLayers, DoubleHoldLayer) { + if (tap_dance.expect_on_double_hold != MO(1)) { + // Do nothing - the DoubleHoldKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double hold of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + key_td->press(); + run_one_scan_loop(); + + // After the tapping term expires, the tap dance finishes and switches the + // layer, but does not send a report. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key does not produce a report. + EXPECT_NO_REPORT(driver); + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double hold of the tap dance key with tapping term delay after the hold +// (test variant for tap dances which send a keycode on double hold). +TEST_P(TapDanceLayers, DoubleHoldKeycode) { + if (tap_dance.expect_on_double_hold == MO(1)) { + // Do nothing - the DoubleHoldLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double hold of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + key_td->press(); + run_one_scan_loop(); + + // After the tapping term expires, the tap dance sends the report with the + // double hold keycode. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold)); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 0 mapping of + // that key. + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold, other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold)); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key sends the release report for the + // corresponding double hold keycode. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } else { + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + } + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double hold of the tap dance key without tapping term delay after the +// hold (test variant for tap dances which switch the layer on double hold). +TEST_P(TapDanceLayers, DoubleHoldFastLayer) { + if (tap_dance.expect_on_double_hold != MO(1)) { + // Do nothing - the DoubleHoldFastKeycode test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double hold of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + key_td->press(); + run_one_scan_loop(); + + // Pressing the other key produces the reports for the layer 1 mapping of + // that key. + EXPECT_REPORT(driver, (other_key.l1.expect_on_press)); + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key does not produce a report. + EXPECT_NO_REPORT(driver); + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the reports for the layer 1 mapping of + // that key. + if (other_key.l1.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l1.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} + +// Test double hold of the tap dance key without tapping term delay after the hold +// (test variant for tap dances which send a keycode on double hold). +TEST_P(TapDanceLayers, DoubleHoldFastKeycode) { + if (tap_dance.expect_on_double_hold == MO(1)) { + // Do nothing - the DoubleHoldFastLayer test would run instead. + return; + } + + TestDriver driver; + InSequence s; + + // No report gets sent immediately after the double hold of the tap dance + // key. + EXPECT_NO_REPORT(driver); + tap_key(*key_td); + key_td->press(); + run_one_scan_loop(); + + // Pressing the other key produces first the report for the tap dance + // double hold keycode, and then the reports for the layer 0 mapping of the + // other key. + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold)); + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold, other_key.l0.expect_on_press)); + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (tap_dance.expect_on_double_hold)); + } + key_other->press(); + run_one_scan_loop(); + + // Releasing the tap dance key sends a release report for the corresponding + // double hold keycode. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_EMPTY_REPORT(driver); + } else { + EXPECT_REPORT(driver, (other_key.l0.expect_on_press)); + } + key_td->release(); + run_one_scan_loop(); + + // Releasing the other key produces the report for the layer 0 mapping of + // that key. + if (other_key.l0.expect_on_release != KC_NO) { + EXPECT_REPORT(driver, (other_key.l0.expect_on_release)); + } + EXPECT_EMPTY_REPORT(driver); + key_other->release(); + run_one_scan_loop(); +} -- cgit v1.2.3