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": [
"KX_CAON"
]
},
"0x7C7C": {
"group": "quantum",
"key": "QK_KEY_CANCELLATION_OFF",
"aliases": [
"KX_CAOF"
]
},
"0x7C7D": {
"group": "quantum",
"key": "QK_KEY_CANCELLATION_TOGGLE",
"aliases": [
"KX_CATG"
]
}
}
}
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
71 changes: 71 additions & 0 deletions docs/features/key_cancellation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# Key Cancellation

This feature iterates over the `key_cancellation_list`, and upon any matches of key down, all specified keys are released.

Imagine you're holding down the `A` key. If you then press the `D` key, the `A` key will stop being active. To use the `A` key again, you need to press it once more.

## Usage {#usage}

Add the following to your `rules.mk`
```make
KEY_CANCELLATION_ENABLE = yes
```

By default, key cancellation is disabled even after adding to `rules.mk`. To enable it, you need to use the `KX_CATG` or `KX_CAON` 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
Your `keymap.c` will then need a Key Cancellation list definition:

```c
#if defined(KEY_CANCELLATION_ENABLE)
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},
};
#endif
```

## Examples {#examples}
### SOCD WASD Example

```c
#if defined(KEY_CANCELLATION_ENABLE)
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_W, KC_S},
[3] = {KC_S, KC_W},
};
#endif
```

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

|Keycode |Aliases |Description |
|----------------------------|----------|---------------------------------------------------|
|`QK_KEY_CANCELLATION_ON` |`KX_CAON` |Turns on the key cancellation feature. |
|`QK_KEY_CANCELLATION_OFF` |`KX_CAOF` |Turns off the key cancellation feature. |
|`QK_KEY_CANCELLATION_TOGGLE`|`KX_CATG` |Toggles the status of the key cancellation feature.|

## User Callback Functions

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


## Functions {#functions}

Additional functions provided to manipulate Key Cancellation:

| Function | Description |
|---------------------------------|---------------------------------------------------|
| `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,
KX_CAON = QK_KEY_CANCELLATION_ON,
KX_CAOF = QK_KEY_CANCELLATION_OFF,
KX_CATG = 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)
111 changes: 111 additions & 0 deletions quantum/process_keycode/process_key_cancellation.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright 2024 Harrison Chan (@xelus22)
// SPDX-License-Identifier: GPL-2.0-or-later

#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 (record->event.pressed) {
switch (keycode) {
case QK_KEY_CANCELLATION_ON:
key_cancellation_enable();
return false;
case QK_KEY_CANCELLATION_OFF:
key_cancellation_disable();
return false;
case QK_KEY_CANCELLATION_TOGGLE:
key_cancellation_toggle();
return false;
default:
break;
}
}

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 (uint16_t i = 0; i < key_cancellation_count(); i++) {
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;
}
20 changes: 20 additions & 0 deletions quantum/process_keycode/process_key_cancellation.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2024 Harrison Chan (@xelus22)
Xelus22 marked this conversation as resolved.
Show resolved Hide resolved
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

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

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);
7 changes: 7 additions & 0 deletions quantum/quantum.c
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,10 @@
# include "process_unicode_common.h"
#endif

#ifdef KEY_CANCELLATION_ENABLE
# include "process_key_cancellation.h"
#endif

#ifdef AUDIO_ENABLE
# ifndef GOODBYE_SONG
# define GOODBYE_SONG SONG(GOODBYE_SOUND)
Expand Down Expand Up @@ -393,6 +397,9 @@ bool process_record_quantum(keyrecord_t *record) {
#endif
#ifdef TRI_LAYER_ENABLE
process_tri_layer(keycode, record) &&
#endif
#ifdef KEY_CANCELLATION_ENABLE
process_key_cancellation(keycode, record) &&
#endif
true)) {
return false;
Expand Down
4 changes: 4 additions & 0 deletions quantum/quantum.h
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,10 @@ extern layer_state_t layer_state;
# include "os_detection.h"
#endif

#ifdef KEY_CANCELLATION_ENABLE
# include "process_key_cancellation.h"
#endif

void set_single_persistent_default_layer(uint8_t default_layer);

#define IS_LAYER_ON(layer) layer_state_is(layer)
Expand Down
Loading