[Core] Add Repeat Key ("repeat last key") as a core feature. (#19700)
Co-authored-by: casuanoob <96005765+casuanoob@users.noreply.github.com> Co-authored-by: Sergey Vlasov <sigprof@gmail.com>master
parent
e1766df185
commit
3993b15f05
@ -0,0 +1,18 @@ |
||||
{ |
||||
"keycodes": { |
||||
"0x7C79": { |
||||
"group": "quantum", |
||||
"key": "QK_REPEAT_KEY", |
||||
"aliases": [ |
||||
"QK_REP" |
||||
] |
||||
}, |
||||
"0x7C7A": { |
||||
"group": "quantum", |
||||
"key": "QK_ALT_REPEAT_KEY", |
||||
"aliases": [ |
||||
"QK_AREP" |
||||
] |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,457 @@ |
||||
# Repeat Key |
||||
|
||||
The Repeat Key performs the action of the last pressed key. Tapping the Repeat |
||||
Key after tapping the <kbd>Z</kbd> key types another "`z`." This is useful for |
||||
typing doubled letters, like the `z` in "`dazzle`": a double tap on <kbd>Z</kbd> |
||||
can instead be a roll from <kbd>Z</kbd> to <kbd>Repeat</kbd>, which is |
||||
potentially faster and more comfortable. The Repeat Key is also useful for |
||||
hotkeys, like repeating Ctrl + Shift + Right Arrow to select by word. |
||||
|
||||
Repeat Key remembers mods that were active with the last key press. These mods |
||||
are combined with any additional mods while pressing the Repeat Key. If the last |
||||
press key was <kbd>Ctrl</kbd> + <kbd>Z</kbd>, then <kbd>Shift</kbd> + |
||||
<kbd>Repeat</kbd> performs Ctrl + Shift + `Z`. |
||||
|
||||
## How do I enable Repeat Key |
||||
|
||||
In your `rules.mk`, add: |
||||
|
||||
```make |
||||
REPEAT_KEY_ENABLE = yes |
||||
``` |
||||
|
||||
Then pick a key in your keymap and assign it the keycode `QK_REPEAT_KEY` (short |
||||
alias `QK_REP`). Optionally, use the keycode `QK_ALT_REPEAT_KEY` (short alias |
||||
`QK_AREP`) on another key. |
||||
|
||||
## Keycodes |
||||
|
||||
|Keycode |Aliases |Description | |
||||
|-----------------------|---------|-------------------------------------| |
||||
|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key | |
||||
|`QK_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key | |
||||
|
||||
## Alternate Repeating |
||||
|
||||
The Alternate Repeat Key performs the "alternate" action of the last pressed key |
||||
if it is defined. By default, Alternate Repeat is defined for navigation keys to |
||||
act in the reverse direction. When the last key is the common "select by word" |
||||
hotkey Ctrl + Shift + Right Arrow, the Alternate Repeat Key performs Ctrl + |
||||
Shift + Left Arrow, which together with the Repeat Key enables convenient |
||||
selection by words in either direction. |
||||
|
||||
Alternate Repeat is enabled with the Repeat Key by default. Optionally, to |
||||
reduce firmware size, Alternate Repeat may be disabled by adding in config.h: |
||||
|
||||
```c |
||||
#define NO_ALT_REPEAT_KEY |
||||
``` |
||||
|
||||
The following alternate keys are defined by default. See |
||||
`get_alt_repeat_key_keycode_user()` below for how to change or add to these |
||||
definitions. Where it makes sense, these definitions also include combinations |
||||
with mods, like Ctrl + Left ↔ Ctrl + Right Arrow. |
||||
|
||||
**Navigation** |
||||
|
||||
|Keycodes |Description | |
||||
|-----------------------------------|-----------------------------------| |
||||
|`KC_LEFT` ↔ `KC_RGHT` | Left ↔ Right Arrow | |
||||
|`KC_UP` ↔ `KC_DOWN` | Up ↔ Down Arrow | |
||||
|`KC_HOME` ↔ `KC_END` | Home ↔ End | |
||||
|`KC_PGUP` ↔ `KC_PGDN` | Page Up ↔ Page Down | |
||||
|`KC_MS_L` ↔ `KC_MS_R` | Mouse Cursor Left ↔ Right | |
||||
|`KC_MS_U` ↔ `KC_MS_D` | Mouse Cursor Up ↔ Down | |
||||
|`KC_WH_L` ↔ `KC_WH_R` | Mouse Wheel Left ↔ Right | |
||||
|`KC_WH_U` ↔ `KC_WH_D` | Mouse Wheel Up ↔ Down | |
||||
|
||||
**Misc** |
||||
|
||||
|Keycodes |Description | |
||||
|-----------------------------------|-----------------------------------| |
||||
|`KC_BSPC` ↔ `KC_DEL` | Backspace ↔ Delete | |
||||
|`KC_LBRC` ↔ `KC_RBRC` | `[` ↔ `]` | |
||||
|`KC_LCBR` ↔ `KC_RCBR` | `{` ↔ `}` | |
||||
|
||||
**Media** |
||||
|
||||
|Keycodes |Description | |
||||
|-----------------------------------|-----------------------------------| |
||||
|`KC_WBAK` ↔ `KC_WFWD` | Browser Back ↔ Forward | |
||||
|`KC_MNXT` ↔ `KC_MPRV` | Next ↔ Previous Media Track | |
||||
|`KC_MFFD` ↔ `KC_MRWD` | Fast Forward ↔ Rewind Media | |
||||
|`KC_VOLU` ↔ `KC_VOLD` | Volume Up ↔ Down | |
||||
|`KC_BRIU` ↔ `KC_BRID` | Brightness Up ↔ Down | |
||||
|
||||
**Hotkeys in Vim, Emacs, and other programs** |
||||
|
||||
|Keycodes |Description | |
||||
|-----------------------------------|-----------------------------------| |
||||
|mod + `KC_F` ↔ mod + `KC_B` | Forward ↔ Backward | |
||||
|mod + `KC_D` ↔ mod + `KC_U` | Down ↔ Up | |
||||
|mod + `KC_N` ↔ mod + `KC_P` | Next ↔ Previous | |
||||
|mod + `KC_A` ↔ mod + `KC_E` | Home ↔ End | |
||||
|mod + `KC_O` ↔ mod + `KC_I` | Vim jump list Older ↔ Newer | |
||||
|`KC_J` ↔ `KC_K` | Down ↔ Up | |
||||
|`KC_H` ↔ `KC_L` | Left ↔ Right | |
||||
|`KC_W` ↔ `KC_B` | Forward ↔ Backward by Word | |
||||
|
||||
(where above, "mod" is Ctrl, Alt, or GUI) |
||||
|
||||
|
||||
## Defining alternate keys |
||||
|
||||
Use the `get_alt_repeat_key_keycode_user()` callback to define the "alternate" |
||||
for additional keys or override the default definitions. For example, to define |
||||
Ctrl + Y as the alternate of Ctrl + Z, and vice versa, add the following in |
||||
keymap.c: |
||||
|
||||
```c |
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
if ((mods & MOD_MASK_CTRL)) { // Was Ctrl held? |
||||
switch (keycode) { |
||||
case KC_Y: return C(KC_Z); // Ctrl + Y reverses to Ctrl + Z. |
||||
case KC_Z: return C(KC_Y); // Ctrl + Z reverses to Ctrl + Y. |
||||
} |
||||
} |
||||
|
||||
return KC_TRNS; // Defer to default definitions. |
||||
} |
||||
``` |
||||
|
||||
The `keycode` and `mods` args are the keycode and mods that were active with the |
||||
last pressed key. The meaning of the return value from this function is: |
||||
|
||||
* `KC_NO` – do nothing (any predefined alternate key is not used); |
||||
* `KC_TRNS` – use the default alternate key if it exists; |
||||
* anything else – use the specified keycode. Any keycode may be returned |
||||
as an alternate key, including custom keycodes. |
||||
|
||||
Another example, defining Shift + Tab as the alternate of Tab, and vice versa: |
||||
|
||||
```c |
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
bool shifted = (mods & MOD_MASK_SHIFT); // Was Shift held? |
||||
switch (keycode) { |
||||
case KC_TAB: |
||||
if (shifted) { // If the last key was Shift + Tab, |
||||
return KC_TAB; // ... the reverse is Tab. |
||||
} else { // Otherwise, the last key was Tab, |
||||
return S(KC_TAB); // ... and the reverse is Shift + Tab. |
||||
} |
||||
} |
||||
|
||||
return KC_TRNS; |
||||
} |
||||
``` |
||||
|
||||
#### Eliminating SFBs |
||||
|
||||
Alternate Repeat can be configured more generally to perform an action that |
||||
"complements" the last key. Alternate Repeat is not limited to reverse |
||||
repeating, and it need not be symmetric. You can use it to eliminate cases of |
||||
same-finger bigrams in your layout, that is, pairs of letters typed by the same |
||||
finger. The following addresses the top 5 same-finger bigrams in English on |
||||
QWERTY, so that for instance "`ed`" may be typed as <kbd>E</kbd>, <kbd>Alt |
||||
Repeat</kbd>. |
||||
|
||||
```c |
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
switch (keycode) { |
||||
case KC_E: return KC_D; // For "ED" bigram. |
||||
case KC_D: return KC_E; // For "DE" bigram. |
||||
case KC_C: return KC_E; // For "CE" bigram. |
||||
case KC_L: return KC_O; // For "LO" bigram. |
||||
case KC_U: return KC_N; // For "UN" bigram. |
||||
} |
||||
|
||||
return KC_TRNS; |
||||
} |
||||
``` |
||||
|
||||
#### Typing shortcuts |
||||
|
||||
A useful possibility is having Alternate Repeat press [a |
||||
macro](feature_macros.md). This way macros can be used without having to |
||||
dedicate keys to them. The following defines a couple shortcuts. |
||||
|
||||
* Typing <kbd>K</kbd>, <kbd>Alt Repeat</kbd> produces "`keyboard`," with the |
||||
initial "`k`" typed as usual and the "`eybord`" produced by the macro. |
||||
* Typing <kbd>.</kbd>, <kbd>Alt Repeat</kbd> produces "`../`," handy for "up |
||||
directory" on the shell. Similary, <kbd>.</kbd> types the initial "`.`" and |
||||
"`./`" is produced by the macro. |
||||
|
||||
```c |
||||
enum custom_keycodes { |
||||
M_KEYBOARD = SAFE_RANGE, |
||||
M_UPDIR, |
||||
// Other custom keys... |
||||
}; |
||||
|
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
switch (keycode) { |
||||
case KC_K: return M_KEYBOARD; |
||||
case KC_DOT: return M_UPDIR; |
||||
} |
||||
|
||||
return KC_TRNS; |
||||
} |
||||
|
||||
bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case M_KEYBOARD: SEND_STRING(/*k*/"eyboard"); break; |
||||
case M_UPDIR: SEND_STRING(/*.*/"./"); break; |
||||
} |
||||
return true; |
||||
} |
||||
``` |
||||
|
||||
## Ignoring certain keys and mods |
||||
|
||||
In tracking what is "the last key" to be repeated or alternate repeated, |
||||
modifier and layer switch keys are always ignored. This makes it possible to set |
||||
some mods and change layers between pressing a key and repeating it. By default, |
||||
all other (non-modifier, non-layer switch) keys are remembered so that they are |
||||
eligible for repeating. To configure additional keys to be ignored, define |
||||
`remember_last_key_user()` in your keymap.c. |
||||
|
||||
#### Ignoring a key |
||||
|
||||
The following ignores the Backspace key: |
||||
|
||||
```c |
||||
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, |
||||
uint8_t* remembered_mods) { |
||||
switch (keycode) { |
||||
case KC_BSPC: |
||||
return false; // Ignore backspace. |
||||
} |
||||
|
||||
return true; // Other keys can be repeated. |
||||
} |
||||
``` |
||||
|
||||
Then for instance, the Repeat key in <kbd>Left Arrow</kbd>, |
||||
<kbd>Backspace</kbd>, <kbd>Repeat</kbd> sends Left Arrow again instead of |
||||
repeating Backspace. |
||||
|
||||
The `remember_last_key_user()` callback is called on every key press excluding |
||||
modifiers and layer switches. Returning true indicates the key is remembered, |
||||
while false means it is ignored. |
||||
|
||||
#### Filtering remembered mods |
||||
|
||||
The `remembered_mods` arg represents the mods that will be remembered with |
||||
this key. It can be modified to forget certain mods. This may be |
||||
useful to forget capitalization when repeating shifted letters, so that "Aaron" |
||||
does not becom "AAron": |
||||
|
||||
```c |
||||
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, |
||||
uint8_t* remembered_mods) { |
||||
// Forget Shift on letter keys when Shift or AltGr are the only mods. |
||||
switch (keycode) { |
||||
case KC_A ... KC_Z: |
||||
if ((*remembered_mods & ~(MOD_MASK_SHIFT | MOD_BIT(KC_RALT))) == 0) { |
||||
*remembered_mods &= ~MOD_MASK_SHIFT; |
||||
} |
||||
break; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
``` |
||||
|
||||
#### Further conditions |
||||
|
||||
Besides checking the keycode, this callback could also make conditions based on |
||||
the current layer state (with `IS_LAYER_ON(layer)`) or mods (`get_mods()`). For |
||||
example, the following ignores keys on layer 2 as well as key combinations |
||||
involving GUI: |
||||
|
||||
```c |
||||
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, |
||||
uint8_t* remembered_mods) { |
||||
if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) { |
||||
return false; // Ignore layer 2 keys and GUI chords. |
||||
} |
||||
|
||||
return true; // Other keys can be repeated. |
||||
} |
||||
``` |
||||
|
||||
?> See [Layer Functions](feature_layers.md#functions) and [Checking Modifier |
||||
State](feature_advanced_keycodes.md#checking-modifier-state) for further |
||||
details. |
||||
|
||||
|
||||
## Handle how a key is repeated |
||||
|
||||
By default, pressing the Repeat Key will simply behave as if the last key |
||||
were pressed again. This also works with macro keys with custom handlers, |
||||
invoking the macro again. In case fine-tuning is needed for sensible repetition, |
||||
you can handle how a key is repeated with `get_repeat_key_count()` within |
||||
`process_record_user()`. |
||||
|
||||
The `get_repeat_key_count()` function returns a signed count of times the key |
||||
has been repeated or alternate repeated. When a key is pressed as usual, |
||||
`get_repeat_key_count()` is 0. On the first repeat, it is 1, then the second |
||||
repeat, 2, and so on. Negative counts are used similarly for alternate |
||||
repeating. For instance supposing `MY_MACRO` is a custom keycode used in the |
||||
layout: |
||||
|
||||
```c |
||||
bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case MY_MACRO: |
||||
if (get_repeat_key_count() > 0) { |
||||
// MY_MACRO is being repeated! |
||||
if (record->event.pressed) { |
||||
SEND_STRING("repeat!"); |
||||
} |
||||
} else { |
||||
// MY_MACRO is being used normally. |
||||
if (record->event.pressed) { |
||||
SEND_STRING("macro"); |
||||
} |
||||
} |
||||
return false; |
||||
|
||||
// Other macros... |
||||
} |
||||
return true; |
||||
} |
||||
``` |
||||
|
||||
## Handle how a key is alternate repeated |
||||
|
||||
Pressing the Alternate Repeat Key behaves as if the "alternate" of the last |
||||
pressed key were pressed, if an alternate is defined. To define how a particular |
||||
key is alternate repeated, use the `get_alt_repeat_key_keycode_user()` callback |
||||
as described above to define which keycode to use as its alternate. Beyond this, |
||||
`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior |
||||
when alternate repeating. |
||||
|
||||
The following example defines `MY_MACRO` as its own alternate, and specially |
||||
handles repeating and alternate repeating: |
||||
|
||||
```c |
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
switch (keycode) { |
||||
case MY_MACRO: return MY_MACRO; // MY_MACRO is its own alternate. |
||||
} |
||||
return KC_TRNS; |
||||
} |
||||
|
||||
bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case MY_MACRO: |
||||
if (get_repeat_key_count() > 0) { // Repeating. |
||||
if (record->event.pressed) { |
||||
SEND_STRING("repeat!"); |
||||
} |
||||
} else if (get_repeat_key_count() < 0) { // Alternate repeating. |
||||
if (record->event.pressed) { |
||||
SEND_STRING("alt repeat!"); |
||||
} |
||||
} else { // Used normally. |
||||
if (record->event.pressed) { |
||||
SEND_STRING("macro"); |
||||
} |
||||
} |
||||
return false; |
||||
|
||||
// Other macros... |
||||
} |
||||
return true; |
||||
} |
||||
``` |
||||
|
||||
|
||||
## Functions |
||||
|
||||
| Function | Description | |
||||
|--------------------------------|------------------------------------------------------------------------| |
||||
| `get_last_keycode()` | The last key's keycode, the key to be repeated. | |
||||
| `get_last_mods()` | Mods to apply when repeating. | |
||||
| `set_last_keycode(kc)` | Set the keycode to be repeated. | |
||||
| `set_last_mods(mods)` | Set the mods to apply when repeating. | |
||||
| `get_repeat_key_count()` | Signed count of times the key has been repeated or alternate repeated. | |
||||
| `get_alt_repeat_key_keycode()` | Keycode to be used for alternate repeating. | |
||||
|
||||
|
||||
## Additional "Alternate" keys |
||||
|
||||
By leveraging `get_last_keycode()` in macros, it is possible to define |
||||
additional, distinct "Alternate Repeat"-like keys. The following defines two |
||||
keys `ALTREP2` and `ALTREP3` and implements ten shortcuts with them for common |
||||
English 5-gram letter patterns, taking inspiration from |
||||
[Stenotype](feature_stenography.md): |
||||
|
||||
|
||||
| Typing | Produces | Typing | Produces | |
||||
|----------------------------------|----------|----------------------------------|----------| |
||||
| <kbd>A</kbd>, <kbd>ALTREP2</kbd> | `ation` | <kbd>A</kbd>, <kbd>ALTREP3</kbd> | `about` | |
||||
| <kbd>I</kbd>, <kbd>ALTREP2</kbd> | `ition` | <kbd>I</kbd>, <kbd>ALTREP3</kbd> | `inter` | |
||||
| <kbd>S</kbd>, <kbd>ALTREP2</kbd> | `ssion` | <kbd>S</kbd>, <kbd>ALTREP3</kbd> | `state` | |
||||
| <kbd>T</kbd>, <kbd>ALTREP2</kbd> | `their` | <kbd>T</kbd>, <kbd>ALTREP3</kbd> | `there` | |
||||
| <kbd>W</kbd>, <kbd>ALTREP2</kbd> | `which` | <kbd>W</kbd>, <kbd>ALTREP3</kbd> | `would` | |
||||
|
||||
```c |
||||
enum custom_keycodes { |
||||
ALTREP2 = SAFE_RANGE, |
||||
ALTREP3, |
||||
}; |
||||
|
||||
// Use ALTREP2 and ALTREP3 in your layout... |
||||
|
||||
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, |
||||
uint8_t* remembered_mods) { |
||||
switch (keycode) { |
||||
case ALTREP2: |
||||
case ALTREP3: |
||||
return false; // Ignore ALTREP keys. |
||||
} |
||||
|
||||
return true; // Other keys can be repeated. |
||||
} |
||||
|
||||
static void process_altrep2(uint16_t keycode, uint8_t mods) { |
||||
switch (keycode) { |
||||
case KC_A: SEND_STRING(/*a*/"tion"); break; |
||||
case KC_I: SEND_STRING(/*i*/"tion"); break; |
||||
case KC_S: SEND_STRING(/*s*/"sion"); break; |
||||
case KC_T: SEND_STRING(/*t*/"heir"); break; |
||||
case KC_W: SEND_STRING(/*w*/"hich"); break; |
||||
} |
||||
} |
||||
|
||||
static void process_altrep3(uint16_t keycode, uint8_t mods) { |
||||
switch (keycode) { |
||||
case KC_A: SEND_STRING(/*a*/"bout"); break; |
||||
case KC_I: SEND_STRING(/*i*/"nter"); break; |
||||
case KC_S: SEND_STRING(/*s*/"tate"); break; |
||||
case KC_T: SEND_STRING(/*t*/"here"); break; |
||||
case KC_W: SEND_STRING(/*w*/"ould"); break; |
||||
} |
||||
} |
||||
|
||||
bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case ALTREP2: |
||||
if (record->event.pressed) { |
||||
process_altrep2(get_last_keycode(), get_last_mods()); |
||||
} |
||||
return false; |
||||
|
||||
case ALTREP3: |
||||
if (record->event.pressed) { |
||||
process_altrep3(get_last_keycode(), get_last_mods()); |
||||
} |
||||
return false; |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
``` |
||||
|
@ -0,0 +1,109 @@ |
||||
// Copyright 2022-2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "process_repeat_key.h" |
||||
|
||||
// Default implementation of remember_last_key_user().
|
||||
__attribute__((weak)) bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
return true; |
||||
} |
||||
|
||||
static bool remember_last_key(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
switch (keycode) { |
||||
// Ignore MO, TO, TG, TT, and TL layer switch keys.
|
||||
case QK_MOMENTARY ... QK_MOMENTARY_MAX: |
||||
case QK_TO ... QK_TO_MAX: |
||||
case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: |
||||
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: |
||||
// Ignore mod keys.
|
||||
case KC_LCTL ... KC_RGUI: |
||||
case KC_HYPR: |
||||
case KC_MEH: |
||||
#ifndef NO_ACTION_ONESHOT // Ignore one-shot keys.
|
||||
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: |
||||
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: |
||||
#endif // NO_ACTION_ONESHOT
|
||||
#ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys.
|
||||
case QK_TRI_LAYER_LOWER: |
||||
case QK_TRI_LAYER_UPPER: |
||||
#endif // TRI_LAYER_ENABLE
|
||||
return false; |
||||
|
||||
// Ignore hold events on tap-hold keys.
|
||||
#ifndef NO_ACTION_TAPPING |
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX: |
||||
# ifndef NO_ACTION_LAYER |
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: |
||||
# endif // NO_ACTION_LAYER
|
||||
if (record->tap.count == 0) { |
||||
return false; |
||||
} |
||||
break; |
||||
#endif // NO_ACTION_TAPPING
|
||||
|
||||
#ifdef SWAP_HANDS_ENABLE |
||||
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: |
||||
if (IS_SWAP_HANDS_KEYCODE(keycode) || record->tap.count == 0) { |
||||
return false; |
||||
} |
||||
break; |
||||
#endif // SWAP_HANDS_ENABLE
|
||||
|
||||
case QK_REPEAT_KEY: |
||||
#ifndef NO_ALT_REPEAT_KEY |
||||
case QK_ALT_REPEAT_KEY: |
||||
#endif // NO_ALT_REPEAT_KEY
|
||||
return false; |
||||
} |
||||
|
||||
return remember_last_key_user(keycode, record, remembered_mods); |
||||
} |
||||
|
||||
bool process_last_key(uint16_t keycode, keyrecord_t* record) { |
||||
if (get_repeat_key_count()) { |
||||
return true; |
||||
} |
||||
|
||||
if (record->event.pressed) { |
||||
uint8_t remembered_mods = get_mods() | get_weak_mods(); |
||||
#ifndef NO_ACTION_ONESHOT |
||||
remembered_mods |= get_oneshot_mods(); |
||||
#endif // NO_ACTION_ONESHOT
|
||||
|
||||
if (remember_last_key(keycode, record, &remembered_mods)) { |
||||
set_last_record(keycode, record); |
||||
set_last_mods(remembered_mods); |
||||
} |
||||
} |
||||
|
||||
return true; |
||||
} |
||||
|
||||
bool process_repeat_key(uint16_t keycode, keyrecord_t* record) { |
||||
if (get_repeat_key_count()) { |
||||
return true; |
||||
} |
||||
|
||||
if (keycode == QK_REPEAT_KEY) { |
||||
repeat_key_invoke(&record->event); |
||||
return false; |
||||
#ifndef NO_ALT_REPEAT_KEY |
||||
} else if (keycode == QK_ALT_REPEAT_KEY) { |
||||
alt_repeat_key_invoke(&record->event); |
||||
return false; |
||||
#endif // NO_ALT_REPEAT_KEY
|
||||
} |
||||
|
||||
return true; |
||||
} |
@ -0,0 +1,62 @@ |
||||
// Copyright 2022-2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "quantum.h" |
||||
|
||||
/**
|
||||
* @brief Process handler for remembering the last key. |
||||
* |
||||
* @param keycode Keycode registered by matrix press, per keymap |
||||
* @param record keyrecord_t structure |
||||
* @return true Continue processing keycodes, and send to host |
||||
* @return false Stop processing keycodes, and don't send to host |
||||
*/ |
||||
bool process_last_key(uint16_t keycode, keyrecord_t* record); |
||||
|
||||
/**
|
||||
* @brief Optional callback defining which keys are remembered. |
||||
* |
||||
* @param keycode Keycode that was just pressed |
||||
* @param record keyrecord_t structure |
||||
* @param remembered_mods Mods that will be remembered with this key |
||||
* @return true Key is remembered |
||||
* @return false Key is ignored |
||||
* |
||||
* Modifier and layer switch keys are always ignored. For all other keys, this |
||||
* callback is called on every key press. Returning true means that the key is |
||||
* remembered, false means it is ignored. By default, all non-modifier, |
||||
* non-layer switch keys are remembered. |
||||
* |
||||
* The `remembered_mods` arg represents the mods that will be remembered with |
||||
* this key. It can be modified to forget certain mods, for instance to forget |
||||
* capitalization when repeating shifted letters: |
||||
* |
||||
* // Forget Shift on letter keys.
|
||||
* if (KC_A <= keycode && keycode <= KC_Z && (*remembered_mods & ~MOD_MASK_SHIFT) == 0) { |
||||
* *remembered_mods = 0; |
||||
* } |
||||
*/ |
||||
bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods); |
||||
|
||||
/**
|
||||
* @brief Process handler for Repeat Key feature. |
||||
* |
||||
* @param keycode Keycode registered by matrix press, per keymap |
||||
* @param record keyrecord_t structure |
||||
* @return true Continue processing keycodes, and send to host |
||||
* @return false Stop processing keycodes, and don't send to host |
||||
*/ |
||||
bool process_repeat_key(uint16_t keycode, keyrecord_t* record); |
@ -0,0 +1,282 @@ |
||||
// Copyright 2022-2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#include "repeat_key.h" |
||||
|
||||
// Variables saving the state of the last key press.
|
||||
static keyrecord_t last_record = {0}; |
||||
static uint8_t last_mods = 0; |
||||
// Signed count of the number of times the last key has been repeated or
|
||||
// alternate repeated: it is 0 when a key is pressed normally, positive when
|
||||
// repeated, and negative when alternate repeated.
|
||||
static int8_t last_repeat_count = 0; |
||||
// The repeat_count, but set to 0 outside of repeat_key_invoke() so that it is
|
||||
// nonzero only while a repeated key is being processed.
|
||||
static int8_t processing_repeat_count = 0; |
||||
|
||||
uint16_t get_last_keycode(void) { |
||||
return last_record.keycode; |
||||
} |
||||
|
||||
uint8_t get_last_mods(void) { |
||||
return last_mods; |
||||
} |
||||
|
||||
void set_last_keycode(uint16_t keycode) { |
||||
set_last_record(keycode, &(keyrecord_t){ |
||||
#ifndef NO_ACTION_TAPPING |
||||
.tap.interrupted = false, |
||||
.tap.count = 1, |
||||
#endif |
||||
}); |
||||
} |
||||
|
||||
void set_last_mods(uint8_t mods) { |
||||
last_mods = mods; |
||||
} |
||||
|
||||
void set_last_record(uint16_t keycode, keyrecord_t* record) { |
||||
last_record = *record; |
||||
last_record.keycode = keycode; |
||||
last_repeat_count = 0; |
||||
} |
||||
|
||||
/** @brief Updates `last_repeat_count` in direction `dir`. */ |
||||
static void update_last_repeat_count(int8_t dir) { |
||||
if (dir * last_repeat_count < 0) { |
||||
last_repeat_count = dir; |
||||
} else if (dir * last_repeat_count < 127) { |
||||
last_repeat_count += dir; |
||||
} |
||||
} |
||||
|
||||
int8_t get_repeat_key_count(void) { |
||||
return processing_repeat_count; |
||||
} |
||||
|
||||
void repeat_key_invoke(const keyevent_t* event) { |
||||
// It is possible (e.g. in rolled presses) that the last key changes while
|
||||
// the Repeat Key is pressed. To prevent stuck keys, it is important to
|
||||
// remember separately what key record was processed on press so that the
|
||||
// the corresponding record is generated on release.
|
||||
static keyrecord_t registered_record = {0}; |
||||
static int8_t registered_repeat_count = 0; |
||||
// Since this function calls process_record(), it may recursively call
|
||||
// itself. We return early if `processing_repeat_count` is nonzero to
|
||||
// prevent infinite recursion.
|
||||
if (processing_repeat_count || !last_record.keycode) { |
||||
return; |
||||
} |
||||
|
||||
if (event->pressed) { |
||||
update_last_repeat_count(1); |
||||
// On press, apply the last mods state, stacking on top of current mods.
|
||||
register_weak_mods(last_mods); |
||||
registered_record = last_record; |
||||
registered_repeat_count = last_repeat_count; |
||||
} |
||||
|
||||
// Generate a keyrecord and plumb it into the event pipeline.
|
||||
registered_record.event = *event; |
||||
processing_repeat_count = registered_repeat_count; |
||||
process_record(®istered_record); |
||||
processing_repeat_count = 0; |
||||
|
||||
// On release, restore the mods state.
|
||||
if (!event->pressed) { |
||||
unregister_weak_mods(last_mods); |
||||
} |
||||
} |
||||
|
||||
#ifndef NO_ALT_REPEAT_KEY |
||||
/**
|
||||
* @brief Find alternate keycode from a table of opposing keycode pairs. |
||||
* @param table Array of pairs of basic keycodes, declared as PROGMEM. |
||||
* @param table_size_bytes The size of the table in bytes. |
||||
* @param target The basic keycode to find. |
||||
* @return The alternate basic keycode, or KC_NO if none was found. |
||||
* |
||||
* @note The table keycodes and target must be basic keycodes. |
||||
* |
||||
* This helper is used several times below to define alternate keys. Given a |
||||
* table of pairs of basic keycodes, the function finds the pair containing |
||||
* `target` and returns the other keycode in the pair. |
||||
*/ |
||||
static uint8_t find_alt_keycode(const uint8_t (*table)[2], uint8_t table_size_bytes, uint8_t target) { |
||||
const uint8_t* keycodes = (const uint8_t*)table; |
||||
for (uint8_t i = 0; i < table_size_bytes; ++i) { |
||||
if (target == pgm_read_byte(keycodes + i)) { |
||||
// Xor (i ^ 1) the index to get the other element in the pair.
|
||||
return pgm_read_byte(keycodes + (i ^ 1)); |
||||
} |
||||
} |
||||
return KC_NO; |
||||
} |
||||
|
||||
uint16_t get_alt_repeat_key_keycode(void) { |
||||
uint16_t keycode = last_record.keycode; |
||||
uint8_t mods = last_mods; |
||||
|
||||
// Call the user callback first to give it a chance to override the default
|
||||
// alternate key definitions that follow.
|
||||
uint16_t alt_keycode = get_alt_repeat_key_keycode_user(keycode, mods); |
||||
|
||||
if (alt_keycode != KC_TRANSPARENT) { |
||||
return alt_keycode; |
||||
} |
||||
|
||||
// Convert 8-bit mods to the 5-bit format used in keycodes. This is lossy:
|
||||
// if left and right handed mods were mixed, they all become right handed.
|
||||
mods = ((mods & 0xf0) ? /* set right hand bit */ 0x10 : 0) |
||||
// Combine right and left hand mods.
|
||||
| (((mods >> 4) | mods) & 0xf); |
||||
|
||||
switch (keycode) { |
||||
case QK_MODS ... QK_MODS_MAX: // Unpack modifier + basic key.
|
||||
mods |= QK_MODS_GET_MODS(keycode); |
||||
keycode = QK_MODS_GET_BASIC_KEYCODE(keycode); |
||||
break; |
||||
|
||||
# ifndef NO_ACTION_TAPPING |
||||
case QK_MOD_TAP ... QK_MOD_TAP_MAX: |
||||
keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode); |
||||
break; |
||||
# ifndef NO_ACTION_LAYER |
||||
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: |
||||
keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode); |
||||
break; |
||||
# endif // NO_ACTION_LAYER
|
||||
# endif // NO_ACTION_TAPPING
|
||||
|
||||
# ifdef SWAP_HANDS_ENABLE |
||||
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: |
||||
if (IS_SWAP_HANDS_KEYCODE(keycode)) { |
||||
return KC_NO; |
||||
} |
||||
keycode = QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode); |
||||
break; |
||||
# endif // SWAP_HANDS_ENABLE
|
||||
} |
||||
|
||||
if (IS_QK_BASIC(keycode)) { |
||||
if ((mods & (MOD_LCTL | MOD_LALT | MOD_LGUI))) { |
||||
// The last key was pressed with a modifier other than Shift.
|
||||
// The following maps
|
||||
// mod + F <-> mod + B
|
||||
// and a few others, supporting several core hotkeys used in
|
||||
// Emacs, Vim, less, and other programs.
|
||||
// clang-format off
|
||||
static const uint8_t pairs[][2] PROGMEM = { |
||||
{KC_F , KC_B }, // Forward / Backward.
|
||||
{KC_D , KC_U }, // Down / Up.
|
||||
{KC_N , KC_P }, // Next / Previous.
|
||||
{KC_A , KC_E }, // Home / End.
|
||||
{KC_O , KC_I }, // Older / Newer in Vim jump list.
|
||||
}; |
||||
// clang-format on
|
||||
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode); |
||||
} else { |
||||
// The last key was pressed with no mods or only Shift. The
|
||||
// following map a few more Vim hotkeys.
|
||||
// clang-format off
|
||||
static const uint8_t pairs[][2] PROGMEM = { |
||||
{KC_J , KC_K }, // Down / Up.
|
||||
{KC_H , KC_L }, // Left / Right.
|
||||
// These two lines map W and E to B, and B to W.
|
||||
{KC_W , KC_B }, // Forward / Backward by word.
|
||||
{KC_E , KC_B }, // Forward / Backward by word.
|
||||
}; |
||||
// clang-format on
|
||||
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode); |
||||
} |
||||
|
||||
if (!alt_keycode) { |
||||
// The following key pairs are considered with any mods.
|
||||
// clang-format off
|
||||
static const uint8_t pairs[][2] PROGMEM = { |
||||
{KC_LEFT, KC_RGHT}, // Left / Right Arrow.
|
||||
{KC_UP , KC_DOWN}, // Up / Down Arrow.
|
||||
{KC_HOME, KC_END }, // Home / End.
|
||||
{KC_PGUP, KC_PGDN}, // Page Up / Page Down.
|
||||
{KC_BSPC, KC_DEL }, // Backspace / Delete.
|
||||
{KC_LBRC, KC_RBRC}, // Brackets [ ] and { }.
|
||||
#ifdef EXTRAKEY_ENABLE |
||||
{KC_WBAK, KC_WFWD}, // Browser Back / Forward.
|
||||
{KC_MNXT, KC_MPRV}, // Next / Previous Media Track.
|
||||
{KC_MFFD, KC_MRWD}, // Fast Forward / Rewind Media.
|
||||
{KC_VOLU, KC_VOLD}, // Volume Up / Down.
|
||||
{KC_BRIU, KC_BRID}, // Brightness Up / Down.
|
||||
#endif // EXTRAKEY_ENABLE
|
||||
#ifdef MOUSEKEY_ENABLE |
||||
{KC_MS_L, KC_MS_R}, // Mouse Cursor Left / Right.
|
||||
{KC_MS_U, KC_MS_D}, // Mouse Cursor Up / Down.
|
||||
{KC_WH_L, KC_WH_R}, // Mouse Wheel Left / Right.
|
||||
{KC_WH_U, KC_WH_D}, // Mouse Wheel Up / Down.
|
||||
#endif // MOUSEKEY_ENABLE
|
||||
}; |
||||
// clang-format on
|
||||
alt_keycode = find_alt_keycode(pairs, sizeof(pairs), keycode); |
||||
} |
||||
|
||||
if (alt_keycode) { |
||||
// Combine basic keycode with mods.
|
||||
return (mods << 8) | alt_keycode; |
||||
} |
||||
} |
||||
|
||||
return KC_NO; // No alternate key found.
|
||||
} |
||||
|
||||
void alt_repeat_key_invoke(const keyevent_t* event) { |
||||
static keyrecord_t registered_record = {0}; |
||||
static int8_t registered_repeat_count = 0; |
||||
// Since this function calls process_record(), it may recursively call
|
||||
// itself. We return early if `processing_repeat_count` is nonzero to
|
||||
// prevent infinite recursion.
|
||||
if (processing_repeat_count) { |
||||
return; |
||||
} |
||||
|
||||
if (event->pressed) { |
||||
registered_record = (keyrecord_t){ |
||||
# ifndef NO_ACTION_TAPPING |
||||
.tap.interrupted = false, |
||||
.tap.count = 0, |
||||
# endif |
||||
.keycode = get_alt_repeat_key_keycode(), |
||||
}; |
||||
} |
||||
|
||||
// Early return if there is no alternate key defined.
|
||||
if (!registered_record.keycode) { |
||||
return; |
||||
} |
||||
|
||||
if (event->pressed) { |
||||
update_last_repeat_count(-1); |
||||
registered_repeat_count = last_repeat_count; |
||||
} |
||||
|
||||
// Generate a keyrecord and plumb it into the event pipeline.
|
||||
registered_record.event = *event; |
||||
processing_repeat_count = registered_repeat_count; |
||||
process_record(®istered_record); |
||||
processing_repeat_count = 0; |
||||
} |
||||
|
||||
// Default implementation of get_alt_repeat_key_keycode_user().
|
||||
__attribute__((weak)) uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
return KC_TRANSPARENT; |
||||
} |
||||
#endif // NO_ALT_REPEAT_KEY
|
@ -0,0 +1,80 @@ |
||||
// Copyright 2022-2023 Google LLC
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// https://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "quantum.h" |
||||
|
||||
uint16_t get_last_keycode(void); /**< Keycode of the last key. */ |
||||
uint8_t get_last_mods(void); /**< Mods active with the last key. */ |
||||
void set_last_keycode(uint16_t keycode); /**< Sets the last key. */ |
||||
void set_last_mods(uint8_t mods); /**< Sets the last mods. */ |
||||
|
||||
/** @brief Gets the record for the last key. */ |
||||
keyrecord_t* get_last_record(void); |
||||
|
||||
/** @brief Sets keycode and record info for the last key. */ |
||||
void set_last_record(uint16_t keycode, keyrecord_t* record); |
||||
|
||||
/**
|
||||
* @brief Signed count of times the key has been repeated or alternate repeated. |
||||
* |
||||
* @note The count is nonzero only while a repeated or alternate-repeated key is |
||||
* being processed. |
||||
* |
||||
* When a key is pressed normally, the count is 0. When the Repeat Key is used |
||||
* to repeat a key, the count is 1 on the first repeat, 2 on the second repeat, |
||||
* and continuing up to 127. |
||||
* |
||||
* Negative counts are used similarly for alternate repeating. When the |
||||
* Alternate Repeat Key is used, the count is -1 on the first alternate repeat, |
||||
* -2 on the second, continuing down to -127. |
||||
*/ |
||||
int8_t get_repeat_key_count(void); |
||||
|
||||
/**
|
||||
* @brief Calls `process_record()` on a generated record repeating the last key. |
||||
* @param event Event information in the generated record. |
||||
*/ |
||||
void repeat_key_invoke(const keyevent_t* event); |
||||
|
||||
#ifndef NO_ALT_REPEAT_KEY |
||||
|
||||
/**
|
||||
* @brief Keycode to be used for alternate repeating. |
||||
* |
||||
* Alternate Repeat performs this keycode based on the last eligible pressed key |
||||
* and mods, get_last_keycode() and get_last_mods(). For example, when the last |
||||
* key was KC_UP, this function returns KC_DOWN. The function returns KC_NO if |
||||
* the last key doesn't have a defined alternate. |
||||
*/ |
||||
uint16_t get_alt_repeat_key_keycode(void); |
||||
|
||||
/**
|
||||
* @brief Calls `process_record()` to alternate repeat the last key. |
||||
* @param event Event information in the generated record. |
||||
*/ |
||||
void alt_repeat_key_invoke(const keyevent_t* event); |
||||
|
||||
/**
|
||||
* @brief Optional user callback to define additional alternate keys. |
||||
* |
||||
* When `get_alt_repeat_key_keycode()` is called, it first calls this callback. |
||||
* It should return a keycode representing the "alternate" of the given keycode |
||||
* and mods. Returning KC_NO defers to the default definitions in |
||||
* `get_alt_repeat_key_keycode()`. |
||||
*/ |
||||
uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods); |
||||
|
||||
#endif // NO_ALT_REPEAT_KEY
|
@ -0,0 +1,18 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "test_common.h" |
@ -0,0 +1,18 @@ |
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
REPEAT_KEY_ENABLE = yes
|
||||
|
||||
EXTRAKEY_ENABLE = yes
|
@ -0,0 +1,523 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <functional> |
||||
|
||||
#include "keyboard_report_util.hpp" |
||||
#include "keycode.h" |
||||
#include "test_common.hpp" |
||||
#include "test_fixture.hpp" |
||||
#include "test_keymap_key.hpp" |
||||
|
||||
using ::testing::AnyNumber; |
||||
using ::testing::InSequence; |
||||
|
||||
namespace { |
||||
|
||||
bool process_record_user_default(uint16_t keycode, keyrecord_t* record) { |
||||
return true; |
||||
} |
||||
|
||||
bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
return true; |
||||
} |
||||
|
||||
uint16_t get_alt_repeat_key_keycode_user_default(uint16_t keycode, uint8_t mods) { |
||||
return KC_TRNS; |
||||
} |
||||
|
||||
// Indirections so that process_record_user() can be replaced with other
|
||||
// functions in the test cases below.
|
||||
std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default; |
||||
std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default; |
||||
std::function<uint16_t(uint16_t, uint8_t)> get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default; |
||||
|
||||
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
return process_record_user_fun(keycode, record); |
||||
} |
||||
|
||||
extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
return remember_last_key_user_fun(keycode, record, remembered_mods); |
||||
} |
||||
|
||||
extern "C" uint16_t get_alt_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) { |
||||
return get_alt_repeat_key_keycode_user_fun(keycode, mods); |
||||
} |
||||
|
||||
class AltRepeatKey : public TestFixture { |
||||
public: |
||||
bool process_record_user_was_called_; |
||||
|
||||
void SetUp() override { |
||||
process_record_user_fun = process_record_user_default; |
||||
remember_last_key_user_fun = remember_last_key_user_default; |
||||
get_alt_repeat_key_keycode_user_fun = get_alt_repeat_key_keycode_user_default; |
||||
} |
||||
|
||||
void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) { |
||||
process_record_user_was_called_ = false; |
||||
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) { |
||||
EXPECT_EQ(record->event.pressed, expected_press); |
||||
EXPECT_KEYCODE_EQ(keycode, expected_keycode); |
||||
EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count); |
||||
// Tests below use this to verify process_record_user() was called.
|
||||
process_record_user_was_called_ = true; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
// Expects that the characters of `s` are sent.
|
||||
// NOTE: This implementation is limited to chars a-z, A-Z.
|
||||
void ExpectString(TestDriver& driver, const std::string& s) { |
||||
InSequence seq; |
||||
for (int c : s) { |
||||
switch (c) { |
||||
case 'a' ... 'z': { // Lowercase letter.
|
||||
uint16_t keycode = c - ('a' - KC_A); |
||||
EXPECT_REPORT(driver, (keycode)); |
||||
} break; |
||||
|
||||
case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
|
||||
uint16_t keycode = c - ('A' - KC_A); |
||||
EXPECT_REPORT(driver, (KC_LSFT, keycode)); |
||||
} break; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
TEST_F(AltRepeatKey, AlternateBasic) { |
||||
TestDriver driver; |
||||
KeymapKey key_bspc(0, 0, 0, KC_BSPC); |
||||
KeymapKey key_pgdn(0, 1, 0, KC_PGDN); |
||||
KeymapKey key_pgup(0, 2, 0, KC_PGUP); |
||||
KeymapKey key_repeat(0, 4, 0, QK_REP); |
||||
KeymapKey key_alt_repeat(0, 5, 0, QK_AREP); |
||||
set_keymap({key_bspc, key_pgdn, key_pgup, key_repeat, key_alt_repeat}); |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_BSPC)); |
||||
EXPECT_REPORT(driver, (KC_DEL)); |
||||
EXPECT_REPORT(driver, (KC_DEL)); |
||||
EXPECT_REPORT(driver, (KC_BSPC)); |
||||
EXPECT_REPORT(driver, (KC_DEL)); |
||||
EXPECT_REPORT(driver, (KC_PGDN)); |
||||
EXPECT_REPORT(driver, (KC_PGUP)); |
||||
EXPECT_REPORT(driver, (KC_PGUP)); |
||||
EXPECT_REPORT(driver, (KC_PGDN)); |
||||
} |
||||
|
||||
tap_key(key_bspc); |
||||
|
||||
for (int n = 1; n <= 2; ++n) { // Tap the Alternate Repeat Key twice.
|
||||
ExpectProcessRecordUserCalledWith(true, KC_DEL, -n); |
||||
key_alt_repeat.press(); // Press the Alternate Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
// Expect the corresponding release event.
|
||||
ExpectProcessRecordUserCalledWith(false, KC_DEL, -n); |
||||
key_alt_repeat.release(); // Release the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
} |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_keys(key_repeat, key_alt_repeat); |
||||
tap_keys(key_pgdn, key_alt_repeat); |
||||
tap_keys(key_pgup, key_alt_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
struct TestParamsAlternateKeyCodes { |
||||
uint16_t keycode; |
||||
uint8_t mods; |
||||
uint16_t expected_alt_keycode; |
||||
}; |
||||
|
||||
// Tests `get_alt_repeat_key_keycode()` for various keycodes.
|
||||
TEST_F(AltRepeatKey, GetAltRepeatKeyKeycode) { |
||||
for (const auto& params : std::vector<TestParamsAlternateKeyCodes>({ |
||||
// clang-format off
|
||||
// Each line tests one call to `get_alt_repeat_key_keycode()`:
|
||||
// {keycode, mods, expected_alt_keycode}.
|
||||
// Arrows.
|
||||
{KC_LEFT, 0, KC_RGHT}, |
||||
{KC_RGHT, 0, KC_LEFT}, |
||||
{KC_LEFT, MOD_BIT(KC_LSFT), LSFT(KC_RGHT)}, |
||||
{KC_LEFT, MOD_BIT(KC_RSFT), RSFT(KC_RGHT)}, |
||||
{KC_LEFT, MOD_BIT(KC_LCTL) | MOD_BIT(KC_LSFT), C(S(KC_RGHT))}, |
||||
{KC_LEFT, MOD_BIT(KC_LGUI), LGUI(KC_RGHT)}, |
||||
{C(KC_LEFT), MOD_BIT(KC_LSFT), C(S(KC_RGHT))}, |
||||
{KC_UP, 0, KC_DOWN}, |
||||
// Navigation keys.
|
||||
{KC_PGUP, 0, KC_PGDN}, |
||||
{KC_HOME, 0, KC_END }, |
||||
// Media keys.
|
||||
{KC_WBAK, 0, KC_WFWD}, |
||||
{KC_MNXT, 0, KC_MPRV}, |
||||
{KC_MRWD, 0, KC_MFFD}, |
||||
{KC_VOLU, 0, KC_VOLD}, |
||||
{KC_BRIU, 0, KC_BRID}, |
||||
// Emacs navigation.
|
||||
{KC_N, MOD_BIT(KC_LCTL), C(KC_P)}, |
||||
{KC_B, MOD_BIT(KC_LCTL), LCTL(KC_F)}, |
||||
{KC_B, MOD_BIT(KC_RCTL), RCTL(KC_F)}, |
||||
{KC_B, MOD_BIT(KC_LALT), LALT(KC_F)}, |
||||
{KC_F, MOD_BIT(KC_LCTL), C(KC_B)}, |
||||
{KC_A, MOD_BIT(KC_LCTL), C(KC_E)}, |
||||
{KC_D, MOD_BIT(KC_LCTL), C(KC_U)}, |
||||
// Vim navigation.
|
||||
{KC_J, 0, KC_K}, |
||||
{KC_K, 0, KC_J}, |
||||
{KC_H, 0, KC_L}, |
||||
{KC_B, 0, KC_W}, |
||||
{KC_W, 0, KC_B}, |
||||
{KC_E, 0, KC_B}, |
||||
{KC_B, MOD_BIT(KC_LSFT), S(KC_W)}, |
||||
{KC_W, MOD_BIT(KC_LSFT), S(KC_B)}, |
||||
{KC_E, MOD_BIT(KC_LSFT), S(KC_B)}, |
||||
{KC_O, MOD_BIT(KC_LCTL), C(KC_I)}, |
||||
{KC_I, MOD_BIT(KC_LCTL), C(KC_O)}, |
||||
// Other.
|
||||
{KC_DEL, 0, KC_BSPC}, |
||||
{KC_LBRC, 0, KC_RBRC}, |
||||
{KC_LCBR, 0, KC_RCBR}, |
||||
// Some keys where the last key is a tap-hold key.
|
||||
{LSFT_T(KC_F), MOD_BIT(KC_RCTL), RCTL(KC_B)}, |
||||
{LT(1, KC_A), MOD_BIT(KC_RGUI), RGUI(KC_E)}, |
||||
{RALT_T(KC_J), 0, KC_K}, |
||||
// Some keys where no alternate is defined.
|
||||
{KC_A, 0, KC_NO}, |
||||
{KC_F1, 0, KC_NO}, |
||||
{QK_LEAD, 0, KC_NO}, |
||||
{MO(1), 0, KC_NO}, |
||||
// clang-format on
|
||||
})) { |
||||
SCOPED_TRACE(std::string("Input keycode: ") + get_keycode_identifier_or_default(params.keycode)); |
||||
set_last_keycode(params.keycode); |
||||
set_last_mods(params.mods); |
||||
|
||||
const uint16_t actual = get_alt_repeat_key_keycode(); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), params.expected_alt_keycode); |
||||
} |
||||
} |
||||
|
||||
// Test adding to and overriding the above through the
|
||||
// `get_alt_repeat_key_keycode_user()` callback.
|
||||
TEST_F(AltRepeatKey, GetAltRepeatKeyKeycodeUser) { |
||||
get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t { |
||||
bool shifted = (mods & MOD_MASK_SHIFT); |
||||
switch (keycode) { |
||||
case KC_LEFT: |
||||
return KC_ENT; |
||||
case MO(1): |
||||
return TG(1); |
||||
case KC_TAB: // Tab <-> Shift + Tab example.
|
||||
if (shifted) { |
||||
return KC_TAB; |
||||
} else { |
||||
return S(KC_TAB); |
||||
} |
||||
} |
||||
|
||||
// Ctrl + Y <-> Ctrl + Z example.
|
||||
if ((mods & MOD_MASK_CTRL)) { |
||||
switch (keycode) { |
||||
case KC_Y: |
||||
return C(KC_Z); |
||||
case KC_Z: |
||||
return C(KC_Y); |
||||
} |
||||
} |
||||
|
||||
return KC_NO; |
||||
}; |
||||
|
||||
set_last_keycode(KC_LEFT); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_ENT); |
||||
|
||||
set_last_keycode(MO(1)); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), TG(1)); |
||||
|
||||
set_last_keycode(KC_TAB); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), S(KC_TAB)); |
||||
|
||||
set_last_keycode(KC_TAB); |
||||
set_last_mods(MOD_BIT(KC_LSFT)); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_TAB); |
||||
|
||||
set_last_keycode(KC_Z); |
||||
set_last_mods(MOD_BIT(KC_LCTL)); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Y)); |
||||
|
||||
set_last_keycode(KC_Y); |
||||
set_last_mods(MOD_BIT(KC_LCTL)); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), C(KC_Z)); |
||||
} |
||||
|
||||
// Tests rolling from a key to Alternate Repeat.
|
||||
TEST_F(AltRepeatKey, RollingToAltRepeat) { |
||||
TestDriver driver; |
||||
KeymapKey key_left(0, 0, 0, KC_LEFT); |
||||
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP); |
||||
set_keymap({key_left, key_alt_repeat}); |
||||
|
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LEFT, KC_RGHT)); |
||||
EXPECT_REPORT(driver, (KC_RGHT)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_RGHT)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
} |
||||
|
||||
// Perform a rolled press from Left to Alternate Repeat.
|
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_LEFT, 0); |
||||
key_left.press(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1); |
||||
key_alt_repeat.press(); // Press the Alternate Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_LEFT, 0); |
||||
key_left.release(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1); |
||||
key_alt_repeat.release(); // Release the Alternate Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_key(key_alt_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests rolling from Alternate Repeat to another key.
|
||||
TEST_F(AltRepeatKey, RollingFromAltRepeat) { |
||||
TestDriver driver; |
||||
KeymapKey key_left(0, 0, 0, KC_LEFT); |
||||
KeymapKey key_up(0, 1, 0, KC_UP); |
||||
KeymapKey key_alt_repeat(0, 2, 0, QK_AREP); |
||||
set_keymap({key_left, key_up, key_alt_repeat}); |
||||
|
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_LEFT)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_RGHT)); |
||||
EXPECT_REPORT(driver, (KC_RGHT, KC_UP)); |
||||
EXPECT_REPORT(driver, (KC_UP)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_DOWN)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
} |
||||
|
||||
tap_key(key_left); |
||||
|
||||
// Perform a rolled press from Alternate Repeat to Up.
|
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_RGHT, -1); |
||||
key_alt_repeat.press(); // Press the Alternate Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_UP, 0); |
||||
key_up.press(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_UP); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_RGHT, -1); |
||||
key_alt_repeat.release(); // Release the Alternate Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_UP, 0); |
||||
key_up.release(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_key(key_alt_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests using the Alternate Repeat Key on a macro that doesn't have an
|
||||
// alternate keycode defined.
|
||||
TEST_F(AltRepeatKey, AlternateUnsupportedMacro) { |
||||
TestDriver driver; |
||||
KeymapKey key_foo(0, 0, 0, QK_USER_0); |
||||
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP); |
||||
set_keymap({key_foo, key_alt_repeat}); |
||||
|
||||
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) { |
||||
process_record_user_was_called_ = true; |
||||
switch (keycode) { |
||||
case QK_USER_0: |
||||
if (record->event.pressed) { |
||||
SEND_STRING("foo"); |
||||
} |
||||
break; |
||||
} |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "foofoo"); |
||||
|
||||
process_record_user_was_called_ = false; |
||||
tap_key(key_foo); |
||||
|
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), QK_USER_0); |
||||
EXPECT_KEYCODE_EQ(get_alt_repeat_key_keycode(), KC_NO); |
||||
|
||||
process_record_user_was_called_ = false; |
||||
key_alt_repeat.press(); // Press Alternate Repeat.
|
||||
run_one_scan_loop(); |
||||
|
||||
EXPECT_FALSE(process_record_user_was_called_); |
||||
|
||||
process_record_user_was_called_ = false; |
||||
key_alt_repeat.release(); // Release Alternate Repeat.
|
||||
run_one_scan_loop(); |
||||
|
||||
EXPECT_FALSE(process_record_user_was_called_); |
||||
|
||||
process_record_user_was_called_ = false; |
||||
tap_key(key_foo); |
||||
|
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests a macro with custom alternate behavior.
|
||||
TEST_F(AltRepeatKey, MacroCustomAlternate) { |
||||
TestDriver driver; |
||||
KeymapKey key_foo(0, 0, 0, QK_USER_0); |
||||
KeymapKey key_alt_repeat(0, 1, 0, QK_AREP); |
||||
set_keymap({key_foo, key_alt_repeat}); |
||||
|
||||
get_alt_repeat_key_keycode_user_fun = [](uint16_t keycode, uint8_t mods) -> uint16_t { |
||||
switch (keycode) { |
||||
case QK_USER_0: |
||||
return QK_USER_0; // QK_USER_0 handles its own alternate.
|
||||
default: |
||||
return KC_NO; // No key by default.
|
||||
} |
||||
}; |
||||
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) { |
||||
process_record_user_was_called_ = true; |
||||
switch (keycode) { |
||||
case QK_USER_0: |
||||
if (record->event.pressed) { |
||||
if (get_repeat_key_count() >= 0) { |
||||
SEND_STRING("foo"); |
||||
} else { // Key is being alternate repeated.
|
||||
SEND_STRING("bar"); |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "foobarbar"); |
||||
|
||||
tap_keys(key_foo, key_alt_repeat, key_alt_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests the Additional "Alternate" keys example from the documentation page.
|
||||
TEST_F(AltRepeatKey, AdditionalAlternateKeysExample) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_w(0, 1, 0, KC_W); |
||||
KeymapKey key_altrep2(0, 2, 0, QK_USER_0); |
||||
KeymapKey key_altrep3(0, 3, 0, QK_USER_1); |
||||
set_keymap({key_a, key_w, key_altrep2, key_altrep3}); |
||||
|
||||
remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
switch (keycode) { |
||||
case QK_USER_0: |
||||
case QK_USER_1: |
||||
return false; // Ignore ALTREP keys.
|
||||
} |
||||
return true; // Other keys can be repeated.
|
||||
}; |
||||
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case QK_USER_0: |
||||
if (record->event.pressed) { |
||||
const uint16_t last_key = get_last_keycode(); |
||||
switch (last_key) { |
||||
case KC_A: |
||||
SEND_STRING(/*a*/ "tion"); |
||||
break; |
||||
case KC_W: |
||||
SEND_STRING(/*w*/ "hich"); |
||||
break; |
||||
} |
||||
} |
||||
return false; |
||||
case QK_USER_1: |
||||
if (record->event.pressed) { |
||||
const uint16_t last_key = get_last_keycode(); |
||||
switch (last_key) { |
||||
case KC_A: |
||||
SEND_STRING(/*a*/ "bout"); |
||||
break; |
||||
case KC_W: |
||||
SEND_STRING(/*w*/ "ould"); |
||||
break; |
||||
} |
||||
} |
||||
return false; |
||||
} |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "ationwhichaboutwould"); |
||||
|
||||
tap_keys(key_a, key_altrep2, key_w, key_altrep2); |
||||
tap_keys(key_a, key_altrep3, key_w, key_altrep3); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
} // namespace
|
@ -0,0 +1,20 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "test_common.h" |
||||
|
||||
#define NO_ALT_REPEAT_KEY |
@ -0,0 +1,18 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#pragma once |
||||
|
||||
#include "test_common.h" |
@ -0,0 +1,18 @@ |
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
REPEAT_KEY_ENABLE = yes
|
||||
|
||||
COMBO_ENABLE = yes
|
@ -0,0 +1,67 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include "keyboard_report_util.hpp" |
||||
#include "keycode.h" |
||||
#include "test_common.hpp" |
||||
#include "test_fixture.hpp" |
||||
#include "test_keymap_key.hpp" |
||||
|
||||
using ::testing::AnyNumber; |
||||
using ::testing::InSequence; |
||||
|
||||
namespace { |
||||
|
||||
extern "C" { |
||||
// Define a combo: KC_X + KC_Y = KC_Q.
|
||||
const uint16_t xy_combo[] PROGMEM = {KC_X, KC_Y, COMBO_END}; |
||||
combo_t key_combos[] = {COMBO(xy_combo, KC_Q)}; |
||||
uint16_t COMBO_LEN = sizeof(key_combos) / sizeof(*key_combos); |
||||
} // extern "C"
|
||||
|
||||
class RepeatKey : public TestFixture {}; |
||||
|
||||
// Tests repeating a combo, KC_X + KC_Y = KC_Q, by typing
|
||||
// "X, Repeat, Repeat, {X Y}, Repeat, Repeat". This produces "xxxqqq".
|
||||
TEST_F(RepeatKey, Combo) { |
||||
TestDriver driver; |
||||
KeymapKey key_x(0, 0, 0, KC_X); |
||||
KeymapKey key_y(0, 1, 0, KC_Y); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_x, key_y, key_repeat}); |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_X)); |
||||
EXPECT_REPORT(driver, (KC_X)); |
||||
EXPECT_REPORT(driver, (KC_X)); |
||||
EXPECT_REPORT(driver, (KC_Q)); |
||||
EXPECT_REPORT(driver, (KC_Q)); |
||||
EXPECT_REPORT(driver, (KC_Q)); |
||||
} |
||||
|
||||
tap_keys(key_x, key_repeat, key_repeat); |
||||
tap_combo({key_x, key_y}); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_Q); |
||||
|
||||
tap_keys(key_repeat, key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
} // namespace
|
@ -0,0 +1,18 @@ |
||||
# Copyright 2023 Google LLC
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 2 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
REPEAT_KEY_ENABLE = yes
|
||||
|
||||
AUTO_SHIFT_ENABLE = yes
|
@ -0,0 +1,754 @@ |
||||
// Copyright 2023 Google LLC
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 2 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
#include <functional> |
||||
|
||||
#include "keyboard_report_util.hpp" |
||||
#include "keycode.h" |
||||
#include "test_common.hpp" |
||||
#include "test_fixture.hpp" |
||||
#include "test_keymap_key.hpp" |
||||
|
||||
using ::testing::AnyNumber; |
||||
using ::testing::AnyOf; |
||||
using ::testing::InSequence; |
||||
|
||||
#define FOO_MACRO SAFE_RANGE |
||||
|
||||
namespace { |
||||
|
||||
bool process_record_user_default(uint16_t keycode, keyrecord_t* record) { |
||||
return true; |
||||
} |
||||
|
||||
bool remember_last_key_user_default(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
return true; |
||||
} |
||||
|
||||
// Indirection so that process_record_user() and remember_last_key_user()
|
||||
// can be replaced with other functions in the test cases below.
|
||||
std::function<bool(uint16_t, keyrecord_t*)> process_record_user_fun = process_record_user_default; |
||||
std::function<bool(uint16_t, keyrecord_t*, uint8_t*)> remember_last_key_user_fun = remember_last_key_user_default; |
||||
|
||||
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t* record) { |
||||
return process_record_user_fun(keycode, record); |
||||
} |
||||
extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
return remember_last_key_user_fun(keycode, record, remembered_mods); |
||||
} |
||||
|
||||
class RepeatKey : public TestFixture { |
||||
public: |
||||
bool process_record_user_was_called_; |
||||
|
||||
void SetUp() override { |
||||
autoshift_disable(); |
||||
process_record_user_fun = process_record_user_default; |
||||
remember_last_key_user_fun = remember_last_key_user_default; |
||||
} |
||||
|
||||
void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) { |
||||
process_record_user_was_called_ = false; |
||||
process_record_user_fun = [=](uint16_t keycode, keyrecord_t* record) { |
||||
EXPECT_EQ(record->event.pressed, expected_press); |
||||
EXPECT_KEYCODE_EQ(keycode, expected_keycode); |
||||
EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count); |
||||
// Tests below use this to verify process_record_user() was called.
|
||||
process_record_user_was_called_ = true; |
||||
return true; |
||||
}; |
||||
} |
||||
|
||||
// Expects that the characters of `s` are sent.
|
||||
// NOTE: This implementation is limited to chars a-z, A-Z.
|
||||
void ExpectString(TestDriver& driver, const std::string& s) { |
||||
InSequence seq; |
||||
for (int c : s) { |
||||
switch (c) { |
||||
case 'a' ... 'z': { // Lowercase letter.
|
||||
uint16_t keycode = c - ('a' - KC_A); |
||||
EXPECT_REPORT(driver, (keycode)); |
||||
} break; |
||||
|
||||
case 'A' ... 'Z': { // Capital letter = KC_LSFT + letter key.
|
||||
uint16_t keycode = c - ('A' - KC_A); |
||||
EXPECT_REPORT(driver, (KC_LSFT, keycode)); |
||||
} break; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
// Tests that "A, Repeat, Repeat, B, Repeat" produces "aaabb".
|
||||
TEST_F(RepeatKey, Basic) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_b(0, 1, 0, KC_B); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_a, key_b, key_repeat}); |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "aaabb"); |
||||
|
||||
// When KC_A is pressed, process_record_user() should be called
|
||||
// with a press event with keycode == KC_A and repeat_key_count() == 0.
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, 0); |
||||
key_a.press(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
// After pressing A, the keycode of the key to be repeated is KC_A.
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A); |
||||
EXPECT_EQ(get_last_mods(), 0); |
||||
|
||||
// Expect the corresponding release event when A is released.
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, 0); |
||||
key_a.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
|
||||
// When Repeat is pressed, process_record_user() should be called with a
|
||||
// press event with keycode == KC_A and repeat_key_count() == n.
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, n); |
||||
key_repeat.press(); // Press the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
// Expect the corresponding release event.
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, n); |
||||
key_repeat.release(); // Release the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
} |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_key(key_b); |
||||
// Then after tapping key_b, the keycode to be repeated becomes KC_B.
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B); |
||||
|
||||
tap_key(key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests repeating a macro. The keycode FOO_MACRO sends "foo" when pressed. The
|
||||
// test taps "FOO_MACRO, Repeat, Repeat", producing "foofoofoo".
|
||||
TEST_F(RepeatKey, Macro) { |
||||
TestDriver driver; |
||||
KeymapKey key_foo(0, 0, 0, FOO_MACRO); |
||||
KeymapKey key_repeat(0, 1, 0, QK_REP); |
||||
set_keymap({key_foo, key_repeat}); |
||||
|
||||
// Define process_record_user() to handle FOO_MACRO.
|
||||
process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case FOO_MACRO: |
||||
if (record->event.pressed) { |
||||
SEND_STRING("foo"); |
||||
} |
||||
break; |
||||
} |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "foofoofoo"); |
||||
|
||||
tap_key(key_foo); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO); |
||||
|
||||
tap_keys(key_repeat, key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests a macro with customized repeat behavior: "foo" is sent normally, "bar"
|
||||
// on the first repeat, and "baz" on subsequent repeats. The test taps
|
||||
// "FOO_MACRO, Repeat, Repeat, FOO_MACRO, Repeat", producing "foobarbazfoobar".
|
||||
TEST_F(RepeatKey, MacroCustomRepeat) { |
||||
TestDriver driver; |
||||
KeymapKey key_foo(0, 0, 0, FOO_MACRO); |
||||
KeymapKey key_repeat(0, 1, 0, QK_REP); |
||||
set_keymap({key_foo, key_repeat}); |
||||
|
||||
process_record_user_fun = [](uint16_t keycode, keyrecord_t* record) { |
||||
switch (keycode) { |
||||
case FOO_MACRO: |
||||
if (record->event.pressed) { |
||||
switch (get_repeat_key_count()) { |
||||
case 0: // When pressed normally.
|
||||
SEND_STRING("foo"); |
||||
break; |
||||
case 1: // On first repeat.
|
||||
SEND_STRING("bar"); |
||||
break; |
||||
default: // On subsequent repeats.
|
||||
SEND_STRING("baz"); |
||||
break; |
||||
} |
||||
} |
||||
break; |
||||
} |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "foobarbazfoobar"); |
||||
|
||||
tap_key(key_foo); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), FOO_MACRO); |
||||
|
||||
tap_keys(key_repeat, key_repeat, key_foo, key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests repeating keys on different layers. A 2-layer keymap is defined:
|
||||
// Layer 0: QK_REP , MO(1) , KC_A
|
||||
// Layer 1: KC_TRNS, KC_TRNS, KC_B
|
||||
// The test does the following, which should produce "bbbaaa":
|
||||
// 1. Hold MO(1), switching to layer 1.
|
||||
// 2. Tap KC_B on layer 1.
|
||||
// 3. Release MO(1), switching back to layer 0.
|
||||
// 4. Tap Repeat twice.
|
||||
// 5. Tap KC_A on layer 0.
|
||||
// 6. Hold MO(1), switching to layer 1.
|
||||
// 7. Tap Repeat twice.
|
||||
TEST_F(RepeatKey, AcrossLayers) { |
||||
TestDriver driver; |
||||
KeymapKey key_repeat(0, 0, 0, QK_REP); |
||||
KeymapKey key_mo_1(0, 1, 0, MO(1)); |
||||
KeymapKey regular_key(0, 2, 0, KC_A); |
||||
set_keymap({// Layer 0.
|
||||
key_repeat, key_mo_1, regular_key, |
||||
// Layer 1.
|
||||
KeymapKey{1, 0, 0, KC_TRNS}, KeymapKey{1, 1, 0, KC_TRNS}, KeymapKey{1, 2, 0, KC_B}}); |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "bbbaaa"); |
||||
|
||||
key_mo_1.press(); // Hold the MO(1) layer key.
|
||||
run_one_scan_loop(); |
||||
tap_key(regular_key); // Taps the KC_B key on layer 1.
|
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B); |
||||
|
||||
key_mo_1.release(); // Release the layer key.
|
||||
run_one_scan_loop(); |
||||
tap_keys(key_repeat, key_repeat); |
||||
tap_key(regular_key); // Taps the KC_A key on layer 0.
|
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A); |
||||
|
||||
key_mo_1.press(); // Hold the layer key.
|
||||
run_one_scan_loop(); |
||||
tap_keys(key_repeat, key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests "A(down), Repeat(down), A(up), Repeat(up), Repeat" produces "aaa".
|
||||
TEST_F(RepeatKey, RollingToRepeat) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_repeat(0, 1, 0, QK_REP); |
||||
set_keymap({key_a, key_repeat}); |
||||
|
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
} |
||||
|
||||
// Perform a rolled press from A to Repeat.
|
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, 0); |
||||
key_a.press(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, 1); |
||||
key_repeat.press(); // Press the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, 0); |
||||
key_a.release(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, 1); |
||||
key_repeat.release(); // Release the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_key(key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests "A, Repeat(down), B(down), Repeat(up), B(up), Repeat" produces "aabb".
|
||||
TEST_F(RepeatKey, RollingFromRepeat) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_b(0, 1, 0, KC_B); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_a, key_b, key_repeat}); |
||||
|
||||
{ |
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_REPORT(driver, (KC_A, KC_B)); |
||||
EXPECT_REPORT(driver, (KC_B)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
EXPECT_REPORT(driver, (KC_B)); |
||||
EXPECT_EMPTY_REPORT(driver); |
||||
} |
||||
|
||||
tap_key(key_a); |
||||
|
||||
// Perform a rolled press from Repeat to B.
|
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, 1); |
||||
key_repeat.press(); // Press the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(true, KC_B, 0); |
||||
key_b.press(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, 1); |
||||
key_repeat.release(); // Release the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
ExpectProcessRecordUserCalledWith(false, KC_B, 0); |
||||
key_b.release(); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
tap_key(key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests Repeat Key with a modifier, types "AltGr+C, Repeat, Repeat, C".
|
||||
TEST_F(RepeatKey, RecallMods) { |
||||
TestDriver driver; |
||||
KeymapKey key_c(0, 0, 0, KC_C); |
||||
KeymapKey key_altgr(0, 1, 0, KC_RALT); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_c, key_altgr, key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only KC_RALT.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_RALT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
|
||||
{ // Expect: "AltGr+C, AltGr+C, AltGr+C, C".
|
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_RALT, KC_C)); |
||||
EXPECT_REPORT(driver, (KC_RALT, KC_C)); |
||||
EXPECT_REPORT(driver, (KC_RALT, KC_C)); |
||||
EXPECT_REPORT(driver, (KC_C)); |
||||
} |
||||
|
||||
key_altgr.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_c); |
||||
key_altgr.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_C); |
||||
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_RALT)); |
||||
|
||||
tap_keys(key_repeat, key_repeat, key_c); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests that Repeat Key stacks mods, types
|
||||
// "Ctrl+Left, Repeat, Shift+Repeat, Shift+Repeat, Repeat, Left".
|
||||
TEST_F(RepeatKey, StackMods) { |
||||
TestDriver driver; |
||||
KeymapKey key_left(0, 0, 0, KC_LEFT); |
||||
KeymapKey key_shift(0, 1, 0, KC_LSFT); |
||||
KeymapKey key_ctrl(0, 2, 0, KC_LCTL); |
||||
KeymapKey key_repeat(0, 3, 0, QK_REP); |
||||
set_keymap({key_left, key_shift, key_ctrl, key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only mods.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LCTL), |
||||
KeyboardReport(KC_LSFT), |
||||
KeyboardReport(KC_LCTL, KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
|
||||
{ // Expect: "Ctrl+Left, Ctrl+Shift+Left".
|
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LEFT)); |
||||
EXPECT_REPORT(driver, (KC_LEFT)); |
||||
} |
||||
|
||||
key_ctrl.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_left); |
||||
run_one_scan_loop(); |
||||
key_ctrl.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_LEFT); |
||||
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL)); |
||||
|
||||
tap_key(key_repeat); |
||||
|
||||
key_shift.press(); |
||||
run_one_scan_loop(); |
||||
tap_keys(key_repeat, key_repeat); |
||||
key_shift.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL)); |
||||
|
||||
tap_keys(key_repeat, key_left); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Types: "S(KC_1), Repeat, Ctrl+Repeat, Ctrl+Repeat, Repeat, KC_2".
|
||||
TEST_F(RepeatKey, ShiftedKeycode) { |
||||
TestDriver driver; |
||||
KeymapKey key_exlm(0, 0, 0, S(KC_1)); |
||||
KeymapKey key_2(0, 1, 0, KC_2); |
||||
KeymapKey key_ctrl(0, 2, 0, KC_LCTL); |
||||
KeymapKey key_repeat(0, 3, 0, QK_REP); |
||||
set_keymap({key_exlm, key_2, key_ctrl, key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only mods.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LCTL), |
||||
KeyboardReport(KC_LSFT), |
||||
KeyboardReport(KC_LCTL, KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
|
||||
{ // Expect: "Shift+1, Shift+1, Ctrl+Shift+1, Ctrl+Shift+1, Shift+1, 2".
|
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_LSFT, KC_1)); |
||||
EXPECT_REPORT(driver, (KC_LSFT, KC_1)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_1)); |
||||
EXPECT_REPORT(driver, (KC_LSFT, KC_1)); |
||||
EXPECT_REPORT(driver, (KC_2)); |
||||
} |
||||
|
||||
tap_key(key_exlm); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), S(KC_1)); |
||||
|
||||
tap_key(key_repeat); |
||||
|
||||
key_ctrl.press(); |
||||
run_one_scan_loop(); |
||||
tap_keys(key_repeat, key_repeat); |
||||
key_ctrl.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
tap_keys(key_repeat, key_2); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests Repeat Key with a one-shot Shift, types
|
||||
// "A, OSM(MOD_LSFT), Repeat, Repeat".
|
||||
TEST_F(RepeatKey, WithOneShotShift) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_oneshot_shift(0, 1, 0, OSM(MOD_LSFT)); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_a, key_oneshot_shift, key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only KC_RALT.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
ExpectString(driver, "aAa"); |
||||
|
||||
tap_keys(key_a, key_oneshot_shift, key_repeat, key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests Repeat Key with a mod-tap key, types
|
||||
// "A, Repeat, Repeat, A(down), Repeat, Repeat, A(up), Repeat".
|
||||
TEST_F(RepeatKey, ModTap) { |
||||
TestDriver driver; |
||||
KeymapKey key_mt_a(0, 0, 0, LSFT_T(KC_A)); |
||||
KeymapKey key_repeat(0, 1, 0, QK_REP); |
||||
set_keymap({key_mt_a, key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only KC_LSFT.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
ExpectString(driver, "aaaAAa"); |
||||
|
||||
tap_key(key_mt_a); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), LSFT_T(KC_A)); |
||||
|
||||
tap_keys(key_repeat, key_repeat); |
||||
key_mt_a.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_repeat, TAPPING_TERM + 1); |
||||
tap_key(key_repeat); |
||||
key_mt_a.release(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_repeat); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests with Auto Shift. When repeating an autoshiftable key, it does not
|
||||
// matter how long the original key was held, rather, quickly tapping vs.
|
||||
// long-pressing the Repeat Key determines whether the shifted key is repeated.
|
||||
//
|
||||
// The test does the following, which should produce "aaABbB":
|
||||
// 1. Tap KC_A quickly.
|
||||
// 2. Tap Repeat Key quickly.
|
||||
// 3. Long-press Repeat Key.
|
||||
// 4. Long-press KC_B.
|
||||
// 5. Tap Repeat Key quickly.
|
||||
// 6. Long-press Repeat Key.
|
||||
TEST_F(RepeatKey, AutoShift) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_b(0, 1, 0, KC_B); |
||||
KeymapKey key_repeat(0, 2, 0, QK_REP); |
||||
set_keymap({key_a, key_b, key_repeat}); |
||||
|
||||
autoshift_enable(); |
||||
|
||||
// Allow any number of reports with no keys or only KC_LSFT.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
ExpectString(driver, "aaABbB"); |
||||
|
||||
tap_key(key_a); // Tap A quickly.
|
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A); |
||||
EXPECT_EQ(get_last_mods(), 0); |
||||
|
||||
tap_key(key_repeat); |
||||
tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1); |
||||
|
||||
tap_key(key_b, AUTO_SHIFT_TIMEOUT + 1); // Long press B.
|
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_B); |
||||
EXPECT_EQ(get_last_mods(), 0); |
||||
|
||||
tap_key(key_repeat); |
||||
tap_key(key_repeat, AUTO_SHIFT_TIMEOUT + 1); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Defines `remember_last_key_user()` to forget the Shift mod and types:
|
||||
// "Ctrl+A, Repeat, Shift+A, Repeat, Shift+Repeat".
|
||||
TEST_F(RepeatKey, FilterRememberedMods) { |
||||
TestDriver driver; |
||||
KeymapKey key_a(0, 0, 0, KC_A); |
||||
KeymapKey key_ctrl(0, 1, 0, KC_LCTL); |
||||
KeymapKey key_shift(0, 2, 0, KC_LSFT); |
||||
KeymapKey key_repeat(0, 3, 0, QK_REP); |
||||
set_keymap({key_a, key_ctrl, key_shift, key_repeat}); |
||||
|
||||
remember_last_key_user_fun = [](uint16_t keycode, keyrecord_t* record, uint8_t* remembered_mods) { |
||||
*remembered_mods &= ~MOD_MASK_SHIFT; |
||||
return true; |
||||
}; |
||||
|
||||
// Allow any number of reports with no keys or only mods.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LCTL), |
||||
KeyboardReport(KC_LSFT), |
||||
KeyboardReport(KC_LCTL, KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
|
||||
{ // Expect: "Ctrl+A, Ctrl+A, Shift+A, A, Shift+A".
|
||||
InSequence seq; |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_A)); |
||||
EXPECT_REPORT(driver, (KC_LCTL, KC_A)); |
||||
EXPECT_REPORT(driver, (KC_LSFT, KC_A)); |
||||
EXPECT_REPORT(driver, (KC_A)); |
||||
EXPECT_REPORT(driver, (KC_LSFT, KC_A)); |
||||
} |
||||
|
||||
key_ctrl.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_a); |
||||
|
||||
EXPECT_EQ(get_last_mods(), MOD_BIT(KC_LCTL)); |
||||
|
||||
key_ctrl.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
tap_key(key_repeat); |
||||
key_shift.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_a); |
||||
|
||||
EXPECT_EQ(get_last_mods(), 0); // Shift should be forgotten.
|
||||
|
||||
key_shift.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
tap_key(key_repeat); |
||||
|
||||
key_shift.press(); |
||||
run_one_scan_loop(); |
||||
tap_key(key_repeat); |
||||
key_shift.release(); |
||||
run_one_scan_loop(); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests set_last_keycode() and set_last_mods().
|
||||
TEST_F(RepeatKey, SetRepeatKeyKeycode) { |
||||
TestDriver driver; |
||||
KeymapKey key_repeat(0, 0, 0, QK_REP); |
||||
set_keymap({key_repeat}); |
||||
|
||||
// Allow any number of reports with no keys or only KC_LSFT.
|
||||
// clang-format off
|
||||
EXPECT_CALL(driver, send_keyboard_mock(AnyOf( |
||||
KeyboardReport(), |
||||
KeyboardReport(KC_LSFT)))) |
||||
.Times(AnyNumber()); |
||||
// clang-format on
|
||||
ExpectString(driver, "aaBB"); |
||||
|
||||
set_last_keycode(KC_A); |
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_A); |
||||
|
||||
for (int n = 1; n <= 2; ++n) { // Tap the Repeat Key twice.
|
||||
// When Repeat is pressed, process_record_user() should be called with a
|
||||
// press event with keycode == KC_A and repeat_key_count() == n.
|
||||
ExpectProcessRecordUserCalledWith(true, KC_A, n); |
||||
key_repeat.press(); // Press the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
// Expect the corresponding release event.
|
||||
ExpectProcessRecordUserCalledWith(false, KC_A, n); |
||||
key_repeat.release(); // Release the Repeat Key.
|
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
} |
||||
|
||||
process_record_user_fun = process_record_user_default; |
||||
set_last_keycode(KC_B); |
||||
set_last_mods(MOD_BIT(KC_LSFT)); |
||||
|
||||
tap_keys(key_repeat, key_repeat); |
||||
|
||||
set_last_keycode(KC_NO); |
||||
tap_keys(key_repeat, key_repeat); // Has no effect.
|
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
// Tests the `repeat_key_invoke()` function.
|
||||
TEST_F(RepeatKey, RepeatKeyInvoke) { |
||||
TestDriver driver; |
||||
KeymapKey key_s(0, 0, 0, KC_S); |
||||
set_keymap({key_s}); |
||||
|
||||
// Allow any number of empty reports.
|
||||
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); |
||||
ExpectString(driver, "ss"); |
||||
|
||||
tap_key(key_s); |
||||
|
||||
EXPECT_KEYCODE_EQ(get_last_keycode(), KC_S); |
||||
|
||||
// Calling repeat_key_invoke() should result in process_record_user()
|
||||
// getting a press event with keycode KC_S.
|
||||
ExpectProcessRecordUserCalledWith(true, KC_S, 1); |
||||
keyevent_t event; |
||||
event.key = {0, 0}; |
||||
event.pressed = true; |
||||
event.time = timer_read(); |
||||
event.type = KEY_EVENT; |
||||
repeat_key_invoke(&event); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
// Make the release event.
|
||||
ExpectProcessRecordUserCalledWith(false, KC_S, 1); |
||||
event.pressed = false; |
||||
event.time = timer_read(); |
||||
repeat_key_invoke(&event); |
||||
run_one_scan_loop(); |
||||
EXPECT_TRUE(process_record_user_was_called_); |
||||
|
||||
testing::Mock::VerifyAndClearExpectations(&driver); |
||||
} |
||||
|
||||
} // namespace
|
Loading…
Reference in new issue