diff options
| author | Pascal Getreuer <50221757+getreuer@users.noreply.github.com> | 2023-05-20 05:35:06 -0700 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2023-05-20 22:35:06 +1000 |
| commit | 3993b15f054265071730cdb450f43457dcf4c64a (patch) | |
| tree | 61c5b980ed14428bae3c0278c27937dbb5d33627 | |
| parent | e1766df185869d8a591228d37f3f7b6d5b4049b4 (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>
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 | ||
| 90 | define NAME_ECHO | 91 | define 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 | |||
| 3 | The Repeat Key performs the action of the last pressed key. Tapping the Repeat | ||
| 4 | Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for | ||
| 5 | typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd> | ||
| 6 | can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is | ||
| 7 | potentially faster and more comfortable. The Repeat Key is also useful for | ||
| 8 | hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word. | ||
| 9 | |||
| 10 | Repeat Key remembers mods that were active with the last key press. These mods | ||
| 11 | are combined with any additional mods while pressing the Repeat Key. If the last | ||
| 12 | press 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 | |||
| 17 | In your `rules.mk`, add: | ||
| 18 | |||
| 19 | ```make | ||
| 20 | REPEAT_KEY_ENABLE = yes | ||
| 21 | ``` | ||
| 22 | |||
| 23 | Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short | ||
| 24 | alias `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 | |||
| 36 | The Alternate Repeat Key performs the "alternate" action of the last pressed key | ||
| 37 | if it is defined. By default, Alternate Repeat is defined for navigation keys to | ||
| 38 | act in the reverse direction. When the last key is the common "select by word" | ||
| 39 | hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl + | ||
| 40 | Shift + Left Arrow, which together with the Repeat Key enables convenient | ||
| 41 | selection by words in either direction. | ||
| 42 | |||
| 43 | Alternate Repeat is enabled with the Repeat Key by default. Optionally, to | ||
| 44 | reduce firmware size, Alternate Repeat may be disabled by adding in config.h: | ||
| 45 | |||
| 46 | ```c | ||
| 47 | #define NO_ALT_REPEAT_KEY | ||
| 48 | ``` | ||
| 49 | |||
| 50 | The following alternate keys are defined by default. See | ||
| 51 | `get_alt_repeat_key_keycode_user()` below for how to change or add to these | ||
| 52 | definitions. Where it makes sense, these definitions also include combinations | ||
| 53 | with mods, like Ctrl + Left ↔ Ctrl + Right Arrow. | ||
| 54 | |||
| 55 | **Navigation** | ||
| 56 | |||
| 57 | |Keycodes |Description | | ||
| 58 | |-----------------------------------|-----------------------------------| | ||
| 59 | |`KC_LEFT` ↔ `KC_RGHT` | Left ↔ Right Arrow | | ||
| 60 | |`KC_UP` ↔ `KC_DOWN` | Up ↔ Down Arrow | | ||
| 61 | |`KC_HOME` ↔ `KC_END` | Home ↔ End | | ||
| 62 | |`KC_PGUP` ↔ `KC_PGDN` | Page Up ↔ Page Down | | ||
| 63 | |`KC_MS_L` ↔ `KC_MS_R` | Mouse Cursor Left ↔ Right | | ||
| 64 | |`KC_MS_U` ↔ `KC_MS_D` | Mouse Cursor Up ↔ Down | | ||
| 65 | |`KC_WH_L` ↔ `KC_WH_R` | Mouse Wheel Left ↔ Right | | ||
| 66 | |`KC_WH_U` ↔ `KC_WH_D` | Mouse Wheel Up ↔ Down | | ||
| 67 | |||
| 68 | **Misc** | ||
| 69 | |||
| 70 | |Keycodes |Description | | ||
| 71 | |-----------------------------------|-----------------------------------| | ||
| 72 | |`KC_BSPC` ↔ `KC_DEL` | Backspace ↔ Delete | | ||
| 73 | |`KC_LBRC` ↔ `KC_RBRC` | `[` ↔ `]` | | ||
| 74 | |`KC_LCBR` ↔ `KC_RCBR` | `{` ↔ `}` | | ||
| 75 | |||
| 76 | **Media** | ||
| 77 | |||
| 78 | |Keycodes |Description | | ||
| 79 | |-----------------------------------|-----------------------------------| | ||
| 80 | |`KC_WBAK` ↔ `KC_WFWD` | Browser Back ↔ Forward | | ||
| 81 | |`KC_MNXT` ↔ `KC_MPRV` | Next ↔ Previous Media Track | | ||
| 82 | |`KC_MFFD` ↔ `KC_MRWD` | Fast Forward ↔ Rewind Media | | ||
| 83 | |`KC_VOLU` ↔ `KC_VOLD` | Volume Up ↔ Down | | ||
| 84 | |`KC_BRIU` ↔ `KC_BRID` | Brightness Up ↔ Down | | ||
| 85 | |||
| 86 | **Hotkeys in Vim, Emacs, and other programs** | ||
| 87 | |||
| 88 | |Keycodes |Description | | ||
| 89 | |-----------------------------------|-----------------------------------| | ||
| 90 | |mod + `KC_F` ↔ mod + `KC_B` | Forward ↔ Backward | | ||
| 91 | |mod + `KC_D` ↔ mod + `KC_U` | Down ↔ Up | | ||
| 92 | |mod + `KC_N` ↔ mod + `KC_P` | Next ↔ Previous | | ||
| 93 | |mod + `KC_A` ↔ mod + `KC_E` | Home ↔ End | | ||
| 94 | |mod + `KC_O` ↔ mod + `KC_I` | Vim jump list Older ↔ Newer | | ||
| 95 | |`KC_J` ↔ `KC_K` | Down ↔ Up | | ||
| 96 | |`KC_H` ↔ `KC_L` | Left ↔ Right | | ||
| 97 | |`KC_W` ↔ `KC_B` | Forward ↔ Backward by Word | | ||
| 98 | |||
| 99 | (where above, "mod" is Ctrl, Alt, or GUI) | ||
| 100 | |||
| 101 | |||
| 102 | ## Defining alternate keys | ||
| 103 | |||
| 104 | Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate" | ||
| 105 | for additional keys or override the default definitions. For example, to define | ||
| 106 | Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in | ||
| 107 | keymap.c: | ||
| 108 | |||
| 109 | ```c | ||
| 110 | uint16_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 | |||
| 122 | The `keycode` and `mods` args are the keycode and mods that were active with the | ||
| 123 | last pressed key. The meaning of the return value from this function is: | ||
| 124 | |||
| 125 | * `KC_NO` – do nothing (any predefined alternate key is not used); | ||
| 126 | * `KC_TRNS` – use the default alternate key if it exists; | ||
| 127 | * anything else – use the specified keycode. Any keycode may be returned | ||
| 128 | as an alternate key, including custom keycodes. | ||
| 129 | |||
| 130 | Another example, defining Shift + Tab as the alternate of Tab, and vice versa: | ||
| 131 | |||
| 132 | ```c | ||
| 133 | uint16_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 | |||
| 150 | Alternate Repeat can be configured more generally to perform an action that | ||
| 151 | "complements" the last key. Alternate Repeat is not limited to reverse | ||
| 152 | repeating, and it need not be symmetric. You can use it to eliminate cases of | ||
| 153 | same-finger bigrams in your layout, that is, pairs of letters typed by the same | ||
| 154 | finger. The following addresses the top 5 same-finger bigrams in English on | ||
| 155 | QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt | ||
| 156 | Repeat</kbd>. | ||
| 157 | |||
| 158 | ```c | ||
| 159 | uint16_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 | |||
| 174 | A useful possibility is having Alternate Repeat press [a | ||
| 175 | macro](feature_macros.md). This way macros can be used without having to | ||
| 176 | dedicate 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 | ||
| 185 | enum custom_keycodes { | ||
| 186 | M_KEYBOARD = SAFE_RANGE, | ||
| 187 | M_UPDIR, | ||
| 188 | // Other custom keys... | ||
| 189 | }; | ||
| 190 | |||
| 191 | uint16_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 | |||
| 200 | bool 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 | |||
| 211 | In tracking what is "the last key" to be repeated or alternate repeated, | ||
| 212 | modifier and layer switch keys are always ignored. This makes it possible to set | ||
| 213 | some mods and change layers between pressing a key and repeating it. By default, | ||
| 214 | all other (non-modifier, non-layer switch) keys are remembered so that they are | ||
| 215 | eligible 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 | |||
| 220 | The following ignores the Backspace key: | ||
| 221 | |||
| 222 | ```c | ||
| 223 | bool 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 | |||
| 234 | Then for instance, the Repeat key in <kbd>Left Arrow</kbd>, | ||
| 235 | <kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of | ||
| 236 | repeating Backspace. | ||
| 237 | |||
| 238 | The `remember_last_key_user()` callback is called on every key press excluding | ||
| 239 | modifiers and layer switches. Returning true indicates the key is remembered, | ||
| 240 | while false means it is ignored. | ||
| 241 | |||
| 242 | #### Filtering remembered mods | ||
| 243 | |||
| 244 | The `remembered_mods` arg represents the mods that will be remembered with | ||
| 245 | this key. It can be modified to forget certain mods. This may be | ||
| 246 | useful to forget capitalization when repeating shifted letters, so that "Aaron" | ||
| 247 | does not becom "AAron": | ||
| 248 | |||
| 249 | ```c | ||
| 250 | bool 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 | |||
| 267 | Besides checking the keycode, this callback could also make conditions based on | ||
| 268 | the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For | ||
| 269 | example, the following ignores keys on layer 2 as well as key combinations | ||
| 270 | involving GUI: | ||
| 271 | |||
| 272 | ```c | ||
| 273 | bool 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 | ||
| 284 | State](feature_advanced_keycodes.md#checking-modifier-state) for further | ||
| 285 | details. | ||
| 286 | |||
| 287 | |||
| 288 | ## Handle how a key is repeated | ||
| 289 | |||
| 290 | By default, pressing the Repeat Key will simply behave as if the last key | ||
| 291 | were pressed again. This also works with macro keys with custom handlers, | ||
| 292 | invoking the macro again. In case fine-tuning is needed for sensible repetition, | ||
| 293 | you can handle how a key is repeated with `get_repeat_key_count()` within | ||
| 294 | `process_record_user()`. | ||
| 295 | |||
| 296 | The `get_repeat_key_count()` function returns a signed count of times the key | ||
| 297 | has 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 | ||
| 299 | repeat, 2, and so on. Negative counts are used similarly for alternate | ||
| 300 | repeating. For instance supposing `MY_MACRO` is a custom keycode used in the | ||
| 301 | layout: | ||
| 302 | |||
| 303 | ```c | ||
| 304 | bool 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 | |||
| 328 | Pressing the Alternate Repeat Key behaves as if the "alternate" of the last | ||
| 329 | pressed key were pressed, if an alternate is defined. To define how a particular | ||
| 330 | key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback | ||
| 331 | as 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 | ||
| 333 | when alternate repeating. | ||
| 334 | |||
| 335 | The following example defines `MY_MACRO` as its own alternate, and specially | ||
| 336 | handles repeating and alternate repeating: | ||
| 337 | |||
| 338 | ```c | ||
| 339 | uint16_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 | |||
| 346 | bool 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 | |||
| 385 | By leveraging `get_last_keycode()` in macros, it is possible to define | ||
| 386 | additional, distinct "Alternate Repeat"-like keys. The following defines two | ||
| 387 | keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common | ||
| 388 | English 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 | ||
| 401 | enum custom_keycodes { | ||
| 402 | ALTREP2 = SAFE_RANGE, | ||
| 403 | ALTREP3, | ||
| 404 | }; | ||
| 405 | |||
| 406 | // Use ALTREP2 and ALTREP3 in your layout... | ||
| 407 | |||
| 408 | bool 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 | |||
| 419 | static 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 | |||
| 429 | static 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 | |||
| 439 | bool 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 | |||
| 808 | See 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 | ||
| 808 | See also: [Space Cadet](feature_space_cadet.md) | 817 | See 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 | ||
| 287 | void process_record_handler(keyrecord_t *record) { | 287 | void 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 | |||
| 22 | static 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 | |||
| 73 | bool 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 | |||
| 93 | bool 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 | */ | ||
| 27 | bool 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 | */ | ||
| 52 | bool 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 | */ | ||
| 62 | bool 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. */ |
| 178 | uint16_t get_record_keycode(keyrecord_t *record, bool update_layer_cache) { | 178 | uint16_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 | |||
| 254 | void set_single_persistent_default_layer(uint8_t default_layer); | 259 | void 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. | ||
| 18 | static keyrecord_t last_record = {0}; | ||
| 19 | static 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. | ||
| 23 | static 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. | ||
| 26 | static int8_t processing_repeat_count = 0; | ||
| 27 | |||
| 28 | uint16_t get_last_keycode(void) { | ||
| 29 | return last_record.keycode; | ||
| 30 | } | ||
| 31 | |||
| 32 | uint8_t get_last_mods(void) { | ||
| 33 | return last_mods; | ||
| 34 | } | ||
| 35 | |||
| 36 | void 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 | |||
| 45 | void set_last_mods(uint8_t mods) { | ||
| 46 | last_mods = mods; | ||
| 47 | } | ||
| 48 | |||
| 49 | void 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`. */ | ||
| 56 | static 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 | |||
| 64 | int8_t get_repeat_key_count(void) { | ||
| 65 | return processing_repeat_count; | ||
| 66 | } | ||
| 67 | |||
| 68 | void 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(®istered_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 | */ | ||
| 116 | static 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 | |||
| 127 | uint16_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 | |||
| 241 | void 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(®istered_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 | |||
| 19 | uint16_t get_last_keycode(void); /**< Keycode of the last key. */ | ||
| 20 | uint8_t get_last_mods(void); /**< Mods active with the last key. */ | ||
| 21 | void set_last_keycode(uint16_t keycode); /**< Sets the last key. */ | ||
| 22 | void set_last_mods(uint8_t mods); /**< Sets the last mods. */ | ||
| 23 | |||
| 24 | /** @brief Gets the record for the last key. */ | ||
| 25 | keyrecord_t* get_last_record(void); | ||
| 26 | |||
| 27 | /** @brief Sets keycode and record info for the last key. */ | ||
| 28 | void 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 | */ | ||
| 44 | int8_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 | */ | ||
| 50 | void 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 | */ | ||
| 62 | uint16_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 | */ | ||
| 68 | void 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 | */ | ||
| 78 | uint16_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 | |||
| 16 | REPEAT_KEY_ENABLE = yes | ||
| 17 | |||
| 18 | EXTRAKEY_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 | |||
| 24 | using ::testing::AnyNumber; | ||
| 25 | using ::testing::InSequence; | ||
| 26 | |||
| 27 | namespace { | ||
| 28 | |||
| 29 | bool process_record_user_default(uint16_t keycode, keyrecord_t* record) { | ||
| 30 | return true; | ||
| 31 | } | ||
| 32 | |||
| 33 | bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { | ||
| 34 | return true; | ||
| 35 | } | ||
| 36 | |||
| 37 | uint16_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. | ||
| 43 | std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default; | ||
| 44 | std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default; | ||
| 45 | std::function<uint16_t(uint16_t, uint8_t)> get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default; | ||
| 46 | |||
| 47 | extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) { | ||
| 48 | return process_record_user_fun(keycode, record); | ||
| 49 | } | ||
| 50 | |||
| 51 | extern "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 | |||
| 55 | extern "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 | |||
| 59 | class 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 | |||
| 101 | TEST_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 | |||
| 148 | struct 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. | ||
| 155 | TEST_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. | ||
| 225 | TEST_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. | ||
| 277 | TEST_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. | ||
| 322 | TEST_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. | ||
| 375 | TEST_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. | ||
| 425 | TEST_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. | ||
| 465 | TEST_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 | |||
| 16 | REPEAT_KEY_ENABLE = yes | ||
| 17 | |||
| 18 | COMBO_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 | |||
| 22 | using ::testing::AnyNumber; | ||
| 23 | using ::testing::InSequence; | ||
| 24 | |||
| 25 | namespace { | ||
| 26 | |||
| 27 | extern "C" { | ||
| 28 | // Define a combo: KC_X + KC_Y = KC_Q. | ||
| 29 | const uint16_t xy_combo[] PROGMEM = {KC_X, KC_Y, COMBO_END}; | ||
| 30 | combo_t key_combos[] = {COMBO(xy_combo, KC_Q)}; | ||
| 31 | uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(*key_combos); | ||
| 32 | } // extern "C" | ||
| 33 | |||
| 34 | class 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". | ||
| 38 | TEST_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 | |||
| 16 | REPEAT_KEY_ENABLE = yes | ||
| 17 | |||
| 18 | AUTO_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 | |||
| 24 | using ::testing::AnyNumber; | ||
| 25 | using ::testing::AnyOf; | ||
| 26 | using ::testing::InSequence; | ||
| 27 | |||
| 28 | #define FOO_MACRO SAFE_RANGE | ||
| 29 | |||
| 30 | namespace { | ||
| 31 | |||
| 32 | bool process_record_user_default(uint16_t keycode, keyrecord_t* record) { | ||
| 33 | return true; | ||
| 34 | } | ||
| 35 | |||
| 36 | bool 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. | ||
| 42 | std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default; | ||
| 43 | std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default; | ||
| 44 | |||
| 45 | extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) { | ||
| 46 | return process_record_user_fun(keycode, record); | ||
| 47 | } | ||
| 48 | extern "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 | |||
| 52 | class 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". | ||
| 95 | TEST_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". | ||
| 149 | TEST_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". | ||
| 183 | TEST_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. | ||
| 234 | TEST_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". | ||
| 269 | TEST_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". | ||
| 314 | TEST_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". | ||
| 366 | TEST_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". | ||
| 405 | TEST_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". | ||
| 459 | TEST_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". | ||
| 506 | TEST_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". | ||
| 529 | TEST_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. | ||
| 571 | TEST_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". | ||
| 610 | TEST_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(). | ||
| 673 | TEST_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. | ||
| 718 | TEST_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 | ||
| 25 | class TestDriver { | 26 | class 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 | |||
| 105 | MATCHER_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. |
