diff options
| author | Pascal Getreuer | 2025-11-11 12:27:12 +0100 |
|---|---|---|
| committer | GitHub | 2025-11-11 12:27:12 +0100 |
| commit | efc5d63383b64291f25c8377bcfae8178dd63302 (patch) | |
| tree | 6da3bafb3a53f478acae6d007cf94f90a2f26057 /tests | |
| parent | 2af9aac61c70b543f29f658984ea66993cc3db04 (diff) | |
[Core] Speculative Hold option for mod-taps: hold mods instantly while unsettled. (#25572)
Diffstat (limited to 'tests')
13 files changed, 3416 insertions, 0 deletions
diff --git a/tests/tap_hold_configurations/speculative_hold/default/config.h b/tests/tap_hold_configurations/speculative_hold/default/config.h new file mode 100644 index 0000000000..e4bcba13c1 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/default/config.h @@ -0,0 +1,23 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2025 Google LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "test_common.h" + +#define SPECULATIVE_HOLD +#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24 diff --git a/tests/tap_hold_configurations/speculative_hold/default/test.mk b/tests/tap_hold_configurations/speculative_hold/default/test.mk new file mode 100644 index 0000000000..c03d99f686 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/default/test.mk @@ -0,0 +1,21 @@ +# Copyright 2022 Vladislav Kucheriavykh +# Copyright 2025 Google LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +KEY_OVERRIDE_ENABLE = yes +MAGIC_ENABLE = yes + +INTROSPECTION_KEYMAP_C = test_keymap.c + diff --git a/tests/tap_hold_configurations/speculative_hold/default/test_keymap.c b/tests/tap_hold_configurations/speculative_hold/default/test_keymap.c new file mode 100644 index 0000000000..db65374618 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/default/test_keymap.c @@ -0,0 +1,20 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +// Shift + Esc = Home +const key_override_t home_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, KC_HOME); + +const key_override_t *key_overrides[] = {&home_esc_override}; diff --git a/tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp b/tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp new file mode 100644 index 0000000000..c92ed5a2d0 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp @@ -0,0 +1,794 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include <functional> + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; + +namespace { + +// Gets the unpacked 8-bit mods corresponding to a given mod-tap keycode. +uint8_t unpack_mod_tap_mods(uint16_t keycode) { + const uint8_t mods5 = QK_MOD_TAP_GET_MODS(keycode); + return (mods5 & 0x10) != 0 ? (mods5 << 4) : mods5; +} + +bool get_speculative_hold_all_keys(uint16_t keycode, keyrecord_t *record) { + return true; // Enable Speculative Hold for all mod-tap keys. +} + +bool process_record_user_default(uint16_t keycode, keyrecord_t *record) { + return true; +} + +// Indirection so that get_speculative_hold() and process_record_user() can be +// replaced with other functions in the test cases below. +std::function<bool(uint16_t, keyrecord_t *)> get_speculative_hold_fun = get_speculative_hold_all_keys; +std::function<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default; + +extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + return get_speculative_hold_fun(keycode, record); +} + +extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) { + return process_record_user_fun(keycode, record); +} + +class SpeculativeHoldDefault : public TestFixture { + public: + void SetUp() override { + get_speculative_hold_fun = get_speculative_hold_all_keys; + process_record_user_fun = process_record_user_default; + } +}; + +TEST_F(SpeculativeHoldDefault, tap_mod_tap) { + TestDriver driver; + InSequence s; + static int process_record_user_calls = 0; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + process_record_user_fun = [](uint16_t keycode, keyrecord_t *record) { + ++process_record_user_calls; + return true; + }; + + // Press mod-tap-hold key. Mod is held speculatively. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(10); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LSHIFT); + // Speculative mod holds and releases are made directly, bypassing regular + // event processing. No calls have been made yet to process_record_user(). + EXPECT_EQ(process_record_user_calls, 0); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); // Speculative mod canceled. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), 0); + // Two calls have now been made, for pressing and releasing KC_P. + EXPECT_EQ(process_record_user_calls, 2); + + // Idle for tapping term of mod tap hold key. + idle_for(TAPPING_TERM - 10); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_mod_tap_neutralized) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, GUI_T(KC_P)); + + set_keymap({mod_tap_key}); + + // Press mod-tap-hold key. Mod is held speculatively. + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key.press(); + idle_for(10); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. Speculative mod is neutralized and canceled. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Idle for tapping term of mod tap hold key. + idle_for(TAPPING_TERM - 10); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, hold_two_mod_taps) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, LCTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RALT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL); + + // Press second mod-tap key. + EXPECT_REPORT(driver, (KC_LCTL, KC_RALT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT); + + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT); + + // Release first mod-tap key. + EXPECT_REPORT(driver, (KC_RALT)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release second mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, two_mod_taps_same_mods) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, GUI_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, GUI_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap second mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap key. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, respects_get_speculative_hold_callback) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 0, 0, LSFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 1, 0, LSFT_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 2, 0, LCTL_T(KC_C)); + auto mod_tap_key4 = KeymapKey(0, 3, 0, LCTL_T(KC_D)); + auto mod_tap_key5 = KeymapKey(0, 4, 0, RSFT_T(KC_E)); + auto mod_tap_key6 = KeymapKey(0, 5, 0, RSFT_T(KC_F)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3, mod_tap_key4, mod_tap_key5, mod_tap_key6}); + + // Enable Speculative Hold selectively for some of the keys. + get_speculative_hold_fun = [](uint16_t keycode, keyrecord_t *record) { + switch (keycode) { + case LSFT_T(KC_B): + case LCTL_T(KC_D): + case RSFT_T(KC_F): + return true; + } + return false; + }; + + for (KeymapKey *mod_tap_key : {&mod_tap_key2, &mod_tap_key4, &mod_tap_key6}) { + SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name); + const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code); + + // Long press and release mod_tap_key. + // For these keys where Speculative Hold is enabled, then the mod should + // activate immediately on keydown. + EXPECT_REPORT(driver, (KC_LCTL + biton(mods))); + mod_tap_key->press(); + run_one_scan_loop(); + EXPECT_EQ(get_speculative_mods(), mods); + EXPECT_EQ(get_mods(), 0); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), mods); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key->release(); + idle_for(TAPPING_TERM + 1); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), 0); + VERIFY_AND_CLEAR(driver); + } + + for (KeymapKey *mod_tap_key : {&mod_tap_key1, &mod_tap_key3, &mod_tap_key5}) { + SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name); + const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code); + + // Long press and release mod_tap_key. + // For these keys where Speculative Hold is disabled, the mod should + // activate when the key has settled after the tapping term. + EXPECT_NO_REPORT(driver); + mod_tap_key->press(); + run_one_scan_loop(); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), 0); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL + biton(mods))); + idle_for(TAPPING_TERM + 1); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), mods); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key->release(); + idle_for(TAPPING_TERM + 1); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), 0); + VERIFY_AND_CLEAR(driver); + } +} + +TEST_F(SpeculativeHoldDefault, respects_magic_mod_config) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_P)); + + set_keymap({mod_tap_key}); + + keymap_config.swap_lctl_lgui = true; + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + keymap_config.swap_lctl_lgui = false; + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, key_overrides) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A)); + auto esc_key = KeymapKey(0, 3, 0, KC_ESC); + + set_keymap({mod_tap_key, esc_key}); + + // Press mod-tap Shift key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press Esc key. + EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_HOME)); + esc_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release Esc key. + EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT)); + esc_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap Shift key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_regular_key_while_mod_tap_key_is_held) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Idle for tapping term of mod tap hold key. + idle_for(TAPPING_TERM - 3); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_a_mod_tap_key_while_another_mod_tap_key_is_held) { + TestDriver driver; + InSequence s; + auto first_mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto second_mod_tap_key = KeymapKey(0, 2, 0, RSFT_T(KC_A)); + + set_keymap({first_mod_tap_key, second_mod_tap_key}); + + // Press first mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT)); + first_mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press second tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT, KC_RSFT)); + second_mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release second tap-hold key. + EXPECT_NO_REPORT(driver); + second_mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + first_mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_mod_tap_key_two_times) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-hold key again. + EXPECT_REPORT(driver, (KC_P)); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_mod_tap_key_twice_and_hold_on_second_time) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-hold key again. + EXPECT_REPORT(driver, (KC_P)); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldDefault, tap_and_hold_mod_tap_key) { + TestDriver driver; + InSequence s; + static int process_record_user_calls = 0; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + process_record_user_fun = [](uint16_t keycode, keyrecord_t *record) { + ++process_record_user_calls; + return true; + }; + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LSHIFT); + EXPECT_EQ(get_mods(), 0); + // Speculative mod holds and releases are made directly, bypassing regular + // event processing. No calls have been made yet to process_record_user(). + EXPECT_EQ(process_record_user_calls, 0); + idle_for(2); + // Now that the key has settled, one call has been made for the hold event. + EXPECT_EQ(process_record_user_calls, 1); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), MOD_BIT_LSHIFT); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + EXPECT_EQ(process_record_user_calls, 2); + VERIFY_AND_CLEAR(driver); +} + +// Test with layer tap and speculative mod tap keys on the same layer, +// rolling from LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldDefault, lt_mt_same_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key, after flow tap term but within tapping term. The + // speculative mod activates. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Wait for the layer tap key to settle. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying a +// nested press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldDefault, lt_mt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys: MT first, LT second. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying a +// nested press with the MT first: +// "MT down, LT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldDefault, mt_lt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, rolling from +// LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + // Press mod tap key. + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LSFT)); + layer_tap_key.release(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, slowly +// rolling from LT to MT key: +// "LT down, (wait), MT down, (wait), LT up, MT up." +TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_slow_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, trying a +// nested press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, trying a +// slow nested press: +// "LT down, (wait), MT down, MT up, LT up." +TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_slow_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +} // namespace diff --git a/tests/tap_hold_configurations/speculative_hold/flow_tap/config.h b/tests/tap_hold_configurations/speculative_hold/flow_tap/config.h new file mode 100644 index 0000000000..6988484226 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/flow_tap/config.h @@ -0,0 +1,25 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2025 Google LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "test_common.h" + +#define SPECULATIVE_HOLD +#define FLOW_TAP_TERM 150 +#define PERMISSIVE_HOLD +#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24 diff --git a/tests/tap_hold_configurations/speculative_hold/flow_tap/test.mk b/tests/tap_hold_configurations/speculative_hold/flow_tap/test.mk new file mode 100644 index 0000000000..ad8675da9d --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/flow_tap/test.mk @@ -0,0 +1,15 @@ +# Copyright 2022 Vladislav Kucheriavykh +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + diff --git a/tests/tap_hold_configurations/speculative_hold/flow_tap/test_tap_hold.cpp b/tests/tap_hold_configurations/speculative_hold/flow_tap/test_tap_hold.cpp new file mode 100644 index 0000000000..0433a2548d --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/flow_tap/test_tap_hold.cpp @@ -0,0 +1,1114 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::AnyNumber; +using testing::InSequence; + +extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + return true; +} + +class SpeculativeHoldFlowTapTest : public TestFixture {}; + +TEST_F(SpeculativeHoldFlowTapTest, tap_mod_tap) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + // Press mod-tap-hold key. Mod is held speculatively. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(10); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LSHIFT); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); // Speculative mod canceled. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), 0); + + // Idle for tapping term of mod tap hold key. + idle_for(TAPPING_TERM - 10); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldFlowTapTest, hold_two_mod_taps) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, LCTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RALT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key1.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL); + + // Press second mod-tap key. + EXPECT_REPORT(driver, (KC_LCTL, KC_RALT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT); + + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + EXPECT_EQ(get_speculative_mods(), 0); + EXPECT_EQ(get_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT); + + // Release first mod-tap key. + EXPECT_REPORT(driver, (KC_RALT)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release second mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldFlowTapTest, two_mod_taps_same_mods) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, GUI_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, GUI_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key1.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Tap second mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LGUI, KC_B)); + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test an input of quick distinct taps. All should be settled as tapped. +TEST_F(SpeculativeHoldFlowTapTest, 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_REPORT(driver, (KC_LSFT)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(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(TAPPING_TERM + 1); + mod_tap_key2.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// By default, Flow Tap is disabled when mods other than Shift and AltGr are on. +TEST_F(SpeculativeHoldFlowTapTest, 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); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test input with two mod-taps in a rolled press quickly after a regular key. +TEST_F(SpeculativeHoldFlowTapTest, rolled_press) { + 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)); + + set_keymap({regular_key, mod_tap_key1, mod_tap_key2}); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key 1 quickly after regular key. The mod-tap should settle + // immediately as tapped, sending `KC_B`. + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key 2 quickly. + EXPECT_REPORT(driver, (KC_B, KC_C)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for longer than the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, long_flow_tap_settled_as_held) { + TestDriver driver; + InSequence s; + auto regular_key = KeymapKey(0, 0, 0, KC_A); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + + set_keymap({regular_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-tap key. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, holding_multiple_mod_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)); + + set_keymap({regular_key, mod_tap_key1, mod_tap_key2}); + + // 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-tap keys. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL)); + mod_tap_key2.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, 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_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, 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(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL)); + 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, 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); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, 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); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, 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(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key 2. + EXPECT_REPORT(driver, (KC_RALT)); + EXPECT_EMPTY_REPORT(driver); + 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_REPORT(driver, (KC_LCTL)); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + 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); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, 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); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, quick_tap) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_A)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + tap_key(mod_tap_key); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, rolling_mt_mt) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + EXPECT_NO_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL)); + idle_for(FLOW_TAP_TERM + 1); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for longer than the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, rolling_lt_mt_regular) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_B)); + auto regular_key = KeymapKey(0, 2, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LCTL)); + idle_for(FLOW_TAP_TERM + 1); + layer_tap_key.press(); + run_one_scan_loop(); + mod_tap_key.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B, KC_C)); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for longer than the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, rolling_lt_regular_mt) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + idle_for(FLOW_TAP_TERM + 1); + layer_tap_key.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B, KC_C)); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for longer than the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, rolling_mt_mt_mt) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_REPORT(driver, (KC_LCTL, KC_LGUI)); + EXPECT_REPORT(driver, (KC_LCTL, KC_LGUI, KC_LALT)); + idle_for(FLOW_TAP_TERM + 1); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B, KC_C)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold for longer than the tapping term. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Release other mod-tap keys. + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +TEST_F(SpeculativeHoldFlowTapTest, roll_release_132) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_REPORT(driver, (KC_LCTL, KC_LGUI)); + EXPECT_REPORT(driver, (KC_LCTL, KC_LGUI, KC_LALT)); + idle_for(FLOW_TAP_TERM + 1); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + idle_for(FLOW_TAP_TERM + 1); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release other mod-tap keys. + EXPECT_REPORT(driver, (KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, rolling +// from LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_same_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key, after flow tap term but within tapping term. The + // speculative mod activates. + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Wait for the layer tap key to settle. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + idle_for(TAPPING_TERM - FLOW_TAP_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying +// a nested press from LT to MT key: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + idle_for(TAPPING_TERM - FLOW_TAP_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys: MT first, LT second. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying +// a nested press with the MT first: +// "MT down, LT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldFlowTapTest, mt_lt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + run_one_scan_loop(); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, rolling from +// LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_different_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + // Press mod tap key. + mod_tap_key.press(); + idle_for(TAPPING_TERM - FLOW_TAP_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_B)); + layer_tap_key.release(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, slowly +// rolling from LT to MT key: +// "LT down, (wait), MT down, (wait), LT up, MT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_different_layer_slow_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, trying a +// nested press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_different_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + mod_tap_key.press(); + idle_for(TAPPING_TERM - FLOW_TAP_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, trying a +// slow nested press: +// "LT down, (wait), MT down, MT up, LT up." +TEST_F(SpeculativeHoldFlowTapTest, lt_mt_different_layer_slow_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + idle_for(FLOW_TAP_TERM + 1); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} diff --git a/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/config.h b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/config.h new file mode 100644 index 0000000000..5b16a60687 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/config.h @@ -0,0 +1,30 @@ +/* Copyright 2022 Isaac Elenbaas + * Copyright 2025 Google LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "test_common.h" + +#define SPECULATIVE_HOLD +#define PERMISSIVE_HOLD + +#define RETRO_SHIFT 2 * TAPPING_TERM +// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested +#define AUTO_SHIFT_TIMEOUT TAPPING_TERM +#define AUTO_SHIFT_MODIFIERS + +#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24 diff --git a/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test.mk b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test.mk new file mode 100644 index 0000000000..7a19bcab59 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test.mk @@ -0,0 +1,17 @@ +# Copyright 2022 Isaac Elenbaas +# Copyright 2025 Google LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +AUTO_SHIFT_ENABLE = yes diff --git a/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test_retro_shift.cpp b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test_retro_shift.cpp new file mode 100644 index 0000000000..b81485865b --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_shift_permissive_hold/test_retro_shift.cpp @@ -0,0 +1,689 @@ +// Copyright 2022 Isaac Elenbaas +// Copyright 2025 Google LLC +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +extern "C" { +bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + return true; +} + +bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + return true; +} +} // extern "C" + +using testing::_; +using testing::AnyNumber; +using testing::AnyOf; +using testing::InSequence; + +class RetroShiftPermissiveHold : public TestFixture {}; + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A)); + + set_keymap({mod_tap_key, mod_tap_regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL, KC_LALT)); + mod_tap_regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A)); + + set_keymap({mod_tap_key, mod_tap_regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL, KC_LALT)); + mod_tap_regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-regular key. + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A)); + + set_keymap({mod_tap_key, mod_tap_regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL, KC_LALT)); + mod_tap_regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-regular key. + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A)); + + set_keymap({mod_tap_key, mod_tap_regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL, KC_LALT)); + mod_tap_regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-regular key. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + 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); + mod_tap_key.release(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_NO_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A)); + + set_keymap({mod_tap_key, mod_tap_regular_key}); + + // Press mod-tap-hold key. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap-regular key. + EXPECT_REPORT(driver, (KC_LCTL, KC_LALT)); + mod_tap_regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-regular key. + 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); + idle_for(AUTO_SHIFT_TIMEOUT); + mod_tap_regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// Test with layer tap and speculative mod tap keys on the same layer, rolling +// from LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_same_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key, after flow tap term but within tapping term. The + // speculative mod activates. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Wait for the layer tap key to settle. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying +// a nested press from LT to MT key: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys: MT first, LT second. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT, KC_C)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying +// a nested press with the MT first: +// "MT down, LT down, (wait out tapping term), LT up, MT up." +TEST_F(RetroShiftPermissiveHold, mt_lt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT)); + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, rolling from +// LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + // Press mod tap key. + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_B)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, slowly +// rolling from LT to MT key: +// "LT down, (wait), MT down, (wait), LT up, MT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_slow_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, try a nested +// press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LSFT, KC_C)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, try a slow +// nested press: +// "LT down, (wait), MT down, MT up, LT up." +TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_slow_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} diff --git a/tests/tap_hold_configurations/speculative_hold/retro_tapping/config.h b/tests/tap_hold_configurations/speculative_hold/retro_tapping/config.h new file mode 100644 index 0000000000..b20ddbdcad --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_tapping/config.h @@ -0,0 +1,24 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2025 Google LLC + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once + +#include "test_common.h" + +#define SPECULATIVE_HOLD +#define RETRO_TAPPING +#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24 diff --git a/tests/tap_hold_configurations/speculative_hold/retro_tapping/test.mk b/tests/tap_hold_configurations/speculative_hold/retro_tapping/test.mk new file mode 100644 index 0000000000..9bc3366e14 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_tapping/test.mk @@ -0,0 +1,15 @@ +# Copyright 2022 Vladislav Kucheriavykh +# Copyright 2025 Google LLC +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. diff --git a/tests/tap_hold_configurations/speculative_hold/retro_tapping/test_tap_hold.cpp b/tests/tap_hold_configurations/speculative_hold/retro_tapping/test_tap_hold.cpp new file mode 100644 index 0000000000..de70bf09fd --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/retro_tapping/test_tap_hold.cpp @@ -0,0 +1,629 @@ +// Copyright 2025 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + return true; +} + +class SpeculativeHoldRetroTappingTest : public TestFixture {}; + +TEST_F(SpeculativeHoldRetroTappingTest, roll_regular_to_lgui_mod) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_B); + + set_keymap({mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_B)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B, KC_LGUI)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LGUI)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Neutralizer invoked by Speculative Hold. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, regular_to_mod_under_tap_term) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A)); + auto regular_key = KeymapKey(0, 2, 0, KC_B); + + set_keymap({mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_B)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B, KC_LSFT)); + mod_tap_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); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_regular) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_B); + + set_keymap({mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Neutralizer invoked by Speculative Hold. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_B, KC_P)); + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_regular) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A)); + auto regular_key = KeymapKey(0, 2, 0, KC_B); + + set_keymap({mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_B)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_under_tap_term) { + TestDriver driver; + InSequence s; + auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A)); + + set_keymap({mod_tap_lgui, mod_tap_lsft}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lsft.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI)); + mod_tap_lgui.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lgui.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_mod_under_tap_term) { + TestDriver driver; + InSequence s; + auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A)); + + set_keymap({mod_tap_lgui, mod_tap_lsft}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lsft.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI)); + mod_tap_lgui.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_REPORT(driver, (KC_LSFT, KC_P)); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lgui.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_over_tap_term) { + TestDriver driver; + InSequence s; + auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A)); + + set_keymap({mod_tap_lgui, mod_tap_lsft}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lsft.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI)); + mod_tap_lgui.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Neutralizer invoked by Retro Tapping. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_REPORT(driver, (KC_P, KC_LSFT)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lgui.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_over_tap_term_offset) { + TestDriver driver; + InSequence s; + auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A)); + + set_keymap({mod_tap_lgui, mod_tap_lsft}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lsft.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI)); + mod_tap_lgui.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LGUI)); + // Neutralizer invoked by Retro Tapping. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_lgui.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_mod_over_tap_term) { + TestDriver driver; + InSequence s; + auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A)); + + set_keymap({mod_tap_lgui, mod_tap_lsft}); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lsft.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI)); + mod_tap_lgui.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LGUI)); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Neutralizer invoked by Retro Tapping. + EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE)); + EXPECT_REPORT(driver, (KC_LGUI)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_REPORT(driver, (KC_P, KC_LSFT)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lgui.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(SpeculativeHoldRetroTappingTest, mod_to_mod_to_mod) { + TestDriver driver; + InSequence s; + auto mod_tap_lalt = KeymapKey(0, 1, 0, LALT_T(KC_R)); + auto mod_tap_lsft = KeymapKey(0, 2, 0, SFT_T(KC_A)); + auto mod_tap_lctl = KeymapKey(0, 3, 0, LCTL_T(KC_C)); + + set_keymap({mod_tap_lalt, mod_tap_lsft, mod_tap_lctl}); + + EXPECT_REPORT(driver, (KC_LALT)); + mod_tap_lalt.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT, KC_LALT)); + mod_tap_lsft.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LSFT)); + mod_tap_lalt.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT)); + mod_tap_lctl.press(); + idle_for(TAPPING_TERM); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_lsft.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_REPORT(driver, (KC_C, KC_LSFT)); + EXPECT_REPORT(driver, (KC_LSFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_lctl.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// Test with layer tap and speculative mod tap keys on the same layer, rolling +// from LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_same_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap key, after flow tap term but within tapping term. The + // speculative mod activates. + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Wait for the layer tap key to settle. + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying a +// nested press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys: MT first, LT second. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with layer tap and speculative mod tap keys on the same layer, trying a +// nested press with the MT first: +// "MT down, LT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldRetroTappingTest, mt_lt_same_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B)); + auto regular_key = KeymapKey(1, 1, 0, KC_C); + + set_keymap({layer_tap_key, mod_tap_key, regular_key}); + + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_key.press(); + run_one_scan_loop(); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, rolling from +// LT to MT key: +// "LT down, MT down, (wait out tapping term), LT up, MT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + // Press layer tap key. + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + // Press mod tap key. + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LCTL)); + layer_tap_key.release(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, slowly +// rolling from LT to MT key: +// "LT down, (wait), MT down, (wait), LT up, MT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_slow_roll) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LCTL)); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_key.release(); + run_one_scan_loop(); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, trying a +// nested press: +// "LT down, MT down, (wait out tapping term), MT up, LT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_key.press(); + run_one_scan_loop(); + mod_tap_key.press(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LCTL)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} + +// Test with a speculative mod tap key reached by a layer tap key, slowly making +// a nested press from LT to MT key: +// "LT down, (wait), MT down, MT up, LT up." +TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_slow_nested_press) { + TestDriver driver; + InSequence s; + auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A)); + auto regular_key = KeymapKey(0, 1, 0, KC_B); + auto placeholder_key = KeymapKey(1, 0, 0, KC_NO); + auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C)); + + set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LCTL)); + layer_tap_key.press(); + idle_for(TAPPING_TERM + 1); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + layer_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + // All mods are released. + EXPECT_EQ(get_mods() | get_speculative_mods(), 0); +} |