summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPascal Getreuer <50221757+getreuer@users.noreply.github.com>2023-05-20 05:35:06 -0700
committerGitHub <noreply@github.com>2023-05-20 22:35:06 +1000
commit3993b15f054265071730cdb450f43457dcf4c64a (patch)
tree61c5b980ed14428bae3c0278c27937dbb5d33627
parente1766df185869d8a591228d37f3f7b6d5b4049b4 (diff)
[Core] Add Repeat Key ("repeat last key") as a core feature. (#19700)
Co-authored-by: casuanoob <96005765+casuanoob@users.noreply.github.com> Co-authored-by: Sergey Vlasov <sigprof@gmail.com>
-rw-r--r--builddefs/generic_features.mk1
-rw-r--r--builddefs/show_options.mk3
-rw-r--r--data/constants/keycodes/keycodes_0.0.3.hjson0
-rw-r--r--data/constants/keycodes/keycodes_0.0.3_quantum.hjson18
-rw-r--r--docs/_summary.md1
-rw-r--r--docs/feature_repeat_key.md457
-rw-r--r--docs/ja/_summary.md1
-rw-r--r--docs/keycodes.md9
-rw-r--r--docs/zh-cn/_summary.md1
-rw-r--r--quantum/action.c4
-rw-r--r--quantum/action.h2
-rw-r--r--quantum/keycodes.h6
-rw-r--r--quantum/process_keycode/process_repeat_key.c109
-rw-r--r--quantum/process_keycode/process_repeat_key.h62
-rw-r--r--quantum/quantum.c5
-rw-r--r--quantum/quantum.h5
-rw-r--r--quantum/repeat_key.c282
-rw-r--r--quantum/repeat_key.h80
-rw-r--r--tests/repeat_key/alt_repeat_key/config.h18
-rw-r--r--tests/repeat_key/alt_repeat_key/test.mk18
-rw-r--r--tests/repeat_key/alt_repeat_key/test_alt_repeat_key.cpp523
-rw-r--r--tests/repeat_key/config.h20
-rw-r--r--tests/repeat_key/repeat_key_combo/config.h18
-rw-r--r--tests/repeat_key/repeat_key_combo/test.mk18
-rw-r--r--tests/repeat_key/repeat_key_combo/test_repeat_key_combo.cpp67
-rw-r--r--tests/repeat_key/test.mk18
-rw-r--r--tests/repeat_key/test_repeat_key.cpp754
-rw-r--r--tests/test_common/keycode_table.cpp2
-rw-r--r--tests/test_common/test_driver.hpp12
29 files changed, 2508 insertions, 6 deletions
diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk
index 5a1ef5c6f0..4e058dcd26 100644
--- a/builddefs/generic_features.mk
+++ b/builddefs/generic_features.mk
@@ -32,6 +32,7 @@ GENERIC_FEATURES = \
32 KEY_OVERRIDE \ 32 KEY_OVERRIDE \
33 LEADER \ 33 LEADER \
34 PROGRAMMABLE_BUTTON \ 34 PROGRAMMABLE_BUTTON \
35 REPEAT_KEY \
35 SECURE \ 36 SECURE \
36 SPACE_CADET \ 37 SPACE_CADET \
37 SWAP_HANDS \ 38 SWAP_HANDS \
diff --git a/builddefs/show_options.mk b/builddefs/show_options.mk
index 9723b45438..8bcc02083b 100644
--- a/builddefs/show_options.mk
+++ b/builddefs/show_options.mk
@@ -85,7 +85,8 @@ OTHER_OPTION_NAMES = \
85 SECURE_ENABLE \ 85 SECURE_ENABLE \
86 CAPS_WORD_ENABLE \ 86 CAPS_WORD_ENABLE \
87 AUTOCORRECT_ENABLE \ 87 AUTOCORRECT_ENABLE \
88 TRI_LAYER_ENABLE 88 TRI_LAYER_ENABLE \
89 REPEAT_KEY_ENABLE
89 90
90define NAME_ECHO 91define NAME_ECHO
91 @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)" 92 @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
diff --git a/data/constants/keycodes/keycodes_0.0.3.hjson b/data/constants/keycodes/keycodes_0.0.3.hjson
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/data/constants/keycodes/keycodes_0.0.3.hjson
diff --git a/data/constants/keycodes/keycodes_0.0.3_quantum.hjson b/data/constants/keycodes/keycodes_0.0.3_quantum.hjson
new file mode 100644
index 0000000000..23a3c9b06d
--- /dev/null
+++ b/data/constants/keycodes/keycodes_0.0.3_quantum.hjson
@@ -0,0 +1,18 @@
1{
2 "keycodes": {
3 "0x7C79": {
4 "group": "quantum",
5 "key": "QK_REPEAT_KEY",
6 "aliases": [
7 "QK_REP"
8 ]
9 },
10 "0x7C7A": {
11 "group": "quantum",
12 "key": "QK_ALT_REPEAT_KEY",
13 "aliases": [
14 "QK_AREP"
15 ]
16 }
17 }
18}
diff --git a/docs/_summary.md b/docs/_summary.md
index ce579cb071..3d9bde6b17 100644
--- a/docs/_summary.md
+++ b/docs/_summary.md
@@ -70,6 +70,7 @@
70 * [Macros](feature_macros.md) 70 * [Macros](feature_macros.md)
71 * [Mouse Keys](feature_mouse_keys.md) 71 * [Mouse Keys](feature_mouse_keys.md)
72 * [Programmable Button](feature_programmable_button.md) 72 * [Programmable Button](feature_programmable_button.md)
73 * [Repeat Key](feature_repeat_key.md)
73 * [Space Cadet Shift](feature_space_cadet.md) 74 * [Space Cadet Shift](feature_space_cadet.md)
74 * [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md) 75 * [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md)
75 76
diff --git a/docs/feature_repeat_key.md b/docs/feature_repeat_key.md
new file mode 100644
index 0000000000..6fa8a724ef
--- /dev/null
+++ b/docs/feature_repeat_key.md
@@ -0,0 +1,457 @@
1# Repeat Key
2
3The Repeat Key performs the action of the last pressed key. Tapping the Repeat
4Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for
5typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd>
6can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is
7potentially faster and more comfortable. The Repeat Key is also useful for
8hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word.
9
10Repeat Key remembers mods that were active with the last key press. These mods
11are combined with any additional mods while pressing the Repeat Key. If the last
12press key was <kbd>Ctrl</kbd> + <kbd>Z</kbd>, then <kbd>Shift</kbd> +
13<kbd>Repeat</kbd> performs Ctrl + Shift + `Z`.
14
15## How do I enable Repeat Key
16
17In your `rules.mk`, add:
18
19```make
20REPEAT_KEY_ENABLE = yes
21```
22
23Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short
24alias `QK_REP`). Optionally, use the keycode `QK_ALT_REPEAT_KEY` (short alias
25`QK_AREP`) on another key.
26
27## Keycodes
28
29|Keycode |Aliases |Description |
30|-----------------------|---------|-------------------------------------|
31|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
32|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key |
33
34## Alternate Repeating
35
36The Alternate Repeat Key performs the "alternate" action of the last pressed key
37if it is defined. By default, Alternate Repeat is defined for navigation keys to
38act in the reverse direction. When the last key is the common "select by word"
39hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl +
40Shift + Left Arrow, which together with the Repeat Key enables convenient
41selection by words in either direction.
42
43Alternate Repeat is enabled with the Repeat Key by default. Optionally, to
44reduce firmware size, Alternate Repeat may be disabled by adding in config.h:
45
46```c
47#define NO_ALT_REPEAT_KEY
48```
49
50The following alternate keys are defined by default. See
51`get_alt_repeat_key_keycode_user()` below for how to change or add to these
52definitions. Where it makes sense, these definitions also include combinations
53with mods, like Ctrl + Left &harr; Ctrl + Right Arrow.
54
55**Navigation**
56
57|Keycodes |Description |
58|-----------------------------------|-----------------------------------|
59|`KC_LEFT` &harr; `KC_RGHT` | Left &harr; Right Arrow |
60|`KC_UP` &harr; `KC_DOWN` | Up &harr; Down Arrow |
61|`KC_HOME` &harr; `KC_END` | Home &harr; End |
62|`KC_PGUP` &harr; `KC_PGDN` | Page Up &harr; Page Down |
63|`KC_MS_L` &harr; `KC_MS_R` | Mouse Cursor Left &harr; Right |
64|`KC_MS_U` &harr; `KC_MS_D` | Mouse Cursor Up &harr; Down |
65|`KC_WH_L` &harr; `KC_WH_R` | Mouse Wheel Left &harr; Right |
66|`KC_WH_U` &harr; `KC_WH_D` | Mouse Wheel Up &harr; Down |
67
68**Misc**
69
70|Keycodes |Description |
71|-----------------------------------|-----------------------------------|
72|`KC_BSPC` &harr; `KC_DEL` | Backspace &harr; Delete |
73|`KC_LBRC` &harr; `KC_RBRC` | `[` &harr; `]` |
74|`KC_LCBR` &harr; `KC_RCBR` | `{` &harr; `}` |
75
76**Media**
77
78|Keycodes |Description |
79|-----------------------------------|-----------------------------------|
80|`KC_WBAK` &harr; `KC_WFWD` | Browser Back &harr; Forward |
81|`KC_MNXT` &harr; `KC_MPRV` | Next &harr; Previous Media Track |
82|`KC_MFFD` &harr; `KC_MRWD` | Fast Forward &harr; Rewind Media |
83|`KC_VOLU` &harr; `KC_VOLD` | Volume Up &harr; Down |
84|`KC_BRIU` &harr; `KC_BRID` | Brightness Up &harr; Down |
85
86**Hotkeys in Vim, Emacs, and other programs**
87
88|Keycodes |Description |
89|-----------------------------------|-----------------------------------|
90|mod + `KC_F` &harr; mod + `KC_B` | Forward &harr; Backward |
91|mod + `KC_D` &harr; mod + `KC_U` | Down &harr; Up |
92|mod + `KC_N` &harr; mod + `KC_P` | Next &harr; Previous |
93|mod + `KC_A` &harr; mod + `KC_E` | Home &harr; End |
94|mod + `KC_O` &harr; mod + `KC_I` | Vim jump list Older &harr; Newer |
95|`KC_J` &harr; `KC_K` | Down &harr; Up |
96|`KC_H` &harr; `KC_L` | Left &harr; Right |
97|`KC_W` &harr; `KC_B` | Forward &harr; Backward by Word |
98
99(where above, "mod" is Ctrl, Alt, or GUI)
100
101
102## Defining alternate keys
103
104Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate"
105for additional keys or override the default definitions. For example, to define
106Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in
107keymap.c:
108
109```c
110uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
111 if ((mods & MOD_MASK_CTRL)) { // Was Ctrl held?
112 switch (keycode) {
113 case KC_Y: return C(KC_Z); // Ctrl + Y reverses to Ctrl + Z.
114 case KC_Z: return C(KC_Y); // Ctrl + Z reverses to Ctrl + Y.
115 }
116 }
117
118 return KC_TRNS; // Defer to default definitions.
119}
120```
121
122The `keycode` and `mods` args are the keycode and mods that were active with the
123last pressed key. The meaning of the return value from this function is:
124
125* `KC_NO` &ndash; do nothing (any predefined alternate key is not used);
126* `KC_TRNS` &ndash; use the default alternate key if it exists;
127* anything else &ndash; use the specified keycode. Any keycode may be returned
128 as an alternate key, including custom keycodes.
129
130Another example, defining Shift + Tab as the alternate of Tab, and vice versa:
131
132```c
133uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
134 bool shifted = (mods & MOD_MASK_SHIFT); // Was Shift held?
135 switch (keycode) {
136 case KC_TAB:
137 if (shifted) { // If the last key was Shift + Tab,
138 return KC_TAB; // ... the reverse is Tab.
139 } else { // Otherwise, the last key was Tab,
140 return S(KC_TAB); // ... and the reverse is Shift + Tab.
141 }
142 }
143
144 return KC_TRNS;
145}
146```
147
148#### Eliminating SFBs
149
150Alternate Repeat can be configured more generally to perform an action that
151"complements" the last key. Alternate Repeat is not limited to reverse
152repeating, and it need not be symmetric. You can use it to eliminate cases of
153same-finger bigrams in your layout, that is, pairs of letters typed by the same
154finger. The following addresses the top 5 same-finger bigrams in English on
155QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt
156Repeat</kbd>.
157
158```c
159uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
160 switch (keycode) {
161 case KC_E: return KC_D; // For "ED" bigram.
162 case KC_D: return KC_E; // For "DE" bigram.
163 case KC_C: return KC_E; // For "CE" bigram.
164 case KC_L: return KC_O; // For "LO" bigram.
165 case KC_U: return KC_N; // For "UN" bigram.
166 }
167
168 return KC_TRNS;
169}
170```
171
172#### Typing shortcuts
173
174A useful possibility is having Alternate Repeat press [a
175macro](feature_macros.md). This way macros can be used without having to
176dedicate keys to them. The following defines a couple shortcuts.
177
178* Typing <kbd>K</kbd>, <kbd>Alt Repeat</kbd> produces "`keyboard`," with the
179 initial "`k`" typed as usual and the "`eybord`" produced by the macro.
180* Typing <kbd>.</kbd>, <kbd>Alt Repeat</kbd> produces "`../`," handy for "up
181 directory" on the shell. Similary, <kbd>.</kbd> types the initial "`.`" and
182 "`./`" is produced by the macro.
183
184```c
185enum custom_keycodes {
186 M_KEYBOARD = SAFE_RANGE,
187 M_UPDIR,
188 // Other custom keys...
189};
190
191uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
192 switch (keycode) {
193 case KC_K: return M_KEYBOARD;
194 case KC_DOT: return M_UPDIR;
195 }
196
197 return KC_TRNS;
198}
199
200bool process_record_user(uint16_t keycode, keyrecord_t* record) {
201 switch (keycode) {
202 case M_KEYBOARD: SEND_STRING(/*k*/"eyboard"); break;
203 case M_UPDIR: SEND_STRING(/*.*/"./"); break;
204 }
205 return true;
206}
207```
208
209## Ignoring certain keys and mods
210
211In tracking what is "the last key" to be repeated or alternate repeated,
212modifier and layer switch keys are always ignored. This makes it possible to set
213some mods and change layers between pressing a key and repeating it. By default,
214all other (non-modifier, non-layer switch) keys are remembered so that they are
215eligible for repeating. To configure additional keys to be ignored, define
216`remember_last_key_user()` in your keymap.c.
217
218#### Ignoring a key
219
220The following ignores the Backspace key:
221
222```c
223bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
224 uint8_t* remembered_mods) {
225 switch (keycode) {
226 case KC_BSPC:
227 return false; // Ignore backspace.
228 }
229
230 return true; // Other keys can be repeated.
231}
232```
233
234Then for instance, the Repeat key in <kbd>Left Arrow</kbd>,
235<kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of
236repeating Backspace.
237
238The `remember_last_key_user()` callback is called on every key press excluding
239modifiers and layer switches. Returning true indicates the key is remembered,
240while false means it is ignored.
241
242#### Filtering remembered mods
243
244The `remembered_mods` arg represents the mods that will be remembered with
245this key. It can be modified to forget certain mods. This may be
246useful to forget capitalization when repeating shifted letters, so that "Aaron"
247does not becom "AAron":
248
249```c
250bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
251 uint8_t* remembered_mods) {
252 // Forget Shift on letter keys when Shift or AltGr are the only mods.
253 switch (keycode) {
254 case KC_A ... KC_Z:
255 if ((*remembered_mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) {
256 *remembered_mods &= ~MOD_MASK_SHIFT;
257 }
258 break;
259 }
260
261 return true;
262}
263```
264
265#### Further conditions
266
267Besides checking the keycode, this callback could also make conditions based on
268the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For
269example, the following ignores keys on layer 2 as well as key combinations
270involving GUI:
271
272```c
273bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
274 uint8_t* remembered_mods) {
275 if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) {
276 return false; // Ignore layer 2 keys and GUI chords.
277 }
278
279 return true; // Other keys can be repeated.
280}
281```
282
283?> See [Layer Functions](feature_layers.md#functions) and [Checking Modifier
284State](feature_advanced_keycodes.md#checking-modifier-state) for further
285details.
286
287
288## Handle how a key is repeated
289
290By default, pressing the Repeat Key will simply behave as if the last key
291were pressed again. This also works with macro keys with custom handlers,
292invoking the macro again. In case fine-tuning is needed for sensible repetition,
293you can handle how a key is repeated with `get_repeat_key_count()` within
294`process_record_user()`.
295
296The `get_repeat_key_count()` function returns a signed count of times the key
297has been repeated or alternate repeated. When a key is pressed as usual,
298`get_repeat_key_count()` is 0. On the first repeat, it is 1, then the second
299repeat, 2, and so on. Negative counts are used similarly for alternate
300repeating. For instance supposing `MY_MACRO` is a custom keycode used in the
301layout:
302
303```c
304bool process_record_user(uint16_t keycode, keyrecord_t* record) {
305 switch (keycode) {
306 case MY_MACRO:
307 if (get_repeat_key_count() > 0) {
308 // MY_MACRO is being repeated!
309 if (record->event.pressed) {
310 SEND_STRING("repeat!");
311 }
312 } else {
313 // MY_MACRO is being used normally.
314 if (record->event.pressed) {
315 SEND_STRING("macro");
316 }
317 }
318 return false;
319
320 // Other macros...
321 }
322 return true;
323}
324```
325
326## Handle how a key is alternate repeated
327
328Pressing the Alternate Repeat Key behaves as if the "alternate" of the last
329pressed key were pressed, if an alternate is defined. To define how a particular
330key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback
331as described above to define which keycode to use as its alternate. Beyond this,
332`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior
333when alternate repeating.
334
335The following example defines `MY_MACRO` as its own alternate, and specially
336handles repeating and alternate repeating:
337
338```c
339uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
340 switch (keycode) {
341 case MY_MACRO: return MY_MACRO; // MY_MACRO is its own alternate.
342 }
343 return KC_TRNS;
344}
345
346bool process_record_user(uint16_t keycode, keyrecord_t* record) {
347 switch (keycode) {
348 case MY_MACRO:
349 if (get_repeat_key_count() > 0) { // Repeating.
350 if (record->event.pressed) {
351 SEND_STRING("repeat!");
352 }
353 } else if (get_repeat_key_count() < 0) { // Alternate repeating.
354 if (record->event.pressed) {
355 SEND_STRING("alt repeat!");
356 }
357 } else { // Used normally.
358 if (record->event.pressed) {
359 SEND_STRING("macro");
360 }
361 }
362 return false;
363
364 // Other macros...
365 }
366 return true;
367}
368```
369
370
371## Functions
372
373| Function | Description |
374|--------------------------------|------------------------------------------------------------------------|
375| `get_last_keycode()` | The last key's keycode, the key to be repeated. |
376| `get_last_mods()` | Mods to apply when repeating. |
377| `set_last_keycode(kc)` | Set the keycode to be repeated. |
378| `set_last_mods(mods)` | Set the mods to apply when repeating. |
379| `get_repeat_key_count()` | Signed count of times the key has been repeated or alternate repeated. |
380| `get_alt_repeat_key_keycode()` | Keycode to be used for alternate repeating. |
381
382
383## Additional "Alternate" keys
384
385By leveraging `get_last_keycode()` in macros, it is possible to define
386additional, distinct "Alternate Repeat"-like keys. The following defines two
387keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common
388English 5-gram letter patterns, taking inspiration from
389[Stenotype](feature_stenography.md):
390
391
392| Typing | Produces | Typing | Produces |
393|----------------------------------|----------|----------------------------------|----------|
394| <kbd>A</kbd>, <kbd>ALTREP2</kbd> | `ation` | <kbd>A</kbd>, <kbd>ALTREP3</kbd> | `about` |
395| <kbd>I</kbd>, <kbd>ALTREP2</kbd> | `ition` | <kbd>I</kbd>, <kbd>ALTREP3</kbd> | `inter` |
396| <kbd>S</kbd>, <kbd>ALTREP2</kbd> | `ssion` | <kbd>S</kbd>, <kbd>ALTREP3</kbd> | `state` |
397| <kbd>T</kbd>, <kbd>ALTREP2</kbd> | `their` | <kbd>T</kbd>, <kbd>ALTREP3</kbd> | `there` |
398| <kbd>W</kbd>, <kbd>ALTREP2</kbd> | `which` | <kbd>W</kbd>, <kbd>ALTREP3</kbd> | `would` |
399
400```c
401enum custom_keycodes {
402 ALTREP2 = SAFE_RANGE,
403 ALTREP3,
404};
405
406// Use ALTREP2 and ALTREP3 in your layout...
407
408bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
409 uint8_t* remembered_mods) {
410 switch (keycode) {
411 case ALTREP2:
412 case ALTREP3:
413 return false; // Ignore ALTREP keys.
414 }
415
416 return true; // Other keys can be repeated.
417}
418
419static void process_altrep2(uint16_t keycode, uint8_t mods) {
420 switch (keycode) {
421 case KC_A: SEND_STRING(/*a*/"tion"); break;
422 case KC_I: SEND_STRING(/*i*/"tion"); break;
423 case KC_S: SEND_STRING(/*s*/"sion"); break;
424 case KC_T: SEND_STRING(/*t*/"heir"); break;
425 case KC_W: SEND_STRING(/*w*/"hich"); break;
426 }
427}
428
429static void process_altrep3(uint16_t keycode, uint8_t mods) {
430 switch (keycode) {
431 case KC_A: SEND_STRING(/*a*/"bout"); break;
432 case KC_I: SEND_STRING(/*i*/"nter"); break;
433 case KC_S: SEND_STRING(/*s*/"tate"); break;
434 case KC_T: SEND_STRING(/*t*/"here"); break;
435 case KC_W: SEND_STRING(/*w*/"ould"); break;
436 }
437}
438
439bool process_record_user(uint16_t keycode, keyrecord_t* record) {
440 switch (keycode) {
441 case ALTREP2:
442 if (record->event.pressed) {
443 process_altrep2(get_last_keycode(), get_last_mods());
444 }
445 return false;
446
447 case ALTREP3:
448 if (record->event.pressed) {
449 process_altrep3(get_last_keycode(), get_last_mods());
450 }
451 return false;
452 }
453
454 return true;
455}
456```
457
diff --git a/docs/ja/_summary.md b/docs/ja/_summary.md
index e49853bfd4..4d6f2348d5 100644
--- a/docs/ja/_summary.md
+++ b/docs/ja/_summary.md
@@ -68,6 +68,7 @@
68 * [モッドタップ](ja/mod_tap.md) 68 * [モッドタップ](ja/mod_tap.md)
69 * [マクロ](ja/feature_macros.md) 69 * [マクロ](ja/feature_macros.md)
70 * [マウスキー](ja/feature_mouse_keys.md) 70 * [マウスキー](ja/feature_mouse_keys.md)
71 * [Repeat Key](ja/feature_repeat_key.md)
71 * [Space Cadet Shift](ja/feature_space_cadet.md) 72 * [Space Cadet Shift](ja/feature_space_cadet.md)
72 * [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md) 73 * [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md)
73 74
diff --git a/docs/keycodes.md b/docs/keycodes.md
index cad050ccf7..e5b6246af7 100644
--- a/docs/keycodes.md
+++ b/docs/keycodes.md
@@ -803,6 +803,15 @@ See also: [Programmable Button](feature_programmable_button.md)
803|`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31| 803|`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31|
804|`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32| 804|`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32|
805 805
806## Repeat Key :id=repeat-key
807
808See also: [Repeat Key](feature_repeat_key.md)
809
810|Keycode |Aliases |Description |
811|-----------------------|---------|-------------------------------------|
812|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
813|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key |
814
806## Space Cadet :id=space-cadet 815## Space Cadet :id=space-cadet
807 816
808See also: [Space Cadet](feature_space_cadet.md) 817See also: [Space Cadet](feature_space_cadet.md)
diff --git a/docs/zh-cn/_summary.md b/docs/zh-cn/_summary.md
index b8c26ac275..0fc92e33d3 100644
--- a/docs/zh-cn/_summary.md
+++ b/docs/zh-cn/_summary.md
@@ -73,6 +73,7 @@
73 * [Mod-Tap](zh-cn/mod_tap.md) 73 * [Mod-Tap](zh-cn/mod_tap.md)
74 * [宏](zh-cn/feature_macros.md) 74 * [宏](zh-cn/feature_macros.md)
75 * [鼠标键](zh-cn/feature_mouse_keys.md) 75 * [鼠标键](zh-cn/feature_mouse_keys.md)
76 * [Repeat Key](zh-cn/feature_repeat_key.md)
76 * [Space Cadet Shift](zh-cn/feature_space_cadet.md) 77 * [Space Cadet Shift](zh-cn/feature_space_cadet.md)
77 * [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md) 78 * [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md)
78 79
diff --git a/quantum/action.c b/quantum/action.c
index 59bfefc495..a45e70c557 100644
--- a/quantum/action.c
+++ b/quantum/action.c
@@ -285,7 +285,7 @@ void process_record(keyrecord_t *record) {
285} 285}
286 286
287void process_record_handler(keyrecord_t *record) { 287void process_record_handler(keyrecord_t *record) {
288#ifdef COMBO_ENABLE 288#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
289 action_t action; 289 action_t action;
290 if (record->keycode) { 290 if (record->keycode) {
291 action = action_for_keycode(record->keycode); 291 action = action_for_keycode(record->keycode);
@@ -1109,7 +1109,7 @@ bool is_tap_record(keyrecord_t *record) {
1109 return false; 1109 return false;
1110 } 1110 }
1111 1111
1112#ifdef COMBO_ENABLE 1112#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
1113 action_t action; 1113 action_t action;
1114 if (record->keycode) { 1114 if (record->keycode) {
1115 action = action_for_keycode(record->keycode); 1115 action = action_for_keycode(record->keycode);
diff --git a/quantum/action.h b/quantum/action.h
index 2a2c294c5a..d5b15c6f17 100644
--- a/quantum/action.h
+++ b/quantum/action.h
@@ -50,7 +50,7 @@ typedef struct {
50#ifndef NO_ACTION_TAPPING 50#ifndef NO_ACTION_TAPPING
51 tap_t tap; 51 tap_t tap;
52#endif 52#endif
53#ifdef COMBO_ENABLE 53#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
54 uint16_t keycode; 54 uint16_t keycode;
55#endif 55#endif
56} keyrecord_t; 56} keyrecord_t;
diff --git a/quantum/keycodes.h b/quantum/keycodes.h
index 34b13c29af..bbf10da36d 100644
--- a/quantum/keycodes.h
+++ b/quantum/keycodes.h
@@ -721,6 +721,8 @@ enum qk_keycode_defines {
721 QK_AUTOCORRECT_TOGGLE = 0x7C76, 721 QK_AUTOCORRECT_TOGGLE = 0x7C76,
722 QK_TRI_LAYER_LOWER = 0x7C77, 722 QK_TRI_LAYER_LOWER = 0x7C77,
723 QK_TRI_LAYER_UPPER = 0x7C78, 723 QK_TRI_LAYER_UPPER = 0x7C78,
724 QK_REPEAT_KEY = 0x7C79,
725 QK_ALT_REPEAT_KEY = 0x7C7A,
724 QK_KB_0 = 0x7E00, 726 QK_KB_0 = 0x7E00,
725 QK_KB_1 = 0x7E01, 727 QK_KB_1 = 0x7E01,
726 QK_KB_2 = 0x7E02, 728 QK_KB_2 = 0x7E02,
@@ -1362,6 +1364,8 @@ enum qk_keycode_defines {
1362 AC_TOGG = QK_AUTOCORRECT_TOGGLE, 1364 AC_TOGG = QK_AUTOCORRECT_TOGGLE,
1363 TL_LOWR = QK_TRI_LAYER_LOWER, 1365 TL_LOWR = QK_TRI_LAYER_LOWER,
1364 TL_UPPR = QK_TRI_LAYER_UPPER, 1366 TL_UPPR = QK_TRI_LAYER_UPPER,
1367 QK_REP = QK_REPEAT_KEY,
1368 QK_AREP = QK_ALT_REPEAT_KEY,
1365}; 1369};
1366 1370
1367// Range Helpers 1371// Range Helpers
@@ -1413,6 +1417,6 @@ enum qk_keycode_defines {
1413#define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31) 1417#define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31)
1414#define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING) 1418#define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING)
1415#define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE) 1419#define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE)
1416#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_TRI_LAYER_UPPER) 1420#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY)
1417#define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31) 1421#define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31)
1418#define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31) 1422#define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31)
diff --git a/quantum/process_keycode/process_repeat_key.c b/quantum/process_keycode/process_repeat_key.c
new file mode 100644
index 0000000000..f819aa226e
--- /dev/null
+++ b/quantum/process_keycode/process_repeat_key.c
@@ -0,0 +1,109 @@
1// Copyright 2022-2023 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 "process_repeat_key.h"
16
17// Default implementation of remember_last_key_user().
18__attribute__((weak)) bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
19 return true;
20}
21
22static bool remember_last_key(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
23 switch (keycode) {
24 // Ignore MO, TO, TG, TT, and TL layer switch keys.
25 case QK_MOMENTARY ... QK_MOMENTARY_MAX:
26 case QK_TO ... QK_TO_MAX:
27 case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
28 case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
29 // Ignore mod keys.
30 case KC_LCTL ... KC_RGUI:
31 case KC_HYPR:
32 case KC_MEH:
33#ifndef NO_ACTION_ONESHOT // Ignore one-shot keys.
34 case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
35 case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
36#endif // NO_ACTION_ONESHOT
37#ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys.
38 case QK_TRI_LAYER_LOWER:
39 case QK_TRI_LAYER_UPPER:
40#endif // TRI_LAYER_ENABLE
41 return false;
42
43 // Ignore hold events on tap-hold keys.
44#ifndef NO_ACTION_TAPPING
45 case QK_MOD_TAP ... QK_MOD_TAP_MAX:
46# ifndef NO_ACTION_LAYER
47 case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
48# endif // NO_ACTION_LAYER
49 if (record->tap.count == 0) {
50 return false;
51 }
52 break;
53#endif // NO_ACTION_TAPPING
54
55#ifdef SWAP_HANDS_ENABLE
56 case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
57 if (IS_SWAP_HANDS_KEYCODE(keycode) || record->tap.count == 0) {
58 return false;
59 }
60 break;
61#endif // SWAP_HANDS_ENABLE
62
63 case QK_REPEAT_KEY:
64#ifndef NO_ALT_REPEAT_KEY
65 case QK_ALT_REPEAT_KEY:
66#endif // NO_ALT_REPEAT_KEY
67 return false;
68 }
69
70 return remember_last_key_user(keycode, record, remembered_mods);
71}
72
73bool process_last_key(uint16_t keycode, keyrecord_t* record) {
74 if (get_repeat_key_count()) {
75 return true;
76 }
77
78 if (record->event.pressed) {
79 uint8_t remembered_mods = get_mods() | get_weak_mods();
80#ifndef NO_ACTION_ONESHOT
81 remembered_mods |= get_oneshot_mods();
82#endif // NO_ACTION_ONESHOT
83
84 if (remember_last_key(keycode, record, &remembered_mods)) {
85 set_last_record(keycode, record);
86 set_last_mods(remembered_mods);
87 }
88 }
89
90 return true;
91}
92
93bool process_repeat_key(uint16_t keycode, keyrecord_t* record) {
94 if (get_repeat_key_count()) {
95 return true;
96 }
97
98 if (keycode == QK_REPEAT_KEY) {
99 repeat_key_invoke(&record->event);
100 return false;
101#ifndef NO_ALT_REPEAT_KEY
102 } else if (keycode == QK_ALT_REPEAT_KEY) {
103 alt_repeat_key_invoke(&record->event);
104 return false;
105#endif // NO_ALT_REPEAT_KEY
106 }
107
108 return true;
109}
diff --git a/quantum/process_keycode/process_repeat_key.h b/quantum/process_keycode/process_repeat_key.h
new file mode 100644
index 0000000000..eddc50f254
--- /dev/null
+++ b/quantum/process_keycode/process_repeat_key.h
@@ -0,0 +1,62 @@
1// Copyright 2022-2023 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#pragma once
16
17#include "quantum.h"
18
19/**
20 * @brief Process handler for remembering the last key.
21 *
22 * @param keycode Keycode registered by matrix press, per keymap
23 * @param record keyrecord_t structure
24 * @return true Continue processing keycodes, and send to host
25 * @return false Stop processing keycodes, and don't send to host
26 */
27bool process_last_key(uint16_t keycode, keyrecord_t* record);
28
29/**
30 * @brief Optional callback defining which keys are remembered.
31 *
32 * @param keycode Keycode that was just pressed
33 * @param record keyrecord_t structure
34 * @param remembered_mods Mods that will be remembered with this key
35 * @return true Key is remembered
36 * @return false Key is ignored
37 *
38 * Modifier and layer switch keys are always ignored. For all other keys, this
39 * callback is called on every key press. Returning true means that the key is
40 * remembered, false means it is ignored. By default, all non-modifier,
41 * non-layer switch keys are remembered.
42 *
43 * The `remembered_mods` arg represents the mods that will be remembered with
44 * this key. It can be modified to forget certain mods, for instance to forget
45 * capitalization when repeating shifted letters:
46 *
47 * // Forget Shift on letter keys.
48 * if (KC_A <= keycode && keycode <= KC_Z && (*remembered_mods & ~MOD_MASK_SHIFT) == 0) {
49 * *remembered_mods = 0;
50 * }
51 */
52bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods);
53
54/**
55 * @brief Process handler for Repeat Key feature.
56 *
57 * @param keycode Keycode registered by matrix press, per keymap
58 * @param record keyrecord_t structure
59 * @return true Continue processing keycodes, and send to host
60 * @return false Stop processing keycodes, and don't send to host
61 */
62bool process_repeat_key(uint16_t keycode, keyrecord_t* record);
diff --git a/quantum/quantum.c b/quantum/quantum.c
index fdc24fa2d0..091cf298f7 100644
--- a/quantum/quantum.c
+++ b/quantum/quantum.c
@@ -176,7 +176,7 @@ void soft_reset_keyboard(void) {
176 176
177/* Convert record into usable keycode via the contained event. */ 177/* Convert record into usable keycode via the contained event. */
178uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { 178uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) {
179#ifdef COMBO_ENABLE 179#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
180 if (record->keycode) { 180 if (record->keycode) {
181 return record->keycode; 181 return record->keycode;
182 } 182 }
@@ -273,6 +273,9 @@ bool process_record_quantum(keyrecord_t *record) {
273 // Must run asap to ensure all keypresses are recorded. 273 // Must run asap to ensure all keypresses are recorded.
274 process_dynamic_macro(keycode, record) && 274 process_dynamic_macro(keycode, record) &&
275#endif 275#endif
276#ifdef REPEAT_KEY_ENABLE
277 process_last_key(keycode, record) && process_repeat_key(keycode, record) &&
278#endif
276#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY) 279#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY)
277 process_clicky(keycode, record) && 280 process_clicky(keycode, record) &&
278#endif 281#endif
diff --git a/quantum/quantum.h b/quantum/quantum.h
index fec92a5244..31a1a63a7a 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -251,6 +251,11 @@ extern layer_state_t layer_state;
251# include "process_tri_layer.h" 251# include "process_tri_layer.h"
252#endif 252#endif
253 253
254#ifdef REPEAT_KEY_ENABLE
255# include "repeat_key.h"
256# include "process_repeat_key.h"
257#endif
258
254void set_single_persistent_default_layer(uint8_t default_layer); 259void set_single_persistent_default_layer(uint8_t default_layer);
255 260
256#define IS_LAYER_ON(layer) layer_state_is(layer) 261#define IS_LAYER_ON(layer) layer_state_is(layer)
diff --git a/quantum/repeat_key.c b/quantum/repeat_key.c
new file mode 100644
index 0000000000..0689c6d454
--- /dev/null
+++ b/quantum/repeat_key.c
@@ -0,0 +1,282 @@
1// Copyright 2022-2023 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 "repeat_key.h"
16
17// Variables saving the state of the last key press.
18static keyrecord_t last_record = {0};
19static uint8_t last_mods = 0;
20// Signed count of the number of times the last key has been repeated or
21// alternate repeated: it is 0 when a key is pressed normally, positive when
22// repeated, and negative when alternate repeated.
23static int8_t last_repeat_count = 0;
24// The repeat_count, but set to 0 outside of repeat_key_invoke() so that it is
25// nonzero only while a repeated key is being processed.
26static int8_t processing_repeat_count = 0;
27
28uint16_t get_last_keycode(void) {
29 return last_record.keycode;
30}
31
32uint8_t get_last_mods(void) {
33 return last_mods;
34}
35
36void set_last_keycode(uint16_t keycode) {
37 set_last_record(keycode, &(keyrecord_t){
38#ifndef NO_ACTION_TAPPING
39 .tap.interrupted = false,
40 .tap.count = 1,
41#endif
42 });
43}
44
45void set_last_mods(uint8_t mods) {
46 last_mods = mods;
47}
48
49void set_last_record(uint16_t keycode, keyrecord_t* record) {
50 last_record = *record;
51 last_record.keycode = keycode;
52 last_repeat_count = 0;
53}
54
55/** @brief Updates `last_repeat_count` in direction `dir`. */
56static void update_last_repeat_count(int8_t dir) {
57 if (dir * last_repeat_count < 0) {
58 last_repeat_count = dir;
59 } else if (dir * last_repeat_count < 127) {
60 last_repeat_count += dir;
61 }
62}
63
64int8_t get_repeat_key_count(void) {
65 return processing_repeat_count;
66}
67
68void repeat_key_invoke(const keyevent_t* event) {
69 // It is possible (e.g. in rolled presses) that the last key changes while
70 // the Repeat Key is pressed. To prevent stuck keys, it is important to
71 // remember separately what key record was processed on press so that the
72 // the corresponding record is generated on release.
73 static keyrecord_t registered_record = {0};
74 static int8_t registered_repeat_count = 0;
75 // Since this function calls process_record(), it may recursively call
76 // itself. We return early if `processing_repeat_count` is nonzero to
77 // prevent infinite recursion.
78 if (processing_repeat_count || !last_record.keycode) {
79 return;
80 }
81
82 if (event->pressed) {
83 update_last_repeat_count(1);
84 // On press, apply the last mods state, stacking on top of current mods.
85 register_weak_mods(last_mods);
86 registered_record = last_record;
87 registered_repeat_count = last_repeat_count;
88 }
89
90 // Generate a keyrecord and plumb it into the event pipeline.
91 registered_record.event = *event;
92 processing_repeat_count = registered_repeat_count;
93 process_record(&registered_record);
94 processing_repeat_count = 0;
95
96 // On release, restore the mods state.
97 if (!event->pressed) {
98 unregister_weak_mods(last_mods);
99 }
100}
101
102#ifndef NO_ALT_REPEAT_KEY
103/**
104 * @brief Find alternate keycode from a table of opposing keycode pairs.
105 * @param table Array of pairs of basic keycodes, declared as PROGMEM.
106 * @param table_size_bytes The size of the table in bytes.
107 * @param target The basic keycode to find.
108 * @return The alternate basic keycode, or KC_NO if none was found.
109 *
110 * @note The table keycodes and target must be basic keycodes.
111 *
112 * This helper is used several times below to define alternate keys. Given a
113 * table of pairs of basic keycodes, the function finds the pair containing
114 * `target` and returns the other keycode in the pair.
115 */
116static uint8_t find_alt_keycode(const uint8_t (*table)[2], uint8_t table_size_bytes, uint8_t target) {
117 const uint8_t* keycodes = (const uint8_t*)table;
118 for (uint8_t i = 0; i < table_size_bytes; ++i) {
119 if (target == pgm_read_byte(keycodes + i)) {
120 // Xor (i ^ 1) the index to get the other element in the pair.
121 return pgm_read_byte(keycodes + (i ^ 1));
122 }
123 }
124 return KC_NO;
125}
126
127uint16_t get_alt_repeat_key_keycode(void) {
128 uint16_t keycode = last_record.keycode;
129 uint8_t mods = last_mods;
130
131 // Call the user callback first to give it a chance to override the default
132 // alternate key definitions that follow.
133 uint16_t alt_keycode = get_alt_repeat_key_keycode_user(keycode, mods);
134
135 if (alt_keycode != KC_TRANSPARENT) {
136 return alt_keycode;
137 }
138
139 // Convert 8-bit mods to the 5-bit format used in keycodes. This is lossy:
140 // if left and right handed mods were mixed, they all become right handed.
141 mods = ((mods & 0xf0) ? /* set right hand bit */ 0x10 : 0)
142 // Combine right and left hand mods.
143 | (((mods >> 4) | mods) & 0xf);
144
145 switch (keycode) {
146 case QK_MODS ... QK_MODS_MAX: // Unpack modifier + basic key.
147 mods |= QK_MODS_GET_MODS(keycode);
148 keycode = QK_MODS_GET_BASIC_KEYCODE(keycode);
149 break;
150
151# ifndef NO_ACTION_TAPPING
152 case QK_MOD_TAP ... QK_MOD_TAP_MAX:
153 keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
154 break;
155# ifndef NO_ACTION_LAYER
156 case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
157 keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
158 break;
159# endif // NO_ACTION_LAYER
160# endif // NO_ACTION_TAPPING
161
162# ifdef SWAP_HANDS_ENABLE
163 case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
164 if (IS_SWAP_HANDS_KEYCODE(keycode)) {
165 return KC_NO;
166 }
167 keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode);
168 break;
169# endif // SWAP_HANDS_ENABLE
170 }
171
172 if (IS_QK_BASIC(keycode)) {
173 if ((mods & (MOD_LCTL | MOD_LALT | MOD_LGUI))) {
174 // The last key was pressed with a modifier other than Shift.
175 // The following maps
176 // mod + F <-> mod + B
177 // and a few others, supporting several core hotkeys used in
178 // Emacs, Vim, less, and other programs.
179 // clang-format off
180 static const uint8_t pairs[][2] PROGMEM = {
181 {KC_F , KC_B }, // Forward / Backward.
182 {KC_D , KC_U }, // Down / Up.
183 {KC_N , KC_P }, // Next / Previous.
184 {KC_A , KC_E }, // Home / End.
185 {KC_O , KC_I }, // Older / Newer in Vim jump list.
186 };
187 // clang-format on
188 alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
189 } else {
190 // The last key was pressed with no mods or only Shift. The
191 // following map a few more Vim hotkeys.
192 // clang-format off
193 static const uint8_t pairs[][2] PROGMEM = {
194 {KC_J , KC_K }, // Down / Up.
195 {KC_H , KC_L }, // Left / Right.
196 // These two lines map W and E to B, and B to W.
197 {KC_W , KC_B }, // Forward / Backward by word.
198 {KC_E , KC_B }, // Forward / Backward by word.
199 };
200 // clang-format on
201 alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
202 }
203
204 if (!alt_keycode) {
205 // The following key pairs are considered with any mods.
206 // clang-format off
207 static const uint8_t pairs[][2] PROGMEM = {
208 {KC_LEFT, KC_RGHT}, // Left / Right Arrow.
209 {KC_UP , KC_DOWN}, // Up / Down Arrow.
210 {KC_HOME, KC_END }, // Home / End.
211 {KC_PGUP, KC_PGDN}, // Page Up / Page Down.
212 {KC_BSPC, KC_DEL }, // Backspace / Delete.
213 {KC_LBRC, KC_RBRC}, // Brackets [ ] and { }.
214#ifdef EXTRAKEY_ENABLE
215 {KC_WBAK, KC_WFWD}, // Browser Back / Forward.
216 {KC_MNXT, KC_MPRV}, // Next / Previous Media Track.
217 {KC_MFFD, KC_MRWD}, // Fast Forward / Rewind Media.
218 {KC_VOLU, KC_VOLD}, // Volume Up / Down.
219 {KC_BRIU, KC_BRID}, // Brightness Up / Down.
220#endif // EXTRAKEY_ENABLE
221#ifdef MOUSEKEY_ENABLE
222 {KC_MS_L, KC_MS_R}, // Mouse Cursor Left / Right.
223 {KC_MS_U, KC_MS_D}, // Mouse Cursor Up / Down.
224 {KC_WH_L, KC_WH_R}, // Mouse Wheel Left / Right.
225 {KC_WH_U, KC_WH_D}, // Mouse Wheel Up / Down.
226#endif // MOUSEKEY_ENABLE
227 };
228 // clang-format on
229 alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
230 }
231
232 if (alt_keycode) {
233 // Combine basic keycode with mods.
234 return (mods << 8) | alt_keycode;
235 }
236 }
237
238 return KC_NO; // No alternate key found.
239}
240
241void alt_repeat_key_invoke(const keyevent_t* event) {
242 static keyrecord_t registered_record = {0};
243 static int8_t registered_repeat_count = 0;
244 // Since this function calls process_record(), it may recursively call
245 // itself. We return early if `processing_repeat_count` is nonzero to
246 // prevent infinite recursion.
247 if (processing_repeat_count) {
248 return;
249 }
250
251 if (event->pressed) {
252 registered_record = (keyrecord_t){
253# ifndef NO_ACTION_TAPPING
254 .tap.interrupted = false,
255 .tap.count = 0,
256# endif
257 .keycode = get_alt_repeat_key_keycode(),
258 };
259 }
260
261 // Early return if there is no alternate key defined.
262 if (!registered_record.keycode) {
263 return;
264 }
265
266 if (event->pressed) {
267 update_last_repeat_count(-1);
268 registered_repeat_count = last_repeat_count;
269 }
270
271 // Generate a keyrecord and plumb it into the event pipeline.
272 registered_record.event = *event;
273 processing_repeat_count = registered_repeat_count;
274 process_record(&registered_record);
275 processing_repeat_count = 0;
276}
277
278// Default implementation of get_alt_repeat_key_keycode_user().
279__attribute__((weak)) uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
280 return KC_TRANSPARENT;
281}
282#endif // NO_ALT_REPEAT_KEY
diff --git a/quantum/repeat_key.h b/quantum/repeat_key.h
new file mode 100644
index 0000000000..06e8364529
--- /dev/null
+++ b/quantum/repeat_key.h
@@ -0,0 +1,80 @@
1// Copyright 2022-2023 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#pragma once
16
17#include "quantum.h"
18
19uint16_t get_last_keycode(void); /**< Keycode of the last key. */
20uint8_t get_last_mods(void); /**< Mods active with the last key. */
21void set_last_keycode(uint16_t keycode); /**< Sets the last key. */
22void set_last_mods(uint8_t mods); /**< Sets the last mods. */
23
24/** @brief Gets the record for the last key. */
25keyrecord_t* get_last_record(void);
26
27/** @brief Sets keycode and record info for the last key. */
28void set_last_record(uint16_t keycode, keyrecord_t* record);
29
30/**
31 * @brief Signed count of times the key has been repeated or alternate repeated.
32 *
33 * @note The count is nonzero only while a repeated or alternate-repeated key is
34 * being processed.
35 *
36 * When a key is pressed normally, the count is 0. When the Repeat Key is used
37 * to repeat a key, the count is 1 on the first repeat, 2 on the second repeat,
38 * and continuing up to 127.
39 *
40 * Negative counts are used similarly for alternate repeating. When the
41 * Alternate Repeat Key is used, the count is -1 on the first alternate repeat,
42 * -2 on the second, continuing down to -127.
43 */
44int8_t get_repeat_key_count(void);
45
46/**
47 * @brief Calls `process_record()` on a generated record repeating the last key.
48 * @param event Event information in the generated record.
49 */
50void repeat_key_invoke(const keyevent_t* event);
51
52#ifndef NO_ALT_REPEAT_KEY
53
54/**
55 * @brief Keycode to be used for alternate repeating.
56 *
57 * Alternate Repeat performs this keycode based on the last eligible pressed key
58 * and mods, get_last_keycode() and get_last_mods(). For example, when the last
59 * key was KC_UP, this function returns KC_DOWN. The function returns KC_NO if
60 * the last key doesn't have a defined alternate.
61 */
62uint16_t get_alt_repeat_key_keycode(void);
63
64/**
65 * @brief Calls `process_record()` to alternate repeat the last key.
66 * @param event Event information in the generated record.
67 */
68void alt_repeat_key_invoke(const keyevent_t* event);
69
70/**
71 * @brief Optional user callback to define additional alternate keys.
72 *
73 * When `get_alt_repeat_key_keycode()` is called, it first calls this callback.
74 * It should return a keycode representing the "alternate" of the given keycode
75 * and mods. Returning KC_NO defers to the default definitions in
76 * `get_alt_repeat_key_keycode()`.
77 */
78uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods);
79
80#endif // NO_ALT_REPEAT_KEY
diff --git a/tests/repeat_key/alt_repeat_key/config.h b/tests/repeat_key/alt_repeat_key/config.h
new file mode 100644
index 0000000000..d0c4ddadbd
--- /dev/null
+++ b/tests/repeat_key/alt_repeat_key/config.h
@@ -0,0 +1,18 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#pragma once
17
18#include "test_common.h"
diff --git a/tests/repeat_key/alt_repeat_key/test.mk b/tests/repeat_key/alt_repeat_key/test.mk
new file mode 100644
index 0000000000..080871c816
--- /dev/null
+++ b/tests/repeat_key/alt_repeat_key/test.mk
@@ -0,0 +1,18 @@
1# Copyright 2023 Google LLC
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16REPEAT_KEY_ENABLE = yes
17
18EXTRAKEY_ENABLE = yes
diff --git a/tests/repeat_key/alt_repeat_key/test_alt_repeat_key.cpp b/tests/repeat_key/alt_repeat_key/test_alt_repeat_key.cpp
new file mode 100644
index 0000000000..ae525acb45
--- /dev/null
+++ b/tests/repeat_key/alt_repeat_key/test_alt_repeat_key.cpp
@@ -0,0 +1,523 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#include <functional>
17
18#include "keyboard_report_util.hpp"
19#include "keycode.h"
20#include "test_common.hpp"
21#include "test_fixture.hpp"
22#include "test_keymap_key.hpp"
23
24using ::testing::AnyNumber;
25using ::testing::InSequence;
26
27namespace {
28
29bool process_record_user_default(uint16_t keycode, keyrecord_t* record) {
30 return true;
31}
32
33bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
34 return true;
35}
36
37uint16_t get_alt_repeat_key_keycode_user_default(uint16_t keycode, uint8_t mods) {
38 return KC_TRNS;
39}
40
41// Indirections so that process_record_user() can be replaced with other
42// functions in the test cases below.
43std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default;
44std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default;
45std::function<uint16_t(uint16_t, uint8_t)> get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default;
46
47extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) {
48 return process_record_user_fun(keycode, record);
49}
50
51extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
52 return remember_last_key_user_fun(keycode, record, remembered_mods);
53}
54
55extern "C" uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
56 return get_alt_repeat_key_keycode_user_fun(keycode, mods);
57}
58
59class AltRepeatKey : public TestFixture {
60 public:
61 bool process_record_user_was_called_;
62
63 void SetUp() override {
64 process_record_user_fun = process_record_user_default;
65 remember_last_key_user_fun = remember_last_key_user_default;
66 get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default;
67 }
68
69 void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) {
70 process_record_user_was_called_ = false;
71 process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
72 EXPECT_EQ(record->event.pressed, expected_press);
73 EXPECT_KEYCODE_EQ(keycode, expected_keycode);
74 EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count);
75 // Tests below use this to verify process_record_user() was called.
76 process_record_user_was_called_ = true;
77 return true;
78 };
79 }
80
81 // Expects that the characters of `s` are sent.
82 // NOTE: This implementation is limited to chars a-z, A-Z.
83 void ExpectString(TestDriver& driver, const std::string& s) {
84 InSequence seq;
85 for (int c : s) {
86 switch (c) {
87 case 'a' ... 'z': { // Lowercase letter.
88 uint16_t keycode = c - ('a' - KC_A);
89 EXPECT_REPORT(driver, (keycode));
90 } break;
91
92 case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
93 uint16_t keycode = c - ('A' - KC_A);
94 EXPECT_REPORT(driver, (KC_LSFT, keycode));
95 } break;
96 }
97 }
98 }
99};
100
101TEST_F(AltRepeatKey, AlternateBasic) {
102 TestDriver driver;
103 KeymapKey key_bspc(0, 0, 0, KC_BSPC);
104 KeymapKey key_pgdn(0, 1, 0, KC_PGDN);
105 KeymapKey key_pgup(0, 2, 0, KC_PGUP);
106 KeymapKey key_repeat(0, 4, 0, QK_REP);
107 KeymapKey key_alt_repeat(0, 5, 0, QK_AREP);
108 set_keymap({key_bspc, key_pgdn, key_pgup, key_repeat, key_alt_repeat});
109
110 // Allow any number of empty reports.
111 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
112 {
113 InSequence seq;
114 EXPECT_REPORT(driver, (KC_BSPC));
115 EXPECT_REPORT(driver, (KC_DEL));
116 EXPECT_REPORT(driver, (KC_DEL));
117 EXPECT_REPORT(driver, (KC_BSPC));
118 EXPECT_REPORT(driver, (KC_DEL));
119 EXPECT_REPORT(driver, (KC_PGDN));
120 EXPECT_REPORT(driver, (KC_PGUP));
121 EXPECT_REPORT(driver, (KC_PGUP));
122 EXPECT_REPORT(driver, (KC_PGDN));
123 }
124
125 tap_key(key_bspc);
126
127 for (int n = 1; n <= 2; ++n) { // Tap the Alternate Repeat Key twice.
128 ExpectProcessRecordUserCalledWith(true, KC_DEL, -n);
129 key_alt_repeat.press(); // Press the Alternate Repeat Key.
130 run_one_scan_loop();
131 EXPECT_TRUE(process_record_user_was_called_);
132
133 // Expect the corresponding release event.
134 ExpectProcessRecordUserCalledWith(false, KC_DEL, -n);
135 key_alt_repeat.release(); // Release the Repeat Key.
136 run_one_scan_loop();
137 EXPECT_TRUE(process_record_user_was_called_);
138 }
139
140 process_record_user_fun = process_record_user_default;
141 tap_keys(key_repeat, key_alt_repeat);
142 tap_keys(key_pgdn, key_alt_repeat);
143 tap_keys(key_pgup, key_alt_repeat);
144
145 testing::Mock::VerifyAndClearExpectations(&driver);
146}
147
148struct TestParamsAlternateKeyCodes {
149 uint16_t keycode;
150 uint8_t mods;
151 uint16_t expected_alt_keycode;
152};
153
154// Tests `get_alt_repeat_key_keycode()` for various keycodes.
155TEST_F(AltRepeatKey, GetAltRepeatKeyKeycode) {
156 for (const auto& params : std::vector<TestParamsAlternateKeyCodes>({
157 // clang-format off
158 // Each line tests one call to `get_alt_repeat_key_keycode()`:
159 // {keycode, mods, expected_alt_keycode}.
160 // Arrows.
161 {KC_LEFT, 0, KC_RGHT},
162 {KC_RGHT, 0, KC_LEFT},
163 {KC_LEFT, MOD_BIT(KC_LSFT), LSFT(KC_RGHT)},
164 {KC_LEFT, MOD_BIT(KC_RSFT), RSFT(KC_RGHT)},
165 {KC_LEFT, MOD_BIT(KC_LCTL) | MOD_BIT(KC_LSFT), C(S(KC_RGHT))},
166 {KC_LEFT, MOD_BIT(KC_LGUI), LGUI(KC_RGHT)},
167 {C(KC_LEFT), MOD_BIT(KC_LSFT), C(S(KC_RGHT))},
168 {KC_UP, 0, KC_DOWN},
169 // Navigation keys.
170 {KC_PGUP, 0, KC_PGDN},
171 {KC_HOME, 0, KC_END },
172 // Media keys.
173 {KC_WBAK, 0, KC_WFWD},
174 {KC_MNXT, 0, KC_MPRV},
175 {KC_MRWD, 0, KC_MFFD},
176 {KC_VOLU, 0, KC_VOLD},
177 {KC_BRIU, 0, KC_BRID},
178 // Emacs navigation.
179 {KC_N, MOD_BIT(KC_LCTL), C(KC_P)},
180 {KC_B, MOD_BIT(KC_LCTL), LCTL(KC_F)},
181 {KC_B, MOD_BIT(KC_RCTL), RCTL(KC_F)},
182 {KC_B, MOD_BIT(KC_LALT), LALT(KC_F)},
183 {KC_F, MOD_BIT(KC_LCTL), C(KC_B)},
184 {KC_A, MOD_BIT(KC_LCTL), C(KC_E)},
185 {KC_D, MOD_BIT(KC_LCTL), C(KC_U)},
186 // Vim navigation.
187 {KC_J, 0, KC_K},
188 {KC_K, 0, KC_J},
189 {KC_H, 0, KC_L},
190 {KC_B, 0, KC_W},
191 {KC_W, 0, KC_B},
192 {KC_E, 0, KC_B},
193 {KC_B, MOD_BIT(KC_LSFT), S(KC_W)},
194 {KC_W, MOD_BIT(KC_LSFT), S(KC_B)},
195 {KC_E, MOD_BIT(KC_LSFT), S(KC_B)},
196 {KC_O, MOD_BIT(KC_LCTL), C(KC_I)},
197 {KC_I, MOD_BIT(KC_LCTL), C(KC_O)},
198 // Other.
199 {KC_DEL, 0, KC_BSPC},
200 {KC_LBRC, 0, KC_RBRC},
201 {KC_LCBR, 0, KC_RCBR},
202 // Some keys where the last key is a tap-hold key.
203 {LSFT_T(KC_F), MOD_BIT(KC_RCTL), RCTL(KC_B)},
204 {LT(1, KC_A), MOD_BIT(KC_RGUI), RGUI(KC_E)},
205 {RALT_T(KC_J), 0, KC_K},
206 // Some keys where no alternate is defined.
207 {KC_A, 0, KC_NO},
208 {KC_F1, 0, KC_NO},
209 {QK_LEAD, 0, KC_NO},
210 {MO(1), 0, KC_NO},
211 // clang-format on
212 })) {
213 SCOPED_TRACE(std::string("Input keycode: ") + get_keycode_identifier_or_default(params.keycode));
214 set_last_keycode(params.keycode);
215 set_last_mods(params.mods);
216
217 const uint16_t actual = get_alt_repeat_key_keycode();
218
219 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), params.expected_alt_keycode);
220 }
221}
222
223// Test adding to and overriding the above through the
224// `get_alt_repeat_key_keycode_user()` callback.
225TEST_F(AltRepeatKey, GetAltRepeatKeyKeycodeUser) {
226 get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t {
227 bool shifted = (mods & MOD_MASK_SHIFT);
228 switch (keycode) {
229 case KC_LEFT:
230 return KC_ENT;
231 case MO(1):
232 return TG(1);
233 case KC_TAB: // Tab <-> Shift + Tab example.
234 if (shifted) {
235 return KC_TAB;
236 } else {
237 return S(KC_TAB);
238 }
239 }
240
241 // Ctrl + Y <-> Ctrl + Z example.
242 if ((mods & MOD_MASK_CTRL)) {
243 switch (keycode) {
244 case KC_Y:
245 return C(KC_Z);
246 case KC_Z:
247 return C(KC_Y);
248 }
249 }
250
251 return KC_NO;
252 };
253
254 set_last_keycode(KC_LEFT);
255 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_ENT);
256
257 set_last_keycode(MO(1));
258 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), TG(1));
259
260 set_last_keycode(KC_TAB);
261 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), S(KC_TAB));
262
263 set_last_keycode(KC_TAB);
264 set_last_mods(MOD_BIT(KC_LSFT));
265 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_TAB);
266
267 set_last_keycode(KC_Z);
268 set_last_mods(MOD_BIT(KC_LCTL));
269 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Y));
270
271 set_last_keycode(KC_Y);
272 set_last_mods(MOD_BIT(KC_LCTL));
273 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Z));
274}
275
276// Tests rolling from a key to Alternate Repeat.
277TEST_F(AltRepeatKey, RollingToAltRepeat) {
278 TestDriver driver;
279 KeymapKey key_left(0, 0, 0, KC_LEFT);
280 KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
281 set_keymap({key_left, key_alt_repeat});
282
283 {
284 InSequence seq;
285 EXPECT_REPORT(driver, (KC_LEFT));
286 EXPECT_REPORT(driver, (KC_LEFT, KC_RGHT));
287 EXPECT_REPORT(driver, (KC_RGHT));
288 EXPECT_EMPTY_REPORT(driver);
289 EXPECT_REPORT(driver, (KC_RGHT));
290 EXPECT_EMPTY_REPORT(driver);
291 }
292
293 // Perform a rolled press from Left to Alternate Repeat.
294
295 ExpectProcessRecordUserCalledWith(true, KC_LEFT, 0);
296 key_left.press();
297 run_one_scan_loop();
298 EXPECT_TRUE(process_record_user_was_called_);
299
300 ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1);
301 key_alt_repeat.press(); // Press the Alternate Repeat Key.
302 run_one_scan_loop();
303 EXPECT_TRUE(process_record_user_was_called_);
304
305 ExpectProcessRecordUserCalledWith(false, KC_LEFT, 0);
306 key_left.release();
307 run_one_scan_loop();
308 EXPECT_TRUE(process_record_user_was_called_);
309
310 ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1);
311 key_alt_repeat.release(); // Release the Alternate Repeat Key.
312 run_one_scan_loop();
313 EXPECT_TRUE(process_record_user_was_called_);
314
315 process_record_user_fun = process_record_user_default;
316 tap_key(key_alt_repeat);
317
318 testing::Mock::VerifyAndClearExpectations(&driver);
319}
320
321// Tests rolling from Alternate Repeat to another key.
322TEST_F(AltRepeatKey, RollingFromAltRepeat) {
323 TestDriver driver;
324 KeymapKey key_left(0, 0, 0, KC_LEFT);
325 KeymapKey key_up(0, 1, 0, KC_UP);
326 KeymapKey key_alt_repeat(0, 2, 0, QK_AREP);
327 set_keymap({key_left, key_up, key_alt_repeat});
328
329 {
330 InSequence seq;
331 EXPECT_REPORT(driver, (KC_LEFT));
332 EXPECT_EMPTY_REPORT(driver);
333 EXPECT_REPORT(driver, (KC_RGHT));
334 EXPECT_REPORT(driver, (KC_RGHT, KC_UP));
335 EXPECT_REPORT(driver, (KC_UP));
336 EXPECT_EMPTY_REPORT(driver);
337 EXPECT_REPORT(driver, (KC_DOWN));
338 EXPECT_EMPTY_REPORT(driver);
339 }
340
341 tap_key(key_left);
342
343 // Perform a rolled press from Alternate Repeat to Up.
344
345 ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1);
346 key_alt_repeat.press(); // Press the Alternate Repeat Key.
347 run_one_scan_loop();
348 EXPECT_TRUE(process_record_user_was_called_);
349
350 ExpectProcessRecordUserCalledWith(true, KC_UP, 0);
351 key_up.press();
352 run_one_scan_loop();
353 EXPECT_TRUE(process_record_user_was_called_);
354
355 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_UP);
356
357 ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1);
358 key_alt_repeat.release(); // Release the Alternate Repeat Key.
359 run_one_scan_loop();
360 EXPECT_TRUE(process_record_user_was_called_);
361
362 ExpectProcessRecordUserCalledWith(false, KC_UP, 0);
363 key_up.release();
364 run_one_scan_loop();
365 EXPECT_TRUE(process_record_user_was_called_);
366
367 process_record_user_fun = process_record_user_default;
368 tap_key(key_alt_repeat);
369
370 testing::Mock::VerifyAndClearExpectations(&driver);
371}
372
373// Tests using the Alternate Repeat Key on a macro that doesn't have an
374// alternate keycode defined.
375TEST_F(AltRepeatKey, AlternateUnsupportedMacro) {
376 TestDriver driver;
377 KeymapKey key_foo(0, 0, 0, QK_USER_0);
378 KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
379 set_keymap({key_foo, key_alt_repeat});
380
381 process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
382 process_record_user_was_called_ = true;
383 switch (keycode) {
384 case QK_USER_0:
385 if (record->event.pressed) {
386 SEND_STRING("foo");
387 }
388 break;
389 }
390 return true;
391 };
392
393 // Allow any number of empty reports.
394 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
395 ExpectString(driver, "foofoo");
396
397 process_record_user_was_called_ = false;
398 tap_key(key_foo);
399
400 EXPECT_TRUE(process_record_user_was_called_);
401 EXPECT_KEYCODE_EQ(get_last_keycode(), QK_USER_0);
402 EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_NO);
403
404 process_record_user_was_called_ = false;
405 key_alt_repeat.press(); // Press Alternate Repeat.
406 run_one_scan_loop();
407
408 EXPECT_FALSE(process_record_user_was_called_);
409
410 process_record_user_was_called_ = false;
411 key_alt_repeat.release(); // Release Alternate Repeat.
412 run_one_scan_loop();
413
414 EXPECT_FALSE(process_record_user_was_called_);
415
416 process_record_user_was_called_ = false;
417 tap_key(key_foo);
418
419 EXPECT_TRUE(process_record_user_was_called_);
420
421 testing::Mock::VerifyAndClearExpectations(&driver);
422}
423
424// Tests a macro with custom alternate behavior.
425TEST_F(AltRepeatKey, MacroCustomAlternate) {
426 TestDriver driver;
427 KeymapKey key_foo(0, 0, 0, QK_USER_0);
428 KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
429 set_keymap({key_foo, key_alt_repeat});
430
431 get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t {
432 switch (keycode) {
433 case QK_USER_0:
434 return QK_USER_0; // QK_USER_0 handles its own alternate.
435 default:
436 return KC_NO; // No key by default.
437 }
438 };
439 process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
440 process_record_user_was_called_ = true;
441 switch (keycode) {
442 case QK_USER_0:
443 if (record->event.pressed) {
444 if (get_repeat_key_count() >= 0) {
445 SEND_STRING("foo");
446 } else { // Key is being alternate repeated.
447 SEND_STRING("bar");
448 }
449 }
450 break;
451 }
452 return true;
453 };
454
455 // Allow any number of empty reports.
456 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
457 ExpectString(driver, "foobarbar");
458
459 tap_keys(key_foo, key_alt_repeat, key_alt_repeat);
460
461 testing::Mock::VerifyAndClearExpectations(&driver);
462}
463
464// Tests the Additional "Alternate" keys example from the documentation page.
465TEST_F(AltRepeatKey, AdditionalAlternateKeysExample) {
466 TestDriver driver;
467 KeymapKey key_a(0, 0, 0, KC_A);
468 KeymapKey key_w(0, 1, 0, KC_W);
469 KeymapKey key_altrep2(0, 2, 0, QK_USER_0);
470 KeymapKey key_altrep3(0, 3, 0, QK_USER_1);
471 set_keymap({key_a, key_w, key_altrep2, key_altrep3});
472
473 remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
474 switch (keycode) {
475 case QK_USER_0:
476 case QK_USER_1:
477 return false; // Ignore ALTREP keys.
478 }
479 return true; // Other keys can be repeated.
480 };
481 process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
482 switch (keycode) {
483 case QK_USER_0:
484 if (record->event.pressed) {
485 const uint16_t last_key = get_last_keycode();
486 switch (last_key) {
487 case KC_A:
488 SEND_STRING(/*a*/ "tion");
489 break;
490 case KC_W:
491 SEND_STRING(/*w*/ "hich");
492 break;
493 }
494 }
495 return false;
496 case QK_USER_1:
497 if (record->event.pressed) {
498 const uint16_t last_key = get_last_keycode();
499 switch (last_key) {
500 case KC_A:
501 SEND_STRING(/*a*/ "bout");
502 break;
503 case KC_W:
504 SEND_STRING(/*w*/ "ould");
505 break;
506 }
507 }
508 return false;
509 }
510 return true;
511 };
512
513 // Allow any number of empty reports.
514 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
515 ExpectString(driver, "ationwhichaboutwould");
516
517 tap_keys(key_a, key_altrep2, key_w, key_altrep2);
518 tap_keys(key_a, key_altrep3, key_w, key_altrep3);
519
520 testing::Mock::VerifyAndClearExpectations(&driver);
521}
522
523} // namespace
diff --git a/tests/repeat_key/config.h b/tests/repeat_key/config.h
new file mode 100644
index 0000000000..003d980c82
--- /dev/null
+++ b/tests/repeat_key/config.h
@@ -0,0 +1,20 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#pragma once
17
18#include "test_common.h"
19
20#define NO_ALT_REPEAT_KEY
diff --git a/tests/repeat_key/repeat_key_combo/config.h b/tests/repeat_key/repeat_key_combo/config.h
new file mode 100644
index 0000000000..d0c4ddadbd
--- /dev/null
+++ b/tests/repeat_key/repeat_key_combo/config.h
@@ -0,0 +1,18 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#pragma once
17
18#include "test_common.h"
diff --git a/tests/repeat_key/repeat_key_combo/test.mk b/tests/repeat_key/repeat_key_combo/test.mk
new file mode 100644
index 0000000000..db6ea7789a
--- /dev/null
+++ b/tests/repeat_key/repeat_key_combo/test.mk
@@ -0,0 +1,18 @@
1# Copyright 2023 Google LLC
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16REPEAT_KEY_ENABLE = yes
17
18COMBO_ENABLE = yes
diff --git a/tests/repeat_key/repeat_key_combo/test_repeat_key_combo.cpp b/tests/repeat_key/repeat_key_combo/test_repeat_key_combo.cpp
new file mode 100644
index 0000000000..2d2fbaa966
--- /dev/null
+++ b/tests/repeat_key/repeat_key_combo/test_repeat_key_combo.cpp
@@ -0,0 +1,67 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#include "keyboard_report_util.hpp"
17#include "keycode.h"
18#include "test_common.hpp"
19#include "test_fixture.hpp"
20#include "test_keymap_key.hpp"
21
22using ::testing::AnyNumber;
23using ::testing::InSequence;
24
25namespace {
26
27extern "C" {
28// Define a combo: KC_X + KC_Y = KC_Q.
29const uint16_t xy_combo[] PROGMEM = {KC_X, KC_Y, COMBO_END};
30combo_t key_combos[] = {COMBO(xy_combo, KC_Q)};
31uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(*key_combos);
32} // extern "C"
33
34class RepeatKey : public TestFixture {};
35
36// Tests repeating a combo, KC_X + KC_Y = KC_Q, by typing
37// "X, Repeat, Repeat, {X Y}, Repeat, Repeat". This produces "xxxqqq".
38TEST_F(RepeatKey, Combo) {
39 TestDriver driver;
40 KeymapKey key_x(0, 0, 0, KC_X);
41 KeymapKey key_y(0, 1, 0, KC_Y);
42 KeymapKey key_repeat(0, 2, 0, QK_REP);
43 set_keymap({key_x, key_y, key_repeat});
44
45 // Allow any number of empty reports.
46 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
47 {
48 InSequence seq;
49 EXPECT_REPORT(driver, (KC_X));
50 EXPECT_REPORT(driver, (KC_X));
51 EXPECT_REPORT(driver, (KC_X));
52 EXPECT_REPORT(driver, (KC_Q));
53 EXPECT_REPORT(driver, (KC_Q));
54 EXPECT_REPORT(driver, (KC_Q));
55 }
56
57 tap_keys(key_x, key_repeat, key_repeat);
58 tap_combo({key_x, key_y});
59
60 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_Q);
61
62 tap_keys(key_repeat, key_repeat);
63
64 testing::Mock::VerifyAndClearExpectations(&driver);
65}
66
67} // namespace
diff --git a/tests/repeat_key/test.mk b/tests/repeat_key/test.mk
new file mode 100644
index 0000000000..aec8ff3bfb
--- /dev/null
+++ b/tests/repeat_key/test.mk
@@ -0,0 +1,18 @@
1# Copyright 2023 Google LLC
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License as published by
5# the Free Software Foundation, either version 2 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU General Public License for more details.
12#
13# You should have received a copy of the GNU General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16REPEAT_KEY_ENABLE = yes
17
18AUTO_SHIFT_ENABLE = yes
diff --git a/tests/repeat_key/test_repeat_key.cpp b/tests/repeat_key/test_repeat_key.cpp
new file mode 100644
index 0000000000..eee44fc104
--- /dev/null
+++ b/tests/repeat_key/test_repeat_key.cpp
@@ -0,0 +1,754 @@
1// Copyright 2023 Google LLC
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 2 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program. If not, see <http://www.gnu.org/licenses/>.
15
16#include <functional>
17
18#include "keyboard_report_util.hpp"
19#include "keycode.h"
20#include "test_common.hpp"
21#include "test_fixture.hpp"
22#include "test_keymap_key.hpp"
23
24using ::testing::AnyNumber;
25using ::testing::AnyOf;
26using ::testing::InSequence;
27
28#define FOO_MACRO SAFE_RANGE
29
30namespace {
31
32bool process_record_user_default(uint16_t keycode, keyrecord_t* record) {
33 return true;
34}
35
36bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
37 return true;
38}
39
40// Indirection so that process_record_user() and remember_last_key_user()
41// can be replaced with other functions in the test cases below.
42std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default;
43std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default;
44
45extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) {
46 return process_record_user_fun(keycode, record);
47}
48extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
49 return remember_last_key_user_fun(keycode, record, remembered_mods);
50}
51
52class RepeatKey : public TestFixture {
53 public:
54 bool process_record_user_was_called_;
55
56 void SetUp() override {
57 autoshift_disable();
58 process_record_user_fun = process_record_user_default;
59 remember_last_key_user_fun = remember_last_key_user_default;
60 }
61
62 void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) {
63 process_record_user_was_called_ = false;
64 process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
65 EXPECT_EQ(record->event.pressed, expected_press);
66 EXPECT_KEYCODE_EQ(keycode, expected_keycode);
67 EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count);
68 // Tests below use this to verify process_record_user() was called.
69 process_record_user_was_called_ = true;
70 return true;
71 };
72 }
73
74 // Expects that the characters of `s` are sent.
75 // NOTE: This implementation is limited to chars a-z, A-Z.
76 void ExpectString(TestDriver& driver, const std::string& s) {
77 InSequence seq;
78 for (int c : s) {
79 switch (c) {
80 case 'a' ... 'z': { // Lowercase letter.
81 uint16_t keycode = c - ('a' - KC_A);
82 EXPECT_REPORT(driver, (keycode));
83 } break;
84
85 case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
86 uint16_t keycode = c - ('A' - KC_A);
87 EXPECT_REPORT(driver, (KC_LSFT, keycode));
88 } break;
89 }
90 }
91 }
92};
93
94// Tests that "A, Repeat, Repeat, B, Repeat" produces "aaabb".
95TEST_F(RepeatKey, Basic) {
96 TestDriver driver;
97 KeymapKey key_a(0, 0, 0, KC_A);
98 KeymapKey key_b(0, 1, 0, KC_B);
99 KeymapKey key_repeat(0, 2, 0, QK_REP);
100 set_keymap({key_a, key_b, key_repeat});
101
102 // Allow any number of empty reports.
103 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
104 ExpectString(driver, "aaabb");
105
106 // When KC_A is pressed, process_record_user() should be called
107 // with a press event with keycode == KC_A and repeat_key_count() == 0.
108 ExpectProcessRecordUserCalledWith(true, KC_A, 0);
109 key_a.press();
110 run_one_scan_loop();
111 EXPECT_TRUE(process_record_user_was_called_);
112
113 // After pressing A, the keycode of the key to be repeated is KC_A.
114 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
115 EXPECT_EQ(get_last_mods(), 0);
116
117 // Expect the corresponding release event when A is released.
118 ExpectProcessRecordUserCalledWith(false, KC_A, 0);
119 key_a.release();
120 run_one_scan_loop();
121
122 for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
123 // When Repeat is pressed, process_record_user() should be called with a
124 // press event with keycode == KC_A and repeat_key_count() == n.
125 ExpectProcessRecordUserCalledWith(true, KC_A, n);
126 key_repeat.press(); // Press the Repeat Key.
127 run_one_scan_loop();
128 EXPECT_TRUE(process_record_user_was_called_);
129
130 // Expect the corresponding release event.
131 ExpectProcessRecordUserCalledWith(false, KC_A, n);
132 key_repeat.release(); // Release the Repeat Key.
133 run_one_scan_loop();
134 EXPECT_TRUE(process_record_user_was_called_);
135 }
136
137 process_record_user_fun = process_record_user_default;
138 tap_key(key_b);
139 // Then after tapping key_b, the keycode to be repeated becomes KC_B.
140 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
141
142 tap_key(key_repeat);
143
144 testing::Mock::VerifyAndClearExpectations(&driver);
145}
146
147// Tests repeating a macro. The keycode FOO_MACRO sends "foo" when pressed. The
148// test taps "FOO_MACRO, Repeat, Repeat", producing "foofoofoo".
149TEST_F(RepeatKey, Macro) {
150 TestDriver driver;
151 KeymapKey key_foo(0, 0, 0, FOO_MACRO);
152 KeymapKey key_repeat(0, 1, 0, QK_REP);
153 set_keymap({key_foo, key_repeat});
154
155 // Define process_record_user() to handle FOO_MACRO.
156 process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) {
157 switch (keycode) {
158 case FOO_MACRO:
159 if (record->event.pressed) {
160 SEND_STRING("foo");
161 }
162 break;
163 }
164 return true;
165 };
166
167 // Allow any number of empty reports.
168 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
169 ExpectString(driver, "foofoofoo");
170
171 tap_key(key_foo);
172
173 EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO);
174
175 tap_keys(key_repeat, key_repeat);
176
177 testing::Mock::VerifyAndClearExpectations(&driver);
178}
179
180// Tests a macro with customized repeat behavior: "foo" is sent normally, "bar"
181// on the first repeat, and "baz" on subsequent repeats. The test taps
182// "FOO_MACRO, Repeat, Repeat, FOO_MACRO, Repeat", producing "foobarbazfoobar".
183TEST_F(RepeatKey, MacroCustomRepeat) {
184 TestDriver driver;
185 KeymapKey key_foo(0, 0, 0, FOO_MACRO);
186 KeymapKey key_repeat(0, 1, 0, QK_REP);
187 set_keymap({key_foo, key_repeat});
188
189 process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) {
190 switch (keycode) {
191 case FOO_MACRO:
192 if (record->event.pressed) {
193 switch (get_repeat_key_count()) {
194 case 0: // When pressed normally.
195 SEND_STRING("foo");
196 break;
197 case 1: // On first repeat.
198 SEND_STRING("bar");
199 break;
200 default: // On subsequent repeats.
201 SEND_STRING("baz");
202 break;
203 }
204 }
205 break;
206 }
207 return true;
208 };
209
210 // Allow any number of empty reports.
211 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
212 ExpectString(driver, "foobarbazfoobar");
213
214 tap_key(key_foo);
215
216 EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO);
217
218 tap_keys(key_repeat, key_repeat, key_foo, key_repeat);
219
220 testing::Mock::VerifyAndClearExpectations(&driver);
221}
222
223// Tests repeating keys on different layers. A 2-layer keymap is defined:
224// Layer 0: QK_REP , MO(1) , KC_A
225// Layer 1: KC_TRNS, KC_TRNS, KC_B
226// The test does the following, which should produce "bbbaaa":
227// 1. Hold MO(1), switching to layer 1.
228// 2. Tap KC_B on layer 1.
229// 3. Release MO(1), switching back to layer 0.
230// 4. Tap Repeat twice.
231// 5. Tap KC_A on layer 0.
232// 6. Hold MO(1), switching to layer 1.
233// 7. Tap Repeat twice.
234TEST_F(RepeatKey, AcrossLayers) {
235 TestDriver driver;
236 KeymapKey key_repeat(0, 0, 0, QK_REP);
237 KeymapKey key_mo_1(0, 1, 0, MO(1));
238 KeymapKey regular_key(0, 2, 0, KC_A);
239 set_keymap({// Layer 0.
240 key_repeat, key_mo_1, regular_key,
241 // Layer 1.
242 KeymapKey{1, 0, 0, KC_TRNS}, KeymapKey{1, 1, 0, KC_TRNS}, KeymapKey{1, 2, 0, KC_B}});
243
244 // Allow any number of empty reports.
245 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
246 ExpectString(driver, "bbbaaa");
247
248 key_mo_1.press(); // Hold the MO(1) layer key.
249 run_one_scan_loop();
250 tap_key(regular_key); // Taps the KC_B key on layer 1.
251
252 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
253
254 key_mo_1.release(); // Release the layer key.
255 run_one_scan_loop();
256 tap_keys(key_repeat, key_repeat);
257 tap_key(regular_key); // Taps the KC_A key on layer 0.
258
259 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
260
261 key_mo_1.press(); // Hold the layer key.
262 run_one_scan_loop();
263 tap_keys(key_repeat, key_repeat);
264
265 testing::Mock::VerifyAndClearExpectations(&driver);
266}
267
268// Tests "A(down), Repeat(down), A(up), Repeat(up), Repeat" produces "aaa".
269TEST_F(RepeatKey, RollingToRepeat) {
270 TestDriver driver;
271 KeymapKey key_a(0, 0, 0, KC_A);
272 KeymapKey key_repeat(0, 1, 0, QK_REP);
273 set_keymap({key_a, key_repeat});
274
275 {
276 InSequence seq;
277 EXPECT_REPORT(driver, (KC_A));
278 EXPECT_EMPTY_REPORT(driver);
279 EXPECT_REPORT(driver, (KC_A));
280 EXPECT_EMPTY_REPORT(driver);
281 EXPECT_REPORT(driver, (KC_A));
282 EXPECT_EMPTY_REPORT(driver);
283 }
284
285 // Perform a rolled press from A to Repeat.
286
287 ExpectProcessRecordUserCalledWith(true, KC_A, 0);
288 key_a.press();
289 run_one_scan_loop();
290 EXPECT_TRUE(process_record_user_was_called_);
291
292 ExpectProcessRecordUserCalledWith(true, KC_A, 1);
293 key_repeat.press(); // Press the Repeat Key.
294 run_one_scan_loop();
295 EXPECT_TRUE(process_record_user_was_called_);
296
297 ExpectProcessRecordUserCalledWith(false, KC_A, 0);
298 key_a.release();
299 run_one_scan_loop();
300 EXPECT_TRUE(process_record_user_was_called_);
301
302 ExpectProcessRecordUserCalledWith(false, KC_A, 1);
303 key_repeat.release(); // Release the Repeat Key.
304 run_one_scan_loop();
305 EXPECT_TRUE(process_record_user_was_called_);
306
307 process_record_user_fun = process_record_user_default;
308 tap_key(key_repeat);
309
310 testing::Mock::VerifyAndClearExpectations(&driver);
311}
312
313// Tests "A, Repeat(down), B(down), Repeat(up), B(up), Repeat" produces "aabb".
314TEST_F(RepeatKey, RollingFromRepeat) {
315 TestDriver driver;
316 KeymapKey key_a(0, 0, 0, KC_A);
317 KeymapKey key_b(0, 1, 0, KC_B);
318 KeymapKey key_repeat(0, 2, 0, QK_REP);
319 set_keymap({key_a, key_b, key_repeat});
320
321 {
322 InSequence seq;
323 EXPECT_REPORT(driver, (KC_A));
324 EXPECT_EMPTY_REPORT(driver);
325 EXPECT_REPORT(driver, (KC_A));
326 EXPECT_REPORT(driver, (KC_A, KC_B));
327 EXPECT_REPORT(driver, (KC_B));
328 EXPECT_EMPTY_REPORT(driver);
329 EXPECT_REPORT(driver, (KC_B));
330 EXPECT_EMPTY_REPORT(driver);
331 }
332
333 tap_key(key_a);
334
335 // Perform a rolled press from Repeat to B.
336
337 ExpectProcessRecordUserCalledWith(true, KC_A, 1);
338 key_repeat.press(); // Press the Repeat Key.
339 run_one_scan_loop();
340 EXPECT_TRUE(process_record_user_was_called_);
341
342 ExpectProcessRecordUserCalledWith(true, KC_B, 0);
343 key_b.press();
344 run_one_scan_loop();
345 EXPECT_TRUE(process_record_user_was_called_);
346
347 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
348
349 ExpectProcessRecordUserCalledWith(false, KC_A, 1);
350 key_repeat.release(); // Release the Repeat Key.
351 run_one_scan_loop();
352 EXPECT_TRUE(process_record_user_was_called_);
353
354 ExpectProcessRecordUserCalledWith(false, KC_B, 0);
355 key_b.release();
356 run_one_scan_loop();
357 EXPECT_TRUE(process_record_user_was_called_);
358
359 process_record_user_fun = process_record_user_default;
360 tap_key(key_repeat);
361
362 testing::Mock::VerifyAndClearExpectations(&driver);
363}
364
365// Tests Repeat Key with a modifier, types "AltGr+C, Repeat, Repeat, C".
366TEST_F(RepeatKey, RecallMods) {
367 TestDriver driver;
368 KeymapKey key_c(0, 0, 0, KC_C);
369 KeymapKey key_altgr(0, 1, 0, KC_RALT);
370 KeymapKey key_repeat(0, 2, 0, QK_REP);
371 set_keymap({key_c, key_altgr, key_repeat});
372
373 // Allow any number of reports with no keys or only KC_RALT.
374 // clang-format off
375 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
376 KeyboardReport(),
377 KeyboardReport(KC_RALT))))
378 .Times(AnyNumber());
379 // clang-format on
380
381 { // Expect: "AltGr+C, AltGr+C, AltGr+C, C".
382 InSequence seq;
383 EXPECT_REPORT(driver, (KC_RALT, KC_C));
384 EXPECT_REPORT(driver, (KC_RALT, KC_C));
385 EXPECT_REPORT(driver, (KC_RALT, KC_C));
386 EXPECT_REPORT(driver, (KC_C));
387 }
388
389 key_altgr.press();
390 run_one_scan_loop();
391 tap_key(key_c);
392 key_altgr.release();
393 run_one_scan_loop();
394
395 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_C);
396 EXPECT_EQ(get_last_mods(), MOD_BIT(KC_RALT));
397
398 tap_keys(key_repeat, key_repeat, key_c);
399
400 testing::Mock::VerifyAndClearExpectations(&driver);
401}
402
403// Tests that Repeat Key stacks mods, types
404// "Ctrl+Left, Repeat, Shift+Repeat, Shift+Repeat, Repeat, Left".
405TEST_F(RepeatKey, StackMods) {
406 TestDriver driver;
407 KeymapKey key_left(0, 0, 0, KC_LEFT);
408 KeymapKey key_shift(0, 1, 0, KC_LSFT);
409 KeymapKey key_ctrl(0, 2, 0, KC_LCTL);
410 KeymapKey key_repeat(0, 3, 0, QK_REP);
411 set_keymap({key_left, key_shift, key_ctrl, key_repeat});
412
413 // Allow any number of reports with no keys or only mods.
414 // clang-format off
415 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
416 KeyboardReport(),
417 KeyboardReport(KC_LCTL),
418 KeyboardReport(KC_LSFT),
419 KeyboardReport(KC_LCTL, KC_LSFT))))
420 .Times(AnyNumber());
421 // clang-format on
422
423 { // Expect: "Ctrl+Left, Ctrl+Shift+Left".
424 InSequence seq;
425 EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
426 EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
427 EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT));
428 EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT));
429 EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
430 EXPECT_REPORT(driver, (KC_LEFT));
431 }
432
433 key_ctrl.press();
434 run_one_scan_loop();
435 tap_key(key_left);
436 run_one_scan_loop();
437 key_ctrl.release();
438 run_one_scan_loop();
439
440 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_LEFT);
441 EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
442
443 tap_key(key_repeat);
444
445 key_shift.press();
446 run_one_scan_loop();
447 tap_keys(key_repeat, key_repeat);
448 key_shift.release();
449 run_one_scan_loop();
450
451 EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
452
453 tap_keys(key_repeat, key_left);
454
455 testing::Mock::VerifyAndClearExpectations(&driver);
456}
457
458// Types: "S(KC_1), Repeat, Ctrl+Repeat, Ctrl+Repeat, Repeat, KC_2".
459TEST_F(RepeatKey, ShiftedKeycode) {
460 TestDriver driver;
461 KeymapKey key_exlm(0, 0, 0, S(KC_1));
462 KeymapKey key_2(0, 1, 0, KC_2);
463 KeymapKey key_ctrl(0, 2, 0, KC_LCTL);
464 KeymapKey key_repeat(0, 3, 0, QK_REP);
465 set_keymap({key_exlm, key_2, key_ctrl, key_repeat});
466
467 // Allow any number of reports with no keys or only mods.
468 // clang-format off
469 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
470 KeyboardReport(),
471 KeyboardReport(KC_LCTL),
472 KeyboardReport(KC_LSFT),
473 KeyboardReport(KC_LCTL, KC_LSFT))))
474 .Times(AnyNumber());
475 // clang-format on
476
477 { // Expect: "Shift+1, Shift+1, Ctrl+Shift+1, Ctrl+Shift+1, Shift+1, 2".
478 InSequence seq;
479 EXPECT_REPORT(driver, (KC_LSFT, KC_1));
480 EXPECT_REPORT(driver, (KC_LSFT, KC_1));
481 EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1));
482 EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1));
483 EXPECT_REPORT(driver, (KC_LSFT, KC_1));
484 EXPECT_REPORT(driver, (KC_2));
485 }
486
487 tap_key(key_exlm);
488
489 EXPECT_KEYCODE_EQ(get_last_keycode(), S(KC_1));
490
491 tap_key(key_repeat);
492
493 key_ctrl.press();
494 run_one_scan_loop();
495 tap_keys(key_repeat, key_repeat);
496 key_ctrl.release();
497 run_one_scan_loop();
498
499 tap_keys(key_repeat, key_2);
500
501 testing::Mock::VerifyAndClearExpectations(&driver);
502}
503
504// Tests Repeat Key with a one-shot Shift, types
505// "A, OSM(MOD_LSFT), Repeat, Repeat".
506TEST_F(RepeatKey, WithOneShotShift) {
507 TestDriver driver;
508 KeymapKey key_a(0, 0, 0, KC_A);
509 KeymapKey key_oneshot_shift(0, 1, 0, OSM(MOD_LSFT));
510 KeymapKey key_repeat(0, 2, 0, QK_REP);
511 set_keymap({key_a, key_oneshot_shift, key_repeat});
512
513 // Allow any number of reports with no keys or only KC_RALT.
514 // clang-format off
515 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
516 KeyboardReport(),
517 KeyboardReport(KC_LSFT))))
518 .Times(AnyNumber());
519 // clang-format on
520 ExpectString(driver, "aAa");
521
522 tap_keys(key_a, key_oneshot_shift, key_repeat, key_repeat);
523
524 testing::Mock::VerifyAndClearExpectations(&driver);
525}
526
527// Tests Repeat Key with a mod-tap key, types
528// "A, Repeat, Repeat, A(down), Repeat, Repeat, A(up), Repeat".
529TEST_F(RepeatKey, ModTap) {
530 TestDriver driver;
531 KeymapKey key_mt_a(0, 0, 0, LSFT_T(KC_A));
532 KeymapKey key_repeat(0, 1, 0, QK_REP);
533 set_keymap({key_mt_a, key_repeat});
534
535 // Allow any number of reports with no keys or only KC_LSFT.
536 // clang-format off
537 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
538 KeyboardReport(),
539 KeyboardReport(KC_LSFT))))
540 .Times(AnyNumber());
541 // clang-format on
542 ExpectString(driver, "aaaAAa");
543
544 tap_key(key_mt_a);
545
546 EXPECT_KEYCODE_EQ(get_last_keycode(), LSFT_T(KC_A));
547
548 tap_keys(key_repeat, key_repeat);
549 key_mt_a.press();
550 run_one_scan_loop();
551 tap_key(key_repeat, TAPPING_TERM + 1);
552 tap_key(key_repeat);
553 key_mt_a.release();
554 run_one_scan_loop();
555 tap_key(key_repeat);
556
557 testing::Mock::VerifyAndClearExpectations(&driver);
558}
559
560// Tests with Auto Shift. When repeating an autoshiftable key, it does not
561// matter how long the original key was held, rather, quickly tapping vs.
562// long-pressing the Repeat Key determines whether the shifted key is repeated.
563//
564// The test does the following, which should produce "aaABbB":
565// 1. Tap KC_A quickly.
566// 2. Tap Repeat Key quickly.
567// 3. Long-press Repeat Key.
568// 4. Long-press KC_B.
569// 5. Tap Repeat Key quickly.
570// 6. Long-press Repeat Key.
571TEST_F(RepeatKey, AutoShift) {
572 TestDriver driver;
573 KeymapKey key_a(0, 0, 0, KC_A);
574 KeymapKey key_b(0, 1, 0, KC_B);
575 KeymapKey key_repeat(0, 2, 0, QK_REP);
576 set_keymap({key_a, key_b, key_repeat});
577
578 autoshift_enable();
579
580 // Allow any number of reports with no keys or only KC_LSFT.
581 // clang-format off
582 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
583 KeyboardReport(),
584 KeyboardReport(KC_LSFT))))
585 .Times(AnyNumber());
586 // clang-format on
587 ExpectString(driver, "aaABbB");
588
589 tap_key(key_a); // Tap A quickly.
590
591 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
592 EXPECT_EQ(get_last_mods(), 0);
593
594 tap_key(key_repeat);
595 tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1);
596
597 tap_key(key_b, AUTO_SHIFT_TIMEOUT + 1); // Long press B.
598
599 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
600 EXPECT_EQ(get_last_mods(), 0);
601
602 tap_key(key_repeat);
603 tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1);
604
605 testing::Mock::VerifyAndClearExpectations(&driver);
606}
607
608// Defines `remember_last_key_user()` to forget the Shift mod and types:
609// "Ctrl+A, Repeat, Shift+A, Repeat, Shift+Repeat".
610TEST_F(RepeatKey, FilterRememberedMods) {
611 TestDriver driver;
612 KeymapKey key_a(0, 0, 0, KC_A);
613 KeymapKey key_ctrl(0, 1, 0, KC_LCTL);
614 KeymapKey key_shift(0, 2, 0, KC_LSFT);
615 KeymapKey key_repeat(0, 3, 0, QK_REP);
616 set_keymap({key_a, key_ctrl, key_shift, key_repeat});
617
618 remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
619 *remembered_mods &= ~MOD_MASK_SHIFT;
620 return true;
621 };
622
623 // Allow any number of reports with no keys or only mods.
624 // clang-format off
625 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
626 KeyboardReport(),
627 KeyboardReport(KC_LCTL),
628 KeyboardReport(KC_LSFT),
629 KeyboardReport(KC_LCTL, KC_LSFT))))
630 .Times(AnyNumber());
631 // clang-format on
632
633 { // Expect: "Ctrl+A, Ctrl+A, Shift+A, A, Shift+A".
634 InSequence seq;
635 EXPECT_REPORT(driver, (KC_LCTL, KC_A));
636 EXPECT_REPORT(driver, (KC_LCTL, KC_A));
637 EXPECT_REPORT(driver, (KC_LSFT, KC_A));
638 EXPECT_REPORT(driver, (KC_A));
639 EXPECT_REPORT(driver, (KC_LSFT, KC_A));
640 }
641
642 key_ctrl.press();
643 run_one_scan_loop();
644 tap_key(key_a);
645
646 EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
647
648 key_ctrl.release();
649 run_one_scan_loop();
650
651 tap_key(key_repeat);
652 key_shift.press();
653 run_one_scan_loop();
654 tap_key(key_a);
655
656 EXPECT_EQ(get_last_mods(), 0); // Shift should be forgotten.
657
658 key_shift.release();
659 run_one_scan_loop();
660
661 tap_key(key_repeat);
662
663 key_shift.press();
664 run_one_scan_loop();
665 tap_key(key_repeat);
666 key_shift.release();
667 run_one_scan_loop();
668
669 testing::Mock::VerifyAndClearExpectations(&driver);
670}
671
672// Tests set_last_keycode() and set_last_mods().
673TEST_F(RepeatKey, SetRepeatKeyKeycode) {
674 TestDriver driver;
675 KeymapKey key_repeat(0, 0, 0, QK_REP);
676 set_keymap({key_repeat});
677
678 // Allow any number of reports with no keys or only KC_LSFT.
679 // clang-format off
680 EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
681 KeyboardReport(),
682 KeyboardReport(KC_LSFT))))
683 .Times(AnyNumber());
684 // clang-format on
685 ExpectString(driver, "aaBB");
686
687 set_last_keycode(KC_A);
688 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
689
690 for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
691 // When Repeat is pressed, process_record_user() should be called with a
692 // press event with keycode == KC_A and repeat_key_count() == n.
693 ExpectProcessRecordUserCalledWith(true, KC_A, n);
694 key_repeat.press(); // Press the Repeat Key.
695 run_one_scan_loop();
696 EXPECT_TRUE(process_record_user_was_called_);
697
698 // Expect the corresponding release event.
699 ExpectProcessRecordUserCalledWith(false, KC_A, n);
700 key_repeat.release(); // Release the Repeat Key.
701 run_one_scan_loop();
702 EXPECT_TRUE(process_record_user_was_called_);
703 }
704
705 process_record_user_fun = process_record_user_default;
706 set_last_keycode(KC_B);
707 set_last_mods(MOD_BIT(KC_LSFT));
708
709 tap_keys(key_repeat, key_repeat);
710
711 set_last_keycode(KC_NO);
712 tap_keys(key_repeat, key_repeat); // Has no effect.
713
714 testing::Mock::VerifyAndClearExpectations(&driver);
715}
716
717// Tests the `repeat_key_invoke()` function.
718TEST_F(RepeatKey, RepeatKeyInvoke) {
719 TestDriver driver;
720 KeymapKey key_s(0, 0, 0, KC_S);
721 set_keymap({key_s});
722
723 // Allow any number of empty reports.
724 EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
725 ExpectString(driver, "ss");
726
727 tap_key(key_s);
728
729 EXPECT_KEYCODE_EQ(get_last_keycode(), KC_S);
730
731 // Calling repeat_key_invoke() should result in process_record_user()
732 // getting a press event with keycode KC_S.
733 ExpectProcessRecordUserCalledWith(true, KC_S, 1);
734 keyevent_t event;
735 event.key = {0, 0};
736 event.pressed = true;
737 event.time = timer_read();
738 event.type = KEY_EVENT;
739 repeat_key_invoke(&event);
740 run_one_scan_loop();
741 EXPECT_TRUE(process_record_user_was_called_);
742
743 // Make the release event.
744 ExpectProcessRecordUserCalledWith(false, KC_S, 1);
745 event.pressed = false;
746 event.time = timer_read();
747 repeat_key_invoke(&event);
748 run_one_scan_loop();
749 EXPECT_TRUE(process_record_user_was_called_);
750
751 testing::Mock::VerifyAndClearExpectations(&driver);
752}
753
754} // namespace
diff --git a/tests/test_common/keycode_table.cpp b/tests/test_common/keycode_table.cpp
index d21630c01b..9ed80cdbcf 100644
--- a/tests/test_common/keycode_table.cpp
+++ b/tests/test_common/keycode_table.cpp
@@ -663,6 +663,8 @@ std::map<uint16_t, std::string> KEYCODE_ID_TABLE = {
663 {QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"}, 663 {QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"},
664 {QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"}, 664 {QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"},
665 {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, 665 {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"},
666 {QK_REPEAT_KEY, "QK_REPEAT_KEY"},
667 {QK_ALT_REPEAT_KEY, "QK_ALT_REPEAT_KEY"},
666 {QK_KB_0, "QK_KB_0"}, 668 {QK_KB_0, "QK_KB_0"},
667 {QK_KB_1, "QK_KB_1"}, 669 {QK_KB_1, "QK_KB_1"},
668 {QK_KB_2, "QK_KB_2"}, 670 {QK_KB_2, "QK_KB_2"},
diff --git a/tests/test_common/test_driver.hpp b/tests/test_common/test_driver.hpp
index 8d09e44840..d8a6885d0f 100644
--- a/tests/test_common/test_driver.hpp
+++ b/tests/test_common/test_driver.hpp
@@ -20,6 +20,7 @@
20#include <stdint.h> 20#include <stdint.h>
21#include "host.h" 21#include "host.h"
22#include "keyboard_report_util.hpp" 22#include "keyboard_report_util.hpp"
23#include "keycode_util.hpp"
23#include "test_logger.hpp" 24#include "test_logger.hpp"
24 25
25class TestDriver { 26class TestDriver {
@@ -98,6 +99,17 @@ class TestDriver {
98 */ 99 */
99#define EXPECT_NO_REPORT(driver) EXPECT_ANY_REPORT(driver).Times(0) 100#define EXPECT_NO_REPORT(driver) EXPECT_ANY_REPORT(driver).Times(0)
100 101
102/** @brief Tests whether keycode `actual` is equal to `expected`. */
103#define EXPECT_KEYCODE_EQ(actual, expected) EXPECT_THAT((actual), KeycodeEq((expected)))
104
105MATCHER_P(KeycodeEq, expected_keycode, "is equal to " + testing::PrintToString(expected_keycode) + ", keycode " + get_keycode_identifier_or_default(expected_keycode)) {
106 if (arg == expected_keycode) {
107 return true;
108 }
109 *result_listener << "keycode " << get_keycode_identifier_or_default(arg);
110 return false;
111}
112
101/** 113/**
102 * @brief Verify and clear all gmock expectations that have been setup until 114 * @brief Verify and clear all gmock expectations that have been setup until
103 * this point. 115 * this point.