qmk_firmware

QMK firmware for my keyboards (Corne, Sweep Ferris) and trackball (Ploopy Adept)
Log | Files | Refs | Submodules | LICENSE

test_tap_hold.cpp (11024B)


      1 // Copyright 2025 Google LLC
      2 //
      3 // Licensed under the Apache License, Version 2.0 (the "License");
      4 // you may not use this file except in compliance with the License.
      5 // You may obtain a copy of the License at
      6 //
      7 //     https://www.apache.org/licenses/LICENSE-2.0
      8 //
      9 // Unless required by applicable law or agreed to in writing, software
     10 // distributed under the License is distributed on an "AS IS" BASIS,
     11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     12 // See the License for the specific language governing permissions and
     13 // limitations under the License.
     14 
     15 #include <functional>
     16 
     17 #include "keyboard_report_util.hpp"
     18 #include "keycode.h"
     19 #include "test_common.hpp"
     20 #include "action_tapping.h"
     21 #include "test_fixture.hpp"
     22 #include "test_keymap_key.hpp"
     23 
     24 using testing::_;
     25 using testing::AnyNumber;
     26 using testing::InSequence;
     27 
     28 namespace {
     29 
     30 // Gets the unpacked 8-bit mods corresponding to a given mod-tap keycode.
     31 uint8_t unpack_mod_tap_mods(uint16_t keycode) {
     32     const uint8_t mods5 = QK_MOD_TAP_GET_MODS(keycode);
     33     return (mods5 & 0x10) != 0 ? (mods5 << 4) : mods5;
     34 }
     35 
     36 bool get_speculative_hold_all_mods(uint16_t keycode, keyrecord_t *record) {
     37     return true; // Enable Speculative Hold for all mod-tap keys.
     38 }
     39 
     40 // Indirection so that get_speculative_hold() can be
     41 // replaced with other functions in the test cases below.
     42 std::function<bool(uint16_t, keyrecord_t *)> get_speculative_hold_fun = get_speculative_hold_all_mods;
     43 
     44 extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
     45     return get_speculative_hold_fun(keycode, record);
     46 }
     47 
     48 class SpeculativeHoldAllMods : public TestFixture {
     49    public:
     50     void SetUp() override {
     51         get_speculative_hold_fun = get_speculative_hold_all_mods;
     52     }
     53 };
     54 
     55 TEST_F(SpeculativeHoldAllMods, tap_mod_tap_neutralized) {
     56     TestDriver driver;
     57     InSequence s;
     58     auto       mod_tap_key = KeymapKey(0, 1, 0, GUI_T(KC_P));
     59 
     60     set_keymap({mod_tap_key});
     61 
     62     // Press mod-tap-hold key. Mod is held speculatively.
     63     EXPECT_REPORT(driver, (KC_LGUI));
     64     mod_tap_key.press();
     65     idle_for(10);
     66     VERIFY_AND_CLEAR(driver);
     67 
     68     // Release mod-tap-hold key. Speculative mod is neutralized and canceled.
     69     EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
     70     EXPECT_REPORT(driver, (KC_LGUI));
     71     EXPECT_EMPTY_REPORT(driver);
     72     EXPECT_REPORT(driver, (KC_P));
     73     EXPECT_EMPTY_REPORT(driver);
     74     mod_tap_key.release();
     75     run_one_scan_loop();
     76     VERIFY_AND_CLEAR(driver);
     77 
     78     // Idle for tapping term of mod tap hold key.
     79     idle_for(TAPPING_TERM - 10);
     80     VERIFY_AND_CLEAR(driver);
     81 }
     82 
     83 TEST_F(SpeculativeHoldAllMods, hold_two_mod_taps) {
     84     TestDriver driver;
     85     InSequence s;
     86     auto       mod_tap_key1 = KeymapKey(0, 1, 0, LCTL_T(KC_A));
     87     auto       mod_tap_key2 = KeymapKey(0, 2, 0, RALT_T(KC_B));
     88 
     89     set_keymap({mod_tap_key1, mod_tap_key2});
     90 
     91     // Press first mod-tap key.
     92     EXPECT_REPORT(driver, (KC_LCTL));
     93     mod_tap_key1.press();
     94     run_one_scan_loop();
     95     VERIFY_AND_CLEAR(driver);
     96     EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL);
     97 
     98     // Press second mod-tap key.
     99     EXPECT_REPORT(driver, (KC_LCTL, KC_RALT));
    100     mod_tap_key2.press();
    101     run_one_scan_loop();
    102     VERIFY_AND_CLEAR(driver);
    103     EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
    104 
    105     EXPECT_NO_REPORT(driver);
    106     idle_for(TAPPING_TERM + 1);
    107     VERIFY_AND_CLEAR(driver);
    108     EXPECT_EQ(get_speculative_mods(), 0);
    109     EXPECT_EQ(get_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
    110 
    111     // Release first mod-tap key.
    112     EXPECT_REPORT(driver, (KC_RALT));
    113     mod_tap_key1.release();
    114     run_one_scan_loop();
    115     VERIFY_AND_CLEAR(driver);
    116 
    117     // Release second mod-tap key.
    118     EXPECT_EMPTY_REPORT(driver);
    119     mod_tap_key2.release();
    120     run_one_scan_loop();
    121     VERIFY_AND_CLEAR(driver);
    122 }
    123 
    124 TEST_F(SpeculativeHoldAllMods, two_mod_taps_same_mods) {
    125     TestDriver driver;
    126     InSequence s;
    127     auto       mod_tap_key1 = KeymapKey(0, 1, 0, GUI_T(KC_A));
    128     auto       mod_tap_key2 = KeymapKey(0, 2, 0, GUI_T(KC_B));
    129 
    130     set_keymap({mod_tap_key1, mod_tap_key2});
    131 
    132     // Press first mod-tap key.
    133     EXPECT_REPORT(driver, (KC_LGUI));
    134     mod_tap_key1.press();
    135     run_one_scan_loop();
    136     VERIFY_AND_CLEAR(driver);
    137 
    138     // Tap second mod-tap key.
    139     EXPECT_NO_REPORT(driver);
    140     mod_tap_key2.press();
    141     run_one_scan_loop();
    142     mod_tap_key2.release();
    143     run_one_scan_loop();
    144     VERIFY_AND_CLEAR(driver);
    145 
    146     // Release first mod-tap key.
    147     EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
    148     EXPECT_REPORT(driver, (KC_LGUI));
    149     EXPECT_EMPTY_REPORT(driver);
    150     EXPECT_REPORT(driver, (KC_A));
    151     EXPECT_REPORT(driver, (KC_A, KC_B));
    152     EXPECT_REPORT(driver, (KC_A));
    153     EXPECT_EMPTY_REPORT(driver);
    154     mod_tap_key1.release();
    155     run_one_scan_loop();
    156     VERIFY_AND_CLEAR(driver);
    157 }
    158 
    159 TEST_F(SpeculativeHoldAllMods, respects_get_speculative_hold_callback) {
    160     TestDriver driver;
    161     InSequence s;
    162     auto       mod_tap_key1 = KeymapKey(0, 0, 0, LSFT_T(KC_A));
    163     auto       mod_tap_key2 = KeymapKey(0, 1, 0, LSFT_T(KC_B));
    164     auto       mod_tap_key3 = KeymapKey(0, 2, 0, LCTL_T(KC_C));
    165     auto       mod_tap_key4 = KeymapKey(0, 3, 0, LCTL_T(KC_D));
    166     auto       mod_tap_key5 = KeymapKey(0, 4, 0, RSFT_T(KC_E));
    167     auto       mod_tap_key6 = KeymapKey(0, 5, 0, RSFT_T(KC_F));
    168 
    169     set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3, mod_tap_key4, mod_tap_key5, mod_tap_key6});
    170 
    171     // Enable Speculative Hold selectively for some of the keys.
    172     get_speculative_hold_fun = [](uint16_t keycode, keyrecord_t *record) {
    173         switch (keycode) {
    174             case LSFT_T(KC_B):
    175             case LCTL_T(KC_D):
    176             case RSFT_T(KC_F):
    177                 return true;
    178         }
    179         return false;
    180     };
    181 
    182     for (KeymapKey *mod_tap_key : {&mod_tap_key2, &mod_tap_key4, &mod_tap_key6}) {
    183         SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
    184         const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
    185 
    186         // Long press and release mod_tap_key.
    187         // For these keys where Speculative Hold is enabled, then the mod should
    188         // activate immediately on keydown.
    189         EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
    190         mod_tap_key->press();
    191         run_one_scan_loop();
    192         EXPECT_EQ(get_speculative_mods(), mods);
    193         EXPECT_EQ(get_mods(), 0);
    194         VERIFY_AND_CLEAR(driver);
    195 
    196         EXPECT_NO_REPORT(driver);
    197         idle_for(TAPPING_TERM + 1);
    198         EXPECT_EQ(get_speculative_mods(), 0);
    199         EXPECT_EQ(get_mods(), mods);
    200         VERIFY_AND_CLEAR(driver);
    201 
    202         EXPECT_EMPTY_REPORT(driver);
    203         mod_tap_key->release();
    204         idle_for(TAPPING_TERM + 1);
    205         EXPECT_EQ(get_speculative_mods(), 0);
    206         EXPECT_EQ(get_mods(), 0);
    207         VERIFY_AND_CLEAR(driver);
    208     }
    209 
    210     for (KeymapKey *mod_tap_key : {&mod_tap_key1, &mod_tap_key3, &mod_tap_key5}) {
    211         SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
    212         const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
    213 
    214         // Long press and release mod_tap_key.
    215         // For these keys where Speculative Hold is disabled, the mod should
    216         // activate when the key has settled after the tapping term.
    217         EXPECT_NO_REPORT(driver);
    218         mod_tap_key->press();
    219         run_one_scan_loop();
    220         EXPECT_EQ(get_speculative_mods(), 0);
    221         EXPECT_EQ(get_mods(), 0);
    222         VERIFY_AND_CLEAR(driver);
    223 
    224         EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
    225         idle_for(TAPPING_TERM + 1);
    226         EXPECT_EQ(get_speculative_mods(), 0);
    227         EXPECT_EQ(get_mods(), mods);
    228         VERIFY_AND_CLEAR(driver);
    229 
    230         EXPECT_EMPTY_REPORT(driver);
    231         mod_tap_key->release();
    232         idle_for(TAPPING_TERM + 1);
    233         EXPECT_EQ(get_speculative_mods(), 0);
    234         EXPECT_EQ(get_mods(), 0);
    235         VERIFY_AND_CLEAR(driver);
    236     }
    237 }
    238 
    239 TEST_F(SpeculativeHoldAllMods, respects_magic_mod_config) {
    240     TestDriver driver;
    241     InSequence s;
    242     auto       mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_P));
    243 
    244     set_keymap({mod_tap_key});
    245 
    246     keymap_config.swap_lctl_lgui = true;
    247 
    248     // Press mod-tap-hold key.
    249     EXPECT_REPORT(driver, (KC_LGUI));
    250     mod_tap_key.press();
    251     run_one_scan_loop();
    252     VERIFY_AND_CLEAR(driver);
    253 
    254     // Release mod-tap-hold key.
    255     EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
    256     EXPECT_REPORT(driver, (KC_LGUI));
    257     EXPECT_EMPTY_REPORT(driver);
    258     EXPECT_REPORT(driver, (KC_P));
    259     EXPECT_EMPTY_REPORT(driver);
    260     mod_tap_key.release();
    261     idle_for(TAPPING_TERM + 1);
    262     VERIFY_AND_CLEAR(driver);
    263 
    264     keymap_config.swap_lctl_lgui = false;
    265 
    266     // Press mod-tap-hold key.
    267     EXPECT_REPORT(driver, (KC_LCTL));
    268     mod_tap_key.press();
    269     run_one_scan_loop();
    270     VERIFY_AND_CLEAR(driver);
    271 
    272     // Release mod-tap-hold key.
    273     EXPECT_EMPTY_REPORT(driver);
    274     EXPECT_REPORT(driver, (KC_P));
    275     EXPECT_EMPTY_REPORT(driver);
    276     mod_tap_key.release();
    277     run_one_scan_loop();
    278     VERIFY_AND_CLEAR(driver);
    279 }
    280 
    281 TEST_F(SpeculativeHoldAllMods, tap_a_mod_tap_key_while_another_mod_tap_key_is_held) {
    282     TestDriver driver;
    283     InSequence s;
    284     auto       first_mod_tap_key  = KeymapKey(0, 1, 0, SFT_T(KC_P));
    285     auto       second_mod_tap_key = KeymapKey(0, 2, 0, RSFT_T(KC_A));
    286 
    287     set_keymap({first_mod_tap_key, second_mod_tap_key});
    288 
    289     // Press first mod-tap-hold key.
    290     EXPECT_REPORT(driver, (KC_LSFT));
    291     first_mod_tap_key.press();
    292     run_one_scan_loop();
    293     VERIFY_AND_CLEAR(driver);
    294 
    295     // Press second tap-hold key.
    296     EXPECT_REPORT(driver, (KC_LSFT, KC_RSFT));
    297     second_mod_tap_key.press();
    298     run_one_scan_loop();
    299     VERIFY_AND_CLEAR(driver);
    300 
    301     // Release second tap-hold key.
    302     EXPECT_NO_REPORT(driver);
    303     second_mod_tap_key.release();
    304     run_one_scan_loop();
    305     VERIFY_AND_CLEAR(driver);
    306 
    307     // Release first mod-tap-hold key.
    308     EXPECT_EMPTY_REPORT(driver);
    309     EXPECT_REPORT(driver, (KC_P));
    310     EXPECT_REPORT(driver, (KC_P, KC_A));
    311     EXPECT_REPORT(driver, (KC_P));
    312     EXPECT_EMPTY_REPORT(driver);
    313     first_mod_tap_key.release();
    314     run_one_scan_loop();
    315     VERIFY_AND_CLEAR(driver);
    316 }
    317 
    318 TEST_F(SpeculativeHoldAllMods, tap_mod_tap_key_two_times) {
    319     TestDriver driver;
    320     InSequence s;
    321     auto       mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
    322 
    323     set_keymap({mod_tap_key});
    324 
    325     // Press mod-tap-hold key.
    326     EXPECT_REPORT(driver, (KC_LSFT));
    327     mod_tap_key.press();
    328     run_one_scan_loop();
    329     VERIFY_AND_CLEAR(driver);
    330 
    331     // Release mod-tap-hold key.
    332     EXPECT_EMPTY_REPORT(driver);
    333     EXPECT_REPORT(driver, (KC_P));
    334     EXPECT_EMPTY_REPORT(driver);
    335     mod_tap_key.release();
    336     run_one_scan_loop();
    337     VERIFY_AND_CLEAR(driver);
    338 
    339     // Press mod-tap-hold key again.
    340     EXPECT_REPORT(driver, (KC_P));
    341     mod_tap_key.press();
    342     idle_for(TAPPING_TERM);
    343     VERIFY_AND_CLEAR(driver);
    344 
    345     // Release mod-tap-hold key.
    346     EXPECT_EMPTY_REPORT(driver);
    347     mod_tap_key.release();
    348     run_one_scan_loop();
    349     VERIFY_AND_CLEAR(driver);
    350 }
    351 
    352 } // namespace