Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Core] Add Repeat Key ("repeat last key") as a core feature. #19700

Merged
merged 47 commits into from
May 20, 2023
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
72ebb0e
Add Repeat Key as a core QMK feature.
getreuer Jan 21, 2023
e7027bf
Add Repeat Key + Combo test and minor formatting.
getreuer Jan 21, 2023
4beacae
Add .hjson defs for QK_REPEAT_KEY.
getreuer Jan 21, 2023
0558fe1
Fix Repeat Key behavior on rolled presses.
getreuer Jan 21, 2023
f9c6fcd
Add Reverse Repeat Key.
getreuer Jan 25, 2023
bc6c00e
Minor edits and elaborations.
getreuer Jan 25, 2023
3c3c5a9
Merge branch 'develop' into core/repeat_key
getreuer Jan 25, 2023
e31690f
A few doc tweaks.
getreuer Jan 26, 2023
d43defc
Use IS_SWAP_HANDS_KEYCODE.
getreuer Jan 27, 2023
ea04ff4
Fix a couple indents.
getreuer Jan 28, 2023
3e01585
Fix code formatting.
getreuer Jan 28, 2023
5ed45d0
Minor doc tweak.
getreuer Jan 28, 2023
a93a533
Remove accidental self-referential #include.
getreuer Feb 3, 2023
b593e90
Run `qmk generate-keycodes-tests -v latest`
getreuer Feb 11, 2023
2814d13
Run `util/regen.sh` to try to pass check
getreuer Feb 11, 2023
2e254f3
Merge branch 'develop' into core/repeat_key
getreuer Feb 13, 2023
bd4c09a
Rename Reverse Repeat -> Alternate Repeat.
getreuer Feb 20, 2023
d9bd088
Merge branch 'develop' into core/repeat_key
getreuer Feb 24, 2023
3c52ad3
Merge branch 'develop' into core/repeat_key
getreuer Feb 28, 2023
d948882
Update for QMK 0.20, bump to keycodes_0.0.3.
getreuer Feb 28, 2023
5a66c54
Merge branch 'develop' into core/repeat_key
getreuer Mar 4, 2023
f558b99
Add Repeat Key to keyboard.jsonschema.
getreuer Mar 5, 2023
49bb750
Remove trailing comma.
getreuer Mar 5, 2023
3c4643d
Remove trailing comma.
getreuer Mar 5, 2023
69babda
Minor doc edits.
getreuer Mar 6, 2023
c40b31e
Merge branch 'develop' into core/repeat_key
getreuer Mar 17, 2023
bd082df
Merge branch 'develop' into core/repeat_key
getreuer Mar 25, 2023
ab21d08
Add Tri Layer keys to default get_repeat_key_eligible().
getreuer Mar 30, 2023
d5b4970
Revise with a "get_repeat_key_eligible_user()".
getreuer Mar 30, 2023
30d0cbe
Format code.
getreuer Mar 30, 2023
15d1d50
Add more Alt Repeat examples to the docs.
getreuer Mar 30, 2023
620b047
Update data/constants/keycodes/keycodes_0.0.2_quantum.hjson
getreuer Mar 31, 2023
a0c5461
Update quantum/repeat_key.c
getreuer Mar 31, 2023
5d74f09
Update quantum/repeat_key.c
getreuer Mar 31, 2023
20bfde4
Alt repeat: use KC_TRNS to defer to defaults.
getreuer Mar 31, 2023
86ac71f
`remembered_mods` arg to enable fitering the mods.
getreuer Mar 31, 2023
ac83f21
Merge branch 'develop' into core/repeat_key
getreuer Apr 7, 2023
8ad9259
Fix test, set event.type.
getreuer Apr 8, 2023
6cb20d4
Add default alt keys for Vim jumplist.
getreuer Apr 14, 2023
85947f5
Merge branch 'develop' into core/repeat_key
getreuer Apr 21, 2023
96c6eb4
Merge branch 'develop' into core/repeat_key
getreuer May 7, 2023
3353606
Documentation fix, updated fun signature.
getreuer May 7, 2023
5525497
Add test of additional alt keys example.
getreuer May 7, 2023
12d58e8
Merge branch 'develop' into core/repeat_key
getreuer May 12, 2023
f4fc703
Make last key logic more separate from repeating.
getreuer May 14, 2023
1f8e074
Merge branch 'develop' into core/repeat_key
getreuer May 14, 2023
c90b285
Fix code formatting.
getreuer May 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions builddefs/generic_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ GENERIC_FEATURES = \
KEY_OVERRIDE \
LEADER \
PROGRAMMABLE_BUTTON \
REPEAT_KEY \
SECURE \
SPACE_CADET \
SWAP_HANDS \
Expand Down
3 changes: 2 additions & 1 deletion builddefs/show_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,8 @@ OTHER_OPTION_NAMES = \
PROGRAMMABLE_BUTTON_ENABLE \
SECURE_ENABLE \
CAPS_WORD_ENABLE \
AUTOCORRECT_ENABLE
AUTOCORRECT_ENABLE \
REPEAT_KEY_ENABLE

define NAME_ECHO
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
Expand Down
Empty file.
18 changes: 18 additions & 0 deletions data/constants/keycodes/keycodes_0.0.2_quantum.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
{
"keycodes": {
"0x7C77": {
"group": "quantum",
"key": "QK_REPEAT_KEY",
"aliases": [
"QK_REP"
]
}
"0x7C78": {
"group": "quantum",
"key": "QK_REVERSE_REPEAT_KEY",
"aliases": [
"QK_RREP"
]
}
}
}
1 change: 1 addition & 0 deletions data/mappings/info_rules.hjson
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
"MOUSEKEY_ENABLE": {"info_key": "mouse_key.enabled", "value_type": "bool"},
"NO_USB_STARTUP_CHECK": {"info_key": "usb.no_startup_check", "value_type": "bool"},
"PIN_COMPATIBLE": {"info_key": "pin_compatible"},
"REPEAT_KEY_ENABLE": {"info_key": "repeat_key.enabled", "value_type": "bool"},
"SECURE_ENABLE": {"info_key": "secure.enabled", "value_type": "bool"},
"SPLIT_KEYBOARD": {"info_key": "split.enabled", "value_type": "bool"},
"SPLIT_TRANSPORT": {"info_key": "split.transport.protocol", "to_c": false},
Expand Down
1 change: 1 addition & 0 deletions docs/_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
* [Macros](feature_macros.md)
* [Mouse Keys](feature_mouse_keys.md)
* [Programmable Button](feature_programmable_button.md)
* [Repeat Key](feature_repeat_key.md)
* [Space Cadet Shift](feature_space_cadet.md)
* [US ANSI Shifted Keys](keycodes_us_ansi_shifted.md)

Expand Down
317 changes: 317 additions & 0 deletions docs/feature_repeat_key.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,317 @@
# 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_REVERSE_REPEAT_KEY` (short
alias `QK_RREP`) on another key.

## Keycodes

|Keycode |Aliases |Description |
|-----------------------|---------|-------------------------------------|
|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
|`QK_REVERSE_REPEAT_KEY`|`QK_RREP`|Perform the reverse of the last key |

## Reverse Repeating

The Reverse Repeat Key performs the "reverse" of the last pressed key if it is
defined. When the last key is the common "select by word" hotkey Ctrl + Shift +
Right Arrow, the Reverse Repeat Key performs Ctrl + Shift + Left Arrow, which
together with the Repeat Key enables convenient selection by words in either
direction.

Reverse Repeat is enabled with the Repeat Key by default. Optionally, to reduce
firmware size, Reverse Repeat may be disabled by adding in config.h:

```c
#define NO_REVERSE_REPEAT_KEY
```

The following reverse keys are defined by default. See
`get_rev_repeat_key_keycode_user()` below for how to change or add to these
definitions. Where it makes sense, these definitions also include combinations
with mods, like Ctrl + Left &harr; Ctrl + Right Arrow.

**Navigation**

|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_LEFT` &harr; `KC_RGHT` | Left &harr; Right Arrow |
|`KC_UP` &harr; `KC_DOWN` | Up &harr; Down Arrow |
|`KC_HOME` &harr; `KC_END` | Home &harr; End |
|`KC_PGUP` &harr; `KC_PGDN` | Page Up &harr; Page Down |
|`KC_MS_L` &harr; `KC_MS_R` | Mouse Cursor Left &harr; Right |
|`KC_MS_U` &harr; `KC_MS_D` | Mouse Cursor Up &harr; Down |
|`KC_WH_L` &harr; `KC_WH_R` | Mouse Wheel Left &harr; Right |
|`KC_WH_U` &harr; `KC_WH_D` | Mouse Wheel Up &harr; Down |

**Misc**

|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_BSPC` &harr; `KC_DEL` | Backspace &harr; Delete |
|`KC_LBRC` &harr; `KC_RBRC` | `[` &harr; `]` |
|`KC_LCBR` &harr; `KC_RCBR` | `{` &harr; `}` |

**Media**

|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|`KC_WBAK` &harr; `KC_WFWD` | Browser Back &harr; Forward |
|`KC_MNXT` &harr; `KC_MPRV` | Next &harr; Previous Media Track |
|`KC_MFFD` &harr; `KC_MRWD` | Fast Forward &harr; Rewind Media |
|`KC_VOLU` &harr; `KC_VOLD` | Volume Up &harr; Down |
|`KC_BRIU` &harr; `KC_BRID` | Brightness Up &harr; Down |

**Hotkeys in Vim, Emacs, and other programs**

|Keycodes |Description |
|-----------------------------------|-----------------------------------|
|mod + `KC_F` &harr; mod + `KC_B` | Forward &harr; Backward |
|mod + `KC_D` &harr; mod + `KC_U` | Down &harr; Up |
|mod + `KC_N` &harr; mod + `KC_P` | Next &harr; Previous |
|mod + `KC_A` &harr; mod + `KC_E` | Home &harr; End |
|`KC_J` &harr; `KC_K` | Down &harr; Up |
|`KC_H` &harr; `KC_L` | Left &harr; Right |
|`KC_W` &harr; `KC_B` | Forward &harr; Backward by Word |

(where above, "mod" is Ctrl, Alt, or GUI)


## Customization

### Defining reverse keys

Use the `get_rev_repeat_key_keycode_user()` callback to define the "reverse"
key for additional keys or override the default definitions.
For example, to define Ctrl + Y as the reverse of Ctrl + Z, and vice versa, add
the following in keymap.c:

```c
uint16_t get_rev_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_NO;
}
```

The `keycode` and `mods` args are the keycode and mods that were active with the
last pressed key. The function returns the keycode for the reverse key, or
`KC_NO` to defer to the default definitions. Any keycode may be returned as a
reverse key, including custom keycodes.

Another example, defining Shift + Tab as the reverse of Tab, and vice versa:

```c
uint16_t get_rev_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_NO;
}
```


### Ignoring certain keys

By default, the Repeat Key and Reverse Repeat Key ignore modifier and layer
switch keys in tracking what the "last" key was. This enables possibly setting
some mods and changing layers between pressing a key and repeating it. To
customize which keys are ignored, define `get_repeat_key_eligible()` in your
keymap.c. The default implementation is

```c
bool get_repeat_key_eligible(uint16_t keycode, keyrecord_t* record) {
switch (keycode) {
// Ignore MO, TO, TG, and TT 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:
// Ignore one-shot keys.
#ifndef NO_ACTION_ONESHOT
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
#endif // NO_ACTION_ONESHOT
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
}

return true;
}
```

This callback is called on every key press. Returning true indicates the key is
eligible for repeating (or reverse repeating), while false means it is ignored.

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 get_repeat_key_eligible(uint16_t keycode, keyrecord_t* record) {
if (IS_LAYER_ON(2) || (get_mods() & MOD_MASK_GUI)) {
return false;
}

switch (keycode) {
// Same as above...
}

return true;
}
```

?> 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 reverse 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 reverse 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 reverse repeated

Pressing the Reverse Repeat Key behaves as if the "reverse" of the last pressed
key were pressed, if a reverse is defined. To define how a particular key is
reverse repeated, use the `get_rev_repeat_key_keycode_user()` callback as
described above to define which keycode to use as its reverse. Beyond this,
`get_repeat_key_count()` may be used in custom handlers to fine-tune behavior
when reverse repeating.

The following example defines `MY_MACRO` as its own reverse, and specially
handles repeating and reverse repeating:

```c
uint16_t get_rev_repeat_key_keycode_user(uint16_t keycode, uint8_t mods) {
switch (keycode) {
case MY_MACRO: return MY_MACRO; // Define MY_MACRO as its own reverse.
}
return KC_NO;
}

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) { // Reverse repeating.
if (record->event.pressed) {
SEND_STRING("reverse!");
}
} else { // Used normally.
if (record->event.pressed) {
SEND_STRING("macro");
}
}
return false;

// Other macros...
}
return true;
}
```


## Functions

| Function | Description |
|-------------------------------|---------------------------------------------|
| `get_repeat_key_count()` | Signed count of times the key has been repeated or reverse repeated. |
| `get_repeat_key_keycode()` | The last key's keycode, the key to be repeated. |
| `get_repeat_key_mods()` | Mods to apply when repeating. |
| `set_repeat_key_keycode(kc)` | Set the keycode to be repeated. |
| `set_repeat_key_mods(mods)` | Set the mods to apply when repeating. |
|`get_rev_repeat_key_keycode()` | Keycode to be used for reverse repeating. |

1 change: 1 addition & 0 deletions docs/ja/_summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
* [モッドタップ](ja/mod_tap.md)
* [マクロ](ja/feature_macros.md)
* [マウスキー](ja/feature_mouse_keys.md)
* [Repeat Key](ja/feature_repeat_key.md)
* [Space Cadet Shift](ja/feature_space_cadet.md)
* [US ANSI シフトキー](ja/keycodes_us_ansi_shifted.md)

Expand Down
9 changes: 9 additions & 0 deletions docs/keycodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,15 @@ See also: [Programmable Button](feature_programmable_button.md)
|`QK_PROGRAMMABLE_BUTTON_31`|`PB_31`|Programmable button 31|
|`QK_PROGRAMMABLE_BUTTON_32`|`PB_32`|Programmable button 32|

## Repeat Key :id=repeat-key

See also: [Repeat Key](feature_repeat_key.md)

|Keycode |Aliases |Description |
|-----------------------|---------|-------------------------------------|
|`QK_REPEAT_KEY` |`QK_REP` |Repeat the last pressed key |
|`QK_REVERSE_REPEAT_KEY`|`QK_RREP`|Perform the reverse of the last key |

## Space Cadet :id=space-cadet

See also: [Space Cadet](feature_space_cadet.md)
Expand Down
Loading