Browse Source

[Feature] Key Overrides (#11422)

pull/13542/head
Jonas Gessner 2 years ago
committed by GitHub
parent
commit
52cfc9259b
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 984 additions and 3 deletions
  1. +5
    -0
      common_features.mk
  2. +1
    -0
      docs/_summary.md
  3. +4
    -0
      docs/config_options.md
  4. +229
    -0
      docs/feature_key_overrides.md
  5. +1
    -0
      docs/syllabus.md
  6. +1
    -0
      docs/understanding_qmk.md
  7. +518
    -0
      quantum/process_keycode/process_key_override.c
  8. +147
    -0
      quantum/process_keycode/process_key_override.h
  9. +24
    -0
      quantum/process_keycode/process_key_override_private.h
  10. +16
    -3
      quantum/quantum.c
  11. +4
    -0
      quantum/quantum.h
  12. +5
    -0
      quantum/quantum_keycodes.h
  13. +1
    -0
      show_options.mk
  14. +28
    -0
      tmk_core/common/action_util.c

+ 5
- 0
common_features.mk View File

@ -335,6 +335,11 @@ ifeq ($(strip $(PRINTING_ENABLE)), yes)
SRC += $(TMK_DIR)/protocol/serial_uart.c
endif
ifeq ($(strip $(KEY_OVERRIDE_ENABLE)), yes)
OPT_DEFS += -DKEY_OVERRIDE_ENABLE
SRC += $(QUANTUM_DIR)/process_keycode/process_key_override.c
endif
ifeq ($(strip $(SERIAL_LINK_ENABLE)), yes)
SERIAL_SRC := $(wildcard $(SERIAL_PATH)/protocol/*.c)
SERIAL_SRC += $(wildcard $(SERIAL_PATH)/system/*.c)


+ 1
- 0
docs/_summary.md View File

@ -77,6 +77,7 @@
* [Combos](feature_combo.md)
* [Debounce API](feature_debounce_type.md)
* [Key Lock](feature_key_lock.md)
* [Key Overrides](feature_key_overrides.md)
* [Layers](feature_layers.md)
* [One Shot Keys](one_shot_keys.md)
* [Pointing Device](feature_pointing_device.md)


+ 4
- 0
docs/config_options.md View File

@ -195,6 +195,8 @@ If you define these options you will enable the associated feature, which may in
* Sets the delay between `register_code` and `unregister_code`, if you're having issues with it registering properly (common on VUSB boards). The value is in milliseconds.
* `#define TAP_HOLD_CAPS_DELAY 80`
* Sets the delay for Tap Hold keys (`LT`, `MT`) when using `KC_CAPSLOCK` keycode, as this has some special handling on MacOS. The value is in milliseconds, and defaults to 80 ms if not defined. For macOS, you may want to set this to 200 or higher.
* `#define KEY_OVERRIDE_REPEAT_DELAY 500`
* Sets the key repeat interval for [key overrides](feature_key_overrides.md).
## RGB Light Configuration
@ -400,6 +402,8 @@ Use these to enable or disable building certain features. The more you have enab
* USB N-Key Rollover - if this doesn't work, see here: https://github.com/tmk/tmk_keyboard/wiki/FAQ#nkro-doesnt-work
* `AUDIO_ENABLE`
* Enable the audio subsystem.
* `KEY_OVERRIDE_ENABLE`
* Enable the key override feature
* `RGBLIGHT_ENABLE`
* Enable keyboard underlight functionality
* `LEADER_ENABLE`


+ 229
- 0
docs/feature_key_overrides.md View File

@ -0,0 +1,229 @@
# Key Overrides
Key overrides allow you to override modifier-key combinations to send a different modifier-key combination or perform completely custom actions. Don't want `shift` + `1` to type `!` on your computer? Use a key override to make your keyboard type something different when you press `shift` + `1`. The general behavior is like this: If `modifiers w` + `key x` are pressed, replace these keys with `modifiers y` + `key z` in the keyboard report.
You can use key overrides in a similar way to momentary layer/fn keys to activate custom keycodes/shortcuts, with a number of benefits: You completely keep the original use of the modifier keys, while being able to save space by removing fn keys from your keyboard. You can also easily configure _combinations of modifiers_ to trigger different actions than individual modifiers, and much more. The possibilities are quite vast and this documentation contains a few examples for inspiration throughout.
##### A few more examples to get started: You could use key overrides to...
- Send `brightness up/down` when pressing `ctrl` + `volume up/down`.
- Send `delete` when pressing `shift` + `backspace`.
- Create custom shortcuts or change existing ones: E.g. Send `ctrl`+`shift`+`z` when `ctrl`+`y` is pressed.
- Run custom code when `ctrl` + `alt` + `esc` is pressed.
## Setup
To enable this feature, you need to add `KEY_OVERRIDE_ENABLE = yes` to your `rules.mk`.
Then, in your `keymap.c` file, you'll need to define the array `key_overrides`, which defines all key overrides to be used. Each override is a value of type `key_override_t`. The array `key_overrides` is `NULL`-terminated and contains pointers to `key_override_t` values (`const key_override_t **`).
## Creating Key Overrides
The `key_override_t` struct has many options that allow you to precisely tune your overrides. The full reference is shown below. Instead of manually creating a `key_override_t` value, it is recommended to use these dedicated initializers:
#### `ko_make_basic(modifiers, key, replacement)`
Returns a `key_override_t`, which sends `replacement` (can be a key-modifer combination), when `key` and `modifiers` are all pressed down. This override still activates if any additional modifiers not specified in `modifiers` are also pressed down. See `ko_make_with_layers_and_negmods` to customize this behavior.
#### `ko_make_with_layers(modifiers, key, replacement, layers)`
Additionally takes a bitmask `layers` that defines on which layers the override is used.
#### `ko_make_with_layers_and_negmods(modifiers, key, replacement, layers, negative_mods)`
Additionally takes a bitmask `negative_mods` that defines which modifiers may not be pressed for this override to activate.
#### `ko_make_with_layers_negmods_and_options(modifiers, key, replacement, layers, negative_mods, options)`
Additionally takes a bitmask `options` that specifies additional options. See `ko_option_t` for available options.
For more customization possibilities, you may directly create a `key_override_t`, which allows you to customize even more behavior. Read further below for details and examples.
## Simple Example
This shows how the mentioned example of sending `delete` when `shift` + `backspace` are pressed is realized:
```c
const key_override_t delete_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_BSPACE, KC_DELETE);
// This globally defines all key overrides to be used
const key_override_t **key_overrides = (const key_override_t *[]){
&delete_key_override,
NULL // Null terminate the array of overrides!
};
```
## Intermediate Difficulty Examples
### Media Controls & Screen Brightness
In this example a single key is configured to control media, volume and screen brightness by using key overrides.
- The key is set to send `play/pause` in the keymap.
The following key overrides will be configured:
- `Ctrl` + `play/pause` will send `next track`.
- `Ctrl` + `Shift` + `play/pause` will send `previous track`.
- `Alt` + `play/pause` will send `volume up`.
- `Alt` + `Shift` + `play/pause` will send `volume down`.
- `Ctrl` + `Alt` + `play/pause` will send `brightness up`.
- `Ctrl` + `Alt` + `Shift` + `play/pause` will send `brightness down`.
```c
const key_override_t next_track_override =
ko_make_with_layers_negmods_and_options(
MOD_MASK_CTRL, // Trigger modifiers: ctrl
KC_MPLY, // Trigger key: play/pause
KC_MNXT, // Replacement key
~0, // Activate on all layers
MOD_MASK_SA, // Do not activate when shift or alt are pressed
ko_option_no_reregister_trigger); // Specifies that the play key is not registered again after lifting ctrl
const key_override_t prev_track_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CS, KC_MPLY,
KC_MPRV, ~0, MOD_MASK_ALT, ko_option_no_reregister_trigger);
const key_override_t vol_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_ALT, KC_MPLY,
KC_VOLU, ~0, MOD_MASK_CS, ko_option_no_reregister_trigger);
const key_override_t vol_down_override = ko_make_with_layers_negmods_and_options(MOD_MASK_SA, KC_MPLY,
KC_VOLD, ~0, MOD_MASK_CTRL, ko_option_no_reregister_trigger);
const key_override_t brightness_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CA, KC_MPLY,
KC_BRIU, ~0, MOD_MASK_SHIFT, ko_option_no_reregister_trigger);
const key_override_t brightness_down_override = ko_make_basic(MOD_MASK_CSA, KC_MPLY, KC_BRID);
// This globally defines all key overrides to be used
const key_override_t **key_overrides = (const key_override_t *[]){
&next_track_override,
&prev_track_override,
&vol_up_override,
&vol_down_override,
&brightness_up_override,
&brightness_down_override,
NULL
};
```
### Flexible macOS-friendly Grave Escape
The [Grave Escape feature](https://docs.qmk.fm/using-qmk/advanced-keycodes/feature_grave_esc) is limited in its configurability and has [bugs when used on macOS](https://docs.qmk.fm/using-qmk/advanced-keycodes/feature_grave_esc#caveats). Key overrides can be used to achieve a similar functionality as Grave Escape, but with more customization and without bugs on macOS.
```c
// Shift + esc = ~
const key_override_t tilde_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, S(KC_GRAVE));
// GUI + esc = `
const key_override_t grave_esc_override = ko_make_basic(MOD_MASK_GUI, KC_ESC, KC_GRAVE);
const key_override_t **key_overrides = (const key_override_t *[]){
&tilde_esc_override,
&grave_esc_override,
NULL
};
```
In addition to not encountering unexpected bugs on macOS, you can also change the behavior as you wish. Instead setting `GUI` + `ESC` = `` ` `` you may change it to an arbitrary other modifier, for example `Ctrl` + `ESC` = `` ` ``.
## Advanced Examples
### Modifiers as Layer Keys
Do you really need a dedicated key to toggle your fn layer? With key overrides, perhaps not. This example shows how you can configure to use `rGUI` + `rAlt` (right GUI and right alt) to access a momentary layer like an fn layer. With this you completely eliminate the need to use a dedicated layer key. Of course the choice of modifier keys can be changed as needed, `rGUI` + `rAlt` is just an example here.
```c
// This is called when the override activates and deactivates. Enable the fn layer on activation and disable on deactivation
bool momentary_layer(bool key_down, void *layer) {
if (key_down) {
layer_on((uint8_t)(uintptr_t)layer);
} else {
layer_off((uint8_t)(uintptr_t)layer);
}
return false;
}
const key_override_t fn_override = {.trigger_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), //
.layers = ~(1 << LAYER_FN), //
.suppressed_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL), //
.options = ko_option_no_unregister_on_other_key_down, //
.negative_mod_mask = (uint8_t) ~(MOD_BIT(KC_RGUI) | MOD_BIT(KC_RCTL)), //
.custom_action = momentary_layer, //
.context = (void *)LAYER_FN, //
.trigger = KC_NO, //
.replacement = KC_NO, //
.enabled = NULL};
```
## Keycodes
You can enable, disable and toggle all key overrides on the fly.
|Keycode |Description |Function Equivalent|
|----------|---------------------------------|--------|
|`KEY_OVERRIDE_ON` |Turns on Key Override feature | `key_override_on(void)`|
|`KEY_OVERRIDE_OFF` |Turns off Key Override feature |`key_override_off(void)`|
|`KEY_OVERRIDE_TOGGLE` |Toggles Key Override feature on and off |`key_override_toggle(void)`|
## Reference for `key_override_t`
Advanced users may need more customization than what is offered by the simple `ko_make` initializers. For this, directly create a `key_override_t` value and set all members. Below is a reference for all members of `key_override_t`.
| Member | Description |
|--------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `uint16_t trigger` | The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (`trigger_mods`) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to `KC_NO` to require only the necessary modifiers to be pressed and no non-modifier. |
| `uint8_t trigger_mods` | Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the `MOD_MASK_XXX` and `MOD_BIT()` macros for this. |
| `layer_state_t layers` | This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit `(1 << i)`. |
| `uint8_t negative_mod_mask` | Which modifiers cannot be down. It must hold that `(active_modifiers & negative_mod_mask) == 0`, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true. |
| `uint8_t suppressed_mods` | Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example. |
| `uint16_t replacement` | The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. `C(KC_A)`), or `KC_NO` (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent. |
| `ko_option_t options` | Options controlling the behavior of the override, such as what actions are allowed to activate the override. |
| `bool (*custom_action)(bool activated, void *context)` | If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally. |
| `void *context` | A context that will be passed to the custom action function. |
| `bool *enabled` | If this points to false this override will not be used. Set to NULL to always have this override enabled. |
### Reference for `ko_option_t`
Bitfield with various options controlling the behavior of a key override.
| Value | Description |
|------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `ko_option_activation_trigger_down` | Allow activating when the trigger key is pressed down. |
| `ko_option_activation_required_mod_down` | Allow activating when a necessary modifier is pressed down. |
| `ko_option_activation_negative_mod_up` | Allow activating when a negative modifier is released. |
| `ko_option_one_mod` | If set, any of the modifiers in `trigger_mods` will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in `trigger_mods` have to be pressed (logical AND of modifiers). |
| `ko_option_no_unregister_on_other_key_down` | If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. |
| `ko_option_no_reregister_trigger` | If set, the trigger key will never be registered again after the override is deactivated. |
| `ko_options_default` | The default options used by the `ko_make_xxx` functions |
## For Advanced Users: Inner Workings
This section explains how a key override works in detail, explaining where each member of `key_override_t` comes into play. Understanding this is essential to be able to take full advantage of all the options offered by key overrides.
#### Activation
When the necessary keys are pressed (`trigger_mods` + `trigger`), the override is 'activated' and the replacement key is registered in the keyboard report (`replacement`), while the `trigger` key is removed from the keyboard report. The trigger modifiers may also be removed from the keyboard report upon activation of an override (`suppressed_mods`). The override will not activate if any of the `negative_modifiers` are pressed.
Overrides can activate in three different cases:
1. The trigger key is pressed down and necessary modifiers are already down.
2. A necessary modifier is pressed down, while the trigger key and other necessary modifiers are already down.
3. A negative modifier is released, while all necessary modifiers and the trigger key are already down.
Use the `option` member to customize which of these events are allowed to activate your overrides (default: all three).
In any case, a key override can only activate if the `trigger` key is the _last_ non-modifier key that was pressed down. This emulates the behavior of how standard OSes (macOS, Windows, Linux) handle normal key input (to understand: Hold down `a`, then also hold down `b`, then hold down `shift`; `B` will be typed but not `A`).
#### Deactivation
An override is 'deactivated' when one of the trigger keys (`trigger_mods`, `trigger`) is lifted, another non-modifier key is pressed down, or one of the `negative_modifiers` is pressed down. When an override deactivates, the `replacement` key is removed from the keyboard report, while the `suppressed_mods` that are still held down are re-added to the keyboard report. By default, the `trigger` key is re-added to the keyboard report if it is still held down and no other non-modifier key has been pressed since. This again emulates the behavior of how standard OSes handle normal key input (To understand: hold down `a`, then also hold down `b`, then also `shift`, then release `b`; `A` will not be typed even though you are holding the `a` and `shift` keys). Use the `option` field `ko_option_no_reregister_trigger` to prevent re-registering the trigger key in all cases.
#### Key Repeat Delay
A third way in which standard OS-handling of modifier-key input is emulated in key overrides is with a ['key repeat delay'](https://www.dummies.com/computers/pcs/set-your-keyboards-repeat-delay-and-repeat-rate/). To explain what this is, let's look at how normal keyboard input is handled by mainstream OSes again: If you hold down `a`, followed by `shift`, you will see the letter `a` is first typed, then for a short moment nothing is typed and then repeating `A`s are typed. Take note that, although shift is pressed down just after `a` is pressed, it takes a moment until `A` is typed. This is caused by the aforementioned key repeat delay, and it is a feature that prevents unwanted repeated characters from being typed.
This applies equally to releasing a modifier: When you hold `shift`, then press `a`, the letter `A` is typed. Now if you release `shift` first, followed by `a` shortly after, you will not see the letter `a` being typed, even though for a short moment of time you were just holding down the key `a`. This is because no modified characters are typed until the key repeat delay has passed.
This exact behavior is implemented in key overrides as well: If a key override for `shift` + `a` = `b` exists, and `a` is pressed and held, followed by `shift`, you will not immediately see the letter `b` being typed. Instead, this event is deferred for a short moment, until the key repeat delay has passed, measured from the moment when the trigger key (`a`) was pressed down.
The duration of the key repeat delay is controlled with the `KEY_OVERRIDE_REPEAT_DELAY` macro. Define this value in your `config.h` file to change it. It is 500ms by default.
## Difference to Combos
Note that key overrides are very different from [combos](https://docs.qmk.fm/#/feature_combo). Combos require that you press down several keys almost _at the same time_ and can work with any combination of non-modifier keys. Key overrides work like keyboard shortcuts (e.g. `ctrl` + `z`): They take combinations of _multiple_ modifiers and _one_ non-modifier key to then perform some custom action. Key overrides are implemented with much care to behave just like normal keyboard shortcuts would in regards to the order of pressed keys, timing, and interacton with other pressed keys. There are a number of optional settings that can be used to really fine-tune the behavior of each key override as well. Using key overrides also does not delay key input for regular key presses, which inherently happens in combos and may be undesirable.

+ 1
- 0
docs/syllabus.md View File

@ -40,6 +40,7 @@ These topics start to dig into some of the features that QMK supports. You don't
* [Tap Dance](feature_tap_dance.md)
* [Combos](feature_combo.md)
* [Userspace](feature_userspace.md)
* [Key Overrides](feature_key_overrides.md)
# Advanced Topics


+ 1
- 0
docs/understanding_qmk.md View File

@ -146,6 +146,7 @@ The `process_record()` function itself is deceptively simple, but hidden within
* [`bool process_audio(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_audio.c#L19)
* [`bool process_steno(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_steno.c#L160)
* [`bool process_music(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_music.c#L114)
* [`bool process_key_override(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/5a1b857dea45a17698f6baa7dd1b7a7ea907fb0a/quantum/process_keycode/process_key_override.c#L397)
* [`bool process_tap_dance(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_tap_dance.c#L141)
* [`bool process_unicode_common(uint16_t keycode, keyrecord_t *record)`](https://github.com/qmk/qmk_firmware/blob/e1203a222bb12ab9733916164a000ef3ac48da93/quantum/process_keycode/process_unicode_common.c#L169)
calls one of:


+ 518
- 0
quantum/process_keycode/process_key_override.c View File

@ -0,0 +1,518 @@
/*
* Copyright 2021 Jonas Gessner
*
* 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 "quantum.h"
#include "report.h"
#include "timer.h"
#include "process_key_override_private.h"
#include <debug.h>
#ifndef KEY_OVERRIDE_REPEAT_DELAY
# define KEY_OVERRIDE_REPEAT_DELAY 500
#endif
// For benchmarking the time it takes to call process_key_override on every key press (needs keyboard debugging enabled as well)
// #define BENCH_KEY_OVERRIDE
// For debug output (needs keyboard debugging enabled as well)
// #define DEBUG_KEY_OVERRIDE
#ifdef DEBUG_KEY_OVERRIDE
# define key_override_printf dprintf
#else
# define key_override_printf(str, ...) \
{}
#endif
// Helpers
// Private functions implemented elsewhere in qmk/tmk
extern uint8_t extract_mod_bits(uint16_t code);
extern void set_weak_override_mods(uint8_t mods);
extern void clear_weak_override_mods(void);
extern void set_suppressed_override_mods(uint8_t mods);
extern void clear_suppressed_override_mods(void);
static uint16_t clear_mods_from(uint16_t keycode) {
switch (keycode) {
case QK_MODS ... QK_MODS_MAX:
break;
default:
return keycode;
}
static const uint16_t all_mods = QK_LCTL | QK_LSFT | QK_LALT | QK_LGUI | QK_RCTL | QK_RSFT | QK_RALT | QK_RGUI;
return (keycode & ~(all_mods));
}
// Internal variables
static const key_override_t *active_override = NULL;
static bool active_override_trigger_is_down = false;
// Used to keep track of what non-modifier key was last pressed down. We never want to activate an override for a trigger key that is not the last non-mod key that was pressed down. OSes internally completely unregister a key that is held when a different key is held down after. We want to respect this here.
static uint16_t last_key_down = 0;
// When was the last key pressed down?
static uint32_t last_key_down_time = 0;
// What timestamp are we comparing to when waiting to register a deferred key?
static uint32_t defer_reference_time = 0;
// What delay should pass until deferred key is registered?
static uint32_t defer_delay = 0;
// Holds the keycode that should be registered at a later time, in order to not get false key presses
static uint16_t deferred_register = 0;
// TODO: in future maybe save in EEPROM?
static bool enabled = true;
// Public variables
__attribute__((weak)) const key_override_t **key_overrides = NULL;
// Forward decls
static const key_override_t *clear_active_override(const bool allow_reregister);
void key_override_on(void) {
enabled = true;
key_override_printf("Key override ON\n");
}
void key_override_off(void) {
enabled = false;
clear_active_override(false);
key_override_printf("Key override OFF\n");
}
void key_override_toggle(void) {
if (key_override_is_enabled()) {
key_override_off();
} else {
key_override_on();
}
}
bool key_override_is_enabled(void) { return enabled; }
// Returns whether the modifiers that are pressed are such that the override should activate
static bool key_override_matches_active_modifiers(const key_override_t *override, const uint8_t mods) {
// Check that negative keys pass
if ((override->negative_mod_mask & mods) != 0) {
return false;
}
// Immediately return true if the override requires no mods down
if (override->trigger_mods == 0) {
return true;
}
if ((override->options & ko_option_one_mod) != 0) {
// At least one of the trigger modifiers must be down
return (override->trigger_mods & mods) != 0;
} else {
// All trigger modifiers must be down, but each mod can be active on either side (if both sides are specified).
// Which mods, regardless of side, are required?
uint8_t one_sided_required_mods = (override->trigger_mods & 0b1111) | (override->trigger_mods >> 4);
// Which of the required modifiers are active?
uint8_t active_required_mods = override->trigger_mods & mods;
// Move the active requird mods to one side
uint8_t one_sided_active_required_mods = (active_required_mods & 0b1111) | (active_required_mods >> 4);
// Check that there is a full match between the required one-sided mods and active required one sided mods
return one_sided_active_required_mods == one_sided_required_mods;
}
return false;
}
static void schedule_deferred_register(const uint16_t keycode) {
if (timer_elapsed32(last_key_down_time) < KEY_OVERRIDE_REPEAT_DELAY) {
// Defer until KEY_OVERRIDE_REPEAT_DELAY has passed since the trigger key was pressed down. This emulates the behavior as holding down a key x, then holding down shift shortly after. Usually the shifted key X is not immediately produced, but rather a 'key repeat delay' passes before any repeated character is output.
defer_reference_time = last_key_down_time;
defer_delay = KEY_OVERRIDE_REPEAT_DELAY;
} else {
// Wait a very short time when a modifier event triggers the override to avoid false activations when e.g. a modifier is pressed just before a key is released (with the intention of pairing the modifier with a different key), or a modifier is lifted shortly before the trigger key is lifted. Operating systems by default reject modifier-events that happen very close to a non-modifier event.
defer_reference_time = timer_read32();
defer_delay = 50; // 50ms
}
deferred_register = keycode;
}
const key_override_t *clear_active_override(const bool allow_reregister) {
if (active_override == NULL) {
return NULL;
}
key_override_printf("Deactivating override\n");
deferred_register = 0;
// Clear the suppressed mods
clear_suppressed_override_mods();
// Unregister the replacement. First remove the weak override mods
clear_weak_override_mods();
const key_override_t *const old = active_override;
const uint8_t mod_free_replacement = clear_mods_from(active_override->replacement);
bool unregister_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered
mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered
// Try firing the custom handler
if (active_override->custom_action != NULL) {
unregister_replacement &= active_override->custom_action(false, active_override->context);
}
// Then unregister the mod-free replacement key if desired
if (unregister_replacement) {
if (IS_KEY(mod_free_replacement)) {
del_key(mod_free_replacement);
} else {
key_override_printf("NOT KEY 1\n");
send_keyboard_report();
unregister_code(mod_free_replacement);
}
}
const uint16_t trigger = active_override->trigger;
const bool reregister_trigger = allow_reregister && // Check if allowed from caller
(active_override->options & ko_option_no_reregister_trigger) == 0 && // Check if override allows
active_override_trigger_is_down && // Check if trigger is even down
trigger != KC_NO && // KC_NO is never registered
trigger < SAFE_RANGE; // A custom keycode should not be registered
// Optionally re-register the trigger if it is still down
if (reregister_trigger) {
key_override_printf("Re-registering trigger deferred: %u\n", trigger);
// This will always be a modifier event, so defer always
schedule_deferred_register(trigger);
}
send_keyboard_report();
active_override = NULL;
active_override_trigger_is_down = false;
return old;
}
/** Checks if the key event is an allowed activation event for the provided override. Does not check things like whether the correct mods or correct trigger key is down. */
static bool check_activation_event(const key_override_t *override, const bool key_down, const bool is_mod) {
ko_option_t options = override->options;
if ((options & ko_options_all_activations) == 0) {
// No activation option provided at all. This is wrong, but let's assume the default activations (ko_options_all_activations) were meant...
options = ko_options_all_activations;
}
if (is_mod) {
if (key_down) {
return (options & ko_option_activation_required_mod_down) != 0;
} else {
return (options & ko_option_activation_negative_mod_up) != 0;
}
} else {
if (key_down) {
return (options & ko_option_activation_trigger_down) != 0;
} else {
return false;
}
}
}
/** Iterates through the list of key overrides and tries activating each, until it finds one that activates or reaches the end of overrides. Returns true if the key action for `keycode` should be sent */
static bool try_activating_override(const uint16_t keycode, const uint8_t layer, const bool key_down, const bool is_mod, const uint8_t active_mods, bool *activated) {
if (key_overrides == NULL) {
return true;
}
for (uint8_t i = 0;; i++) {
const key_override_t *const override = key_overrides[i];
// End of array
if (override == NULL) {
break;
}
// Fast, but not full mods check. Most key presses will not have any mods down, and most overrides will require mods. Hence here we filter overrides that require mods to be down while no mods are down
if (active_mods == 0 && override->trigger_mods != 0) {
key_override_printf("Not activating override: Modifiers don't match\n");
continue;
}
// Check layer
if ((override->layers & (1 << layer)) == 0) {
key_override_printf("Not activating override: Not set to activate on pressed layer\n");
continue;
}
// Check allowed activation events
if (!check_activation_event(override, key_down, is_mod)) {
key_override_printf("Not activating override: Activation event not allowed\n");
continue;
}
const bool is_trigger = override->trigger == keycode;
// Check if trigger lifted. This is a small optimization in order to skip the remaining checks
if (is_trigger && !key_down) {
key_override_printf("Not activating override: Trigger lifted\n");
continue;
}
// If the trigger is KC_NO it means 'no key', so only the required modifiers need to be down.
const bool no_trigger = override->trigger == KC_NO;
// Check if aleady active
if (override == active_override) {
key_override_printf("Not activating override: Alerady actived\n");
continue;
}
// Check if enabled
if (override->enabled != NULL && !((*(override->enabled) & 1))) {
key_override_printf("Not activating override: Not enabled\n");
continue;
}
// Check mods precisely
if (!key_override_matches_active_modifiers(override, active_mods)) {
key_override_printf("Not activating override: Modifiers don't match\n");
continue;
}
// Check if trigger key is down.
const bool trigger_down = is_trigger && key_down;
// At this point, all requirements for activation are checked, except whether the trigger key is pressed. Now we check if the required trigger is down
// If no trigger key is required, yes.
// If the trigger was just pressed, yes.
// If the last non-mod key that was pressed down is the trigger key, yes.
bool should_activate = no_trigger || trigger_down || last_key_down == override->trigger;
if (!should_activate) {
key_override_printf("Not activating override. Trigger not down\n");
continue;
}
key_override_printf("Activating override\n");
clear_active_override(false);
active_override = override;
active_override_trigger_is_down = true;
set_suppressed_override_mods(override->suppressed_mods);
if (!trigger_down && !no_trigger) {
// When activating a key override the trigger is is always unregistered. In the case where the key that newly pressed is not the trigger key, we have to explicitly remove the trigger key from the keyboard report. If the trigger was just pressed down we simply suppress the event which also has the effect of the trigger key not being registered in the keyboard report.
if (IS_KEY(override->trigger)) {
del_key(override->trigger);
} else {
unregister_code(override->trigger);
}
}
const uint16_t mod_free_replacement = clear_mods_from(override->replacement);
bool register_replacement = mod_free_replacement != KC_NO && // KC_NO is never registered
mod_free_replacement < SAFE_RANGE; // Custom keycodes are never registered
// Try firing the custom handler
if (override->custom_action != NULL) {
register_replacement &= override->custom_action(true, override->context);
}
if (register_replacement) {
const uint8_t override_mods = extract_mod_bits(override->replacement);
set_weak_override_mods(override_mods);
// If this is a modifier event that activates the key override we _always_ defer the actual full activation of the override
if (is_mod) {
key_override_printf("Deferring register replacement key\n");
schedule_deferred_register(mod_free_replacement);
send_keyboard_report();
} else {
if (IS_KEY(mod_free_replacement)) {
add_key(mod_free_replacement);
} else {
key_override_printf("NOT KEY 2\n");
send_keyboard_report();
// On macOS there seems to be a race condition when it comes to the keyboard report and consumer keycodes. It seems the OS may recognize a consumer keycode before an updated keyboard report, even if the keyboard report is actually sent before the consumer key. I assume it is some sort of race condition because it happens infrequently and very irregularly. Waiting for about at least 10ms between sending the keyboard report and sending the consumer code has shown to fix this.
wait_ms(10);
register_code(mod_free_replacement);
}
}
} else {
// If not registering the replacement key send keyboard report to update the unregistered keys.
send_keyboard_report();
}
*activated = true;
// If the trigger is down, suppress the event so that it does not get added to the keyboard report.
return !trigger_down;
}
*activated = false;
return true;
}
void matrix_scan_key_override(void) {
if (deferred_register == 0) {
return;
}
if (timer_elapsed32(defer_reference_time) >= defer_delay) {
key_override_printf("Registering deferred key\n");
register_code16(deferred_register);
deferred_register = 0;
defer_reference_time = 0;
defer_delay = 0;
}
}
bool process_key_override(const uint16_t keycode, const keyrecord_t *const record) {
#ifdef BENCH_KEY_OVERRIDE
uint16_t start = timer_read();
#endif
const bool key_down = record->event.pressed;
const bool is_mod = IS_MOD(keycode);
if (key_down) {
switch (keycode) {
case KEY_OVERRIDE_TOGGLE:
key_override_toggle();
return false;
case KEY_OVERRIDE_ON:
key_override_on();
return false;
case KEY_OVERRIDE_OFF:
key_override_off();
return false;
default:
break;
}
}
if (!enabled) {
return true;
}
uint8_t effective_mods = get_mods();
#ifdef KEY_OVERRIDE_INCLUDE_WEAK_MODS
effective_mods |= get_weak_mods();
#endif
#ifndef NO_ACTION_ONESHOT
// Locked one shot mods are added to get_mods(), I think (why??) while oneshot mods are in get_oneshot_mods(). Still OR with get_locked_oneshot_mods because that's where those mods _should_ be saved.
effective_mods |= get_oneshot_locked_mods() | get_oneshot_mods();
#endif
if (is_mod) {
// The mods returned from get_mods() will be updated with this new event _after_ this code runs. Hence we manually update the effective mods here to really know the effective mods.
if (key_down) {
effective_mods |= MOD_BIT(keycode);
} else {
effective_mods &= ~MOD_BIT(keycode);
}
} else {
if (key_down) {
last_key_down = keycode;
last_key_down_time = timer_read32();
deferred_register = 0;
}
// The last key that was pressed was just released. No more keys are therefore sending input
if (!key_down && keycode == last_key_down) {
last_key_down = 0;
last_key_down_time = 0;
// We also cancel any deferred registers because, again, no keys are sending any input. Only the last key that is pressed creates an input this key was just lifted.
deferred_register = 0;
}
}
key_override_printf("key down: %u keycode: %u is mod: %u effective mods: %u\n", key_down, keycode, is_mod, effective_mods);
bool send_key_action = true;
bool activated = false;
// Non-mod key up events never activate a key override
if (is_mod || key_down) {
// Get the exact layer that was hit. It will be cached at this point
const uint8_t layer = read_source_layers_cache(record->event.key);
// Use blocked to ensure the same override is not activated again immediately after it is deactivated
send_key_action = try_activating_override(keycode, layer, key_down, is_mod, effective_mods, &activated);
if (!send_key_action) {
send_keyboard_report();
}
}
if (!activated && active_override != NULL) {
if (is_mod) {
// Check if necessary modifier of current override goes up or a negative mod goes down
if (!key_override_matches_active_modifiers(active_override, effective_mods)) {
key_override_printf("Deactivating override because necessary modifier lifted or negative mod pressed\n");
clear_active_override(true);
}
} else {
// Check if trigger of current override goes up or if override does not allow additional keys to be down and another key goes down
const bool is_trigger = keycode == active_override->trigger;
bool should_deactivate = false;
// Check if trigger key lifted
if (is_trigger && !key_down) {
should_deactivate = true;
active_override_trigger_is_down = false;
key_override_printf("Deactivating override because trigger key up\n");
}
// Check if another key was pressed
if (key_down && (active_override->options & ko_option_no_unregister_on_other_key_down) == 0) {
should_deactivate = true;
key_override_printf("Deactivating override because another key was pressed\n");
}
if (should_deactivate) {
clear_active_override(false);
}
}
}
#ifdef BENCH_KEY_OVERRIDE
uint16_t elapsed = timer_elapsed(start);
dprintf("Processing key overrides took: %u ms\n", elapsed);
#endif
return send_key_action;
}

+ 147
- 0
quantum/process_keycode/process_key_override.h View File

@ -0,0 +1,147 @@
/*
* Copyright 2021 Jonas Gessner
*
* 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 <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "action_layer.h"
/**
* Key overrides allow you to send a different key-modifier combination or perform a custom action when a certain modifier-key combination is pressed.
*
* For example, you may configure a key override to send the delete key when shift + backspace are pressed together, or that your volume keys become screen brightness keys when holding ctrl. The possibilities are quite vast and the documentation contains a few examples for inspiration.
*
* See the documentation and examples here: https://docs.qmk.fm/#/feature_key_overrides
*/
/** Bitfield with various options controlling the behavior of a key override. */
typedef enum {
/** Allow activating when the trigger key is pressed down. */
ko_option_activation_trigger_down = (1 << 0),
/** Allow activating when a necessary modifier is pressed down. */
ko_option_activation_required_mod_down = (1 << 1),
/** Allow activating when a negative modifier is released. */
ko_option_activation_negative_mod_up = (1 << 2),
ko_options_all_activations = ko_option_activation_negative_mod_up | ko_option_activation_required_mod_down | ko_option_activation_trigger_down,
/** If set, any of the modifiers in trigger_mods will be enough to activate the override (logical OR of modifiers). If not set, all the modifiers in trigger_mods have to be pressed (logical AND of modifiers). */
ko_option_one_mod = (1 << 3),
/** If set, the trigger key will never be registered again after the override is deactivated. */
ko_option_no_reregister_trigger = (1 << 4),
/** If set, the override will not deactivate when another key is pressed down. Use only if you really know you need this. */
ko_option_no_unregister_on_other_key_down = (1 << 5),
/** The default options used by the ko_make_xxx functions. */
ko_options_default = ko_options_all_activations,
} ko_option_t;
/** Defines a single key override */
typedef struct {
// The non-modifier keycode that triggers the override. This keycode, and the necessary modifiers (trigger_mods) must be pressed to activate this override. Set this to the keycode of the key that should activate the override. Set to KC_NO to require only the necessary modifiers to be pressed and no non-modifier.
uint16_t trigger;
// Which mods need to be down for activation. If both sides of a modifier are set (e.g. left ctrl and right ctrl) then only one is required to be pressed (e.g. left ctrl suffices). Use the MOD_MASK_XXX and MOD_BIT() macros for this.
uint8_t trigger_mods;
// This is a BITMASK (!), defining which layers this override applies to. To use this override on layer i set the ith bit (1 << i).
layer_state_t layers;
// Which modifiers cannot be down. It must hold that (active_mods & negative_mod_mask) == 0, otherwise the key override will not be activated. An active override will be deactivated once this is no longer true.
uint8_t negative_mod_mask;
// Modifiers to 'suppress' while the override is active. To suppress a modifier means that even though the modifier key is held down, the host OS sees the modifier as not pressed. Can be used to suppress the trigger modifiers, as a trivial example.
uint8_t suppressed_mods;
// The complex keycode to send as replacement when this override is triggered. This can be a simple keycode, a key-modifier combination (e.g. C(KC_A)), or KC_NO (to register no replacement keycode). Use in combination with suppressed_mods to get the correct modifiers to be sent.
uint16_t replacement;
// Options controlling the behavior of the override, such as what actions are allowed to activate the override.
ko_option_t options;
// If not NULL, this function will be called right before the replacement key is registered, along with the provided context and a flag indicating whether the override was activated or deactivated. This function allows you to run some custom actions for specific key overrides. If you return `false`, the replacement key is not registered/unregistered as it would normally. Return `true` to register and unregister the override normally.
bool (*custom_action)(bool activated, void *context);
// A context that will be passed to the custom action function.
void *context;
// If this points to false this override will not be used. Set to NULL to always have this override enabled.
bool *enabled;
} key_override_t;
/** Define this as a null-terminated array of pointers to key overrides. These key overrides will be used by qmk. */
extern const key_override_t **key_overrides;
/** Turns key overrides on */
extern void key_override_on(void);
/** Turns key overrides off */
extern void key_override_off(void);
/** Toggles key overrides on */
extern void key_override_toggle(void);
/** Returns whether key overrides are enabled */
extern bool key_override_is_enabled(void);
/**
* Preferrably use these macros to create key overrides. They fix many of the options to a standard setting that should satisfy most basic use-cases. Only directly create a key_override_t struct when you really need to.
*/
// clang-format off
/**
* Convenience initializer to create a basic key override. Activates the override on all layers.
*/
#define ko_make_basic(trigger_mods, trigger_key, replacement_key) \
ko_make_with_layers(trigger_mods, trigger_key, replacement_key, ~0)
/**
* Convenience initializer to create a basic key override. Provide a bitmap (of type layer_state_t) with the bits set for each layer on which the override should activate.
*/
#define ko_make_with_layers(trigger_mods, trigger_key, replacement_key, layers) \
ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, 0)
/**
* Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed.
*/
#define ko_make_with_layers_and_negmods(trigger_mods, trigger_key, replacement_key, layers, negative_mask) \
ko_make_with_layers_negmods_and_options(trigger_mods, trigger_key, replacement_key, layers, negative_mask, ko_options_default)
/**
* Convenience initializer to create a basic key override. Provide a bitmap with the bits set for each layer on which the override should activate. Also provide a negative modifier mask, that is used to define which modifiers may not be pressed. Provide options for additional control of the behavior of the override.
*/
#define ko_make_with_layers_negmods_and_options(trigger_mods_, trigger_key, replacement_key, layer_mask, negative_mask, options_) \
((const key_override_t){ \
.trigger_mods = (trigger_mods_), \
.layers = (layer_mask), \
.suppressed_mods = (trigger_mods_), \
.options = (options_), \
.negative_mod_mask = (negative_mask), \
.custom_action = NULL, \
.context = NULL, \
.trigger = (trigger_key), \
.replacement = (replacement_key), \
.enabled = NULL \
})
// clang-format on

+ 24
- 0
quantum/process_keycode/process_key_override_private.h View File

@ -0,0 +1,24 @@
/*
* Copyright 2021 Jonas Gessner
*
* 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 <stdint.h>
#include "action.h"
bool process_key_override(const uint16_t keycode, const keyrecord_t *const record);
void matrix_scan_key_override(void);

+ 16
- 3
quantum/quantum.c View File

@ -58,12 +58,16 @@ float bell_song[][2] = SONG(TERMINAL_SOUND);
# include "process_auto_shift.h"
#endif
static void do_code16(uint16_t code, void (*f)(uint8_t)) {
#ifdef KEY_OVERRIDE_ENABLE
# include "process_key_override_private.h"
#endif
uint8_t extract_mod_bits(uint16_t code) {
switch (code) {
case QK_MODS ... QK_MODS_MAX:
break;
default:
return;
return 0;
}
uint8_t mods_to_send = 0;
@ -80,9 +84,11 @@ static void do_code16(uint16_t code, void (*f)(uint8_t)) {
if (code & QK_LGUI) mods_to_send |= MOD_BIT(KC_LGUI);
}
f(mods_to_send);
return mods_to_send;
}
static void do_code16(uint16_t code, void (*f)(uint8_t)) { f(extract_mod_bits(code)); }
void register_code16(uint16_t code) {
if (IS_MOD(code) || code == KC_NO) {
do_code16(code, register_mods);
@ -243,6 +249,9 @@ bool process_record_quantum(keyrecord_t *record) {
#if (defined(AUDIO_ENABLE) || (defined(MIDI_ENABLE) && defined(MIDI_BASIC))) && !defined(NO_MUSIC_MODE)
process_music(keycode, record) &&
#endif
#ifdef KEY_OVERRIDE_ENABLE
process_key_override(keycode, record) &&
#endif
#ifdef TAP_DANCE_ENABLE
process_tap_dance(keycode, record) &&
#endif
@ -408,6 +417,10 @@ void matrix_scan_quantum() {
matrix_scan_music();
#endif
#ifdef KEY_OVERRIDE_ENABLE
matrix_scan_key_override();
#endif
#ifdef SEQUENCER_ENABLE
matrix_scan_sequencer();
#endif


+ 4
- 0
quantum/quantum.h View File

@ -118,6 +118,10 @@ extern layer_state_t layer_state;
# include "process_unicodemap.h"
#endif
#ifdef KEY_OVERRIDE_ENABLE
# include "process_key_override.h"
#endif
#ifdef TAP_DANCE_ENABLE
# include "process_tap_dance.h"
#endif


+ 5
- 0
quantum/quantum_keycodes.h View File

@ -514,6 +514,11 @@ enum quantum_keycodes {
// RGB underglow/matrix (continued)
RGB_MODE_TWINKLE,
// Key Overrides
KEY_OVERRIDE_TOGGLE,
KEY_OVERRIDE_ON,
KEY_OVERRIDE_OFF,
// Start of custom keycode range for keyboards and keymaps - always leave at the end
SAFE_RANGE
};


+ 1
- 0
show_options.mk View File

@ -44,6 +44,7 @@ OTHER_OPTION_NAMES = \
AUTO_SHIFT_MODIFIERS \
COMBO_ENABLE \
KEY_LOCK_ENABLE \
KEY_OVERRIDE_ENABLE \
LEADER_ENABLE \
PRINTING_ENABLE \
STENO_ENABLE \


+ 28
- 0
tmk_core/common/action_util.c View File

@ -27,6 +27,10 @@ extern keymap_config_t keymap_config;
static uint8_t real_mods = 0;
static uint8_t weak_mods = 0;
static uint8_t macro_mods = 0;
#ifdef KEY_OVERRIDE_ENABLE
static uint8_t weak_override_mods = 0;
static uint8_t suppressed_mods = 0;
#endif
#ifdef USB_6KRO_ENABLE
# define RO_ADD(a, b) ((a + b) % KEYBOARD_REPORT_KEYS)
@ -229,6 +233,7 @@ void send_keyboard_report(void) {
keyboard_report->mods = real_mods;
keyboard_report->mods |= weak_mods;
keyboard_report->mods |= macro_mods;
#ifndef NO_ACTION_ONESHOT
if (oneshot_mods) {
# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0))
@ -244,6 +249,13 @@ void send_keyboard_report(void) {
}
#endif
#ifdef KEY_OVERRIDE_ENABLE
// These need to be last to be able to properly control key overrides
keyboard_report->mods &= ~suppressed_mods;
keyboard_report->mods |= weak_override_mods;
#endif
host_keyboard_send(keyboard_report);
}
@ -299,6 +311,22 @@ void set_weak_mods(uint8_t mods) { weak_mods = mods; }
*/
void clear_weak_mods(void) { weak_mods = 0; }
#ifdef KEY_OVERRIDE_ENABLE
/** \brief set weak mods used by key overrides. DO not call this manually
*/
void set_weak_override_mods(uint8_t mods) { weak_override_mods = mods; }
/** \brief clear weak mods used by key overrides. DO not call this manually
*/
void clear_weak_override_mods(void) { weak_override_mods = 0; }
/** \brief set suppressed mods used by key overrides. DO not call this manually
*/
void set_suppressed_override_mods(uint8_t mods) { suppressed_mods = mods; }
/** \brief clear suppressed mods used by key overrides. DO not call this manually
*/
void clear_suppressed_override_mods(void) { suppressed_mods = 0; }
#endif
/* macro modifier */
/** \brief get macro mods
*


Loading…
Cancel
Save