summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--quantum/action_tapping.c2
-rw-r--r--tests/tap_hold_configurations/speculative_hold/all_mods/config.h23
-rw-r--r--tests/tap_hold_configurations/speculative_hold/all_mods/test.mk18
-rw-r--r--tests/tap_hold_configurations/speculative_hold/all_mods/test_tap_hold.cpp352
-rw-r--r--tests/tap_hold_configurations/speculative_hold/default/config.h1
-rw-r--r--tests/tap_hold_configurations/speculative_hold/default/test.mk1
-rw-r--r--tests/tap_hold_configurations/speculative_hold/default/test_tap_hold.cpp305
7 files changed, 417 insertions, 285 deletions
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) {
813 813
814__attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) { 814__attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
815 const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode)); 815 const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode));
816 return (mods & (MOD_LCTL | MOD_LSFT)) == mods; 816 return (mods & (MOD_LCTL | MOD_LSFT)) == (mods & (MOD_HYPR));
817} 817}
818 818
819void speculative_key_settled(keyrecord_t *record) { 819void 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 @@
1/* Copyright 2022 Vladislav Kucheriavykh
2 * Copyright 2025 Google LLC
3 *
4 * This program is free software: you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation, either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18#pragma once
19
20#include "test_common.h"
21
22#define SPECULATIVE_HOLD
23#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 @@
1# Copyright 2022 Vladislav Kucheriavykh
2# Copyright 2025 Google LLC
3#
4# This program is free software: you can redistribute it and/or modify
5# it under the terms of the GNU General Public License as published by
6# the Free Software Foundation, either version 2 of the License, or
7# (at your option) any later version.
8#
9# This program is distributed in the hope that it will be useful,
10# but WITHOUT ANY WARRANTY; without even the implied warranty of
11# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12# GNU General Public License for more details.
13#
14# You should have received a copy of the GNU General Public License
15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16
17MAGIC_ENABLE = yes
18
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 @@
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
24using testing::_;
25using testing::AnyNumber;
26using testing::InSequence;
27
28namespace {
29
30// Gets the unpacked 8-bit mods corresponding to a given mod-tap keycode.
31uint8_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
36bool 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.
42std::function<bool(uint16_t, keyrecord_t *)> get_speculative_hold_fun = get_speculative_hold_all_mods;
43
44extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
45 return get_speculative_hold_fun(keycode, record);
46}
47
48class SpeculativeHoldAllMods : public TestFixture {
49 public:
50 void SetUp() override {
51 get_speculative_hold_fun = get_speculative_hold_all_mods;
52 }
53};
54
55TEST_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
83TEST_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
124TEST_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
159TEST_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
239TEST_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
281TEST_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
318TEST_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
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 @@
20#include "test_common.h" 20#include "test_common.h"
21 21
22#define SPECULATIVE_HOLD 22#define SPECULATIVE_HOLD
23#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 @@
15# along with this program. If not, see <http://www.gnu.org/licenses/>. 15# along with this program. If not, see <http://www.gnu.org/licenses/>.
16 16
17KEY_OVERRIDE_ENABLE = yes 17KEY_OVERRIDE_ENABLE = yes
18MAGIC_ENABLE = yes
19 18
20INTROSPECTION_KEYMAP_C = test_keymap.c 19INTROSPECTION_KEYMAP_C = test_keymap.c
21 20
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;
27 27
28namespace { 28namespace {
29 29
30// Gets the unpacked 8-bit mods corresponding to a given mod-tap keycode.
31uint8_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
36bool get_speculative_hold_all_keys(uint16_t keycode, keyrecord_t *record) {
37 return true; // Enable Speculative Hold for all mod-tap keys.
38}
39
40bool process_record_user_default(uint16_t keycode, keyrecord_t *record) { 30bool process_record_user_default(uint16_t keycode, keyrecord_t *record) {
41 return true; 31 return true;
42} 32}
43 33
44// Indirection so that get_speculative_hold() and process_record_user() can be 34// Indirection so that process_record_user() can be
45// replaced with other functions in the test cases below. 35// replaced with other functions in the test cases below.
46std::function<bool(uint16_t, keyrecord_t *)> get_speculative_hold_fun = get_speculative_hold_all_keys; 36std::function<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default;
47std::function<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default;
48
49extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
50 return get_speculative_hold_fun(keycode, record);
51}
52 37
53extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) { 38extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) {
54 return process_record_user_fun(keycode, record); 39 return process_record_user_fun(keycode, record);
@@ -57,11 +42,30 @@ extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) {
57class SpeculativeHoldDefault : public TestFixture { 42class SpeculativeHoldDefault : public TestFixture {
58 public: 43 public:
59 void SetUp() override { 44 void SetUp() override {
60 get_speculative_hold_fun = get_speculative_hold_all_keys; 45 process_record_user_fun = process_record_user_default;
61 process_record_user_fun = process_record_user_default;
62 } 46 }
63}; 47};
64 48
49TEST_F(SpeculativeHoldDefault, get_speculative_hold) {
50 keyrecord_t record = {};
51
52 // With the default definition of get_speculative_hold(), Speculative Hold
53 // is enabled for Ctrl and Shift.
54 EXPECT_TRUE(get_speculative_hold(MT(MOD_LCTL, KC_NO), &record));
55 EXPECT_TRUE(get_speculative_hold(MT(MOD_LSFT, KC_NO), &record));
56 EXPECT_TRUE(get_speculative_hold(MT(MOD_LCTL | MOD_LSFT, KC_NO), &record));
57 EXPECT_TRUE(get_speculative_hold(MT(MOD_RCTL, KC_NO), &record));
58 EXPECT_TRUE(get_speculative_hold(MT(MOD_RSFT, KC_NO), &record));
59 EXPECT_TRUE(get_speculative_hold(MT(MOD_RCTL | MOD_RSFT, KC_NO), &record));
60
61 EXPECT_FALSE(get_speculative_hold(MT(MOD_LALT, KC_NO), &record));
62 EXPECT_FALSE(get_speculative_hold(MT(MOD_LGUI, KC_NO), &record));
63 EXPECT_FALSE(get_speculative_hold(MT(MOD_RALT, KC_NO), &record));
64 EXPECT_FALSE(get_speculative_hold(MT(MOD_RGUI, KC_NO), &record));
65 EXPECT_FALSE(get_speculative_hold(MT(MOD_MEH, KC_NO), &record));
66 EXPECT_FALSE(get_speculative_hold(MT(MOD_HYPR, KC_NO), &record));
67}
68
65TEST_F(SpeculativeHoldDefault, tap_mod_tap) { 69TEST_F(SpeculativeHoldDefault, tap_mod_tap) {
66 TestDriver driver; 70 TestDriver driver;
67 InSequence s; 71 InSequence s;
@@ -103,232 +107,6 @@ TEST_F(SpeculativeHoldDefault, tap_mod_tap) {
103 VERIFY_AND_CLEAR(driver); 107 VERIFY_AND_CLEAR(driver);
104} 108}
105 109
106TEST_F(SpeculativeHoldDefault, tap_mod_tap_neutralized) {
107 TestDriver driver;
108 InSequence s;
109 auto mod_tap_key = KeymapKey(0, 1, 0, GUI_T(KC_P));
110
111 set_keymap({mod_tap_key});
112
113 // Press mod-tap-hold key. Mod is held speculatively.
114 EXPECT_REPORT(driver, (KC_LGUI));
115 mod_tap_key.press();
116 idle_for(10);
117 VERIFY_AND_CLEAR(driver);
118
119 // Release mod-tap-hold key. Speculative mod is neutralized and canceled.
120 EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
121 EXPECT_REPORT(driver, (KC_LGUI));
122 EXPECT_EMPTY_REPORT(driver);
123 EXPECT_REPORT(driver, (KC_P));
124 EXPECT_EMPTY_REPORT(driver);
125 mod_tap_key.release();
126 run_one_scan_loop();
127 VERIFY_AND_CLEAR(driver);
128
129 // Idle for tapping term of mod tap hold key.
130 idle_for(TAPPING_TERM - 10);
131 VERIFY_AND_CLEAR(driver);
132}
133
134TEST_F(SpeculativeHoldDefault, hold_two_mod_taps) {
135 TestDriver driver;
136 InSequence s;
137 auto mod_tap_key1 = KeymapKey(0, 1, 0, LCTL_T(KC_A));
138 auto mod_tap_key2 = KeymapKey(0, 2, 0, RALT_T(KC_B));
139
140 set_keymap({mod_tap_key1, mod_tap_key2});
141
142 // Press first mod-tap key.
143 EXPECT_REPORT(driver, (KC_LCTL));
144 mod_tap_key1.press();
145 run_one_scan_loop();
146 VERIFY_AND_CLEAR(driver);
147 EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL);
148
149 // Press second mod-tap key.
150 EXPECT_REPORT(driver, (KC_LCTL, KC_RALT));
151 mod_tap_key2.press();
152 run_one_scan_loop();
153 VERIFY_AND_CLEAR(driver);
154 EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
155
156 EXPECT_NO_REPORT(driver);
157 idle_for(TAPPING_TERM + 1);
158 VERIFY_AND_CLEAR(driver);
159 EXPECT_EQ(get_speculative_mods(), 0);
160 EXPECT_EQ(get_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
161
162 // Release first mod-tap key.
163 EXPECT_REPORT(driver, (KC_RALT));
164 mod_tap_key1.release();
165 run_one_scan_loop();
166 VERIFY_AND_CLEAR(driver);
167
168 // Release second mod-tap key.
169 EXPECT_EMPTY_REPORT(driver);
170 mod_tap_key2.release();
171 run_one_scan_loop();
172 VERIFY_AND_CLEAR(driver);
173}
174
175TEST_F(SpeculativeHoldDefault, two_mod_taps_same_mods) {
176 TestDriver driver;
177 InSequence s;
178 auto mod_tap_key1 = KeymapKey(0, 1, 0, GUI_T(KC_A));
179 auto mod_tap_key2 = KeymapKey(0, 2, 0, GUI_T(KC_B));
180
181 set_keymap({mod_tap_key1, mod_tap_key2});
182
183 // Press first mod-tap key.
184 EXPECT_REPORT(driver, (KC_LGUI));
185 mod_tap_key1.press();
186 run_one_scan_loop();
187 VERIFY_AND_CLEAR(driver);
188
189 // Tap second mod-tap key.
190 EXPECT_NO_REPORT(driver);
191 mod_tap_key2.press();
192 run_one_scan_loop();
193 mod_tap_key2.release();
194 run_one_scan_loop();
195 VERIFY_AND_CLEAR(driver);
196
197 // Release first mod-tap key.
198 EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
199 EXPECT_REPORT(driver, (KC_LGUI));
200 EXPECT_EMPTY_REPORT(driver);
201 EXPECT_REPORT(driver, (KC_A));
202 EXPECT_REPORT(driver, (KC_A, KC_B));
203 EXPECT_REPORT(driver, (KC_A));
204 EXPECT_EMPTY_REPORT(driver);
205 mod_tap_key1.release();
206 run_one_scan_loop();
207 VERIFY_AND_CLEAR(driver);
208}
209
210TEST_F(SpeculativeHoldDefault, respects_get_speculative_hold_callback) {
211 TestDriver driver;
212 InSequence s;
213 auto mod_tap_key1 = KeymapKey(0, 0, 0, LSFT_T(KC_A));
214 auto mod_tap_key2 = KeymapKey(0, 1, 0, LSFT_T(KC_B));
215 auto mod_tap_key3 = KeymapKey(0, 2, 0, LCTL_T(KC_C));
216 auto mod_tap_key4 = KeymapKey(0, 3, 0, LCTL_T(KC_D));
217 auto mod_tap_key5 = KeymapKey(0, 4, 0, RSFT_T(KC_E));
218 auto mod_tap_key6 = KeymapKey(0, 5, 0, RSFT_T(KC_F));
219
220 set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3, mod_tap_key4, mod_tap_key5, mod_tap_key6});
221
222 // Enable Speculative Hold selectively for some of the keys.
223 get_speculative_hold_fun = [](uint16_t keycode, keyrecord_t *record) {
224 switch (keycode) {
225 case LSFT_T(KC_B):
226 case LCTL_T(KC_D):
227 case RSFT_T(KC_F):
228 return true;
229 }
230 return false;
231 };
232
233 for (KeymapKey *mod_tap_key : {&mod_tap_key2, &mod_tap_key4, &mod_tap_key6}) {
234 SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
235 const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
236
237 // Long press and release mod_tap_key.
238 // For these keys where Speculative Hold is enabled, then the mod should
239 // activate immediately on keydown.
240 EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
241 mod_tap_key->press();
242 run_one_scan_loop();
243 EXPECT_EQ(get_speculative_mods(), mods);
244 EXPECT_EQ(get_mods(), 0);
245 VERIFY_AND_CLEAR(driver);
246
247 EXPECT_NO_REPORT(driver);
248 idle_for(TAPPING_TERM + 1);
249 EXPECT_EQ(get_speculative_mods(), 0);
250 EXPECT_EQ(get_mods(), mods);
251 VERIFY_AND_CLEAR(driver);
252
253 EXPECT_EMPTY_REPORT(driver);
254 mod_tap_key->release();
255 idle_for(TAPPING_TERM + 1);
256 EXPECT_EQ(get_speculative_mods(), 0);
257 EXPECT_EQ(get_mods(), 0);
258 VERIFY_AND_CLEAR(driver);
259 }
260
261 for (KeymapKey *mod_tap_key : {&mod_tap_key1, &mod_tap_key3, &mod_tap_key5}) {
262 SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
263 const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
264
265 // Long press and release mod_tap_key.
266 // For these keys where Speculative Hold is disabled, the mod should
267 // activate when the key has settled after the tapping term.
268 EXPECT_NO_REPORT(driver);
269 mod_tap_key->press();
270 run_one_scan_loop();
271 EXPECT_EQ(get_speculative_mods(), 0);
272 EXPECT_EQ(get_mods(), 0);
273 VERIFY_AND_CLEAR(driver);
274
275 EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
276 idle_for(TAPPING_TERM + 1);
277 EXPECT_EQ(get_speculative_mods(), 0);
278 EXPECT_EQ(get_mods(), mods);
279 VERIFY_AND_CLEAR(driver);
280
281 EXPECT_EMPTY_REPORT(driver);
282 mod_tap_key->release();
283 idle_for(TAPPING_TERM + 1);
284 EXPECT_EQ(get_speculative_mods(), 0);
285 EXPECT_EQ(get_mods(), 0);
286 VERIFY_AND_CLEAR(driver);
287 }
288}
289
290TEST_F(SpeculativeHoldDefault, respects_magic_mod_config) {
291 TestDriver driver;
292 InSequence s;
293 auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_P));
294
295 set_keymap({mod_tap_key});
296
297 keymap_config.swap_lctl_lgui = true;
298
299 // Press mod-tap-hold key.
300 EXPECT_REPORT(driver, (KC_LGUI));
301 mod_tap_key.press();
302 run_one_scan_loop();
303 VERIFY_AND_CLEAR(driver);
304
305 // Release mod-tap-hold key.
306 EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
307 EXPECT_REPORT(driver, (KC_LGUI));
308 EXPECT_EMPTY_REPORT(driver);
309 EXPECT_REPORT(driver, (KC_P));
310 EXPECT_EMPTY_REPORT(driver);
311 mod_tap_key.release();
312 idle_for(TAPPING_TERM + 1);
313 VERIFY_AND_CLEAR(driver);
314
315 keymap_config.swap_lctl_lgui = false;
316
317 // Press mod-tap-hold key.
318 EXPECT_REPORT(driver, (KC_LCTL));
319 mod_tap_key.press();
320 run_one_scan_loop();
321 VERIFY_AND_CLEAR(driver);
322
323 // Release mod-tap-hold key.
324 EXPECT_EMPTY_REPORT(driver);
325 EXPECT_REPORT(driver, (KC_P));
326 EXPECT_EMPTY_REPORT(driver);
327 mod_tap_key.release();
328 run_one_scan_loop();
329 VERIFY_AND_CLEAR(driver);
330}
331
332TEST_F(SpeculativeHoldDefault, key_overrides) { 110TEST_F(SpeculativeHoldDefault, key_overrides) {
333 TestDriver driver; 111 TestDriver driver;
334 InSequence s; 112 InSequence s;
@@ -401,43 +179,6 @@ TEST_F(SpeculativeHoldDefault, tap_regular_key_while_mod_tap_key_is_held) {
401 VERIFY_AND_CLEAR(driver); 179 VERIFY_AND_CLEAR(driver);
402} 180}
403 181
404TEST_F(SpeculativeHoldDefault, tap_a_mod_tap_key_while_another_mod_tap_key_is_held) {
405 TestDriver driver;
406 InSequence s;
407 auto first_mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
408 auto second_mod_tap_key = KeymapKey(0, 2, 0, RSFT_T(KC_A));
409
410 set_keymap({first_mod_tap_key, second_mod_tap_key});
411
412 // Press first mod-tap-hold key.
413 EXPECT_REPORT(driver, (KC_LSFT));
414 first_mod_tap_key.press();
415 run_one_scan_loop();
416 VERIFY_AND_CLEAR(driver);
417
418 // Press second tap-hold key.
419 EXPECT_REPORT(driver, (KC_LSFT, KC_RSFT));
420 second_mod_tap_key.press();
421 run_one_scan_loop();
422 VERIFY_AND_CLEAR(driver);
423
424 // Release second tap-hold key.
425 EXPECT_NO_REPORT(driver);
426 second_mod_tap_key.release();
427 run_one_scan_loop();
428 VERIFY_AND_CLEAR(driver);
429
430 // Release first mod-tap-hold key.
431 EXPECT_EMPTY_REPORT(driver);
432 EXPECT_REPORT(driver, (KC_P));
433 EXPECT_REPORT(driver, (KC_P, KC_A));
434 EXPECT_REPORT(driver, (KC_P));
435 EXPECT_EMPTY_REPORT(driver);
436 first_mod_tap_key.release();
437 run_one_scan_loop();
438 VERIFY_AND_CLEAR(driver);
439}
440
441TEST_F(SpeculativeHoldDefault, tap_mod_tap_key_two_times) { 182TEST_F(SpeculativeHoldDefault, tap_mod_tap_key_two_times) {
442 TestDriver driver; 183 TestDriver driver;
443 InSequence s; 184 InSequence s;