diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 5d43dd99ea..ccc99bfd8e 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -813,7 +813,7 @@ uint8_t get_speculative_mods(void) { __attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); - return (mods & (MOD_LCTL | MOD_LSFT)) == mods; + return (mods & (MOD_LCTL | MOD_LSFT)) == (mods & (MOD_HYPR)); } void speculative_key_settled(keyrecord_t *record) { diff --git a/tests/tap_hold_configurations/speculative_hold/all_mods/config.h b/tests/tap_hold_configurations/speculative_hold/all_mods/config.h new file mode 100644 index 0000000000..e4bcba13c1 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/all_mods/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 . + */ + +#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/all_mods/test.mk b/tests/tap_hold_configurations/speculative_hold/all_mods/test.mk new file mode 100644 index 0000000000..1765122512 --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/all_mods/test.mk @@ -0,0 +1,18 @@ +# 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 . + +MAGIC_ENABLE = yes + diff --git a/tests/tap_hold_configurations/speculative_hold/all_mods/test_tap_hold.cpp b/tests/tap_hold_configurations/speculative_hold/all_mods/test_tap_hold.cpp new file mode 100644 index 0000000000..764b97ddde --- /dev/null +++ b/tests/tap_hold_configurations/speculative_hold/all_mods/test_tap_hold.cpp @@ -0,0 +1,352 @@ +// 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 + +#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_mods(uint16_t keycode, keyrecord_t *record) { + return true; // Enable Speculative Hold for all mod-tap keys. +} + +// Indirection so that get_speculative_hold() can be +// replaced with other functions in the test cases below. +std::function get_speculative_hold_fun = get_speculative_hold_all_mods; + +extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { + return get_speculative_hold_fun(keycode, record); +} + +class SpeculativeHoldAllMods : public TestFixture { + public: + void SetUp() override { + get_speculative_hold_fun = get_speculative_hold_all_mods; + } +}; + +TEST_F(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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(SpeculativeHoldAllMods, 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); +} + +} // namespace diff --git a/tests/tap_hold_configurations/speculative_hold/default/config.h b/tests/tap_hold_configurations/speculative_hold/default/config.h index e4bcba13c1..9891296f42 100644 --- a/tests/tap_hold_configurations/speculative_hold/default/config.h +++ b/tests/tap_hold_configurations/speculative_hold/default/config.h @@ -20,4 +20,3 @@ #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 index c03d99f686..f5decaeb78 100644 --- a/tests/tap_hold_configurations/speculative_hold/default/test.mk +++ b/tests/tap_hold_configurations/speculative_hold/default/test.mk @@ -15,7 +15,6 @@ # along with this program. If not, see . KEY_OVERRIDE_ENABLE = yes -MAGIC_ENABLE = yes INTROSPECTION_KEYMAP_C = test_keymap.c 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 index c92ed5a2d0..bfa022be11 100644 --- a/tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp +++ b/tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp @@ -27,28 +27,13 @@ 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 +// Indirection so that process_record_user() can be // replaced with other functions in the test cases below. -std::function get_speculative_hold_fun = get_speculative_hold_all_keys; -std::function 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); -} +std::function process_record_user_fun = process_record_user_default; extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) { return process_record_user_fun(keycode, record); @@ -57,11 +42,30 @@ extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *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; + process_record_user_fun = process_record_user_default; } }; +TEST_F(SpeculativeHoldDefault, get_speculative_hold) { + keyrecord_t record = {}; + + // With the default definition of get_speculative_hold(), Speculative Hold + // is enabled for Ctrl and Shift. + EXPECT_TRUE(get_speculative_hold(MT(MOD_LCTL, KC_NO), &record)); + EXPECT_TRUE(get_speculative_hold(MT(MOD_LSFT, KC_NO), &record)); + EXPECT_TRUE(get_speculative_hold(MT(MOD_LCTL | MOD_LSFT, KC_NO), &record)); + EXPECT_TRUE(get_speculative_hold(MT(MOD_RCTL, KC_NO), &record)); + EXPECT_TRUE(get_speculative_hold(MT(MOD_RSFT, KC_NO), &record)); + EXPECT_TRUE(get_speculative_hold(MT(MOD_RCTL | MOD_RSFT, KC_NO), &record)); + + EXPECT_FALSE(get_speculative_hold(MT(MOD_LALT, KC_NO), &record)); + EXPECT_FALSE(get_speculative_hold(MT(MOD_LGUI, KC_NO), &record)); + EXPECT_FALSE(get_speculative_hold(MT(MOD_RALT, KC_NO), &record)); + EXPECT_FALSE(get_speculative_hold(MT(MOD_RGUI, KC_NO), &record)); + EXPECT_FALSE(get_speculative_hold(MT(MOD_MEH, KC_NO), &record)); + EXPECT_FALSE(get_speculative_hold(MT(MOD_HYPR, KC_NO), &record)); +} + TEST_F(SpeculativeHoldDefault, tap_mod_tap) { TestDriver driver; InSequence s; @@ -103,232 +107,6 @@ TEST_F(SpeculativeHoldDefault, tap_mod_tap) { 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; @@ -401,43 +179,6 @@ TEST_F(SpeculativeHoldDefault, tap_regular_key_while_mod_tap_key_is_held) { 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;