diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk index dc34a642307d..d4ee6de50fba 100644 --- a/builddefs/generic_features.mk +++ b/builddefs/generic_features.mk @@ -36,6 +36,7 @@ GENERIC_FEATURES = \ HAPTIC \ KEY_LOCK \ KEY_OVERRIDE \ + KEY_CANCELLATION \ LEADER \ MAGIC \ MOUSEKEY \ diff --git a/builddefs/show_options.mk b/builddefs/show_options.mk index 81d8400a8064..7a48c23c462f 100644 --- a/builddefs/show_options.mk +++ b/builddefs/show_options.mk @@ -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)" diff --git a/data/constants/keycodes/keycodes_0.0.5_quantum.hjson b/data/constants/keycodes/keycodes_0.0.5_quantum.hjson new file mode 100644 index 000000000000..097258dfc8cd --- /dev/null +++ b/data/constants/keycodes/keycodes_0.0.5_quantum.hjson @@ -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" + ] + } + } +} diff --git a/docs/_sidebar.json b/docs/_sidebar.json index 5935865a7ade..97455281628c 100644 --- a/docs/_sidebar.json +++ b/docs/_sidebar.json @@ -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" }, diff --git a/docs/features/key_cancellation.md b/docs/features/key_cancellation.md new file mode 100644 index 000000000000..6a0d5c3f88b4 --- /dev/null +++ b/docs/features/key_cancellation.md @@ -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. + +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 +``` + +## 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. | diff --git a/quantum/eeconfig.h b/quantum/eeconfig.h index fa0dd799d195..66773cf33268 100644 --- a/quantum/eeconfig.h +++ b/quantum/eeconfig.h @@ -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); diff --git a/quantum/keycode_config.h b/quantum/keycode_config.h index d1352c302ea0..5c02b637e3c5 100644 --- a/quantum/keycode_config.h +++ b/quantum/keycode_config.h @@ -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; diff --git a/quantum/keycodes.h b/quantum/keycodes.h index f461e4a586e2..56d2fbad9217 100644 --- a/quantum/keycodes.h +++ b/quantum/keycodes.h @@ -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, @@ -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 @@ -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) @@ -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 diff --git a/quantum/keymap_introspection.c b/quantum/keymap_introspection.c index 327df6e277b6..2dd5ef001ec6 100644 --- a/quantum/keymap_introspection.c +++ b/quantum/keymap_introspection.c @@ -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) diff --git a/quantum/keymap_introspection.h b/quantum/keymap_introspection.h index 719825c674d4..f026c6c03f1a 100644 --- a/quantum/keymap_introspection.h +++ b/quantum/keymap_introspection.h @@ -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) diff --git a/quantum/process_keycode/process_key_cancellation.c b/quantum/process_keycode/process_key_cancellation.c new file mode 100644 index 000000000000..2bc02741d282 --- /dev/null +++ b/quantum/process_keycode/process_key_cancellation.c @@ -0,0 +1,111 @@ +// Copyright 2024 Harrison Chan (@xelus22) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "process_key_cancellation.h" +#include +#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 + */ +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) { + 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) { + del_key(key_cancellation.unpress); + } + } + + return true; +} diff --git a/quantum/process_keycode/process_key_cancellation.h b/quantum/process_keycode/process_key_cancellation.h new file mode 100644 index 000000000000..ba96c08d212f --- /dev/null +++ b/quantum/process_keycode/process_key_cancellation.h @@ -0,0 +1,20 @@ +// Copyright 2024 Harrison Chan (@xelus22) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include +#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); diff --git a/quantum/quantum.c b/quantum/quantum.c index 011f9d73e4af..1785b96c65ad 100644 --- a/quantum/quantum.c +++ b/quantum/quantum.c @@ -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) @@ -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; diff --git a/quantum/quantum.h b/quantum/quantum.h index 5446ab1ad719..e1f5cd678e3b 100644 --- a/quantum/quantum.h +++ b/quantum/quantum.h @@ -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) diff --git a/tests/key_cancellation/config.h b/tests/key_cancellation/config.h new file mode 100644 index 000000000000..5b5309bab1fb --- /dev/null +++ b/tests/key_cancellation/config.h @@ -0,0 +1,6 @@ +// Copyright 2024 Harrison Chan (@xelus22) +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include "test_common.h" diff --git a/tests/key_cancellation/test.mk b/tests/key_cancellation/test.mk new file mode 100644 index 000000000000..51fddf714d0e --- /dev/null +++ b/tests/key_cancellation/test.mk @@ -0,0 +1,10 @@ +# Copyright 2021 Christopher Courtney, aka Drashna Jael're (@drashna) +# SPDX-License-Identifier: GPL-2.0-or-later + +# -------------------------------------------------------------------------------- +# Keep this file, even if it is empty, as a marker that this folder contains tests +# -------------------------------------------------------------------------------- + +KEY_CANCELLATION_ENABLE = yes + +INTROSPECTION_KEYMAP_C = test_key_cancellations.c diff --git a/tests/key_cancellation/test_key_cancellation.cpp b/tests/key_cancellation/test_key_cancellation.cpp new file mode 100644 index 000000000000..5d2f1d77826c --- /dev/null +++ b/tests/key_cancellation/test_key_cancellation.cpp @@ -0,0 +1,180 @@ +// Copyright 2024 Harrison Chan (@xelus22) +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "keycode.h" +#include "test_common.hpp" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::InSequence; + +class KeyInterrupt : public TestFixture { + public: + void SetUp() override { + key_cancellation_enable(); + } +}; + +// Test that verifies enable/disable/toggling works +TEST_F(KeyInterrupt, OnOffToggle) { + EXPECT_EQ(key_cancellation_is_enabled(), true); + + key_cancellation_disable(); + EXPECT_EQ(key_cancellation_is_enabled(), false); + key_cancellation_disable(); + EXPECT_EQ(key_cancellation_is_enabled(), false); + + key_cancellation_enable(); + EXPECT_EQ(key_cancellation_is_enabled(), true); + key_cancellation_enable(); + EXPECT_EQ(key_cancellation_is_enabled(), true); + + key_cancellation_toggle(); + EXPECT_EQ(key_cancellation_is_enabled(), false); + key_cancellation_toggle(); + EXPECT_EQ(key_cancellation_is_enabled(), true); +} + +// Test that holding A, and then pressing D, releases A and sends only D +TEST_F(KeyInterrupt, a_hold_d_key_cancellation) { + TestDriver driver; + KeymapKey key_a(0, 0, 0, KC_A); + KeymapKey key_d(0, 1, 0, KC_D); + + set_keymap({key_a, key_d}); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// Test that holding D, and then pressing A, releases D and sends only A +TEST_F(KeyInterrupt, d_hold_a_key_cancellation) { + TestDriver driver; + KeymapKey key_a(0, 0, 0, KC_A); + KeymapKey key_d(0, 1, 0, KC_D); + + set_keymap({key_a, key_d}); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// Test that holding W, holding A, and then pressing D, does NOT release W, but only releases A +TEST_F(KeyInterrupt, w_a_hold_d_key_cancellation) { + TestDriver driver; + KeymapKey key_a(0, 0, 0, KC_A); + KeymapKey key_d(0, 1, 0, KC_D); + KeymapKey key_w(0, 2, 0, KC_W); + + set_keymap({key_a, key_d, key_w}); + + /* Press W key */ + EXPECT_REPORT(driver, (KC_W)); + key_w.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A, KC_W)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D, KC_W)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// Test that holding D and F, then press A, releases D and F, and sends only A +TEST_F(KeyInterrupt, d_f_hold_a_key_cancellation) { + TestDriver driver; + KeymapKey key_a(0, 0, 0, KC_A); + KeymapKey key_d(0, 1, 0, KC_D); + KeymapKey key_f(0, 2, 0, KC_F); + + set_keymap({key_a, key_d, key_f}); + + /* Press F key */ + EXPECT_REPORT(driver, (KC_F)); + key_f.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D, KC_F)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// 2 Consecutive key cancellation, A, D, A, D +TEST_F(KeyInterrupt, d_and_a_consecutive_key_cancellation) { + TestDriver driver; + KeymapKey key_a(0, 0, 0, KC_A); + KeymapKey key_d(0, 1, 0, KC_D); + KeymapKey key_f(0, 2, 0, KC_F); + + set_keymap({key_a, key_d, key_f}); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Check that releasing A does nothing */ + EXPECT_NO_REPORT(driver); + key_a.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press A key */ + EXPECT_REPORT(driver, (KC_A)); + key_a.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Check that releasing D does nothing */ + EXPECT_NO_REPORT(driver); + key_d.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + /* Press D key */ + EXPECT_REPORT(driver, (KC_D)); + key_d.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/key_cancellation/test_key_cancellations.c b/tests/key_cancellation/test_key_cancellations.c new file mode 100644 index 000000000000..463b088ea321 --- /dev/null +++ b/tests/key_cancellation/test_key_cancellations.c @@ -0,0 +1,11 @@ +// Copyright 2024 Harrison Chan (@xelus22) +// SPDX-License-Identifier: GPL-2.0-or-later +#include "quantum.h" + +//clang-format off +const key_cancellation_t PROGMEM key_cancellation_list[] = { + [0] = {KC_D, KC_A}, + [1] = {KC_A, KC_D}, + [2] = {KC_A, KC_F}, +}; +// clang-format on diff --git a/tests/test_common/keycode_table.cpp b/tests/test_common/keycode_table.cpp index 4c28ab4d20dd..88b9507b900d 100644 --- a/tests/test_common/keycode_table.cpp +++ b/tests/test_common/keycode_table.cpp @@ -687,6 +687,9 @@ std::map KEYCODE_ID_TABLE = { {QK_TRI_LAYER_UPPER, "QK_TRI_LAYER_UPPER"}, {QK_REPEAT_KEY, "QK_REPEAT_KEY"}, {QK_ALT_REPEAT_KEY, "QK_ALT_REPEAT_KEY"}, + {QK_KEY_CANCELLATION_ON, "QK_KEY_CANCELLATION_ON"}, + {QK_KEY_CANCELLATION_OFF, "QK_KEY_CANCELLATION_OFF"}, + {QK_KEY_CANCELLATION_TOGGLE, "QK_KEY_CANCELLATION_TOGGLE"}, {QK_KB_0, "QK_KB_0"}, {QK_KB_1, "QK_KB_1"}, {QK_KB_2, "QK_KB_2"},