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] Feature: Add Key Cancellation #24000

Open
wants to merge 16 commits into
base: develop
Choose a base branch
from
Open
1 change: 1 addition & 0 deletions builddefs/generic_features.mk
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ GENERIC_FEATURES = \
HAPTIC \
KEY_LOCK \
KEY_OVERRIDE \
KEY_CANCELLATION \
LEADER \
MAGIC \
MOUSEKEY \
Expand Down
3 changes: 2 additions & 1 deletion builddefs/show_options.mk
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ OTHER_OPTION_NAMES = \
CAPS_WORD_ENABLE \
AUTOCORRECT_ENABLE \
TRI_LAYER_ENABLE \
REPEAT_KEY_ENABLE
REPEAT_KEY_ENABLE \
KEY_CANCELLATION_ENABLE

define NAME_ECHO
@printf " %-30s = %-16s # %s\\n" "$1" "$($1)" "$(origin $1)"
Expand Down
25 changes: 25 additions & 0 deletions data/constants/keycodes/keycodes_0.0.5_quantum.hjson
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"keycodes": {
"0x7C7B": {
"group": "quantum",
"key": "QK_KEY_CANCELLATION_ON",
"aliases": [
"KI_ON"
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
]
},
"0x7C7C": {
"group": "quantum",
"key": "QK_KEY_CANCELLATION_OFF",
"aliases": [
"KI_OFF"
zvecr marked this conversation as resolved.
Show resolved Hide resolved
]
},
"0x7C7D": {
"group": "quantum",
"key": "QK_KEY_CANCELLATION_TOGGLE",
"aliases": [
"KI_TOGG"
zvecr marked this conversation as resolved.
Show resolved Hide resolved
]
}
}
}
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 @@
"JOYSTICK_DRIVER": {"info_key": "joystick.driver"},
"JOYSTICK_ENABLE": {"info_key": "joystick.enabled", "value_type": "bool"},
"KEYBOARD_SHARED_EP": {"info_key": "usb.shared_endpoint.keyboard", "value_type": "bool"},
"KEY_CANCELLATION_ENABLE": {"info_key": "key_cancellation.enabled", "value_type": "bool"},
"LAYOUTS": {"info_key": "community_layouts", "value_type": "list"},
"LED_MATRIX_DRIVER": {"info_key": "led_matrix.driver"},
"LTO_ENABLE": {"info_key": "build.lto", "value_type": "bool"},
Expand Down
6 changes: 6 additions & 0 deletions data/schemas/keyboard.jsonschema
Original file line number Diff line number Diff line change
Expand Up @@ -372,6 +372,12 @@
}
}
},
"key_cancellation": {
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
}
},
"keycodes": {"$ref": "qmk.definitions.v1#/keycode_decl_array"},
"layout_aliases": {
"type": "object",
Expand Down
1 change: 1 addition & 0 deletions docs/_sidebar.json
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@
{ "text": "EEPROM", "link": "/feature_eeprom" },
{ "text": "Key Lock", "link": "/features/key_lock" },
{ "text": "Key Overrides", "link": "/features/key_overrides" },
{ "text": "Key Cancellation", "link": "/features/key_cancellation" },
{ "text": "Layers", "link": "/feature_layers" },
{ "text": "One Shot Keys", "link": "/one_shot_keys" },
{ "text": "OS Detection", "link": "/features/os_detection" },
Expand Down
60 changes: 60 additions & 0 deletions docs/features/key_cancellation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Key Cancellation

Upon a selected key press down, if another key is pressed, it will release another key. This is similar to the "Kill switch" function created by Realforce.

## How do I enable key cancellation {#how-do-i-enable-key-cancellation}

In `info.json` add this:
```json
"features" : {
// ... other features
"key_cancellation" : true
}
```
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved

**OR**

in `rules.mk` add this:
```make
KEY_CANCELLATION_ENABLE = yes
```

By default, key cancellation is disabled. To enable it, you need to use the `KI_TOGG` or `KI_ON` keycode to enable it. The status is stored in persistent memory, so you shouldn't need to enable it again.

Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
### Keycodes {#keycodes}
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved

|Keycode |Aliases |Description |
|-------------------------|---------|------------------------------------------------|
|`QK_KEY_CANCELLATION_ON` |`KI_ON` |Turns on the key cancellation feature. |
|`QK_KEY_CANCELLATION_OFF` |`KI_OFF` |Turns off the key cancellation feature. |
|`QK_KEY_CANCELLATION_TOGGLE`|`KI_TOGG`|Toggles the status of the key cancellation feature.|

## User Callback Functions

```c
bool process_key_cancellation_user(uint16_t keycode, keyrecord_t *record);
```

## Process Key Cancellation Example

```c
const key_cancellation_t PROGMEM key_cancellation_list[] = {
// on key down
// | key to be released
// | |
[0] = {KC_D, KC_A},
[1] = {KC_A, KC_D},
[2] = {KC_A, KC_F},
};
```

Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
## Key Cancellation Status

Additional user callback functions to manipulate Key Cancellation:

| Function | Description |
|------------------------------|------------------------------------------------|
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
| `key_cancellation_enable()` | Turns Key Cancellation on. |
| `key_cancellation_disable()` | Turns Key Cancellation off. |
| `key_cancellation_toggle()` | Toggles Key Cancellation. |
| `key_cancellation_is_enabled()` | Returns true if Key Cancellation is currently on. |
6 changes: 6 additions & 0 deletions quantum/eeconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ typedef struct PACKED {
#define EECONFIG_KEYMAP_SWAP_GRAVE_ESC (1 << 5)
#define EECONFIG_KEYMAP_SWAP_BACKSLASH_BACKSPACE (1 << 6)
#define EECONFIG_KEYMAP_NKRO (1 << 7)
#define EECONFIG_KEYMAP_SWAP_LCTL_LGUI (1 << 8)
#define EECONFIG_KEYMAP_SWAP_RCTL_RGUI (1 << 9)
#define EECONFIG_KEYMAP_ONESHOT (1 << 10)
#define EECONFIG_KEYMAP_SWAP_ESC_CAPS (1 << 11)
#define EECONFIG_KEYMAP_AUTOCORRECT (1 << 12)
#define EECONFIG_KEYMAP_KEY_CANCELLATION (1 << 13)

bool eeconfig_is_enabled(void);
bool eeconfig_is_disabled(void);
Expand Down
1 change: 1 addition & 0 deletions quantum/keycode_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef union {
bool oneshot_enable : 1;
bool swap_escape_capslock : 1;
bool autocorrect_enable : 1;
bool key_cancellation_enable : 1;
};
} keymap_config_t;

Expand Down
10 changes: 8 additions & 2 deletions quantum/keycodes.h
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,9 @@ enum qk_keycode_defines {
QK_TRI_LAYER_UPPER = 0x7C78,
QK_REPEAT_KEY = 0x7C79,
QK_ALT_REPEAT_KEY = 0x7C7A,
QK_KEY_CANCELLATION_ON = 0x7C7B,
QK_KEY_CANCELLATION_OFF = 0x7C7C,
QK_KEY_CANCELLATION_TOGGLE = 0x7C7D,
QK_KB_0 = 0x7E00,
QK_KB_1 = 0x7E01,
QK_KB_2 = 0x7E02,
Expand Down Expand Up @@ -1419,6 +1422,9 @@ enum qk_keycode_defines {
TL_UPPR = QK_TRI_LAYER_UPPER,
QK_REP = QK_REPEAT_KEY,
QK_AREP = QK_ALT_REPEAT_KEY,
KI_ON = QK_KEY_CANCELLATION_ON,
KI_OFF = QK_KEY_CANCELLATION_OFF,
KI_TOGG = QK_KEY_CANCELLATION_TOGGLE,
};

// Range Helpers
Expand Down Expand Up @@ -1473,7 +1479,7 @@ enum qk_keycode_defines {
#define IS_UNDERGLOW_KEYCODE(code) ((code) >= QK_UNDERGLOW_TOGGLE && (code) <= QK_UNDERGLOW_SPEED_DOWN)
#define IS_RGB_KEYCODE(code) ((code) >= RGB_MODE_PLAIN && (code) <= RGB_MODE_TWINKLE)
#define IS_RGB_MATRIX_KEYCODE(code) ((code) >= QK_RGB_MATRIX_ON && (code) <= QK_RGB_MATRIX_SPEED_DOWN)
#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_ALT_REPEAT_KEY)
#define IS_QUANTUM_KEYCODE(code) ((code) >= QK_BOOTLOADER && (code) <= QK_KEY_CANCELLATION_TOGGLE)
#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)

Expand All @@ -1498,6 +1504,6 @@ enum qk_keycode_defines {
#define UNDERGLOW_KEYCODE_RANGE QK_UNDERGLOW_TOGGLE ... QK_UNDERGLOW_SPEED_DOWN
#define RGB_KEYCODE_RANGE RGB_MODE_PLAIN ... RGB_MODE_TWINKLE
#define RGB_MATRIX_KEYCODE_RANGE QK_RGB_MATRIX_ON ... QK_RGB_MATRIX_SPEED_DOWN
#define QUANTUM_KEYCODE_RANGE QK_BOOTLOADER ... QK_ALT_REPEAT_KEY
#define QUANTUM_KEYCODE_RANGE QK_BOOTLOADER ... QK_KEY_CANCELLATION_TOGGLE
#define KB_KEYCODE_RANGE QK_KB_0 ... QK_KB_31
#define USER_KEYCODE_RANGE QK_USER_0 ... QK_USER_31
22 changes: 22 additions & 0 deletions quantum/keymap_introspection.c
Original file line number Diff line number Diff line change
Expand Up @@ -165,3 +165,25 @@ __attribute__((weak)) const key_override_t* key_override_get(uint16_t key_overri
}

#endif // defined(KEY_OVERRIDE_ENABLE)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Key Cancellation

#if defined(KEY_CANCELLATION_ENABLE)
uint16_t key_cancellation_count_raw(void) {
return sizeof(key_cancellation_list) / sizeof(key_cancellation_t);
}
__attribute__((weak)) uint16_t key_cancellation_count(void) {
return key_cancellation_count_raw();
}

key_cancellation_t key_cancellation_get_raw(uint16_t idx) {
key_cancellation_t ret;
memcpy_P(&ret, &key_cancellation_list[idx], sizeof(key_cancellation_t));
return ret;
}

__attribute__((weak)) key_cancellation_t key_cancellation_get(uint16_t idx) {
return key_cancellation_get_raw(idx);
}
#endif // defined(KEY_CANCELLATION_ENABLE)
17 changes: 17 additions & 0 deletions quantum/keymap_introspection.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,3 +109,20 @@ const key_override_t* key_override_get_raw(uint16_t key_override_idx);
const key_override_t* key_override_get(uint16_t key_override_idx);

#endif // defined(KEY_OVERRIDE_ENABLE)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Key Cancellation

#if defined(KEY_CANCELLATION_ENABLE)
// Forward declaration of key_cancellation_t so we don't need to deal with header reordering
struct key_cancellation_t;
typedef struct key_cancellation_t key_cancellation_t;
// Get the number of key cancellations defined in the user's keymap, stored in firmware rather than any other persistent storage
uint16_t key_cancellation_count_raw(void);
// Get the number of key cancellations defined in the user's keymap, potentially stored dynamically
uint16_t key_cancellation_count(void);
// Get the keycodes for the key cancellation, stored in firmware rather than any other persistent storage
key_cancellation_t key_cancellation_get_raw(uint16_t idx);
// Get the keycodes for the key cancellation, stored in firmware, potentially stored dynamically
key_cancellation_t key_cancellation_get(uint16_t idx);
#endif // defined(KEY_CANCELLATION_ENABLE)
122 changes: 122 additions & 0 deletions quantum/process_keycode/process_key_cancellation.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
/* Copyright 2024 Harrison Chan (@xelus22)
*
* 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/>.
*/
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved

#include "process_key_cancellation.h"
#include <string.h>
#include "keycodes.h"
#include "keycode_config.h"
#include "action_util.h"
#include "keymap_introspection.h"

/**
* @brief function for querying the enabled state of key cancellation
*
* @return true if enabled
* @return false if disabled
*/
bool key_cancellation_is_enabled(void) {
return keymap_config.key_cancellation_enable;
}

/**
* @brief Enables key cancellation and saves state to eeprom
*
*/
void key_cancellation_enable(void) {
keymap_config.key_cancellation_enable = true;
eeconfig_update_keymap(keymap_config.raw);
}

/**
* @brief Disables key cancellation and saves state to eeprom
*
*/
void key_cancellation_disable(void) {
keymap_config.key_cancellation_enable = false;
eeconfig_update_keymap(keymap_config.raw);
}

/**
* @brief Toggles key cancellation's status and save state to eeprom
*
*/
void key_cancellation_toggle(void) {
keymap_config.key_cancellation_enable = !keymap_config.key_cancellation_enable;
eeconfig_update_keymap(keymap_config.raw);
}

/**
* @brief handler for user to override whether key cancellation should process this keypress
*
* @param keycode Keycode registered by matrix press, per keymap
* @param record keyrecord_t structure
* @return true Allow key cancellation
* @return false Stop processing and escape from key cancellation
*/
__attribute__((weak)) bool process_key_cancellation_user(uint16_t keycode, keyrecord_t *record) {
return true;
}

/**
* @brief Process handler for key_cancellation feature
*
* @param keycode Keycode registered by matrix press, per keymap
* @param record keyrecord_t structure
* @return true Continue processing keycodes, and send to host
* @return false Stop processing keycodes, and don't send to host
*/
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
bool process_key_cancellation(uint16_t keycode, keyrecord_t *record) {
if ((keycode >= QK_KEY_CANCELLATION_ON && keycode <= QK_KEY_CANCELLATION_TOGGLE) && record->event.pressed) {
if (keycode == QK_KEY_CANCELLATION_ON) {
key_cancellation_enable();
} else if (keycode == QK_KEY_CANCELLATION_OFF) {
key_cancellation_disable();
} else if (keycode == QK_KEY_CANCELLATION_TOGGLE) {
key_cancellation_toggle();
} else {
return true;
}

return false;
}
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved

if (!keymap_config.key_cancellation_enable) {
return true;
}

if (!record->event.pressed) {
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

// only supports basic keycodes
if (!IS_BASIC_KEYCODE(keycode)) {
return true;
}

if (!process_key_cancellation_user(keycode, record)) {
return true;
}

// loop through all the keyup and keydown events
for (int i = 0; i < key_cancellation_count(); i++) {
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
key_cancellation_t key_cancellation = key_cancellation_get(i);
if (keycode == key_cancellation.press) {
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
del_key(key_cancellation.unpress);
}
}

return true;
}
21 changes: 21 additions & 0 deletions quantum/process_keycode/process_key_cancellation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright 2024 Harrison Chan (@xelus22)
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved

#pragma once

#include <stdint.h>
#include <stdbool.h>
#include "action.h"

#define NUM_INTERRUPT_KEYCODES 2

Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
typedef struct key_cancellation_t {
uint16_t press, unpress;
} key_cancellation_t;

bool process_key_cancellation(uint16_t keycode, keyrecord_t *record);
bool process_key_cancellation_user(uint16_t keycode, keyrecord_t *record);

bool key_cancellation_is_enabled(void);
void key_cancellation_enable(void);
void key_cancellation_disable(void);
void key_cancellation_toggle(void);
Loading