diff options
29 files changed, 2508 insertions, 6 deletions
diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index 5a1ef5c6f0..4e058dcd26 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -32,6 +32,7 @@ GENERIC_FEATURES = \ KEY_OVERRIDE \ LEADER \ PROGRAMMABLE_BUTTON \ + REPEAT_KEY \ SECURE \ SPACE_CADET \ SWAP_HANDS \ diff --git a/builddefs/show_options.mk b/builddefs/show_options.mk index 9723b45438..8bcc02083b 100644 --- a/builddefs/show_options.mk +++ b/builddefs/show_options.mk @@ -85,7 +85,8 @@ OTHER_OPTION_NAMES = \ SECURE_ENABLE \ CAPS_WORD_ENABLE \ AUTOCORRECT_ENABLE \ - TRI_LAYER_ENABLE + TRI_LAYER_ENABLE \ + REPEAT_KEY_ENABLE define NAME_ECHO @printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)" diff --git a/data/constants/keycodes/keycodes_0.0.3.hjson b/data/constants/keycodes/keycodes_0.0.3.hjson new file mode 100644 index 0000000000..e69de29bb2 --- /dev/null +++ b/data/constants/keycodes/keycodes_0.0.3.hjson diff --git a/data/constants/keycodes/keycodes_0.0.3_quantum.hjson b/data/constants/keycodes/keycodes_0.0.3_quantum.hjson new file mode 100644 index 0000000000..23a3c9b06d --- /dev/null +++ b/data/constants/keycodes/keycodes_0.0.3_quantum.hjson @@ -0,0 +1,18 @@ +{ + "keycodes": { + "0x7C79": { + "group": "quantum", + "key": "QK_REPEAT_KEY", + "aliases": [ + "QK_REP" + ] + }, + "0x7C7A": { + "group": "quantum", + "key": "QK_ALT_REPEAT_KEY", + "aliases": [ + "QK_AREP" + ] + } + } +} diff --git a/docs/_summary.md b/docs/_summary.md index ce579cb071..3d9bde6b17 100644 --- a/docs/_summary.md +++ b/docs/_summary.md @@ -70,6 +70,7 @@ * [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) diff --git a/docs/feature_repeat_key.md b/docs/feature_repeat_key.md new file mode 100644 index 0000000000..6fa8a724ef --- /dev/null +++ b/docs/feature_repeat_key.md @@ -0,0 +1,457 @@ +# 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; +} +``` + diff --git a/docs/ja/_summary.md b/docs/ja/_summary.md index e49853bfd4..4d6f2348d5 100644 --- a/docs/ja/_summary.md +++ b/docs/ja/_summary.md @@ -68,6 +68,7 @@ * [モッドタップ](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) diff --git a/docs/keycodes.md b/docs/keycodes.md index cad050ccf7..e5b6246af7 100644 --- a/docs/keycodes.md +++ b/docs/keycodes.md @@ -803,6 +803,15 @@ See also: [Programmable Button](feature_programmable_button.md) |`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_ALT_REPEAT_KEY` |`QK_AREP`|Perform alternate of the last key | + ## Space Cadet :id=space-cadet See also: [Space Cadet](feature_space_cadet.md) diff --git a/docs/zh-cn/_summary.md b/docs/zh-cn/_summary.md index b8c26ac275..0fc92e33d3 100644 --- a/docs/zh-cn/_summary.md +++ b/docs/zh-cn/_summary.md @@ -73,6 +73,7 @@ * [Mod-Tap](zh-cn/mod_tap.md) * [宏](zh-cn/feature_macros.md) * [鼠标键](zh-cn/feature_mouse_keys.md) + * [Repeat Key](zh-cn/feature_repeat_key.md) * [Space Cadet Shift](zh-cn/feature_space_cadet.md) * [US ANSI上档键值](zh-cn/keycodes_us_ansi_shifted.md) diff --git a/quantum/action.c b/quantum/action.c index 59bfefc495..a45e70c557 100644 --- a/quantum/action.c +++ b/quantum/action.c @@ -285,7 +285,7 @@ void process_record(keyrecord_t *record) { } void process_record_handler(keyrecord_t *record) { -#ifdef COMBO_ENABLE +#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE) action_t action; if (record->keycode) { action = action_for_keycode(record->keycode); @@ -1109,7 +1109,7 @@ bool is_tap_record(keyrecord_t *record) { return false; } -#ifdef COMBO_ENABLE +#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE) action_t action; if (record->keycode) { action = action_for_keycode(record->keycode); diff --git a/quantum/action.h b/quantum/action.h index 2a2c294c5a..d5b15c6f17 100644 --- a/quantum/action.h +++ b/quantum/action.h @@ -50,7 +50,7 @@ typedef struct { #ifndef NO_ACTION_TAPPING tap_t tap; #endif -#ifdef COMBO_ENABLE +#if defined(COMBO_ENABLE) || defined(REPEAT_KEY_ENABLE) uint16_t keycode; #endif } keyrecord_t; diff --git a/quantum/keycodes.h b/quantum/keycodes.h index 34b13c29af..bbf10da36d 100644 --- a/quantum/keycodes.h +++ b/quantum/keycodes.h @@ -721,6 +721,8 @@ enum qk_keycode_defines { QK_AUTOCORRECT_TOGGLE = 0x7C76, QK_TRI_LAYER_LOWER = 0x7C77, QK_TRI_LAYER_UPPER = 0x7C78, + QK_REPEAT_KEY = 0x7C79, + QK_ALT_REPEAT_KEY = 0x7C7A, QK_KB_0 = 0x7E00, QK_KB_1 = 0x7E01, QK_KB_2 = 0x7E02, @@ -1362,6 +1364,8 @@ enum qk_keycode_defines { AC_TOGG = QK_AUTOCORRECT_TOGGLE, TL_LOWR = QK_TRI_LAYER_LOWER, TL_UPPR = QK_TRI_LAYER_UPPER, + QK_REP = QK_REPEAT_KEY, + QK_AREP = QK_ALT_REPEAT_KEY, }; // Range Helpers @@ -1413,6 +1417,6 @@ enum qk_keycode_defines { #define IS_MACRO_KEYCODE(code) ((code) >= QK_MACRO_0 && (code) <= QK_MACRO_31) #define IS_BACKLIGHT_KEYCODE(code) ((code) >= QK_BACKLIGHT_ON && (code) <= QK_BACKLIGHT_TOGGLE_BREATHING) #define IS_RGB_KEYCODE(code) ((code) >= RGB_TOG && (code) <= RGB_MODE_TWINKLE) -#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_TRI_LAYER_UPPER) +#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY) #define IS_KB_KEYCODE(code) ((code) >= QK_KB_0 && (code) <= QK_KB_31) #define IS_USER_KEYCODE(code) ((code) >= QK_USER_0 && (code) <= QK_USER_31) diff --git a/quantum/process_keycode/process_repeat_key.c b/quantum/process_keycode/process_repeat_key.c new file mode 100644 index 0000000000..f819aa226e --- /dev/null +++ b/quantum/process_keycode/process_repeat_key.c @@ -0,0 +1,109 @@ +// 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; +} diff --git a/quantum/process_keycode/process_repeat_key.h b/quantum/process_keycode/process_repeat_key.h new file mode 100644 index 0000000000..eddc50f254 --- /dev/null +++ b/quantum/process_keycode/process_repeat_key.h @@ -0,0 +1,62 @@ +// 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. |