[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>
master
Pascal Getreuer 2023-05-20 05:35:06 -07:00 committed by GitHub
parent e1766df185
commit 3993b15f05
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
29 changed files with 2508 additions and 6 deletions

View File

@ -32,6 +32,7 @@ GENERIC_FEATURES = \
KEY_OVERRIDE \ KEY_OVERRIDE \
LEADER \ LEADER \
PROGRAMMABLE_BUTTON \ PROGRAMMABLE_BUTTON \
REPEAT_KEY \
SECURE \ SECURE \
SPACE_CADET \ SPACE_CADET \
SWAP_HANDS \ SWAP_HANDS \

View File

@ -85,7 +85,8 @@ OTHER_OPTION_NAMES = \
SECURE_ENABLE \ SECURE_ENABLE \
CAPS_WORD_ENABLE \ CAPS_WORD_ENABLE \
AUTOCORRECT_ENABLE \ AUTOCORRECT_ENABLE \
TRI_LAYER_ENABLE TRI_LAYER_ENABLE \
REPEAT_KEY_ENABLE
define NAME_ECHO define NAME_ECHO
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)" @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"

View File

@ -0,0 +1,18 @@
{
"keycodes": {
"0x7C79": {
"group": "quantum",
"key": "QK_REPEAT_KEY",
"aliases": [
"QK_REP"
]
},
"0x7C7A": {
"group": "quantum",
"key": "QK_ALT_REPEAT_KEY",
"aliases": [
"QK_AREP"
]
}
}
}

View File

@ -70,6 +70,7 @@
* [Macros](feature_macros.md) * [Macros](feature_macros.md)
* [Mouse Keys](feature_mouse_keys.md) * [Mouse Keys](feature_mouse_keys.md)
* [Programmable Button](feature_programmable_button.md) * [Programmable Button](feature_programmable_button.md)
* [Repeat Key](feature_repeat_key.md)
* [Space Cadet Shift](feature_space_cadet.md) * [Space Cadet Shift](feature_space_cadet.md)
* [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md) * [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md)

457
docs/feature_repeat_key.md Normal file
View File

@ -0,0 +1,457 @@
# Repeat Key
The Repeat Key performs the action of the last pressed key. Tapping the Repeat
Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for
typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd>
can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is
potentially faster and more comfortable. The Repeat Key is also useful for
hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word.
Repeat Key remembers mods that were active with the last key press. These mods
are combined with any additional mods while pressing the Repeat Key. If the last
press key was <kbd>Ctrl</kbd> + <kbd>Z</kbd>, then <kbd>Shift</kbd> +
<kbd>Repeat</kbd> performs Ctrl + Shift + `Z`.
## How do I enable Repeat Key
In your `rules.mk`, add:
```make
REPEAT_KEY_ENABLE = yes
```
Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short
alias `QK_REP`). Optionally, use the keycode `QK_ALT_REPEAT_KEY` (short alias
`QK_AREP`) on another key.
## Keycodes
|Keycode |Aliases |Description |
|-----------------------|---------|-------------------------------------|
|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key |
## Alternate Repeating
The Alternate Repeat Key performs the "alternate" action of the last pressed key
if it is defined. By default, Alternate Repeat is defined for navigation keys to
act in the reverse direction. When the last key is the common "select by word"
hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl +
Shift + Left Arrow, which together with the Repeat Key enables convenient
selection by words in either direction.
Alternate Repeat is enabled with the Repeat Key by default. Optionally, to
reduce firmware size, Alternate Repeat may be disabled by adding in config.h:
```c
#define NO_ALT_REPEAT_KEY
```
The following alternate keys are defined by default. See
`get_alt_repeat_key_keycode_user()` below for how to change or add to these
definitions. Where it makes sense, these definitions also include combinations
with mods, like Ctrl + Left &harr; Ctrl + Right Arrow.
**Navigation**
|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_LEFT` &harr; `KC_RGHT` | Left &harr; Right Arrow |
|`KC_UP` &harr; `KC_DOWN` | Up &harr; Down Arrow |
|`KC_HOME` &harr; `KC_END` | Home &harr; End |
|`KC_PGUP` &harr; `KC_PGDN` | Page Up &harr; Page Down |
|`KC_MS_L` &harr; `KC_MS_R` | Mouse Cursor Left &harr; Right |
|`KC_MS_U` &harr; `KC_MS_D` | Mouse Cursor Up &harr; Down |
|`KC_WH_L` &harr; `KC_WH_R` | Mouse Wheel Left &harr; Right |
|`KC_WH_U` &harr; `KC_WH_D` | Mouse Wheel Up &harr; Down |
**Misc**
|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_BSPC` &harr; `KC_DEL` | Backspace &harr; Delete |
|`KC_LBRC` &harr; `KC_RBRC` | `[` &harr; `]` |
|`KC_LCBR` &harr; `KC_RCBR` | `{` &harr; `}` |
**Media**
|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_WBAK` &harr; `KC_WFWD` | Browser Back &harr; Forward |
|`KC_MNXT` &harr; `KC_MPRV` | Next &harr; Previous Media Track |
|`KC_MFFD` &harr; `KC_MRWD` | Fast Forward &harr; Rewind Media |
|`KC_VOLU` &harr; `KC_VOLD` | Volume Up &harr; Down |
|`KC_BRIU` &harr; `KC_BRID` | Brightness Up &harr; Down |
**Hotkeys in Vim, Emacs, and other programs**
|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|mod + `KC_F` &harr; mod + `KC_B` | Forward &harr; Backward |
|mod + `KC_D` &harr; mod + `KC_U` | Down &harr; Up |
|mod + `KC_N` &harr; mod + `KC_P` | Next &harr; Previous |
|mod + `KC_A` &harr; mod + `KC_E` | Home &harr; End |
|mod + `KC_O` &harr; mod + `KC_I` | Vim jump list Older &harr; Newer |
|`KC_J` &harr; `KC_K` | Down &harr; Up |
|`KC_H` &harr; `KC_L` | Left &harr; Right |
|`KC_W` &harr; `KC_B` | Forward &harr; Backward by Word |
(where above, "mod" is Ctrl, Alt, or GUI)
## Defining alternate keys
Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate"
for additional keys or override the default definitions. For example, to define
Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in
keymap.c:
```c
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
if ((mods & MOD_MASK_CTRL)) { // Was Ctrl held?
switch (keycode) {
case KC_Y: return C(KC_Z); // Ctrl + Y reverses to Ctrl + Z.
case KC_Z: return C(KC_Y); // Ctrl + Z reverses to Ctrl + Y.
}
}
return KC_TRNS; // Defer to default definitions.
}
```
The `keycode` and `mods` args are the keycode and mods that were active with the
last pressed key. The meaning of the return value from this function is:
* `KC_NO` &ndash; do nothing (any predefined alternate key is not used);
* `KC_TRNS` &ndash; use the default alternate key if it exists;
* anything else &ndash; use the specified keycode. Any keycode may be returned
as an alternate key, including custom keycodes.
Another example, defining Shift + Tab as the alternate of Tab, and vice versa:
```c
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
bool shifted = (mods & MOD_MASK_SHIFT); // Was Shift held?
switch (keycode) {
case KC_TAB:
if (shifted) { // If the last key was Shift + Tab,
return KC_TAB; // ... the reverse is Tab.
} else { // Otherwise, the last key was Tab,
return S(KC_TAB); // ... and the reverse is Shift + Tab.
}
}
return KC_TRNS;
}
```
#### Eliminating SFBs
Alternate Repeat can be configured more generally to perform an action that
"complements" the last key. Alternate Repeat is not limited to reverse
repeating, and it need not be symmetric. You can use it to eliminate cases of
same-finger bigrams in your layout, that is, pairs of letters typed by the same
finger. The following addresses the top 5 same-finger bigrams in English on
QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt
Repeat</kbd>.
```c
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case KC_E: return KC_D; // For "ED" bigram.
case KC_D: return KC_E; // For "DE" bigram.
case KC_C: return KC_E; // For "CE" bigram.
case KC_L: return KC_O; // For "LO" bigram.
case KC_U: return KC_N; // For "UN" bigram.
}
return KC_TRNS;
}
```
#### Typing shortcuts
A useful possibility is having Alternate Repeat press [a
macro](feature_macros.md). This way macros can be used without having to
dedicate keys to them. The following defines a couple shortcuts.
* Typing <kbd>K</kbd>, <kbd>Alt Repeat</kbd> produces "`keyboard`," with the
initial "`k`" typed as usual and the "`eybord`" produced by the macro.
* Typing <kbd>.</kbd>, <kbd>Alt Repeat</kbd> produces "`../`," handy for "up
directory" on the shell. Similary, <kbd>.</kbd> types the initial "`.`" and
"`./`" is produced by the macro.
```c
enum custom_keycodes {
M_KEYBOARD = SAFE_RANGE,
M_UPDIR,
// Other custom keys...
};
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case KC_K: return M_KEYBOARD;
case KC_DOT: return M_UPDIR;
}
return KC_TRNS;
}
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case M_KEYBOARD: SEND_STRING(/*k*/"eyboard"); break;
case M_UPDIR: SEND_STRING(/*.*/"./"); break;
}
return true;
}
```
## Ignoring certain keys and mods
In tracking what is "the last key" to be repeated or alternate repeated,
modifier and layer switch keys are always ignored. This makes it possible to set
some mods and change layers between pressing a key and repeating it. By default,
all other (non-modifier, non-layer switch) keys are remembered so that they are
eligible for repeating. To configure additional keys to be ignored, define
`remember_last_key_user()` in your keymap.c.
#### Ignoring a key
The following ignores the Backspace key:
```c
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
uint8_t* remembered_mods) {
switch (keycode) {
case KC_BSPC:
return false; // Ignore backspace.
}
return true; // Other keys can be repeated.
}
```
Then for instance, the Repeat key in <kbd>Left Arrow</kbd>,
<kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of
repeating Backspace.
The `remember_last_key_user()` callback is called on every key press excluding
modifiers and layer switches. Returning true indicates the key is remembered,
while false means it is ignored.
#### Filtering remembered mods
The `remembered_mods` arg represents the mods that will be remembered with
this key. It can be modified to forget certain mods. This may be
useful to forget capitalization when repeating shifted letters, so that "Aaron"
does not becom "AAron":
```c
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
uint8_t* remembered_mods) {
// Forget Shift on letter keys when Shift or AltGr are the only mods.
switch (keycode) {
case KC_A ... KC_Z:
if ((*remembered_mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) {
*remembered_mods &= ~MOD_MASK_SHIFT;
}
break;
}
return true;
}
```
#### Further conditions
Besides checking the keycode, this callback could also make conditions based on
the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For
example, the following ignores keys on layer 2 as well as key combinations
involving GUI:
```c
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
uint8_t* remembered_mods) {
if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) {
return false; // Ignore layer 2 keys and GUI chords.
}
return true; // Other keys can be repeated.
}
```
?> See [Layer Functions](feature_layers.md#functions) and [Checking Modifier
State](feature_advanced_keycodes.md#checking-modifier-state) for further
details.
## Handle how a key is repeated
By default, pressing the Repeat Key will simply behave as if the last key
were pressed again. This also works with macro keys with custom handlers,
invoking the macro again. In case fine-tuning is needed for sensible repetition,
you can handle how a key is repeated with `get_repeat_key_count()` within
`process_record_user()`.
The `get_repeat_key_count()` function returns a signed count of times the key
has been repeated or alternate repeated. When a key is pressed as usual,
`get_repeat_key_count()` is 0. On the first repeat, it is 1, then the second
repeat, 2, and so on. Negative counts are used similarly for alternate
repeating. For instance supposing `MY_MACRO` is a custom keycode used in the
layout:
```c
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case MY_MACRO:
if (get_repeat_key_count() > 0) {
// MY_MACRO is being repeated!
if (record->event.pressed) {
SEND_STRING("repeat!");
}
} else {
// MY_MACRO is being used normally.
if (record->event.pressed) {
SEND_STRING("macro");
}
}
return false;
// Other macros...
}
return true;
}
```
## Handle how a key is alternate repeated
Pressing the Alternate Repeat Key behaves as if the "alternate" of the last
pressed key were pressed, if an alternate is defined. To define how a particular
key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback
as described above to define which keycode to use as its alternate. Beyond this,
`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior
when alternate repeating.
The following example defines `MY_MACRO` as its own alternate, and specially
handles repeating and alternate repeating:
```c
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case MY_MACRO: return MY_MACRO; // MY_MACRO is its own alternate.
}
return KC_TRNS;
}
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case MY_MACRO:
if (get_repeat_key_count() > 0) { // Repeating.
if (record->event.pressed) {
SEND_STRING("repeat!");
}
} else if (get_repeat_key_count() < 0) { // Alternate repeating.
if (record->event.pressed) {
SEND_STRING("alt repeat!");
}
} else { // Used normally.
if (record->event.pressed) {
SEND_STRING("macro");
}
}
return false;
// Other macros...
}
return true;
}
```
## Functions
| Function | Description |
|--------------------------------|------------------------------------------------------------------------|
| `get_last_keycode()` | The last key's keycode, the key to be repeated. |
| `get_last_mods()` | Mods to apply when repeating. |
| `set_last_keycode(kc)` | Set the keycode to be repeated. |
| `set_last_mods(mods)` | Set the mods to apply when repeating. |
| `get_repeat_key_count()` | Signed count of times the key has been repeated or alternate repeated. |
| `get_alt_repeat_key_keycode()` | Keycode to be used for alternate repeating. |
## Additional "Alternate" keys
By leveraging `get_last_keycode()` in macros, it is possible to define
additional, distinct "Alternate Repeat"-like keys. The following defines two
keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common
English 5-gram letter patterns, taking inspiration from
[Stenotype](feature_stenography.md):
| Typing | Produces | Typing | Produces |
|----------------------------------|----------|----------------------------------|----------|
| <kbd>A</kbd>, <kbd>ALTREP2</kbd> | `ation` | <kbd>A</kbd>, <kbd>ALTREP3</kbd> | `about` |
| <kbd>I</kbd>, <kbd>ALTREP2</kbd> | `ition` | <kbd>I</kbd>, <kbd>ALTREP3</kbd> | `inter` |
| <kbd>S</kbd>, <kbd>ALTREP2</kbd> | `ssion` | <kbd>S</kbd>, <kbd>ALTREP3</kbd> | `state` |
| <kbd>T</kbd>, <kbd>ALTREP2</kbd> | `their` | <kbd>T</kbd>, <kbd>ALTREP3</kbd> | `there` |
| <kbd>W</kbd>, <kbd>ALTREP2</kbd> | `which` | <kbd>W</kbd>, <kbd>ALTREP3</kbd> | `would` |
```c
enum custom_keycodes {
ALTREP2 = SAFE_RANGE,
ALTREP3,
};
// Use ALTREP2 and ALTREP3 in your layout...
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record,
uint8_t* remembered_mods) {
switch (keycode) {
case ALTREP2:
case ALTREP3:
return false; // Ignore ALTREP keys.
}
return true; // Other keys can be repeated.
}
static void process_altrep2(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case KC_A: SEND_STRING(/*a*/"tion"); break;
case KC_I: SEND_STRING(/*i*/"tion"); break;
case KC_S: SEND_STRING(/*s*/"sion"); break;
case KC_T: SEND_STRING(/*t*/"heir"); break;
case KC_W: SEND_STRING(/*w*/"hich"); break;
}
}
static void process_altrep3(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case KC_A: SEND_STRING(/*a*/"bout"); break;
case KC_I: SEND_STRING(/*i*/"nter"); break;
case KC_S: SEND_STRING(/*s*/"tate"); break;
case KC_T: SEND_STRING(/*t*/"here"); break;
case KC_W: SEND_STRING(/*w*/"ould"); break;
}
}
bool process_record_user(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case ALTREP2:
if (record->event.pressed) {
process_altrep2(get_last_keycode(), get_last_mods());
}
return false;
case ALTREP3:
if (record->event.pressed) {
process_altrep3(get_last_keycode(), get_last_mods());
}
return false;
}
return true;
}
```

View File

@ -68,6 +68,7 @@
* [モッドタップ](ja/mod_tap.md) * [モッドタップ](ja/mod_tap.md)
* [マクロ](ja/feature_macros.md) * [マクロ](ja/feature_macros.md)
* [マウスキー](ja/feature_mouse_keys.md) * [マウスキー](ja/feature_mouse_keys.md)
* [Repeat Key](ja/feature_repeat_key.md)
* [Space Cadet Shift](ja/feature_space_cadet.md) * [Space Cadet Shift](ja/feature_space_cadet.md)
* [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md) * [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md)

View File

@ -803,6 +803,15 @@ See also: [Programmable Button](feature_programmable_button.md)
|`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31| |`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31|
|`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32| |`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32|
## Repeat Key :id=repeat-key
See also: [Repeat Key](feature_repeat_key.md)
|Keycode |Aliases |Description |
|-----------------------|---------|-------------------------------------|
|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key |
## Space Cadet :id=space-cadet ## Space Cadet :id=space-cadet
See also: [Space Cadet](feature_space_cadet.md) See also: [Space Cadet](feature_space_cadet.md)

View File

@ -73,6 +73,7 @@
* [Mod-Tap](zh-cn/mod_tap.md) * [Mod-Tap](zh-cn/mod_tap.md)
* [](zh-cn/feature_macros.md) * [](zh-cn/feature_macros.md)
* [鼠标键](zh-cn/feature_mouse_keys.md) * [鼠标键](zh-cn/feature_mouse_keys.md)
* [Repeat Key](zh-cn/feature_repeat_key.md)
* [Space Cadet Shift](zh-cn/feature_space_cadet.md) * [Space Cadet Shift](zh-cn/feature_space_cadet.md)
* [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md) * [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md)

View File

@ -285,7 +285,7 @@ void process_record(keyrecord_t *record) {
} }
void process_record_handler(keyrecord_t *record) { void process_record_handler(keyrecord_t *record) {
#ifdef COMBO_ENABLE #if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
action_t action; action_t action;
if (record->keycode) { if (record->keycode) {
action = action_for_keycode(record->keycode); action = action_for_keycode(record->keycode);
@ -1109,7 +1109,7 @@ bool is_tap_record(keyrecord_t *record) {
return false; return false;
} }
#ifdef COMBO_ENABLE #if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
action_t action; action_t action;
if (record->keycode) { if (record->keycode) {
action = action_for_keycode(record->keycode); action = action_for_keycode(record->keycode);

View File

@ -50,7 +50,7 @@ typedef struct {
#ifndef NO_ACTION_TAPPING #ifndef NO_ACTION_TAPPING
tap_t tap; tap_t tap;
#endif #endif
#ifdef COMBO_ENABLE #if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
uint16_t keycode; uint16_t keycode;
#endif #endif
} keyrecord_t; } keyrecord_t;

View File

@ -721,6 +721,8 @@ enum qk_keycode_defines {
QK_AUTOCORRECT_TOGGLE = 0x7C76, QK_AUTOCORRECT_TOGGLE = 0x7C76,
QK_TRI_LAYER_LOWER = 0x7C77, QK_TRI_LAYER_LOWER = 0x7C77,
QK_TRI_LAYER_UPPER = 0x7C78, QK_TRI_LAYER_UPPER = 0x7C78,
QK_REPEAT_KEY = 0x7C79,
QK_ALT_REPEAT_KEY = 0x7C7A,
QK_KB_0 = 0x7E00, QK_KB_0 = 0x7E00,
QK_KB_1 = 0x7E01, QK_KB_1 = 0x7E01,
QK_KB_2 = 0x7E02, QK_KB_2 = 0x7E02,
@ -1362,6 +1364,8 @@ enum qk_keycode_defines {
AC_TOGG = QK_AUTOCORRECT_TOGGLE, AC_TOGG = QK_AUTOCORRECT_TOGGLE,
TL_LOWR = QK_TRI_LAYER_LOWER, TL_LOWR = QK_TRI_LAYER_LOWER,
TL_UPPR = QK_TRI_LAYER_UPPER, TL_UPPR = QK_TRI_LAYER_UPPER,
QK_REP = QK_REPEAT_KEY,
QK_AREP = QK_ALT_REPEAT_KEY,
}; };
// Range Helpers // Range Helpers
@ -1413,6 +1417,6 @@ enum qk_keycode_defines {
#define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31) #define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31)
#define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING) #define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING)
#define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE) #define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE)
#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_TRI_LAYER_UPPER) #define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY)
#define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31) #define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31)
#define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31) #define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31)

View File

@ -0,0 +1,109 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "process_repeat_key.h"
// Default implementation of remember_last_key_user().
__attribute__((weak)) bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
return true;
}
static bool remember_last_key(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
switch (keycode) {
// Ignore MO, TO, TG, TT, and TL layer switch keys.
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
case QK_TO ... QK_TO_MAX:
case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX:
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
// Ignore mod keys.
case KC_LCTL ... KC_RGUI:
case KC_HYPR:
case KC_MEH:
#ifndef NO_ACTION_ONESHOT // Ignore one-shot keys.
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
#endif // NO_ACTION_ONESHOT
#ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys.
case QK_TRI_LAYER_LOWER:
case QK_TRI_LAYER_UPPER:
#endif // TRI_LAYER_ENABLE
return false;
// Ignore hold events on tap-hold keys.
#ifndef NO_ACTION_TAPPING
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
# ifndef NO_ACTION_LAYER
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
# endif // NO_ACTION_LAYER
if (record->tap.count == 0) {
return false;
}
break;
#endif // NO_ACTION_TAPPING
#ifdef SWAP_HANDS_ENABLE
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
if (IS_SWAP_HANDS_KEYCODE(keycode) || record->tap.count == 0) {
return false;
}
break;
#endif // SWAP_HANDS_ENABLE
case QK_REPEAT_KEY:
#ifndef NO_ALT_REPEAT_KEY
case QK_ALT_REPEAT_KEY:
#endif // NO_ALT_REPEAT_KEY
return false;
}
return remember_last_key_user(keycode, record, remembered_mods);
}
bool process_last_key(uint16_t keycode, keyrecord_t* record) {
if (get_repeat_key_count()) {
return true;
}
if (record->event.pressed) {
uint8_t remembered_mods = get_mods() | get_weak_mods();
#ifndef NO_ACTION_ONESHOT
remembered_mods |= get_oneshot_mods();
#endif // NO_ACTION_ONESHOT
if (remember_last_key(keycode, record, &remembered_mods)) {
set_last_record(keycode, record);
set_last_mods(remembered_mods);
}
}
return true;
}
bool process_repeat_key(uint16_t keycode, keyrecord_t* record) {
if (get_repeat_key_count()) {
return true;
}
if (keycode == QK_REPEAT_KEY) {
repeat_key_invoke(&record->event);
return false;
#ifndef NO_ALT_REPEAT_KEY
} else if (keycode == QK_ALT_REPEAT_KEY) {
alt_repeat_key_invoke(&record->event);
return false;
#endif // NO_ALT_REPEAT_KEY
}
return true;
}

View File

@ -0,0 +1,62 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "quantum.h"
/**
* @brief Process handler for remembering the last key.
*
* @param keycode Keycode registered by matrix press, per keymap
* @param record keyrecord_t structure
* @return true Continue processing keycodes, and send to host
* @return false Stop processing keycodes, and don't send to host
*/
bool process_last_key(uint16_t keycode, keyrecord_t* record);
/**
* @brief Optional callback defining which keys are remembered.
*
* @param keycode Keycode that was just pressed
* @param record keyrecord_t structure
* @param remembered_mods Mods that will be remembered with this key
* @return true Key is remembered
* @return false Key is ignored
*
* Modifier and layer switch keys are always ignored. For all other keys, this
* callback is called on every key press. Returning true means that the key is
* remembered, false means it is ignored. By default, all non-modifier,
* non-layer switch keys are remembered.
*
* The `remembered_mods` arg represents the mods that will be remembered with
* this key. It can be modified to forget certain mods, for instance to forget
* capitalization when repeating shifted letters:
*
* // Forget Shift on letter keys.
* if (KC_A <= keycode && keycode <= KC_Z && (*remembered_mods & ~MOD_MASK_SHIFT) == 0) {
* *remembered_mods = 0;
* }
*/
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods);
/**
* @brief Process handler for Repeat Key feature.
*
* @param keycode Keycode registered by matrix press, per keymap
* @param record keyrecord_t structure
* @return true Continue processing keycodes, and send to host
* @return false Stop processing keycodes, and don't send to host
*/
bool process_repeat_key(uint16_t keycode, keyrecord_t* record);

View File

@ -176,7 +176,7 @@ void soft_reset_keyboard(void) {
/* Convert record into usable keycode via the contained event. */ /* Convert record into usable keycode via the contained event. */
uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) {
#ifdef COMBO_ENABLE #if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE)
if (record->keycode) { if (record->keycode) {
return record->keycode; return record->keycode;
} }
@ -273,6 +273,9 @@ bool process_record_quantum(keyrecord_t *record) {
// Must run asap to ensure all keypresses are recorded. // Must run asap to ensure all keypresses are recorded.
process_dynamic_macro(keycode, record) && process_dynamic_macro(keycode, record) &&
#endif #endif
#ifdef REPEAT_KEY_ENABLE
process_last_key(keycode, record) && process_repeat_key(keycode, record) &&
#endif
#if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY) #if defined(AUDIO_ENABLE) && defined(AUDIO_CLICKY)
process_clicky(keycode, record) && process_clicky(keycode, record) &&
#endif #endif

View File

@ -251,6 +251,11 @@ extern layer_state_t layer_state;
# include "process_tri_layer.h" # include "process_tri_layer.h"
#endif #endif
#ifdef REPEAT_KEY_ENABLE
# include "repeat_key.h"
# include "process_repeat_key.h"
#endif
void set_single_persistent_default_layer(uint8_t default_layer); void set_single_persistent_default_layer(uint8_t default_layer);
#define IS_LAYER_ON(layer) layer_state_is(layer) #define IS_LAYER_ON(layer) layer_state_is(layer)

282
quantum/repeat_key.c Normal file
View File

@ -0,0 +1,282 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "repeat_key.h"
// Variables saving the state of the last key press.
static keyrecord_t last_record = {0};
static uint8_t last_mods = 0;
// Signed count of the number of times the last key has been repeated or
// alternate repeated: it is 0 when a key is pressed normally, positive when
// repeated, and negative when alternate repeated.
static int8_t last_repeat_count = 0;
// The repeat_count, but set to 0 outside of repeat_key_invoke() so that it is
// nonzero only while a repeated key is being processed.
static int8_t processing_repeat_count = 0;
uint16_t get_last_keycode(void) {
return last_record.keycode;
}
uint8_t get_last_mods(void) {
return last_mods;
}
void set_last_keycode(uint16_t keycode) {
set_last_record(keycode, &(keyrecord_t){
#ifndef NO_ACTION_TAPPING
.tap.interrupted = false,
.tap.count = 1,
#endif
});
}
void set_last_mods(uint8_t mods) {
last_mods = mods;
}
void set_last_record(uint16_t keycode, keyrecord_t* record) {
last_record = *record;
last_record.keycode = keycode;
last_repeat_count = 0;
}
/** @brief Updates `last_repeat_count` in direction `dir`. */
static void update_last_repeat_count(int8_t dir) {
if (dir * last_repeat_count < 0) {
last_repeat_count = dir;
} else if (dir * last_repeat_count < 127) {
last_repeat_count += dir;
}
}
int8_t get_repeat_key_count(void) {
return processing_repeat_count;
}
void repeat_key_invoke(const keyevent_t* event) {
// It is possible (e.g. in rolled presses) that the last key changes while
// the Repeat Key is pressed. To prevent stuck keys, it is important to
// remember separately what key record was processed on press so that the
// the corresponding record is generated on release.
static keyrecord_t registered_record = {0};
static int8_t registered_repeat_count = 0;
// Since this function calls process_record(), it may recursively call
// itself. We return early if `processing_repeat_count` is nonzero to
// prevent infinite recursion.
if (processing_repeat_count || !last_record.keycode) {
return;
}
if (event->pressed) {
update_last_repeat_count(1);
// On press, apply the last mods state, stacking on top of current mods.
register_weak_mods(last_mods);
registered_record = last_record;
registered_repeat_count = last_repeat_count;
}
// Generate a keyrecord and plumb it into the event pipeline.
registered_record.event = *event;
processing_repeat_count = registered_repeat_count;
process_record(&registered_record);
processing_repeat_count = 0;
// On release, restore the mods state.
if (!event->pressed) {
unregister_weak_mods(last_mods);
}
}
#ifndef NO_ALT_REPEAT_KEY
/**
* @brief Find alternate keycode from a table of opposing keycode pairs.
* @param table Array of pairs of basic keycodes, declared as PROGMEM.
* @param table_size_bytes The size of the table in bytes.
* @param target The basic keycode to find.
* @return The alternate basic keycode, or KC_NO if none was found.
*
* @note The table keycodes and target must be basic keycodes.
*
* This helper is used several times below to define alternate keys. Given a
* table of pairs of basic keycodes, the function finds the pair containing
* `target` and returns the other keycode in the pair.
*/
static uint8_t find_alt_keycode(const uint8_t (*table)[2], uint8_t table_size_bytes, uint8_t target) {
const uint8_t* keycodes = (const uint8_t*)table;
for (uint8_t i = 0; i < table_size_bytes; ++i) {
if (target == pgm_read_byte(keycodes + i)) {
// Xor (i ^ 1) the index to get the other element in the pair.
return pgm_read_byte(keycodes + (i ^ 1));
}
}
return KC_NO;
}
uint16_t get_alt_repeat_key_keycode(void) {
uint16_t keycode = last_record.keycode;
uint8_t mods = last_mods;
// Call the user callback first to give it a chance to override the default
// alternate key definitions that follow.
uint16_t alt_keycode = get_alt_repeat_key_keycode_user(keycode, mods);
if (alt_keycode != KC_TRANSPARENT) {
return alt_keycode;
}
// Convert 8-bit mods to the 5-bit format used in keycodes. This is lossy:
// if left and right handed mods were mixed, they all become right handed.
mods = ((mods & 0xf0) ? /* set right hand bit */ 0x10 : 0)
// Combine right and left hand mods.
| (((mods >> 4) | mods) & 0xf);
switch (keycode) {
case QK_MODS ... QK_MODS_MAX: // Unpack modifier + basic key.
mods |= QK_MODS_GET_MODS(keycode);
keycode = QK_MODS_GET_BASIC_KEYCODE(keycode);
break;
# ifndef NO_ACTION_TAPPING
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
break;
# ifndef NO_ACTION_LAYER
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
break;
# endif // NO_ACTION_LAYER
# endif // NO_ACTION_TAPPING
# ifdef SWAP_HANDS_ENABLE
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
if (IS_SWAP_HANDS_KEYCODE(keycode)) {
return KC_NO;
}
keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode);
break;
# endif // SWAP_HANDS_ENABLE
}
if (IS_QK_BASIC(keycode)) {
if ((mods & (MOD_LCTL | MOD_LALT | MOD_LGUI))) {
// The last key was pressed with a modifier other than Shift.
// The following maps
// mod + F <-> mod + B
// and a few others, supporting several core hotkeys used in
// Emacs, Vim, less, and other programs.
// clang-format off
static const uint8_t pairs[][2] PROGMEM = {
{KC_F , KC_B }, // Forward / Backward.
{KC_D , KC_U }, // Down / Up.
{KC_N , KC_P }, // Next / Previous.
{KC_A , KC_E }, // Home / End.
{KC_O , KC_I }, // Older / Newer in Vim jump list.
};
// clang-format on
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
} else {
// The last key was pressed with no mods or only Shift. The
// following map a few more Vim hotkeys.
// clang-format off
static const uint8_t pairs[][2] PROGMEM = {
{KC_J , KC_K }, // Down / Up.
{KC_H , KC_L }, // Left / Right.
// These two lines map W and E to B, and B to W.
{KC_W , KC_B }, // Forward / Backward by word.
{KC_E , KC_B }, // Forward / Backward by word.
};
// clang-format on
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
}
if (!alt_keycode) {
// The following key pairs are considered with any mods.
// clang-format off
static const uint8_t pairs[][2] PROGMEM = {
{KC_LEFT, KC_RGHT}, // Left / Right Arrow.
{KC_UP , KC_DOWN}, // Up / Down Arrow.
{KC_HOME, KC_END }, // Home / End.
{KC_PGUP, KC_PGDN}, // Page Up / Page Down.
{KC_BSPC, KC_DEL }, // Backspace / Delete.
{KC_LBRC, KC_RBRC}, // Brackets [ ] and { }.
#ifdef EXTRAKEY_ENABLE
{KC_WBAK, KC_WFWD}, // Browser Back / Forward.
{KC_MNXT, KC_MPRV}, // Next / Previous Media Track.
{KC_MFFD, KC_MRWD}, // Fast Forward / Rewind Media.
{KC_VOLU, KC_VOLD}, // Volume Up / Down.
{KC_BRIU, KC_BRID}, // Brightness Up / Down.
#endif // EXTRAKEY_ENABLE
#ifdef MOUSEKEY_ENABLE
{KC_MS_L, KC_MS_R}, // Mouse Cursor Left / Right.
{KC_MS_U, KC_MS_D}, // Mouse Cursor Up / Down.
{KC_WH_L, KC_WH_R}, // Mouse Wheel Left / Right.
{KC_WH_U, KC_WH_D}, // Mouse Wheel Up / Down.
#endif // MOUSEKEY_ENABLE
};
// clang-format on
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode);
}
if (alt_keycode) {
// Combine basic keycode with mods.
return (mods << 8) | alt_keycode;
}
}
return KC_NO; // No alternate key found.
}
void alt_repeat_key_invoke(const keyevent_t* event) {
static keyrecord_t registered_record = {0};
static int8_t registered_repeat_count = 0;
// Since this function calls process_record(), it may recursively call
// itself. We return early if `processing_repeat_count` is nonzero to
// prevent infinite recursion.
if (processing_repeat_count) {
return;
}
if (event->pressed) {
registered_record = (keyrecord_t){
# ifndef NO_ACTION_TAPPING
.tap.interrupted = false,
.tap.count = 0,
# endif
.keycode = get_alt_repeat_key_keycode(),
};
}
// Early return if there is no alternate key defined.
if (!registered_record.keycode) {
return;
}
if (event->pressed) {
update_last_repeat_count(-1);
registered_repeat_count = last_repeat_count;
}
// Generate a keyrecord and plumb it into the event pipeline.
registered_record.event = *event;
processing_repeat_count = registered_repeat_count;
process_record(&registered_record);
processing_repeat_count = 0;
}
// Default implementation of get_alt_repeat_key_keycode_user().
__attribute__((weak)) uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
return KC_TRANSPARENT;
}
#endif // NO_ALT_REPEAT_KEY

80
quantum/repeat_key.h Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2022-2023 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#pragma once
#include "quantum.h"
uint16_t get_last_keycode(void); /**< Keycode of the last key. */
uint8_t get_last_mods(void); /**< Mods active with the last key. */
void set_last_keycode(uint16_t keycode); /**< Sets the last key. */
void set_last_mods(uint8_t mods); /**< Sets the last mods. */
/** @brief Gets the record for the last key. */
keyrecord_t* get_last_record(void);
/** @brief Sets keycode and record info for the last key. */
void set_last_record(uint16_t keycode, keyrecord_t* record);
/**
* @brief Signed count of times the key has been repeated or alternate repeated.
*
* @note The count is nonzero only while a repeated or alternate-repeated key is
* being processed.
*
* When a key is pressed normally, the count is 0. When the Repeat Key is used
* to repeat a key, the count is 1 on the first repeat, 2 on the second repeat,
* and continuing up to 127.
*
* Negative counts are used similarly for alternate repeating. When the
* Alternate Repeat Key is used, the count is -1 on the first alternate repeat,
* -2 on the second, continuing down to -127.
*/
int8_t get_repeat_key_count(void);
/**
* @brief Calls `process_record()` on a generated record repeating the last key.
* @param event Event information in the generated record.
*/
void repeat_key_invoke(const keyevent_t* event);
#ifndef NO_ALT_REPEAT_KEY
/**
* @brief Keycode to be used for alternate repeating.
*
* Alternate Repeat performs this keycode based on the last eligible pressed key
* and mods, get_last_keycode() and get_last_mods(). For example, when the last
* key was KC_UP, this function returns KC_DOWN. The function returns KC_NO if
* the last key doesn't have a defined alternate.
*/
uint16_t get_alt_repeat_key_keycode(void);
/**
* @brief Calls `process_record()` to alternate repeat the last key.
* @param event Event information in the generated record.
*/
void alt_repeat_key_invoke(const keyevent_t* event);
/**
* @brief Optional user callback to define additional alternate keys.
*
* When `get_alt_repeat_key_keycode()` is called, it first calls this callback.
* It should return a keycode representing the "alternate" of the given keycode
* and mods. Returning KC_NO defers to the default definitions in
* `get_alt_repeat_key_keycode()`.
*/
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods);
#endif // NO_ALT_REPEAT_KEY

View File

@ -0,0 +1,18 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include "test_common.h"

View File

@ -0,0 +1,18 @@
# Copyright 2023 Google LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
REPEAT_KEY_ENABLE = yes
EXTRAKEY_ENABLE = yes

View File

@ -0,0 +1,523 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <functional>
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using ::testing::AnyNumber;
using ::testing::InSequence;
namespace {
bool process_record_user_default(uint16_t keycode, keyrecord_t* record) {
return true;
}
bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
return true;
}
uint16_t get_alt_repeat_key_keycode_user_default(uint16_t keycode, uint8_t mods) {
return KC_TRNS;
}
// Indirections so that process_record_user() can be replaced with other
// functions in the test cases below.
std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default;
std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default;
std::function<uint16_t(uint16_t, uint8_t)> get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default;
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) {
return process_record_user_fun(keycode, record);
}
extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
return remember_last_key_user_fun(keycode, record, remembered_mods);
}
extern "C" uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
return get_alt_repeat_key_keycode_user_fun(keycode, mods);
}
class AltRepeatKey : public TestFixture {
public:
bool process_record_user_was_called_;
void SetUp() override {
process_record_user_fun = process_record_user_default;
remember_last_key_user_fun = remember_last_key_user_default;
get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default;
}
void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) {
process_record_user_was_called_ = false;
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
EXPECT_EQ(record->event.pressed, expected_press);
EXPECT_KEYCODE_EQ(keycode, expected_keycode);
EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count);
// Tests below use this to verify process_record_user() was called.
process_record_user_was_called_ = true;
return true;
};
}
// Expects that the characters of `s` are sent.
// NOTE: This implementation is limited to chars a-z, A-Z.
void ExpectString(TestDriver& driver, const std::string& s) {
InSequence seq;
for (int c : s) {
switch (c) {
case 'a' ... 'z': { // Lowercase letter.
uint16_t keycode = c - ('a' - KC_A);
EXPECT_REPORT(driver, (keycode));
} break;
case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
uint16_t keycode = c - ('A' - KC_A);
EXPECT_REPORT(driver, (KC_LSFT, keycode));
} break;
}
}
}
};
TEST_F(AltRepeatKey, AlternateBasic) {
TestDriver driver;
KeymapKey key_bspc(0, 0, 0, KC_BSPC);
KeymapKey key_pgdn(0, 1, 0, KC_PGDN);
KeymapKey key_pgup(0, 2, 0, KC_PGUP);
KeymapKey key_repeat(0, 4, 0, QK_REP);
KeymapKey key_alt_repeat(0, 5, 0, QK_AREP);
set_keymap({key_bspc, key_pgdn, key_pgup, key_repeat, key_alt_repeat});
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
{
InSequence seq;
EXPECT_REPORT(driver, (KC_BSPC));
EXPECT_REPORT(driver, (KC_DEL));
EXPECT_REPORT(driver, (KC_DEL));
EXPECT_REPORT(driver, (KC_BSPC));
EXPECT_REPORT(driver, (KC_DEL));
EXPECT_REPORT(driver, (KC_PGDN));
EXPECT_REPORT(driver, (KC_PGUP));
EXPECT_REPORT(driver, (KC_PGUP));
EXPECT_REPORT(driver, (KC_PGDN));
}
tap_key(key_bspc);
for (int n = 1; n <= 2; ++n) { // Tap the Alternate Repeat Key twice.
ExpectProcessRecordUserCalledWith(true, KC_DEL, -n);
key_alt_repeat.press(); // Press the Alternate Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
// Expect the corresponding release event.
ExpectProcessRecordUserCalledWith(false, KC_DEL, -n);
key_alt_repeat.release(); // Release the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
}
process_record_user_fun = process_record_user_default;
tap_keys(key_repeat, key_alt_repeat);
tap_keys(key_pgdn, key_alt_repeat);
tap_keys(key_pgup, key_alt_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
struct TestParamsAlternateKeyCodes {
uint16_t keycode;
uint8_t mods;
uint16_t expected_alt_keycode;
};
// Tests `get_alt_repeat_key_keycode()` for various keycodes.
TEST_F(AltRepeatKey, GetAltRepeatKeyKeycode) {
for (const auto& params : std::vector<TestParamsAlternateKeyCodes>({
// clang-format off
// Each line tests one call to `get_alt_repeat_key_keycode()`:
// {keycode, mods, expected_alt_keycode}.
// Arrows.
{KC_LEFT, 0, KC_RGHT},
{KC_RGHT, 0, KC_LEFT},
{KC_LEFT, MOD_BIT(KC_LSFT), LSFT(KC_RGHT)},
{KC_LEFT, MOD_BIT(KC_RSFT), RSFT(KC_RGHT)},
{KC_LEFT, MOD_BIT(KC_LCTL) | MOD_BIT(KC_LSFT), C(S(KC_RGHT))},
{KC_LEFT, MOD_BIT(KC_LGUI), LGUI(KC_RGHT)},
{C(KC_LEFT), MOD_BIT(KC_LSFT), C(S(KC_RGHT))},
{KC_UP, 0, KC_DOWN},
// Navigation keys.
{KC_PGUP, 0, KC_PGDN},
{KC_HOME, 0, KC_END },
// Media keys.
{KC_WBAK, 0, KC_WFWD},
{KC_MNXT, 0, KC_MPRV},
{KC_MRWD, 0, KC_MFFD},
{KC_VOLU, 0, KC_VOLD},
{KC_BRIU, 0, KC_BRID},
// Emacs navigation.
{KC_N, MOD_BIT(KC_LCTL), C(KC_P)},
{KC_B, MOD_BIT(KC_LCTL), LCTL(KC_F)},
{KC_B, MOD_BIT(KC_RCTL), RCTL(KC_F)},
{KC_B, MOD_BIT(KC_LALT), LALT(KC_F)},
{KC_F, MOD_BIT(KC_LCTL), C(KC_B)},
{KC_A, MOD_BIT(KC_LCTL), C(KC_E)},
{KC_D, MOD_BIT(KC_LCTL), C(KC_U)},
// Vim navigation.
{KC_J, 0, KC_K},
{KC_K, 0, KC_J},
{KC_H, 0, KC_L},
{KC_B, 0, KC_W},
{KC_W, 0, KC_B},
{KC_E, 0, KC_B},
{KC_B, MOD_BIT(KC_LSFT), S(KC_W)},
{KC_W, MOD_BIT(KC_LSFT), S(KC_B)},
{KC_E, MOD_BIT(KC_LSFT), S(KC_B)},
{KC_O, MOD_BIT(KC_LCTL), C(KC_I)},
{KC_I, MOD_BIT(KC_LCTL), C(KC_O)},
// Other.
{KC_DEL, 0, KC_BSPC},
{KC_LBRC, 0, KC_RBRC},
{KC_LCBR, 0, KC_RCBR},
// Some keys where the last key is a tap-hold key.
{LSFT_T(KC_F), MOD_BIT(KC_RCTL), RCTL(KC_B)},
{LT(1, KC_A), MOD_BIT(KC_RGUI), RGUI(KC_E)},
{RALT_T(KC_J), 0, KC_K},
// Some keys where no alternate is defined.
{KC_A, 0, KC_NO},
{KC_F1, 0, KC_NO},
{QK_LEAD, 0, KC_NO},
{MO(1), 0, KC_NO},
// clang-format on
})) {
SCOPED_TRACE(std::string("Input keycode: ") + get_keycode_identifier_or_default(params.keycode));
set_last_keycode(params.keycode);
set_last_mods(params.mods);
const uint16_t actual = get_alt_repeat_key_keycode();
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), params.expected_alt_keycode);
}
}
// Test adding to and overriding the above through the
// `get_alt_repeat_key_keycode_user()` callback.
TEST_F(AltRepeatKey, GetAltRepeatKeyKeycodeUser) {
get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t {
bool shifted = (mods & MOD_MASK_SHIFT);
switch (keycode) {
case KC_LEFT:
return KC_ENT;
case MO(1):
return TG(1);
case KC_TAB: // Tab <-> Shift + Tab example.
if (shifted) {
return KC_TAB;
} else {
return S(KC_TAB);
}
}
// Ctrl + Y <-> Ctrl + Z example.
if ((mods & MOD_MASK_CTRL)) {
switch (keycode) {
case KC_Y:
return C(KC_Z);
case KC_Z:
return C(KC_Y);
}
}
return KC_NO;
};
set_last_keycode(KC_LEFT);
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_ENT);
set_last_keycode(MO(1));
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), TG(1));
set_last_keycode(KC_TAB);
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), S(KC_TAB));
set_last_keycode(KC_TAB);
set_last_mods(MOD_BIT(KC_LSFT));
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_TAB);
set_last_keycode(KC_Z);
set_last_mods(MOD_BIT(KC_LCTL));
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Y));
set_last_keycode(KC_Y);
set_last_mods(MOD_BIT(KC_LCTL));
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Z));
}
// Tests rolling from a key to Alternate Repeat.
TEST_F(AltRepeatKey, RollingToAltRepeat) {
TestDriver driver;
KeymapKey key_left(0, 0, 0, KC_LEFT);
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
set_keymap({key_left, key_alt_repeat});
{
InSequence seq;
EXPECT_REPORT(driver, (KC_LEFT));
EXPECT_REPORT(driver, (KC_LEFT, KC_RGHT));
EXPECT_REPORT(driver, (KC_RGHT));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_RGHT));
EXPECT_EMPTY_REPORT(driver);
}
// Perform a rolled press from Left to Alternate Repeat.
ExpectProcessRecordUserCalledWith(true, KC_LEFT, 0);
key_left.press();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1);
key_alt_repeat.press(); // Press the Alternate Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_LEFT, 0);
key_left.release();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1);
key_alt_repeat.release(); // Release the Alternate Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
process_record_user_fun = process_record_user_default;
tap_key(key_alt_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests rolling from Alternate Repeat to another key.
TEST_F(AltRepeatKey, RollingFromAltRepeat) {
TestDriver driver;
KeymapKey key_left(0, 0, 0, KC_LEFT);
KeymapKey key_up(0, 1, 0, KC_UP);
KeymapKey key_alt_repeat(0, 2, 0, QK_AREP);
set_keymap({key_left, key_up, key_alt_repeat});
{
InSequence seq;
EXPECT_REPORT(driver, (KC_LEFT));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_RGHT));
EXPECT_REPORT(driver, (KC_RGHT, KC_UP));
EXPECT_REPORT(driver, (KC_UP));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_DOWN));
EXPECT_EMPTY_REPORT(driver);
}
tap_key(key_left);
// Perform a rolled press from Alternate Repeat to Up.
ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1);
key_alt_repeat.press(); // Press the Alternate Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(true, KC_UP, 0);
key_up.press();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_UP);
ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1);
key_alt_repeat.release(); // Release the Alternate Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_UP, 0);
key_up.release();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
process_record_user_fun = process_record_user_default;
tap_key(key_alt_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests using the Alternate Repeat Key on a macro that doesn't have an
// alternate keycode defined.
TEST_F(AltRepeatKey, AlternateUnsupportedMacro) {
TestDriver driver;
KeymapKey key_foo(0, 0, 0, QK_USER_0);
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
set_keymap({key_foo, key_alt_repeat});
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
process_record_user_was_called_ = true;
switch (keycode) {
case QK_USER_0:
if (record->event.pressed) {
SEND_STRING("foo");
}
break;
}
return true;
};
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "foofoo");
process_record_user_was_called_ = false;
tap_key(key_foo);
EXPECT_TRUE(process_record_user_was_called_);
EXPECT_KEYCODE_EQ(get_last_keycode(), QK_USER_0);
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_NO);
process_record_user_was_called_ = false;
key_alt_repeat.press(); // Press Alternate Repeat.
run_one_scan_loop();
EXPECT_FALSE(process_record_user_was_called_);
process_record_user_was_called_ = false;
key_alt_repeat.release(); // Release Alternate Repeat.
run_one_scan_loop();
EXPECT_FALSE(process_record_user_was_called_);
process_record_user_was_called_ = false;
tap_key(key_foo);
EXPECT_TRUE(process_record_user_was_called_);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests a macro with custom alternate behavior.
TEST_F(AltRepeatKey, MacroCustomAlternate) {
TestDriver driver;
KeymapKey key_foo(0, 0, 0, QK_USER_0);
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP);
set_keymap({key_foo, key_alt_repeat});
get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t {
switch (keycode) {
case QK_USER_0:
return QK_USER_0; // QK_USER_0 handles its own alternate.
default:
return KC_NO; // No key by default.
}
};
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
process_record_user_was_called_ = true;
switch (keycode) {
case QK_USER_0:
if (record->event.pressed) {
if (get_repeat_key_count() >= 0) {
SEND_STRING("foo");
} else { // Key is being alternate repeated.
SEND_STRING("bar");
}
}
break;
}
return true;
};
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "foobarbar");
tap_keys(key_foo, key_alt_repeat, key_alt_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests the Additional "Alternate" keys example from the documentation page.
TEST_F(AltRepeatKey, AdditionalAlternateKeysExample) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_w(0, 1, 0, KC_W);
KeymapKey key_altrep2(0, 2, 0, QK_USER_0);
KeymapKey key_altrep3(0, 3, 0, QK_USER_1);
set_keymap({key_a, key_w, key_altrep2, key_altrep3});
remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
switch (keycode) {
case QK_USER_0:
case QK_USER_1:
return false; // Ignore ALTREP keys.
}
return true; // Other keys can be repeated.
};
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case QK_USER_0:
if (record->event.pressed) {
const uint16_t last_key = get_last_keycode();
switch (last_key) {
case KC_A:
SEND_STRING(/*a*/ "tion");
break;
case KC_W:
SEND_STRING(/*w*/ "hich");
break;
}
}
return false;
case QK_USER_1:
if (record->event.pressed) {
const uint16_t last_key = get_last_keycode();
switch (last_key) {
case KC_A:
SEND_STRING(/*a*/ "bout");
break;
case KC_W:
SEND_STRING(/*w*/ "ould");
break;
}
}
return false;
}
return true;
};
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "ationwhichaboutwould");
tap_keys(key_a, key_altrep2, key_w, key_altrep2);
tap_keys(key_a, key_altrep3, key_w, key_altrep3);
testing::Mock::VerifyAndClearExpectations(&driver);
}
} // namespace

20
tests/repeat_key/config.h Normal file
View File

@ -0,0 +1,20 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include "test_common.h"
#define NO_ALT_REPEAT_KEY

View File

@ -0,0 +1,18 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#pragma once
#include "test_common.h"

View File

@ -0,0 +1,18 @@
# Copyright 2023 Google LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
REPEAT_KEY_ENABLE = yes
COMBO_ENABLE = yes

View File

@ -0,0 +1,67 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using ::testing::AnyNumber;
using ::testing::InSequence;
namespace {
extern "C" {
// Define a combo: KC_X + KC_Y = KC_Q.
const uint16_t xy_combo[] PROGMEM = {KC_X, KC_Y, COMBO_END};
combo_t key_combos[] = {COMBO(xy_combo, KC_Q)};
uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(*key_combos);
} // extern "C"
class RepeatKey : public TestFixture {};
// Tests repeating a combo, KC_X + KC_Y = KC_Q, by typing
// "X, Repeat, Repeat, {X Y}, Repeat, Repeat". This produces "xxxqqq".
TEST_F(RepeatKey, Combo) {
TestDriver driver;
KeymapKey key_x(0, 0, 0, KC_X);
KeymapKey key_y(0, 1, 0, KC_Y);
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_x, key_y, key_repeat});
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
{
InSequence seq;
EXPECT_REPORT(driver, (KC_X));
EXPECT_REPORT(driver, (KC_X));
EXPECT_REPORT(driver, (KC_X));
EXPECT_REPORT(driver, (KC_Q));
EXPECT_REPORT(driver, (KC_Q));
EXPECT_REPORT(driver, (KC_Q));
}
tap_keys(key_x, key_repeat, key_repeat);
tap_combo({key_x, key_y});
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_Q);
tap_keys(key_repeat, key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
} // namespace

18
tests/repeat_key/test.mk Normal file
View File

@ -0,0 +1,18 @@
# Copyright 2023 Google LLC
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
REPEAT_KEY_ENABLE = yes
AUTO_SHIFT_ENABLE = yes

View File

@ -0,0 +1,754 @@
// Copyright 2023 Google LLC
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
#include <functional>
#include "keyboard_report_util.hpp"
#include "keycode.h"
#include "test_common.hpp"
#include "test_fixture.hpp"
#include "test_keymap_key.hpp"
using ::testing::AnyNumber;
using ::testing::AnyOf;
using ::testing::InSequence;
#define FOO_MACRO SAFE_RANGE
namespace {
bool process_record_user_default(uint16_t keycode, keyrecord_t* record) {
return true;
}
bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
return true;
}
// Indirection so that process_record_user() and remember_last_key_user()
// can be replaced with other functions in the test cases below.
std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default;
std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default;
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) {
return process_record_user_fun(keycode, record);
}
extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
return remember_last_key_user_fun(keycode, record, remembered_mods);
}
class RepeatKey : public TestFixture {
public:
bool process_record_user_was_called_;
void SetUp() override {
autoshift_disable();
process_record_user_fun = process_record_user_default;
remember_last_key_user_fun = remember_last_key_user_default;
}
void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) {
process_record_user_was_called_ = false;
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) {
EXPECT_EQ(record->event.pressed, expected_press);
EXPECT_KEYCODE_EQ(keycode, expected_keycode);
EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count);
// Tests below use this to verify process_record_user() was called.
process_record_user_was_called_ = true;
return true;
};
}
// Expects that the characters of `s` are sent.
// NOTE: This implementation is limited to chars a-z, A-Z.
void ExpectString(TestDriver& driver, const std::string& s) {
InSequence seq;
for (int c : s) {
switch (c) {
case 'a' ... 'z': { // Lowercase letter.
uint16_t keycode = c - ('a' - KC_A);
EXPECT_REPORT(driver, (keycode));
} break;
case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
uint16_t keycode = c - ('A' - KC_A);
EXPECT_REPORT(driver, (KC_LSFT, keycode));
} break;
}
}
}
};
// Tests that "A, Repeat, Repeat, B, Repeat" produces "aaabb".
TEST_F(RepeatKey, Basic) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_b(0, 1, 0, KC_B);
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_a, key_b, key_repeat});
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "aaabb");
// When KC_A is pressed, process_record_user() should be called
// with a press event with keycode == KC_A and repeat_key_count() == 0.
ExpectProcessRecordUserCalledWith(true, KC_A, 0);
key_a.press();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
// After pressing A, the keycode of the key to be repeated is KC_A.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
EXPECT_EQ(get_last_mods(), 0);
// Expect the corresponding release event when A is released.
ExpectProcessRecordUserCalledWith(false, KC_A, 0);
key_a.release();
run_one_scan_loop();
for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
// When Repeat is pressed, process_record_user() should be called with a
// press event with keycode == KC_A and repeat_key_count() == n.
ExpectProcessRecordUserCalledWith(true, KC_A, n);
key_repeat.press(); // Press the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
// Expect the corresponding release event.
ExpectProcessRecordUserCalledWith(false, KC_A, n);
key_repeat.release(); // Release the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
}
process_record_user_fun = process_record_user_default;
tap_key(key_b);
// Then after tapping key_b, the keycode to be repeated becomes KC_B.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
tap_key(key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests repeating a macro. The keycode FOO_MACRO sends "foo" when pressed. The
// test taps "FOO_MACRO, Repeat, Repeat", producing "foofoofoo".
TEST_F(RepeatKey, Macro) {
TestDriver driver;
KeymapKey key_foo(0, 0, 0, FOO_MACRO);
KeymapKey key_repeat(0, 1, 0, QK_REP);
set_keymap({key_foo, key_repeat});
// Define process_record_user() to handle FOO_MACRO.
process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case FOO_MACRO:
if (record->event.pressed) {
SEND_STRING("foo");
}
break;
}
return true;
};
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "foofoofoo");
tap_key(key_foo);
EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO);
tap_keys(key_repeat, key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests a macro with customized repeat behavior: "foo" is sent normally, "bar"
// on the first repeat, and "baz" on subsequent repeats. The test taps
// "FOO_MACRO, Repeat, Repeat, FOO_MACRO, Repeat", producing "foobarbazfoobar".
TEST_F(RepeatKey, MacroCustomRepeat) {
TestDriver driver;
KeymapKey key_foo(0, 0, 0, FOO_MACRO);
KeymapKey key_repeat(0, 1, 0, QK_REP);
set_keymap({key_foo, key_repeat});
process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
case FOO_MACRO:
if (record->event.pressed) {
switch (get_repeat_key_count()) {
case 0: // When pressed normally.
SEND_STRING("foo");
break;
case 1: // On first repeat.
SEND_STRING("bar");
break;
default: // On subsequent repeats.
SEND_STRING("baz");
break;
}
}
break;
}
return true;
};
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "foobarbazfoobar");
tap_key(key_foo);
EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO);
tap_keys(key_repeat, key_repeat, key_foo, key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests repeating keys on different layers. A 2-layer keymap is defined:
// Layer 0: QK_REP , MO(1) , KC_A
// Layer 1: KC_TRNS, KC_TRNS, KC_B
// The test does the following, which should produce "bbbaaa":
// 1. Hold MO(1), switching to layer 1.
// 2. Tap KC_B on layer 1.
// 3. Release MO(1), switching back to layer 0.
// 4. Tap Repeat twice.
// 5. Tap KC_A on layer 0.
// 6. Hold MO(1), switching to layer 1.
// 7. Tap Repeat twice.
TEST_F(RepeatKey, AcrossLayers) {
TestDriver driver;
KeymapKey key_repeat(0, 0, 0, QK_REP);
KeymapKey key_mo_1(0, 1, 0, MO(1));
KeymapKey regular_key(0, 2, 0, KC_A);
set_keymap({// Layer 0.
key_repeat, key_mo_1, regular_key,
// Layer 1.
KeymapKey{1, 0, 0, KC_TRNS}, KeymapKey{1, 1, 0, KC_TRNS}, KeymapKey{1, 2, 0, KC_B}});
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "bbbaaa");
key_mo_1.press(); // Hold the MO(1) layer key.
run_one_scan_loop();
tap_key(regular_key); // Taps the KC_B key on layer 1.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
key_mo_1.release(); // Release the layer key.
run_one_scan_loop();
tap_keys(key_repeat, key_repeat);
tap_key(regular_key); // Taps the KC_A key on layer 0.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
key_mo_1.press(); // Hold the layer key.
run_one_scan_loop();
tap_keys(key_repeat, key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests "A(down), Repeat(down), A(up), Repeat(up), Repeat" produces "aaa".
TEST_F(RepeatKey, RollingToRepeat) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_repeat(0, 1, 0, QK_REP);
set_keymap({key_a, key_repeat});
{
InSequence seq;
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
}
// Perform a rolled press from A to Repeat.
ExpectProcessRecordUserCalledWith(true, KC_A, 0);
key_a.press();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(true, KC_A, 1);
key_repeat.press(); // Press the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_A, 0);
key_a.release();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_A, 1);
key_repeat.release(); // Release the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
process_record_user_fun = process_record_user_default;
tap_key(key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests "A, Repeat(down), B(down), Repeat(up), B(up), Repeat" produces "aabb".
TEST_F(RepeatKey, RollingFromRepeat) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_b(0, 1, 0, KC_B);
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_a, key_b, key_repeat});
{
InSequence seq;
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
}
tap_key(key_a);
// Perform a rolled press from Repeat to B.
ExpectProcessRecordUserCalledWith(true, KC_A, 1);
key_repeat.press(); // Press the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(true, KC_B, 0);
key_b.press();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
ExpectProcessRecordUserCalledWith(false, KC_A, 1);
key_repeat.release(); // Release the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
ExpectProcessRecordUserCalledWith(false, KC_B, 0);
key_b.release();
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
process_record_user_fun = process_record_user_default;
tap_key(key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests Repeat Key with a modifier, types "AltGr+C, Repeat, Repeat, C".
TEST_F(RepeatKey, RecallMods) {
TestDriver driver;
KeymapKey key_c(0, 0, 0, KC_C);
KeymapKey key_altgr(0, 1, 0, KC_RALT);
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_c, key_altgr, key_repeat});
// Allow any number of reports with no keys or only KC_RALT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_RALT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "AltGr+C, AltGr+C, AltGr+C, C".
InSequence seq;
EXPECT_REPORT(driver, (KC_RALT, KC_C));
EXPECT_REPORT(driver, (KC_RALT, KC_C));
EXPECT_REPORT(driver, (KC_RALT, KC_C));
EXPECT_REPORT(driver, (KC_C));
}
key_altgr.press();
run_one_scan_loop();
tap_key(key_c);
key_altgr.release();
run_one_scan_loop();
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_C);
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_RALT));
tap_keys(key_repeat, key_repeat, key_c);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests that Repeat Key stacks mods, types
// "Ctrl+Left, Repeat, Shift+Repeat, Shift+Repeat, Repeat, Left".
TEST_F(RepeatKey, StackMods) {
TestDriver driver;
KeymapKey key_left(0, 0, 0, KC_LEFT);
KeymapKey key_shift(0, 1, 0, KC_LSFT);
KeymapKey key_ctrl(0, 2, 0, KC_LCTL);
KeymapKey key_repeat(0, 3, 0, QK_REP);
set_keymap({key_left, key_shift, key_ctrl, key_repeat});
// Allow any number of reports with no keys or only mods.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LCTL),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL, KC_LSFT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "Ctrl+Left, Ctrl+Shift+Left".
InSequence seq;
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT));
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT));
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT));
EXPECT_REPORT(driver, (KC_LEFT));
}
key_ctrl.press();
run_one_scan_loop();
tap_key(key_left);
run_one_scan_loop();
key_ctrl.release();
run_one_scan_loop();
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_LEFT);
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
tap_key(key_repeat);
key_shift.press();
run_one_scan_loop();
tap_keys(key_repeat, key_repeat);
key_shift.release();
run_one_scan_loop();
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
tap_keys(key_repeat, key_left);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Types: "S(KC_1), Repeat, Ctrl+Repeat, Ctrl+Repeat, Repeat, KC_2".
TEST_F(RepeatKey, ShiftedKeycode) {
TestDriver driver;
KeymapKey key_exlm(0, 0, 0, S(KC_1));
KeymapKey key_2(0, 1, 0, KC_2);
KeymapKey key_ctrl(0, 2, 0, KC_LCTL);
KeymapKey key_repeat(0, 3, 0, QK_REP);
set_keymap({key_exlm, key_2, key_ctrl, key_repeat});
// Allow any number of reports with no keys or only mods.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LCTL),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL, KC_LSFT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "Shift+1, Shift+1, Ctrl+Shift+1, Ctrl+Shift+1, Shift+1, 2".
InSequence seq;
EXPECT_REPORT(driver, (KC_LSFT, KC_1));
EXPECT_REPORT(driver, (KC_LSFT, KC_1));
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1));
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1));
EXPECT_REPORT(driver, (KC_LSFT, KC_1));
EXPECT_REPORT(driver, (KC_2));
}
tap_key(key_exlm);
EXPECT_KEYCODE_EQ(get_last_keycode(), S(KC_1));
tap_key(key_repeat);
key_ctrl.press();
run_one_scan_loop();
tap_keys(key_repeat, key_repeat);
key_ctrl.release();
run_one_scan_loop();
tap_keys(key_repeat, key_2);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests Repeat Key with a one-shot Shift, types
// "A, OSM(MOD_LSFT), Repeat, Repeat".
TEST_F(RepeatKey, WithOneShotShift) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_oneshot_shift(0, 1, 0, OSM(MOD_LSFT));
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_a, key_oneshot_shift, key_repeat});
// Allow any number of reports with no keys or only KC_RALT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
ExpectString(driver, "aAa");
tap_keys(key_a, key_oneshot_shift, key_repeat, key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests Repeat Key with a mod-tap key, types
// "A, Repeat, Repeat, A(down), Repeat, Repeat, A(up), Repeat".
TEST_F(RepeatKey, ModTap) {
TestDriver driver;
KeymapKey key_mt_a(0, 0, 0, LSFT_T(KC_A));
KeymapKey key_repeat(0, 1, 0, QK_REP);
set_keymap({key_mt_a, key_repeat});
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
ExpectString(driver, "aaaAAa");
tap_key(key_mt_a);
EXPECT_KEYCODE_EQ(get_last_keycode(), LSFT_T(KC_A));
tap_keys(key_repeat, key_repeat);
key_mt_a.press();
run_one_scan_loop();
tap_key(key_repeat, TAPPING_TERM + 1);
tap_key(key_repeat);
key_mt_a.release();
run_one_scan_loop();
tap_key(key_repeat);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests with Auto Shift. When repeating an autoshiftable key, it does not
// matter how long the original key was held, rather, quickly tapping vs.
// long-pressing the Repeat Key determines whether the shifted key is repeated.
//
// The test does the following, which should produce "aaABbB":
// 1. Tap KC_A quickly.
// 2. Tap Repeat Key quickly.
// 3. Long-press Repeat Key.
// 4. Long-press KC_B.
// 5. Tap Repeat Key quickly.
// 6. Long-press Repeat Key.
TEST_F(RepeatKey, AutoShift) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_b(0, 1, 0, KC_B);
KeymapKey key_repeat(0, 2, 0, QK_REP);
set_keymap({key_a, key_b, key_repeat});
autoshift_enable();
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
ExpectString(driver, "aaABbB");
tap_key(key_a); // Tap A quickly.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
EXPECT_EQ(get_last_mods(), 0);
tap_key(key_repeat);
tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1);
tap_key(key_b, AUTO_SHIFT_TIMEOUT + 1); // Long press B.
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B);
EXPECT_EQ(get_last_mods(), 0);
tap_key(key_repeat);
tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1);
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Defines `remember_last_key_user()` to forget the Shift mod and types:
// "Ctrl+A, Repeat, Shift+A, Repeat, Shift+Repeat".
TEST_F(RepeatKey, FilterRememberedMods) {
TestDriver driver;
KeymapKey key_a(0, 0, 0, KC_A);
KeymapKey key_ctrl(0, 1, 0, KC_LCTL);
KeymapKey key_shift(0, 2, 0, KC_LSFT);
KeymapKey key_repeat(0, 3, 0, QK_REP);
set_keymap({key_a, key_ctrl, key_shift, key_repeat});
remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) {
*remembered_mods &= ~MOD_MASK_SHIFT;
return true;
};
// Allow any number of reports with no keys or only mods.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LCTL),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL, KC_LSFT))))
.Times(AnyNumber());
// clang-format on
{ // Expect: "Ctrl+A, Ctrl+A, Shift+A, A, Shift+A".
InSequence seq;
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
}
key_ctrl.press();
run_one_scan_loop();
tap_key(key_a);
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL));
key_ctrl.release();
run_one_scan_loop();
tap_key(key_repeat);
key_shift.press();
run_one_scan_loop();
tap_key(key_a);
EXPECT_EQ(get_last_mods(), 0); // Shift should be forgotten.
key_shift.release();
run_one_scan_loop();
tap_key(key_repeat);
key_shift.press();
run_one_scan_loop();
tap_key(key_repeat);
key_shift.release();
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests set_last_keycode() and set_last_mods().
TEST_F(RepeatKey, SetRepeatKeyKeycode) {
TestDriver driver;
KeymapKey key_repeat(0, 0, 0, QK_REP);
set_keymap({key_repeat});
// Allow any number of reports with no keys or only KC_LSFT.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
ExpectString(driver, "aaBB");
set_last_keycode(KC_A);
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A);
for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
// When Repeat is pressed, process_record_user() should be called with a
// press event with keycode == KC_A and repeat_key_count() == n.
ExpectProcessRecordUserCalledWith(true, KC_A, n);
key_repeat.press(); // Press the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
// Expect the corresponding release event.
ExpectProcessRecordUserCalledWith(false, KC_A, n);
key_repeat.release(); // Release the Repeat Key.
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
}
process_record_user_fun = process_record_user_default;
set_last_keycode(KC_B);
set_last_mods(MOD_BIT(KC_LSFT));
tap_keys(key_repeat, key_repeat);
set_last_keycode(KC_NO);
tap_keys(key_repeat, key_repeat); // Has no effect.
testing::Mock::VerifyAndClearExpectations(&driver);
}
// Tests the `repeat_key_invoke()` function.
TEST_F(RepeatKey, RepeatKeyInvoke) {
TestDriver driver;
KeymapKey key_s(0, 0, 0, KC_S);
set_keymap({key_s});
// Allow any number of empty reports.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
ExpectString(driver, "ss");
tap_key(key_s);
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_S);
// Calling repeat_key_invoke() should result in process_record_user()
// getting a press event with keycode KC_S.
ExpectProcessRecordUserCalledWith(true, KC_S, 1);
keyevent_t event;
event.key = {0, 0};
event.pressed = true;
event.time = timer_read();
event.type = KEY_EVENT;
repeat_key_invoke(&event);
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
// Make the release event.
ExpectProcessRecordUserCalledWith(false, KC_S, 1);
event.pressed = false;
event.time = timer_read();
repeat_key_invoke(&event);
run_one_scan_loop();
EXPECT_TRUE(process_record_user_was_called_);
testing::Mock::VerifyAndClearExpectations(&driver);
}
} // namespace

View File

@ -663,6 +663,8 @@ std::map<uint16_t, std::string> KEYCODE_ID_TABLE = {
{QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"}, {QK_AUTOCORRECT_TOGGLE, "QK_AUTOCORRECT_TOGGLE"},
{QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"}, {QK_TRI_LAYER_LOWER, "QK_TRI_LAYER_LOWER"},
{QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"},
{QK_REPEAT_KEY, "QK_REPEAT_KEY"},
{QK_ALT_REPEAT_KEY, "QK_ALT_REPEAT_KEY"},
{QK_KB_0, "QK_KB_0"}, {QK_KB_0, "QK_KB_0"},
{QK_KB_1, "QK_KB_1"}, {QK_KB_1, "QK_KB_1"},
{QK_KB_2, "QK_KB_2"}, {QK_KB_2, "QK_KB_2"},

View File

@ -20,6 +20,7 @@
#include <stdint.h> #include <stdint.h>
#include "host.h" #include "host.h"
#include "keyboard_report_util.hpp" #include "keyboard_report_util.hpp"
#include "keycode_util.hpp"
#include "test_logger.hpp" #include "test_logger.hpp"
class TestDriver { class TestDriver {
@ -98,6 +99,17 @@ class TestDriver {
*/ */
#define EXPECT_NO_REPORT(driver) EXPECT_ANY_REPORT(driver).Times(0) #define EXPECT_NO_REPORT(driver) EXPECT_ANY_REPORT(driver).Times(0)
/** @brief Tests whether keycode `actual` is equal to `expected`. */
#define EXPECT_KEYCODE_EQ(actual, expected) EXPECT_THAT((actual), KeycodeEq((expected)))
MATCHER_P(KeycodeEq, expected_keycode, "is equal to " + testing::PrintToString(expected_keycode) + ", keycode " + get_keycode_identifier_or_default(expected_keycode)) {
if (arg == expected_keycode) {
return true;
}
*result_listener << "keycode " << get_keycode_identifier_or_default(arg);
return false;
}
/** /**
* @brief Verify and clear all gmock expectations that have been setup until * @brief Verify and clear all gmock expectations that have been setup until
* this point. * this point.