From 25396e0e4cbf5cd3685bcd87736ed64e93785e6c Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Sun, 5 Jan 2025 19:23:44 -0800 Subject: [PATCH 1/6] Add Chordal Hold to core. --- data/mappings/info_config.hjson | 1 + data/schemas/keyboard.jsonschema | 7 +- docs/reference_info_json.md | 4 + docs/tap_hold.md | 163 ++++++++++++++ lib/python/qmk/cli/generate/keyboard_c.py | 135 +++++++++++- quantum/action_tapping.c | 255 ++++++++++++++++++++-- quantum/action_tapping.h | 67 ++++++ 7 files changed, 607 insertions(+), 25 deletions(-) diff --git a/data/mappings/info_config.hjson b/data/mappings/info_config.hjson index b61ca040714..f30d4328ce7 100644 --- a/data/mappings/info_config.hjson +++ b/data/mappings/info_config.hjson @@ -192,6 +192,7 @@ "SPLIT_WPM_ENABLE": {"info_key": "split.transport.sync.wpm", "value_type": "flag"}, // Tapping + "CHORDAL_HOLD": {"info_key": "tapping.chordal_hold", "value_type": "flag"}, "HOLD_ON_OTHER_KEY_PRESS": {"info_key": "tapping.hold_on_other_key_press", "value_type": "flag"}, "HOLD_ON_OTHER_KEY_PRESS_PER_KEY": {"info_key": "tapping.hold_on_other_key_press_per_key", "value_type": "flag"}, "PERMISSIVE_HOLD": {"info_key": "tapping.permissive_hold", "value_type": "flag"}, diff --git a/data/schemas/keyboard.jsonschema b/data/schemas/keyboard.jsonschema index 0d9181ac262..b34ef08ba14 100644 --- a/data/schemas/keyboard.jsonschema +++ b/data/schemas/keyboard.jsonschema @@ -385,7 +385,11 @@ "h": {"$ref": "qmk.definitions.v1#/key_unit"}, "w": {"$ref": "qmk.definitions.v1#/key_unit"}, "x": {"$ref": "qmk.definitions.v1#/key_unit"}, - "y": {"$ref": "qmk.definitions.v1#/key_unit"} + "y": {"$ref": "qmk.definitions.v1#/key_unit"}, + "hand": { + "type": "string", + "enum": ["L", "R", "*"] + } } } } @@ -863,6 +867,7 @@ "tapping": { "type": "object", "properties": { + "chordal_hold": {"type": "boolean"}, "force_hold": {"type": "boolean"}, "force_hold_per_key": {"type": "boolean"}, "ignore_mod_tap_interrupt": {"type": "boolean"}, diff --git a/docs/reference_info_json.md b/docs/reference_info_json.md index 2db2cd14277..23f28c4a1f4 100644 --- a/docs/reference_info_json.md +++ b/docs/reference_info_json.md @@ -74,6 +74,8 @@ You can create `info.json` files at every level under `qmk_firmware/keyboards/= 6u, it is probably the spacebar. + i = [i for i, key in enumerate(self.keys) if key.w >= 6.0] + self.spacebar = self.keys[i[0]] if len(i) == 1 else None + + def is_symmetric(self, tol: float = 0.02): + """Whether the key positions are symmetric about x_mid.""" + x = sorted([key.cx for key in self.keys]) + for i in range(len(x)): + x_i_mirrored = 2.0 * self.x_mid - x[i] + # Find leftmost x element greater than or equal to (x_i_mirrored - tol). + j = bisect.bisect_left(x, x_i_mirrored - tol) + if j == len(x) or abs(x[j] - x_i_mirrored) > tol: + return False + + return True + + def widest_horizontal_gap(self): + """Finds the x midpoint of the widest horizontal gap between keys.""" + x = sorted([key.cx for key in self.keys]) + x_mid = self.x_mid + max_sep = 0 + for i in range(len(x) - 1): + sep = x[i + 1] - x[i] + if sep > max_sep: + max_sep = sep + x_mid = (x[i + 1] + x[i]) / 2 + + return x_mid + + +def _gen_chordal_hold_layout(info_data): + """Convert info.json content to chordal_hold_layout + """ + # NOTE: If there are multiple layouts, only the first is read. + for layout_name, layout_json in info_data['layouts'].items(): + layout = Layout(layout_json) + break + + if layout.is_symmetric(): + # If the layout is symmetric (e.g. most split keyboards), guess the + # handedness based on the sign of (x - layout.x_mid). + hand_signs = [key.x - layout.x_mid for key in layout.keys] + elif layout.spacebar is not None: + # If the layout has a spacebar, form a dividing line through the spacebar, + # nearly vertical but with a slight angle to follow typical row stagger. + x0 = layout.spacebar.cx - 0.05 + y0 = layout.spacebar.cy - 1.0 + hand_signs = [(key.x - x0) - (key.y - y0) / 3.0 for key in layout.keys] + else: + # Fallback: assume handedness based on the widest horizontal separation. + x_mid = layout.widest_horizontal_gap() + hand_signs = [key.x - x_mid for key in layout.keys] + + for key, hand_sign in zip(layout.keys, hand_signs): + if key.hand is None: + if key == layout.spacebar or abs(hand_sign) <= 0.02: + key.hand = '*' + else: + key.hand = 'L' if hand_sign < 0.0 else 'R' + + lines = [] + lines.append('#ifdef CHORDAL_HOLD') + line = ('__attribute__((weak)) const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = ' + layout_name + '(') + + x_prev = None + for key in layout.keys: + if x_prev is None or key.x < x_prev: + lines.append(line) + line = ' ' + line += f"'{key.hand}', " + x_prev = key.x + + lines.append(line[:-2]) + lines.append(');') + lines.append('#endif') return lines @@ -101,10 +227,11 @@ def generate_keyboard_c(cli): kb_info_json = info_json(cli.args.keyboard) # Build the layouts.h file. - keyboard_h_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', ''] + keyboard_c_lines = [GPL2_HEADER_C_LIKE, GENERATED_HEADER_C_LIKE, '#include QMK_KEYBOARD_H', ''] - keyboard_h_lines.extend(_gen_led_configs(kb_info_json)) - keyboard_h_lines.extend(_gen_matrix_mask(kb_info_json)) + keyboard_c_lines.extend(_gen_led_configs(kb_info_json)) + keyboard_c_lines.extend(_gen_matrix_mask(kb_info_json)) + keyboard_c_lines.extend(_gen_chordal_hold_layout(kb_info_json)) # Show the results - dump_lines(cli.args.output, keyboard_h_lines, cli.args.quiet) + dump_lines(cli.args.output, keyboard_c_lines, cli.args.quiet) diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 8f238490f2a..3b8a19c07b7 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -49,6 +49,42 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re } # endif +# if defined(CHORDAL_HOLD) +extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM; + +# define REGISTERED_TAPS_SIZE 8 +// Array of tap-hold keys that have been settled as tapped but not yet released. +static keypos_t registered_taps[REGISTERED_TAPS_SIZE] = {}; +static uint8_t num_registered_taps = 0; + +/** Adds `key` to the registered_taps array. */ +static void registered_taps_add(keypos_t key); +/** Returns the index of `key` in registered_taps, or -1 if not found. */ +static int8_t registered_tap_find(keypos_t key); +/** Removes index `i` from the registered_taps array. */ +static void registered_taps_del_index(uint8_t i); +/** Logs the registered_taps array for debugging. */ +static void debug_registered_taps(void); + +/** \brief Finds which queued events should be held according to Chordal Hold. + * + * In a situation with multiple unsettled tap-hold key presses, scan the queue + * up until the first release, non-tap-hold, or one-shot event and find the + * lastest event in the queue that settles as held according to + * get_chordal_hold(). + * + * \return Index of the first tap, or equivalently, one past the latest hold. + */ +static uint8_t waiting_buffer_find_chordal_hold_tap(void); + +/** Processes queued events up to and including `key` as tapped. */ +static void waiting_buffer_chordal_hold_taps_until(keypos_t key); + +static bool is_mt_or_lt(uint16_t keycode) { + return IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode); +} +# endif // CHORDAL_HOLD + # ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) { return false; @@ -166,6 +202,20 @@ void action_tapping_process(keyrecord_t record) { bool process_tapping(keyrecord_t *keyp) { const keyevent_t event = keyp->event; +# if defined(CHORDAL_HOLD) + if (!event.pressed) { + const int8_t i = registered_tap_find(event.key); + if (i != -1) { + // If a tap-hold key was previously settled as tapped, set its + // tap.count correspondingly on release. + keyp->tap.count = 1; + registered_taps_del_index(i); + ac_dprintf("Found tap release for [%d]\n", i); + debug_registered_taps(); + } + } +# endif // CHORDAL_HOLD + // state machine is in the "reset" state, no tapping key is to be // processed if (IS_NOEVENT(tapping_key.event)) { @@ -188,7 +238,7 @@ bool process_tapping(keyrecord_t *keyp) { return true; } -# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY) +# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(CHORDAL_HOLD) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY) TAP_DEFINE_KEYCODE; # endif @@ -199,6 +249,7 @@ bool process_tapping(keyrecord_t *keyp) { // early return for tick events return true; } + if (tapping_key.tap.count == 0) { if (IS_TAPPING_RECORD(keyp) && !event.pressed) { // first tap! @@ -212,6 +263,25 @@ bool process_tapping(keyrecord_t *keyp) { // enqueue return false; } +# if defined(CHORDAL_HOLD) + else if (is_mt_or_lt(tapping_keycode) && !event.pressed && waiting_buffer_typed(event) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { + // Key release that is not a chord with the tapping key. + // Settle the tapping key and any other pending tap-hold + // keys preceding the press of this key as tapped. + + ac_dprintf("Tapping: End. Chord considered a tap\n"); + tapping_key.tap.count = 1; + registered_taps_add(tapping_key.event.key); + process_record(&tapping_key); + tapping_key = (keyrecord_t){0}; + + waiting_buffer_chordal_hold_taps_until(event.key); + debug_registered_taps(); + debug_waiting_buffer(); + // enqueue + return false; + } +# endif // CHORDAL_HOLD /* Process a key typed within TAPPING_TERM * This can register the key before settlement of tapping, * useful for long TAPPING_TERM but may prevent fast typing. @@ -229,6 +299,22 @@ bool process_tapping(keyrecord_t *keyp) { // clang-format on ac_dprintf("Tapping: End. No tap. Interfered by typing key\n"); process_record(&tapping_key); + +# if defined(CHORDAL_HOLD) + uint8_t first_tap = waiting_buffer_find_chordal_hold_tap(); + ac_dprintf("first_tap = %u\n", first_tap); + if (first_tap < WAITING_BUFFER_SIZE) { + for (; waiting_buffer_tail != first_tap; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { + ac_dprintf("Processing [%u]\n", waiting_buffer_tail); + process_record(&waiting_buffer[waiting_buffer_tail]); + } + } + + waiting_buffer_chordal_hold_taps_until(event.key); + debug_registered_taps(); + debug_waiting_buffer(); +# endif // CHORDAL_HOLD + tapping_key = (keyrecord_t){0}; debug_tapping_key(); // enqueue @@ -237,6 +323,19 @@ bool process_tapping(keyrecord_t *keyp) { /* Process release event of a key pressed before tapping starts * Without this unexpected repeating will occur with having fast repeating setting * https://github.com/tmk/tmk_keyboard/issues/60 + * + * NOTE: This workaround causes events to process out of order, + * e.g. in a rolled press of three tap-hold keys like + * + * "A down, B down, C down, A up, B up, C up" + * + * events are processed as + * + * "A down, B down, A up, B up, C down, C up" + * + * It seems incorrect to process keyp before the tapping key. + * This workaround is old, from 2013. This might no longer + * be needed for the original problem it was meant to address. */ else if (!event.pressed && !waiting_buffer_typed(event)) { // Modifier/Layer should be retained till end of this tapping. @@ -271,19 +370,47 @@ bool process_tapping(keyrecord_t *keyp) { // set interrupted flag when other key pressed during tapping if (event.pressed) { tapping_key.tap.interrupted = true; - if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS + +# if defined(CHORDAL_HOLD) + if (is_mt_or_lt(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { + // In process_action(), HOLD_ON_OTHER_KEY_PRESS + // will revert interrupted events to holds, so + // this needs to be set false. + tapping_key.tap.interrupted = false; + + if (!is_tap_record(keyp)) { + ac_dprintf("Tapping: End. Chord considered a tap\n"); + tapping_key.tap.count = 1; + registered_taps_add(tapping_key.event.key); + debug_registered_taps(); + process_record(&tapping_key); + tapping_key = (keyrecord_t){0}; + } + } else +# endif // CHORDAL_HOLD + if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS # if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT) - // Auto Shift cannot evaluate this early - // Retro Shift uses the hold action for all nested taps even without HOLD_ON_OTHER_KEY_PRESS, so this is fine to skip - && !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp)) + // Auto Shift cannot evaluate this early + // Retro Shift uses the hold action for all nested taps even without HOLD_ON_OTHER_KEY_PRESS, so this is fine to skip + && !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp)) # endif - ) { + ) { + // Settle the tapping key as *held*, since + // HOLD_ON_OTHER_KEY_PRESS is enabled for this key. ac_dprintf("Tapping: End. No tap. Interfered by pressed key\n"); process_record(&tapping_key); - tapping_key = (keyrecord_t){0}; + +# if defined(CHORDAL_HOLD) + if (waiting_buffer_tail != waiting_buffer_head && is_tap_record(&waiting_buffer[waiting_buffer_tail])) { + tapping_key = waiting_buffer[waiting_buffer_tail]; + // Pop tail from the queue. + waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE; + } else +# endif // CHORDAL_HOLD + { + tapping_key = (keyrecord_t){0}; + } debug_tapping_key(); - // enqueue - return false; } } // enqueue @@ -520,26 +647,114 @@ void waiting_buffer_scan_tap(void) { } } -/** \brief Tapping key debug print - * - * FIXME: Needs docs - */ +# ifdef CHORDAL_HOLD +__attribute__((weak)) bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record) { + return get_chordal_hold_default(tap_hold_record, other_record); +} + +bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record) { + if (tap_hold_record->event.type != KEY_EVENT || other_record->event.type != KEY_EVENT) { + return true; // Return true on combos or other non-key events. + } + + char tap_hold_hand = chordal_hold_handedness(tap_hold_record->event.key); + if (tap_hold_hand == '*') { + return true; + } + char other_hand = chordal_hold_handedness(other_record->event.key); + return other_hand == '*' || tap_hold_hand != other_hand; +} + +__attribute__((weak)) char chordal_hold_handedness(keypos_t key) { + return (char)pgm_read_byte(&chordal_hold_layout[key.row][key.col]); +} + +static void registered_taps_add(keypos_t key) { + if (num_registered_taps >= REGISTERED_TAPS_SIZE) { + ac_dprintf("TAPS OVERFLOW: CLEAR ALL STATES\n"); + clear_keyboard(); + num_registered_taps = 0; + } + + registered_taps[num_registered_taps] = key; + ++num_registered_taps; +} + +static int8_t registered_tap_find(keypos_t key) { + for (int8_t i = 0; i < num_registered_taps; ++i) { + if (KEYEQ(registered_taps[i], key)) { + return i; + } + } + return -1; +} + +static void registered_taps_del_index(uint8_t i) { + if (i < num_registered_taps) { + --num_registered_taps; + if (i < num_registered_taps) { + registered_taps[i] = registered_taps[num_registered_taps]; + } + } +} + +static void debug_registered_taps(void) { + ac_dprintf("registered_taps = { "); + for (int8_t i = 0; i < num_registered_taps; ++i) { + ac_dprintf("%02X%02X ", registered_taps[i].row, registered_taps[i].col); + } + ac_dprintf("}\n"); +} + +static uint8_t waiting_buffer_find_chordal_hold_tap(void) { + keyrecord_t *prev = &tapping_key; + uint16_t prev_keycode = get_record_keycode(&tapping_key, false); + uint8_t first_tap = WAITING_BUFFER_SIZE; + for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { + keyrecord_t * cur = &waiting_buffer[i]; + const uint16_t cur_keycode = get_record_keycode(cur, false); + if (!cur->event.pressed || !is_tap_record(prev) || !is_mt_or_lt(prev_keycode)) { + break; + } else if (get_chordal_hold(prev_keycode, prev, cur_keycode, cur)) { + first_tap = i; // Track one index past the latest hold. + } + prev = cur; + prev_keycode = cur_keycode; + } + return first_tap; +} + +static void waiting_buffer_chordal_hold_taps_until(keypos_t key) { + while (waiting_buffer_tail != waiting_buffer_head) { + keyrecord_t *record = &waiting_buffer[waiting_buffer_tail]; + ac_dprintf("waiting_buffer_chordal_hold_taps_until: processing [%u]\n", waiting_buffer_tail); + if (is_tap_record(record)) { + record->tap.count = 1; + registered_taps_add(record->event.key); + } + process_record(record); + waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE; + + if (KEYEQ(key, record->event.key) && record->event.pressed) { + break; + } + } +} +# endif // CHORDAL_HOLD + +/** \brief Logs tapping key if ACTION_DEBUG is enabled. */ static void debug_tapping_key(void) { ac_dprintf("TAPPING_KEY="); debug_record(tapping_key); ac_dprintf("\n"); } -/** \brief Waiting buffer debug print - * - * FIXME: Needs docs - */ +/** \brief Logs waiting buffer if ACTION_DEBUG is enabled. */ static void debug_waiting_buffer(void) { - ac_dprintf("{ "); + ac_dprintf("{"); for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) { - ac_dprintf("[%u]=", i); + ac_dprintf(" [%u]=", i); debug_record(waiting_buffer[i]); - ac_dprintf(" "); } ac_dprintf("}\n"); } diff --git a/quantum/action_tapping.h b/quantum/action_tapping.h index 6b518b82988..77c45f504fa 100644 --- a/quantum/action_tapping.h +++ b/quantum/action_tapping.h @@ -46,6 +46,73 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record); bool get_retro_tapping(uint16_t keycode, keyrecord_t *record); bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record); +#ifdef CHORDAL_HOLD +/** + * Callback to say when a key chord before the tapping term may be held. + * + * In keymap.c, define the callback + * + * bool get_chordal_hold(uint16_t tap_hold_keycode, + * keyrecord_t* tap_hold_record, + * uint16_t other_keycode, + * keyrecord_t* other_record) { + * // Conditions... + * } + * + * This callback is called when: + * + * 1. `tap_hold_keycode` is pressed. + * 2. `other_keycode` is pressed while `tap_hold_keycode` is still held, + * provided `other_keycode` is *not* also a tap-hold key and it is pressed + * before the tapping term. + * + * If false is returned, this has the effect of immediately settling the + * tap-hold key as tapped. If true is returned, the tap-hold key is still + * unsettled, and may be settled as held depending on configuration and + * subsequent events. + * + * @param tap_hold_keycode Keycode of the tap-hold key. + * @param tap_hold_record Record from the tap-hold press event. + * @param other_keycode Keycode of the other key. + * @param other_record Record from the other key's press event. + * @return True if the tap-hold key may be considered held; false if tapped. + */ +bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t *tap_hold_record, uint16_t other_keycode, keyrecord_t *other_record); + +/** + * Default "opposite hands rule" for whether a key chord may settle as held. + * + * This function returns true when the tap-hold key and other key are on + * "opposite hands." In detail, handedness of the two keys are compared. If + * handedness values differ, or if either handedness is '*', the function + * returns true, indicating that it may be held. Otherwise, it returns false, + * in which case the tap-hold key is immediately settled at tapped. + * + * @param tap_hold_record Record of the active tap-hold key press. + * @param other_record Record of the other, interrupting key press. + * @return True if the tap-hold key may be considered held; false if tapped. + */ +bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_record); + +/** + * Gets the handedness of a key. + * + * This function returns: + * 'L' for keys pressed by the left hand, + * 'R' for keys on the right hand, + * '*' for keys exempt from the "opposite hands rule." This could be used + * perhaps on thumb keys or keys that might be pressed by either hand. + * + * @param key A key matrix position. + * @return Handedness value. + */ +char chordal_hold_handedness(keypos_t key); + +# ifdef CHORDAL_HOLD_LAYOUT +extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM; +# endif +#endif + #ifdef DYNAMIC_TAPPING_TERM_ENABLE extern uint16_t g_tapping_term; #endif From 04a50c9bf15d0be0bda16d90f01579e3546c36f1 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Sun, 5 Jan 2025 21:43:35 -0800 Subject: [PATCH 2/6] Chordal Hold for Svalboard with dynamic enable. --- keyboards/svalboard/config.h | 1 + keyboards/svalboard/info.json | 24 +++++----- keyboards/svalboard/keymaps/keymap_support.c | 8 ++++ keyboards/svalboard/keymaps/keymap_support.h | 1 + keyboards/svalboard/keymaps/vial/vial.json | 5 ++ keyboards/svalboard/svalboard.c | 3 +- quantum/action_tapping.c | 50 ++++++++++++++------ quantum/action_tapping.h | 7 ++- 8 files changed, 70 insertions(+), 29 deletions(-) diff --git a/keyboards/svalboard/config.h b/keyboards/svalboard/config.h index 204e9070e4e..1f916296ed3 100644 --- a/keyboards/svalboard/config.h +++ b/keyboards/svalboard/config.h @@ -97,5 +97,6 @@ along with this program. If not, see . #define SPLIT_TRANSACTION_IDS_KB KEYBOARD_SYNC_A +#define CHORDAL_HOLD #define PERMISSIVE_HOLD #define ACHORDION_STREAK diff --git a/keyboards/svalboard/info.json b/keyboards/svalboard/info.json index f806f7c2a55..1174843526f 100644 --- a/keyboards/svalboard/info.json +++ b/keyboards/svalboard/info.json @@ -109,18 +109,18 @@ {"label": "l4s", "matrix": [4, 0], "x": 0.5, "y": 2.5, "h": 0.5}, {"label": "l4w", "matrix": [4, 4], "x": 0, "y": 1.5, "w": 0.5}, {"label": "l4ss", "matrix": [4, 5], "x": 0, "y": 1.5, "w": 0.5}, - {"label": "rtd", "matrix": [5, 2], "x": 11, "y": 4, "h": 2}, - {"label": "rti", "matrix": [5, 3], "x": 12, "y": 3.5, "w": 0.5, "h": 1.5}, - {"label": "rtu", "matrix": [5, 4], "x": 12.5, "y": 3.5, "w": 0.5}, - {"label": "rtuo", "matrix": [5, 1], "x": 10.5, "y": 3.5, "w": 0.5, "h": 1.5}, - {"label": "rtlo", "matrix": [5, 0], "x": 10.5, "y": 5, "w": 0.5, "h": 1.5}, - {"label": "rtdd", "matrix": [5, 5], "x": 11.5, "y": 5, "w": 0.5, "h": 1.5}, - {"label": "ltd", "matrix": [0, 2], "x": 8.5, "y": 4, "h": 2}, - {"label": "lti", "matrix": [0, 3], "x": 8, "y": 3.5, "w": 0.5, "h": 1.5}, - {"label": "ltu", "matrix": [0, 4], "x": 7.5, "y": 3.5, "w": 0.5}, - {"label": "ltuo", "matrix": [0, 1], "x": 9.5, "y": 3.5, "w": 0.5, "h": 1.5}, - {"label": "ltlo", "matrix": [0, 0], "x": 9.5, "y": 5, "w": 0.5, "h": 1.5}, - {"label": "ltdd", "matrix": [0, 5], "x": 8.5, "y": 5, "w": 0.5, "h": 1.5} + {"label": "rtd", "matrix": [5, 2], "x": 11, "y": 4, "h": 2, "hand": "*"}, + {"label": "rti", "matrix": [5, 3], "x": 12, "y": 3.5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "rtu", "matrix": [5, 4], "x": 12.5, "y": 3.5, "w": 0.5, "hand": "*"}, + {"label": "rtuo", "matrix": [5, 1], "x": 10.5, "y": 3.5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "rtlo", "matrix": [5, 0], "x": 10.5, "y": 5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "rtdd", "matrix": [5, 5], "x": 11.5, "y": 5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "ltd", "matrix": [0, 2], "x": 8.5, "y": 4, "h": 2, "hand": "*"}, + {"label": "lti", "matrix": [0, 3], "x": 8, "y": 3.5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "ltu", "matrix": [0, 4], "x": 7.5, "y": 3.5, "w": 0.5, "hand": "*"}, + {"label": "ltuo", "matrix": [0, 1], "x": 9.5, "y": 3.5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "ltlo", "matrix": [0, 0], "x": 9.5, "y": 5, "w": 0.5, "h": 1.5, "hand": "*"}, + {"label": "ltdd", "matrix": [0, 5], "x": 8.5, "y": 5, "w": 0.5, "h": 1.5, "hand": "*"} ] } } diff --git a/keyboards/svalboard/keymaps/keymap_support.c b/keyboards/svalboard/keymaps/keymap_support.c index f6154b10f5d..4a53c814d8d 100644 --- a/keyboards/svalboard/keymaps/keymap_support.c +++ b/keyboards/svalboard/keymaps/keymap_support.c @@ -156,6 +156,11 @@ void mh_change_timeouts(void) { write_eeprom_kb(); } +void toggle_chordal_hold(void) { + chordal_hold_toggle(); + write_eeprom_kb(); +} + void toggle_achordion(void) { global_saved_values.disable_achordion = !global_saved_values.disable_achordion; write_eeprom_kb(); @@ -281,6 +286,9 @@ bool process_record_kb(uint16_t keycode, keyrecord_t *record) { case SV_CAPS_WORD: caps_word_toggle(); return false; + case SV_TOGGLE_CHORDAL_HOLD: + toggle_chordal_hold(); + return false; case SV_TOGGLE_ACHORDION: toggle_achordion(); return false; diff --git a/keyboards/svalboard/keymaps/keymap_support.h b/keyboards/svalboard/keymaps/keymap_support.h index 867ef5a4641..97b5f5e4445 100644 --- a/keyboards/svalboard/keymaps/keymap_support.h +++ b/keyboards/svalboard/keymaps/keymap_support.h @@ -31,6 +31,7 @@ enum my_keycodes { SV_RECALIBRATE_POINTER, SV_MH_CHANGE_TIMEOUTS, SV_CAPS_WORD, + SV_TOGGLE_CHORDAL_HOLD, SV_TOGGLE_ACHORDION, SV_TOGGLE_23_67, SV_TOGGLE_45_67, diff --git a/keyboards/svalboard/keymaps/vial/vial.json b/keyboards/svalboard/keymaps/vial/vial.json index f7da3a61fd3..59965c0bf71 100644 --- a/keyboards/svalboard/keymaps/vial/vial.json +++ b/keyboards/svalboard/keymaps/vial/vial.json @@ -51,6 +51,11 @@ "title": "Toggle Caps Word.", "name": "SV_CAPS_WORD" }, + { + "shortName": "Toggle\nChordal", + "title": "Toggle Chordal Hold", + "name": "SV_TOGGLE_CHORDAL_HOLD" + }, { "shortName": "Toggle\nACH", "title": "Toggle Achordion", diff --git a/keyboards/svalboard/svalboard.c b/keyboards/svalboard/svalboard.c index f9b37dc7316..533980da965 100644 --- a/keyboards/svalboard/svalboard.c +++ b/keyboards/svalboard/svalboard.c @@ -79,7 +79,8 @@ void output_keyboard_info(void) { yes_or_no(global_saved_values.left_scroll), dpi_choices[global_saved_values.left_dpi_index], yes_or_no(global_saved_values.right_scroll), dpi_choices[global_saved_values.right_dpi_index]); send_string(output_buffer); - sprintf(output_buffer, "Achordion: %s, MH Keys Timer: %d\n", + sprintf(output_buffer, "Chordal Hold: %s, Achordion: %s, MH Keys Timer: %d\n", + yes_or_no(chordal_hold_is_enabled()), yes_or_no(!global_saved_values.disable_achordion), mh_timer_choices[global_saved_values.mh_timer_index]); send_string(output_buffer); diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 3b8a19c07b7..83944b7d8d7 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -50,6 +50,26 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re # endif # if defined(CHORDAL_HOLD) +// Make it possible to enable Chordal Hold dynamically for Vial. +static bool chordal_hold_enabled = false; +bool chordal_hold_is_enabled(void) { + return chordal_hold_enabled; +} +void chordal_hold_enable(void) { + if (!chordal_hold_enabled) { + chordal_hold_toggle(); + } +} +void chordal_hold_disable(void) { + if (chordal_hold_enabled) { + chordal_hold_toggle(); + } +} +void chordal_hold_toggle(void) { + chordal_hold_enabled = !chordal_hold_enabled; + clear_keyboard(); +} + extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM; # define REGISTERED_TAPS_SIZE 8 @@ -203,7 +223,7 @@ bool process_tapping(keyrecord_t *keyp) { const keyevent_t event = keyp->event; # if defined(CHORDAL_HOLD) - if (!event.pressed) { + if (chordal_hold_enabled && !event.pressed) { const int8_t i = registered_tap_find(event.key); if (i != -1) { // If a tap-hold key was previously settled as tapped, set its @@ -264,7 +284,7 @@ bool process_tapping(keyrecord_t *keyp) { return false; } # if defined(CHORDAL_HOLD) - else if (is_mt_or_lt(tapping_keycode) && !event.pressed && waiting_buffer_typed(event) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { + else if (chordal_hold_enabled && is_mt_or_lt(tapping_keycode) && !event.pressed && waiting_buffer_typed(event) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { // Key release that is not a chord with the tapping key. // Settle the tapping key and any other pending tap-hold // keys preceding the press of this key as tapped. @@ -301,18 +321,20 @@ bool process_tapping(keyrecord_t *keyp) { process_record(&tapping_key); # if defined(CHORDAL_HOLD) - uint8_t first_tap = waiting_buffer_find_chordal_hold_tap(); - ac_dprintf("first_tap = %u\n", first_tap); - if (first_tap < WAITING_BUFFER_SIZE) { - for (; waiting_buffer_tail != first_tap; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { - ac_dprintf("Processing [%u]\n", waiting_buffer_tail); - process_record(&waiting_buffer[waiting_buffer_tail]); + if (chordal_hold_enabled) { + uint8_t first_tap = waiting_buffer_find_chordal_hold_tap(); + ac_dprintf("first_tap = %u\n", first_tap); + if (first_tap < WAITING_BUFFER_SIZE) { + for (; waiting_buffer_tail != first_tap; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { + ac_dprintf("Processing [%u]\n", waiting_buffer_tail); + process_record(&waiting_buffer[waiting_buffer_tail]); + } } - } - waiting_buffer_chordal_hold_taps_until(event.key); - debug_registered_taps(); - debug_waiting_buffer(); + waiting_buffer_chordal_hold_taps_until(event.key); + debug_registered_taps(); + debug_waiting_buffer(); + } # endif // CHORDAL_HOLD tapping_key = (keyrecord_t){0}; @@ -372,7 +394,7 @@ bool process_tapping(keyrecord_t *keyp) { tapping_key.tap.interrupted = true; # if defined(CHORDAL_HOLD) - if (is_mt_or_lt(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { + if (chordal_hold_enabled && is_mt_or_lt(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { // In process_action(), HOLD_ON_OTHER_KEY_PRESS // will revert interrupted events to holds, so // this needs to be set false. @@ -401,7 +423,7 @@ bool process_tapping(keyrecord_t *keyp) { process_record(&tapping_key); # if defined(CHORDAL_HOLD) - if (waiting_buffer_tail != waiting_buffer_head && is_tap_record(&waiting_buffer[waiting_buffer_tail])) { + if (chordal_hold_enabled && waiting_buffer_tail != waiting_buffer_head && is_tap_record(&waiting_buffer[waiting_buffer_tail])) { tapping_key = waiting_buffer[waiting_buffer_tail]; // Pop tail from the queue. waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE; diff --git a/quantum/action_tapping.h b/quantum/action_tapping.h index 77c45f504fa..4b3db17b8c5 100644 --- a/quantum/action_tapping.h +++ b/quantum/action_tapping.h @@ -108,9 +108,12 @@ bool get_chordal_hold_default(keyrecord_t *tap_hold_record, keyrecord_t *other_r */ char chordal_hold_handedness(keypos_t key); -# ifdef CHORDAL_HOLD_LAYOUT extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM; -# endif + +bool chordal_hold_is_enabled(void); +void chordal_hold_enable(void); +void chordal_hold_disable(void); +void chordal_hold_toggle(void); #endif #ifdef DYNAMIC_TAPPING_TERM_ENABLE From b472ab5b644b521334115b7f8bc8fbefade6a8f3 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Thu, 9 Jan 2025 20:05:24 -0800 Subject: [PATCH 3/6] Save Chordal Hold setting in EEPROM. --- keyboards/svalboard/keymaps/keymap_support.c | 1 + keyboards/svalboard/svalboard.c | 5 ++++- keyboards/svalboard/svalboard.h | 3 ++- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/keyboards/svalboard/keymaps/keymap_support.c b/keyboards/svalboard/keymaps/keymap_support.c index 4a53c814d8d..3712906f001 100644 --- a/keyboards/svalboard/keymaps/keymap_support.c +++ b/keyboards/svalboard/keymaps/keymap_support.c @@ -158,6 +158,7 @@ void mh_change_timeouts(void) { void toggle_chordal_hold(void) { chordal_hold_toggle(); + global_saved_values.enable_chordal_hold = chordal_hold_is_enabled(); write_eeprom_kb(); } diff --git a/keyboards/svalboard/svalboard.c b/keyboards/svalboard/svalboard.c index 533980da965..a37af3fb891 100644 --- a/keyboards/svalboard/svalboard.c +++ b/keyboards/svalboard/svalboard.c @@ -54,6 +54,9 @@ void read_eeprom_kb(void) { write_eeprom_kb(); } sval_active_layer = 0; + if (global_saved_values.enable_chordal_hold) { + chordal_hold_enable(); + } } static const char YES[] = "yes"; @@ -80,7 +83,7 @@ void output_keyboard_info(void) { yes_or_no(global_saved_values.right_scroll), dpi_choices[global_saved_values.right_dpi_index]); send_string(output_buffer); sprintf(output_buffer, "Chordal Hold: %s, Achordion: %s, MH Keys Timer: %d\n", - yes_or_no(chordal_hold_is_enabled()), + yes_or_no(global_saved_values.enable_chordal_hold), yes_or_no(!global_saved_values.disable_achordion), mh_timer_choices[global_saved_values.mh_timer_index]); send_string(output_buffer); diff --git a/keyboards/svalboard/svalboard.h b/keyboards/svalboard/svalboard.h index 7b79ff621fa..bbc57b08e76 100644 --- a/keyboards/svalboard/svalboard.h +++ b/keyboards/svalboard/svalboard.h @@ -32,7 +32,8 @@ struct saved_values { bool left_scroll :1; bool right_scroll :1; bool disable_achordion: 1; - unsigned int unused0 :5; + bool enable_chordal_hold: 1; + unsigned int unused0: 4; uint8_t left_dpi_index; uint8_t right_dpi_index; uint8_t mh_timer_index; From 4bc38a5388f2e7cc72c89267066ccecd9503d497 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Thu, 9 Jan 2025 20:40:20 -0800 Subject: [PATCH 4/6] Add Chordal Hold unit tests. --- keyboards/svalboard/svalboard.c | 2 + quantum/action_tapping.c | 2 +- .../chordal_hold/default/config.h | 21 + .../chordal_hold/default/test.mk | 17 + .../chordal_hold/default/test_keymap.c | 22 + .../default/test_one_shot_keys.cpp | 168 ++++ .../chordal_hold/default/test_tap_hold.cpp | 264 ++++++ .../hold_on_other_key_press/config.h | 22 + .../hold_on_other_key_press/test.mk | 17 + .../hold_on_other_key_press/test_keymap.c | 22 + .../hold_on_other_key_press/test_tap_hold.cpp | 645 +++++++++++++++ .../chordal_hold/permissive_hold/config.h | 22 + .../chordal_hold/permissive_hold/test.mk | 17 + .../permissive_hold/test_keymap.c | 22 + .../permissive_hold/test_one_shot_keys.cpp | 174 ++++ .../permissive_hold/test_tap_hold.cpp | 775 ++++++++++++++++++ .../retro_shift_permissive_hold/config.h | 28 + .../retro_shift_permissive_hold/test.mk | 18 + .../retro_shift_permissive_hold/test_keymap.c | 22 + .../test_retro_shift.cpp | 420 ++++++++++ 20 files changed, 2699 insertions(+), 1 deletion(-) create mode 100644 tests/tap_hold_configurations/chordal_hold/default/config.h create mode 100644 tests/tap_hold_configurations/chordal_hold/default/test.mk create mode 100644 tests/tap_hold_configurations/chordal_hold/default/test_keymap.c create mode 100644 tests/tap_hold_configurations/chordal_hold/default/test_one_shot_keys.cpp create mode 100644 tests/tap_hold_configurations/chordal_hold/default/test_tap_hold.cpp create mode 100644 tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h create mode 100644 tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk create mode 100644 tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_keymap.c create mode 100644 tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp create mode 100644 tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h create mode 100644 tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk create mode 100644 tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c create mode 100644 tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp create mode 100644 tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp create mode 100644 tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h create mode 100644 tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk create mode 100644 tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_keymap.c create mode 100644 tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp diff --git a/keyboards/svalboard/svalboard.c b/keyboards/svalboard/svalboard.c index a37af3fb891..3a5ebc8cac3 100644 --- a/keyboards/svalboard/svalboard.c +++ b/keyboards/svalboard/svalboard.c @@ -56,6 +56,8 @@ void read_eeprom_kb(void) { sval_active_layer = 0; if (global_saved_values.enable_chordal_hold) { chordal_hold_enable(); + } else { + chordal_hold_disable(); } } diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 83944b7d8d7..06ee59467f8 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -51,7 +51,7 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re # if defined(CHORDAL_HOLD) // Make it possible to enable Chordal Hold dynamically for Vial. -static bool chordal_hold_enabled = false; +static bool chordal_hold_enabled = true; bool chordal_hold_is_enabled(void) { return chordal_hold_enabled; } diff --git a/tests/tap_hold_configurations/chordal_hold/default/config.h b/tests/tap_hold_configurations/chordal_hold/default/config.h new file mode 100644 index 00000000000..2ba155df730 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/config.h @@ -0,0 +1,21 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2024 Google LLC + * + * 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 . + */ + +#pragma once + +#include "test_common.h" +#define CHORDAL_HOLD diff --git a/tests/tap_hold_configurations/chordal_hold/default/test.mk b/tests/tap_hold_configurations/chordal_hold/default/test.mk new file mode 100644 index 00000000000..2b049cea3b4 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/test.mk @@ -0,0 +1,17 @@ +# Copyright 2022 Vladislav Kucheriavykh +# Copyright 2024 Google LLC +# +# 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 . + +INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/tap_hold_configurations/chordal_hold/default/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/default/test_keymap.c new file mode 100644 index 00000000000..8a6a2c59b0a --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/test_keymap.c @@ -0,0 +1,22 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, +}; diff --git a/tests/tap_hold_configurations/chordal_hold/default/test_one_shot_keys.cpp b/tests/tap_hold_configurations/chordal_hold/default/test_one_shot_keys.cpp new file mode 100644 index 00000000000..ac4edd08b23 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/test_one_shot_keys.cpp @@ -0,0 +1,168 @@ +/* Copyright 2021 Stefan Kerkmann + * Copyright 2024 Google LLC + * + * 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 . + */ + +#include "action_util.h" +#include "keyboard_report_util.hpp" +#include "test_common.hpp" + +using testing::_; +using testing::InSequence; + +class OneShot : public TestFixture {}; +class OneShotParametrizedTestFixture : public ::testing::WithParamInterface>, public OneShot {}; + +TEST_P(OneShotParametrizedTestFixture, OSMWithAdditionalKeypress) { + TestDriver driver; + KeymapKey osm_key = GetParam().first; + KeymapKey regular_key = GetParam().second; + + set_keymap({osm_key, regular_key}); + + // Press and release OSM. + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_P(OneShotParametrizedTestFixture, OSMAsRegularModifierWithAdditionalKeypress) { + TestDriver driver; + KeymapKey osm_key = GetParam().first; + KeymapKey regular_key = GetParam().second; + + set_keymap({osm_key, regular_key}); + + // Press OSM. + EXPECT_NO_REPORT(driver); + osm_key.press(); + run_one_scan_loop(); + // Press regular key. + regular_key.press(); + run_one_scan_loop(); + // Release regular key. + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSM. + EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code)); + EXPECT_EMPTY_REPORT(driver); + osm_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// clang-format off + +INSTANTIATE_TEST_CASE_P( + OneShotModifierTests, + OneShotParametrizedTestFixture, + ::testing::Values( + // First is osm key, second is regular key. + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LCTL), KC_LCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LALT), KC_LALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LGUI), KC_LGUI}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RCTL), KC_RCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RSFT), KC_RSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RALT), KC_RALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RGUI), KC_RGUI}, KeymapKey{0, 1, 1, KC_A}) + )); +// clang-format on + +TEST_F(OneShot, OSLWithAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osl_key1 = KeymapKey{1, 0, 0, KC_X}; + KeymapKey regular_key0 = KeymapKey{0, 1, 0, KC_Y}; + KeymapKey regular_key1 = KeymapKey{1, 1, 0, KC_A}; + + set_keymap({osl_key, osl_key1, regular_key0, regular_key1}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (regular_key1.report_code)); + EXPECT_EMPTY_REPORT(driver); + regular_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_NO_REPORT(driver); + regular_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSLWithOsmAndAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osm_key = KeymapKey{1, 1, 0, OSM(MOD_LSFT), KC_LSFT}; + KeymapKey regular_key = KeymapKey{1, 1, 1, KC_A}; + KeymapKey blank_key = KeymapKey{1, 0, 0, KC_NO}; + + set_keymap({osl_key, osm_key, regular_key, blank_key}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Press and release OSM. + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code)); + EXPECT_EMPTY_REPORT(driver); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/default/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/default/test_tap_hold.cpp new file mode 100644 index 00000000000..70949e218c9 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/test_tap_hold.cpp @@ -0,0 +1,264 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +class ChordalHoldDefault : public TestFixture {}; + +TEST_F(ChordalHoldDefault, chord_nested_press_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + // Tap regular key. + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, chord_rolled_press_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key and regular key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, non_chord_with_mod_tap_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key and regular key both on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_P)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, tap_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, hold_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, two_mod_taps_same_hand_hold_til_timeout) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Continue holding til the tapping term. + EXPECT_REPORT(driver, (KC_RIGHT_CTRL)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_RIGHT_SHIFT)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, three_mod_taps_same_hand_streak_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + // + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_C)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order, with the first two keys released + // before pressing KC_C. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldDefault, tap_regular_key_while_layer_tap_key_is_held) { + TestDriver driver; + InSequence s; + auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B); + + set_keymap({layer_tap_hold_key, regular_key, layer_key}); + + EXPECT_NO_REPORT(driver); + layer_tap_hold_key.press(); // Press layer-tap-hold key. + run_one_scan_loop(); + regular_key.press(); // Press regular key. + run_one_scan_loop(); + regular_key.release(); // Release regular key. + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + layer_tap_hold_key.release(); // Release layer-tap-hold key. + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h new file mode 100644 index 00000000000..87094b2faca --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/config.h @@ -0,0 +1,22 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2024 Google LLC + * + * 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 . + */ + +#pragma once + +#include "test_common.h" +#define CHORDAL_HOLD +#define HOLD_ON_OTHER_KEY_PRESS diff --git a/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk new file mode 100644 index 00000000000..2b049cea3b4 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test.mk @@ -0,0 +1,17 @@ +# Copyright 2022 Vladislav Kucheriavykh +# Copyright 2024 Google LLC +# +# 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 . + +INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_keymap.c new file mode 100644 index 00000000000..8a6a2c59b0a --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_keymap.c @@ -0,0 +1,22 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, +}; diff --git a/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp new file mode 100644 index 00000000000..ff6600428b6 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/hold_on_other_key_press/test_tap_hold.cpp @@ -0,0 +1,645 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +class ChordalHoldHoldOnOtherKeyPress : public TestFixture {}; + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_with_mod_tap_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_nested_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, chord_rolled_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, non_chord_with_mod_tap_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key and regular key both on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_P)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, tap_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, hold_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_same_hand_hold_til_timeout) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Continue holding til the tapping term. + EXPECT_REPORT(driver, (KC_RIGHT_CTRL)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_RIGHT_SHIFT)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_opposite_hands) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press first mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press second mod-tap key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release second mod-tap key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release first mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_same_hand) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + // + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A)); + // EXPECT_REPORT(driver, (KC_A, KC_B)); + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_C)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order, with the first two keys released + // before pressing KC_C. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_orders) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 3, 2, 1. + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 3, 1, 2. + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A)); + // EXPECT_REPORT(driver, (KC_A, KC_B)); + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_A, KC_C)); + // EXPECT_REPORT(driver, (KC_A)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order, with the first two keys released + // before pressing KC_C. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_C)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + idle_for(TAPPING_TERM); + // Press mod-tap keys. + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + // Release keys 2, 3, 1. + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_two_left_one_right) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 2, then key 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 1, then key 2. + EXPECT_REPORT(driver, (KC_LEFT_CTRL)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_one_held_two_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.press(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key3.press(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, tap_regular_key_while_layer_tap_key_is_held) { + TestDriver driver; + InSequence s; + auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + auto no_key = KeymapKey(1, 1, 0, XXXXXXX); + auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B); + + set_keymap({layer_tap_hold_key, regular_key, no_key, layer_key}); + + // Press layer-tap-hold key. + EXPECT_NO_REPORT(driver); + layer_tap_hold_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_B)); + regular_key.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release layer-tap-hold key. + EXPECT_NO_REPORT(driver); + layer_tap_hold_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(0)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldHoldOnOtherKeyPress, nested_tap_of_layer_0_layer_tap_keys) { + TestDriver driver; + InSequence s; + // The keys are layer-taps on layer 0 but regular keys on layer 1. + auto first_layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A)); + auto second_layer_tap_key = KeymapKey(0, MATRIX_COLS - 1, 0, LT(1, KC_P)); + auto first_key_on_layer = KeymapKey(1, 1, 0, KC_B); + auto second_key_on_layer = KeymapKey(1, MATRIX_COLS - 1, 0, KC_Q); + + set_keymap({first_layer_tap_key, second_layer_tap_key, first_key_on_layer, second_key_on_layer}); + + // Press first layer-tap key. + EXPECT_NO_REPORT(driver); + first_layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press second layer-tap key. + EXPECT_REPORT(driver, (KC_Q)); + second_layer_tap_key.press(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release second layer-tap key. + EXPECT_EMPTY_REPORT(driver); + second_layer_tap_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release first layer-tap key. + EXPECT_NO_REPORT(driver); + first_layer_tap_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(0)); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h b/tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h new file mode 100644 index 00000000000..f7bb7ab0ec4 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/config.h @@ -0,0 +1,22 @@ +/* Copyright 2022 Vladislav Kucheriavykh + * Copyright 2024 Google LLC + * + * 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 . + */ + +#pragma once + +#include "test_common.h" +#define CHORDAL_HOLD +#define PERMISSIVE_HOLD diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk new file mode 100644 index 00000000000..2b049cea3b4 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test.mk @@ -0,0 +1,17 @@ +# Copyright 2022 Vladislav Kucheriavykh +# Copyright 2024 Google LLC +# +# 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 . + +INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c new file mode 100644 index 00000000000..8a6a2c59b0a --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_keymap.c @@ -0,0 +1,22 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, +}; diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp new file mode 100644 index 00000000000..e48cba73e24 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_one_shot_keys.cpp @@ -0,0 +1,174 @@ +/* Copyright 2021 Stefan Kerkmann + * Copyright 2024 Google LLC + * + * 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 . + */ + +#include "action_util.h" +#include "keyboard_report_util.hpp" +#include "test_common.hpp" + +using testing::_; +using testing::InSequence; + +class OneShot : public TestFixture {}; +class OneShotParametrizedTestFixture : public ::testing::WithParamInterface>, public OneShot {}; + +TEST_P(OneShotParametrizedTestFixture, OSMWithAdditionalKeypress) { + TestDriver driver; + KeymapKey osm_key = GetParam().first; + KeymapKey regular_key = GetParam().second; + + set_keymap({osm_key, regular_key}); + + // Press and release OSM. + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_P(OneShotParametrizedTestFixture, OSMAsRegularModifierWithAdditionalKeypress) { + TestDriver driver; + KeymapKey osm_key = GetParam().first; + KeymapKey regular_key = GetParam().second; + + set_keymap({osm_key, regular_key}); + + // Press OSM. + EXPECT_NO_REPORT(driver); + osm_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (osm_key.report_code)).Times(2); + EXPECT_REPORT(driver, (regular_key.report_code, osm_key.report_code)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSM. + EXPECT_EMPTY_REPORT(driver); + osm_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +// clang-format off + +INSTANTIATE_TEST_CASE_P( + OneShotModifierTests, + OneShotParametrizedTestFixture, + ::testing::Values( + // First is osm key, second is regular key. + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LCTL), KC_LCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LALT), KC_LALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_LGUI), KC_LGUI}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RCTL), KC_RCTL}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RSFT), KC_RSFT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RALT), KC_RALT}, KeymapKey{0, 1, 1, KC_A}), + std::make_pair(KeymapKey{0, 0, 0, OSM(MOD_RGUI), KC_RGUI}, KeymapKey{0, 1, 1, KC_A}) + )); +// clang-format on + +TEST_F(OneShot, OSLWithAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osl_key1 = KeymapKey{1, 0, 0, KC_X}; + KeymapKey regular_key0 = KeymapKey{0, 1, 0, KC_Y}; + KeymapKey regular_key1 = KeymapKey{1, 1, 0, KC_A}; + + set_keymap({osl_key, osl_key1, regular_key0, regular_key1}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (regular_key1.report_code)); + EXPECT_EMPTY_REPORT(driver); + regular_key1.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_NO_REPORT(driver); + regular_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSLWithOsmAndAdditionalKeypress) { + TestDriver driver; + InSequence s; + KeymapKey osl_key = KeymapKey{0, 0, 0, OSL(1)}; + KeymapKey osm_key = KeymapKey{1, 1, 0, OSM(MOD_LSFT), KC_LSFT}; + KeymapKey regular_key = KeymapKey{1, 1, 1, KC_A}; + KeymapKey blank_key = KeymapKey{1, 0, 0, KC_NO}; + + set_keymap({osl_key, osm_key, regular_key, blank_key}); + + // Press OSL key. + EXPECT_NO_REPORT(driver); + osl_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release OSL key. + EXPECT_NO_REPORT(driver); + osl_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Press and release OSM. + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (osm_key.report_code, regular_key.report_code)); + EXPECT_EMPTY_REPORT(driver); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp new file mode 100644 index 00000000000..91d0cfea2fb --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/permissive_hold/test_tap_hold.cpp @@ -0,0 +1,775 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using testing::_; +using testing::InSequence; + +class ChordalHoldPermissiveHold : public TestFixture {}; + +TEST_F(ChordalHoldPermissiveHold, chordal_hold_handedness) { + EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 0}), 'L'); + EXPECT_EQ(chordal_hold_handedness({.col = MATRIX_COLS - 1, .row = 0}), 'R'); + EXPECT_EQ(chordal_hold_handedness({.col = 0, .row = 2}), '*'); +} + +TEST_F(ChordalHoldPermissiveHold, get_chordal_hold_default) { + auto make_record = [](uint8_t row, uint8_t col, keyevent_type_t type = KEY_EVENT) { + return keyrecord_t{ + .event = + { + .key = {.col = col, .row = row}, + .type = type, + .pressed = true, + }, + }; + }; + // Create two records on the left hand. + keyrecord_t record_l0 = make_record(0, 0); + keyrecord_t record_l1 = make_record(1, 0); + // Create a record on the right hand. + keyrecord_t record_r = make_record(0, MATRIX_COLS - 1); + + // Function should return true when records are on opposite hands. + EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_r)); + EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_l0)); + // ... and false when on the same hand. + EXPECT_FALSE(get_chordal_hold_default(&record_l0, &record_l1)); + EXPECT_FALSE(get_chordal_hold_default(&record_l1, &record_l0)); + // But (2, 0) has handedness '*', for which true is returned for chords + // with either hand. + keyrecord_t record_l2 = make_record(2, 0); + EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_l0)); + EXPECT_TRUE(get_chordal_hold_default(&record_l2, &record_r)); + + // Create a record resulting from a combo. + keyrecord_t record_combo = make_record(0, 0, COMBO_EVENT); + // Function returns true in all cases. + EXPECT_TRUE(get_chordal_hold_default(&record_l0, &record_combo)); + EXPECT_TRUE(get_chordal_hold_default(&record_r, &record_combo)); + EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_l0)); + EXPECT_TRUE(get_chordal_hold_default(&record_combo, &record_r)); +} + +TEST_F(ChordalHoldPermissiveHold, chord_nested_press_settled_as_hold) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap regular key. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + tap_key(regular_key); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, chord_rolled_press_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + // Regular key on the right hand. + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap key and regular key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, non_chord_with_mod_tap_settled_as_tap) { + TestDriver driver; + InSequence s; + // Mod-tap key and regular key both on the left hand. + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + auto regular_key = KeymapKey(0, 2, 0, KC_A); + + set_keymap({mod_tap_key, regular_key}); + + // Press mod-tap-hold key. + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press regular key. + EXPECT_REPORT(driver, (KC_P)); + EXPECT_REPORT(driver, (KC_P, KC_A)); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_P)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap-hold key. + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, tap_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_NO_REPORT(driver); + mod_tap_key.press(); + idle_for(TAPPING_TERM - 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, hold_mod_tap_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P)); + + set_keymap({mod_tap_key}); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key.press(); + idle_for(TAPPING_TERM + 1); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_same_hand_hold_til_timeout) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, MATRIX_COLS - 2, 0, RCTL_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Continue holding til the tapping term. + EXPECT_REPORT(driver, (KC_RIGHT_CTRL)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT)); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_RIGHT_SHIFT)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_opposite_hands) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_same_hand) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, RSFT_T(KC_B)); + + set_keymap({mod_tap_key1, mod_tap_key2}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release mod-tap keys. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key2.release(); + run_one_scan_loop(); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + // + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_C)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order, with the first two keys released + // before pressing KC_C. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, 3, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_A)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 2, 3, 1. + // + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_A, KC_C)); + // EXPECT_REPORT(driver, (KC_A)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_C)); + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_NO_REPORT(driver); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_opposite_hands_roll) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 1, 2, 3. + // + // NOTE: The correct order of events should be + // EXPECT_REPORT(driver, (KC_A, KC_B)); + // EXPECT_REPORT(driver, (KC_A, KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_B, KC_C)); + // EXPECT_REPORT(driver, (KC_C)); + // EXPECT_EMPTY_REPORT(driver); + // + // However, due to a workaround for https://github.com/tmk/tmk_keyboard/issues/60, + // the events are processed out of order, with the first two keys released + // before pressing KC_C. + EXPECT_REPORT(driver, (KC_A)); + EXPECT_REPORT(driver, (KC_A, KC_B)); + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_REPORT(driver, (KC_C)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + mod_tap_key2.release(); + run_one_scan_loop(); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_two_left_one_right) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 2, then key 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 3. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_LEFT_CTRL)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release key 1, then key 2. + EXPECT_REPORT(driver, (KC_LEFT_CTRL)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, three_mod_taps_one_held_two_tapped) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto mod_tap_key3 = KeymapKey(0, MATRIX_COLS - 1, 0, RSFT_T(KC_C)); + + set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3}); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 2, 1. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + mod_tap_key3.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys 3, 1, 2. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + mod_tap_key3.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, two_mod_taps_one_regular_key) { + TestDriver driver; + InSequence s; + auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_A)); + auto mod_tap_key2 = KeymapKey(0, MATRIX_COLS - 2, 0, CTL_T(KC_B)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_C); + + set_keymap({mod_tap_key1, mod_tap_key2, regular_key}); + + // Press keys. + EXPECT_NO_REPORT(driver); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C)); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press mod-tap keys. + EXPECT_NO_REPORT(driver); + idle_for(TAPPING_TERM); + mod_tap_key1.press(); + run_one_scan_loop(); + mod_tap_key2.press(); + run_one_scan_loop(); + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release keys. + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B, KC_C)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); + regular_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_REPORT(driver, (KC_B)); + mod_tap_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + EXPECT_EMPTY_REPORT(driver); + mod_tap_key2.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, tap_regular_key_while_layer_tap_key_is_held) { + TestDriver driver; + InSequence s; + auto layer_tap_hold_key = KeymapKey(0, 1, 0, LT(1, KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + auto no_key = KeymapKey(1, 1, 0, XXXXXXX); + auto layer_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_B); + + set_keymap({layer_tap_hold_key, regular_key, no_key, layer_key}); + + // Press layer-tap-hold key. + EXPECT_NO_REPORT(driver); + layer_tap_hold_key.press(); + run_one_scan_loop(); + // Press regular key. + regular_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release regular key. + EXPECT_REPORT(driver, (KC_B)); + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release layer-tap-hold key. + EXPECT_NO_REPORT(driver); + layer_tap_hold_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(0)); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(ChordalHoldPermissiveHold, nested_tap_of_layer_0_layer_tap_keys) { + TestDriver driver; + InSequence s; + // The keys are layer-taps on layer 2 but regular keys on layer 1. + auto first_layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A)); + auto second_layer_tap_key = KeymapKey(0, MATRIX_COLS - 1, 0, LT(1, KC_P)); + auto first_key_on_layer = KeymapKey(1, 1, 0, KC_B); + auto second_key_on_layer = KeymapKey(1, MATRIX_COLS - 1, 0, KC_Q); + + set_keymap({first_layer_tap_key, second_layer_tap_key, first_key_on_layer, second_key_on_layer}); + + // Press first layer-tap key. + EXPECT_NO_REPORT(driver); + first_layer_tap_key.press(); + run_one_scan_loop(); + // Press second layer-tap key. + second_layer_tap_key.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release second layer-tap key. + EXPECT_REPORT(driver, (KC_Q)); + EXPECT_EMPTY_REPORT(driver); + second_layer_tap_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(1)); + VERIFY_AND_CLEAR(driver); + + // Release first layer-tap key. + EXPECT_NO_REPORT(driver); + first_layer_tap_key.release(); + run_one_scan_loop(); + EXPECT_TRUE(layer_state_is(0)); + VERIFY_AND_CLEAR(driver); +} diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h new file mode 100644 index 00000000000..4d704c59788 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/config.h @@ -0,0 +1,28 @@ +/* Copyright 2022 Isaac Elenbaas + * Copyright 2024 Google LLC + * + * 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 . + */ + +#pragma once + +#include "test_common.h" + +#define CHORDAL_HOLD +#define PERMISSIVE_HOLD + +#define RETRO_SHIFT 2 * TAPPING_TERM +// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested +#define AUTO_SHIFT_TIMEOUT TAPPING_TERM +#define AUTO_SHIFT_MODIFIERS diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk new file mode 100644 index 00000000000..c39dfa9cb17 --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test.mk @@ -0,0 +1,18 @@ +# Copyright 2022 Isaac Elenbaas +# Copyright 2024 Google LLC +# +# 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 . + +AUTO_SHIFT_ENABLE = yes +INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_keymap.c b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_keymap.c new file mode 100644 index 00000000000..8a6a2c59b0a --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_keymap.c @@ -0,0 +1,22 @@ +// Copyright 2024 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM = { + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'*', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, + {'L', 'L', 'L', 'L', 'L', 'R', 'R', 'R', 'R', 'R'}, +}; diff --git a/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp new file mode 100644 index 00000000000..16fbabbd8ac --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/retro_shift_permissive_hold/test_retro_shift.cpp @@ -0,0 +1,420 @@ +/* Copyright 2022 Isaac Elenbaas + * Copyright 2024 Google LLC + * + * 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 . + */ + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "action_tapping.h" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) { + return true; +} + +using testing::_; +using testing::AnyNumber; +using testing::AnyOf; +using testing::InSequence; + +class RetroShiftPermissiveHold : public TestFixture {}; + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LCTL, KC_A)); + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT), + KeyboardReport(KC_LCTL)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A)); + // clang-format off + EXPECT_CALL(driver, send_keyboard_mock(AnyOf( + KeyboardReport(KC_LCTL, KC_LSFT), + KeyboardReport(KC_LSFT)))) + .Times(AnyNumber()); + // clang-format on + EXPECT_REPORT(driver, (KC_LCTL)); + mod_tap_regular_key.release(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_REPORT(driver, (KC_A)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A); + + set_keymap({mod_tap_hold_key, regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + idle_for(AUTO_SHIFT_TIMEOUT); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release regular key. */ + EXPECT_NO_REPORT(driver); + regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} + +TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) { + TestDriver driver; + InSequence s; + auto mod_tap_hold_key = KeymapKey(0, 0, 0, CTL_T(KC_P)); + auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, ALT_T(KC_A)); + + set_keymap({mod_tap_hold_key, mod_tap_regular_key}); + + /* Press mod-tap-hold key. */ + EXPECT_NO_REPORT(driver); + mod_tap_hold_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Press mod-tap-regular key. */ + EXPECT_NO_REPORT(driver); + mod_tap_regular_key.press(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-hold key. */ + EXPECT_REPORT(driver, (KC_P)); + EXPECT_EMPTY_REPORT(driver); + mod_tap_hold_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); + + /* Release mod-tap-regular key. */ + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_A)); + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_EMPTY_REPORT(driver); + idle_for(AUTO_SHIFT_TIMEOUT); + mod_tap_regular_key.release(); + run_one_scan_loop(); + testing::Mock::VerifyAndClearExpectations(&driver); +} From e0a6463045f9de806f6ee81346524a4bc18feac1 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Thu, 9 Jan 2025 20:59:46 -0800 Subject: [PATCH 5/6] Move toggle keycode to end of range. --- keyboards/svalboard/keymaps/keymap_support.h | 2 +- keyboards/svalboard/keymaps/vial/vial.json | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/keyboards/svalboard/keymaps/keymap_support.h b/keyboards/svalboard/keymaps/keymap_support.h index 97b5f5e4445..18b845ffe0d 100644 --- a/keyboards/svalboard/keymaps/keymap_support.h +++ b/keyboards/svalboard/keymaps/keymap_support.h @@ -31,7 +31,6 @@ enum my_keycodes { SV_RECALIBRATE_POINTER, SV_MH_CHANGE_TIMEOUTS, SV_CAPS_WORD, - SV_TOGGLE_CHORDAL_HOLD, SV_TOGGLE_ACHORDION, SV_TOGGLE_23_67, SV_TOGGLE_45_67, @@ -41,6 +40,7 @@ enum my_keycodes { SV_SCROLL_HOLD, SV_SCROLL_TOGGLE, SV_OUTPUT_STATUS, + SV_TOGGLE_CHORDAL_HOLD, // New keycodes should go here, to avoid breaking existing keymaps - order must match vial.json KC_NORMAL_HOLD = SAFE_RANGE, KC_FUNC_HOLD, diff --git a/keyboards/svalboard/keymaps/vial/vial.json b/keyboards/svalboard/keymaps/vial/vial.json index 59965c0bf71..c615fc907a4 100644 --- a/keyboards/svalboard/keymaps/vial/vial.json +++ b/keyboards/svalboard/keymaps/vial/vial.json @@ -51,11 +51,6 @@ "title": "Toggle Caps Word.", "name": "SV_CAPS_WORD" }, - { - "shortName": "Toggle\nChordal", - "title": "Toggle Chordal Hold", - "name": "SV_TOGGLE_CHORDAL_HOLD" - }, { "shortName": "Toggle\nACH", "title": "Toggle Achordion", @@ -100,6 +95,11 @@ "shortName": "Output\nStatus", "title": "Output the current internal state of the keyboard.", "name": "SV_OUTPUT_STATUS" + }, + { + "shortName": "Toggle\nChordal", + "title": "Toggle Chordal Hold", + "name": "SV_TOGGLE_CHORDAL_HOLD" } ], "layouts": { From 32c8e2a2dc26eaaff820e198d3efeec66a0192e5 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Sat, 11 Jan 2025 16:33:48 -0800 Subject: [PATCH 6/6] Updated BAD_KEYCODE_CONDITIONAL. --- keyboards/svalboard/keymaps/keymap_support.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/keyboards/svalboard/keymaps/keymap_support.c b/keyboards/svalboard/keymaps/keymap_support.c index 3712906f001..2818ffc05fc 100644 --- a/keyboards/svalboard/keymaps/keymap_support.c +++ b/keyboards/svalboard/keymaps/keymap_support.c @@ -218,7 +218,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t *record) { // The keycodes below are all that are forced to drop you out of mouse mode. // The intent is for this list to eventually become just KC_NO, and KC_TRNS // as more functionality is exported to keybard, and those keys are removed - // from the firmmware. - ilc - 2024-10-05 + // from the firmware. - ilc - 2024-10-05 #define BAD_KEYCODE_CONDITONAL (keycode == KC_NO || \ keycode == KC_TRNS || \ keycode == SV_LEFT_DPI_INC || \ @@ -227,6 +227,7 @@ bool process_record_kb(uint16_t keycode, keyrecord_t *record) { keycode == SV_RIGHT_DPI_DEC || \ keycode == SV_LEFT_SCROLL_TOGGLE || \ keycode == SV_RIGHT_SCROLL_TOGGLE || \ + keycode == SV_TOGGLE_CHORDAL_HOLD || \ keycode == SV_TOGGLE_ACHORDION || \ keycode == SV_MH_CHANGE_TIMEOUTS)