From 2b1eff29c4c91378d3cc1e01951955213b951d32 Mon Sep 17 00:00:00 2001 From: Pascal Getreuer Date: Tue, 24 Dec 2024 11:40:00 -0800 Subject: [PATCH] Revise to allow combining multiple same-hand mods. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This commit revises Chordal Hold as described in discussion in https://github.com/qmk/qmk_firmware/pull/24560#discussion_r1894655238 1. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, RCTL_T(KC_A)↑" before the tapping term, RCTL_T(KC_A) is settled as tapped. 2. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, RSFT_T(KC_C)↑", both RCTL_T(KC_A) and RSFT_T(KC_C) are settled as tapped. 3. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, KC_U↓" (all keys on the same side), both RCTL_T(KC_A) and RSFT_T(KC_C) are settled as tapped. 4. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓, LSFT_T(KC_T)↓", with the third key on the other side, we allow Permissive Hold or Hold On Other Keypress to decide how/when to settle the keys. 5. In "RCTL_T(KC_A)↓, RSFT_T(KC_C)↓" held until the tapping term, the keys are settled as held. 1–3 provide same-hand roll protection. 4–5 are for combining multiple same-hand modifiers. I've updated the unit tests and have been running it on my keyboard, for a few hours so far, and all seems good. I really like this scheme. It allows combining same-side mods, yet it also has roll protection on streaks. For me, this feels like Achordion, but clearly better streak handling and improved responsiveness. --- docs/tap_hold.md | 80 ------ quantum/action_tapping.c | 149 ++++++++--- .../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 | 237 ++++++++++++++++++ .../hold_on_other_key_press/test_tap_hold.cpp | 40 ++- .../permissive_hold/test_one_shot_keys.cpp | 25 ++ .../permissive_hold/test_tap_hold.cpp | 141 ++++------- 10 files changed, 663 insertions(+), 237 deletions(-) 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 diff --git a/docs/tap_hold.md b/docs/tap_hold.md index f59e7d661b52..254d5de5ec1e 100644 --- a/docs/tap_hold.md +++ b/docs/tap_hold.md @@ -587,86 +587,6 @@ As shown in the last line above, you may use `get_chordal_hold_default(tap_hold_record, other_record)` to get the default tap vs. hold decision according to the opposite hands rule. -If you use home row mods, you may want to produce a hotkey like Ctrl+Shift+V by -holding Ctrl and Shift mod-taps on one hand while tapping `KC_V` with the other -hand, say: - -- `RCTL_T(KC_K)` Down -- `RSFT_T(KC_L)` Down (on the same hand as `RCTL_T(KC_K)`) -- `KC_V` Down -- `KC_V` Up -- `RCTL_T(KC_K)` Up -- `RSFT_T(KC_L)` Up - -However, supposing `RCTL_T(KC_K)` and `RSFT_T(KC_L)` are on the same hand, -Chordal Hold by default considers `RCTL_T(KC_K)` tapped, producing "`kV`" -instead of the desired Ctrl+Shift+V. - -To address this, `get_chordal_hold()` may be defined to allow chords between any -pair of mod-tap keys with - -```c -bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record, - uint16_t other_keycode, keyrecord_t* other_record) { - // Allow hold between any pair of mod-tap keys. - if (IS_QK_MOD_TAP(tap_hold_keycode) && IS_QK_MOD_TAP(other_keycode)) { - return true; - } - - // Otherwise defer to the opposite hands rule. - return get_chordal_hold_default(tap_hold_record, other_record); -} -``` - -Or to allow one-handed chords of specific mod-taps but not others, use: - -```c -bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record, - uint16_t other_keycode, keyrecord_t* other_record) { - switch (tap_hold_keycode) { - case RCTL_T(KC_K): - if (other_keycode == RSFT_T(KC_L)) { - // Allow hold in "RCTL_T(KC_K) down, RSFT_T(KC_L) down". - return true; - } - break; - - case RSFT_T(KC_L): - if (other_keycode == RCTL_T(KC_K)) { - // Allow hold in "RSFT_T(KC_L) down, RCTL_T(KC_K) down". - return true; - } - break; - } - // Otherwise defer to the opposite hands rule. - return get_chordal_hold_default(tap_hold_record, other_record); -} -``` - -Above, two exceptions are defined, one where `RCTL_T(KC_K)` is pressed first and -another where `RSFT_T(KC_L)` is held first, such that Ctrl+Shift+V could be done -by holding the mod-taps in either order. For yet finer control, you could choose -to define an exception for one order but not the other: - -```c -bool get_chordal_hold(uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record, - uint16_t other_keycode, keyrecord_t* other_record) { - switch (tap_hold_keycode) { - case RCTL_T(KC_K): - if (other_keycode == RSFT_T(KC_L)) { - // Allow hold in "RCTL_T(KC_K) down, RSFT_T(KC_L), down". - return true; - } - break; - - // ... but RSFT_T(KC_L) is considered tapped in - // "RSFT_T(KC_L) down, RCTL_T(KC_K) down". - } - // Otherwise defer to the opposite hands rule. - return get_chordal_hold_default(tap_hold_record, other_record); -} -``` - ## Retro Tapping diff --git a/quantum/action_tapping.c b/quantum/action_tapping.c index 984bf098a8be..984fd81a8a7b 100644 --- a/quantum/action_tapping.c +++ b/quantum/action_tapping.c @@ -49,7 +49,7 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re } # endif -# ifdef CHORDAL_HOLD +# if defined(CHORDAL_HOLD) extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM; # define REGISTERED_TAPS_SIZE 8 @@ -65,11 +65,23 @@ static int8_t registered_tap_find(keypos_t key); static void registered_taps_del_index(uint8_t i); /** Logs the registered_taps array for debugging. */ static void debug_registered_taps(void); -/** Processes and pops buffered events until the first tap-hold event. */ -static void waiting_buffer_process_regular(void); -static bool is_one_shot(uint16_t keycode) { - return IS_QK_ONE_SHOT_MOD(keycode) || IS_QK_ONE_SHOT_LAYER(keycode); +/** \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 @@ -190,7 +202,7 @@ void action_tapping_process(keyrecord_t record) { bool process_tapping(keyrecord_t *keyp) { const keyevent_t event = keyp->event; -# ifdef CHORDAL_HOLD +# if defined(CHORDAL_HOLD) if (!event.pressed) { const int8_t i = registered_tap_find(event.key); if (i != -1) { @@ -198,7 +210,7 @@ bool process_tapping(keyrecord_t *keyp) { // tap.count correspondingly on release. keyp->tap.count = 1; registered_taps_del_index(i); - ac_dprintf("CHORDAL_HOLD: Found tap release for [%d]\n", i); + ac_dprintf("Found tap release for [%d]\n", i); debug_registered_taps(); } } @@ -251,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. @@ -268,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 @@ -325,23 +372,22 @@ bool process_tapping(keyrecord_t *keyp) { tapping_key.tap.interrupted = true; # if defined(CHORDAL_HOLD) - if (!is_one_shot(tapping_keycode) && !get_chordal_hold(tapping_keycode, &tapping_key, get_record_keycode(keyp, false), keyp)) { + 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; - 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}; - - // Process regular keys in the waiting buffer. - waiting_buffer_process_regular(); + 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 +# 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 @@ -349,23 +395,23 @@ bool process_tapping(keyrecord_t *keyp) { && !(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); + // 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); -# ifdef 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 - { - 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(); } - debug_tapping_key(); - } } // enqueue return false; @@ -660,16 +706,39 @@ static void debug_registered_taps(void) { ac_dprintf("}\n"); } -static void waiting_buffer_process_regular(void) { - for (; waiting_buffer_tail != waiting_buffer_head; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) { - if (is_tap_record(&waiting_buffer[waiting_buffer_tail])) { - break; // Stop once a tap-hold key event is reached. +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. } - ac_dprintf("waiting_buffer_process_regular: processing [%u]\n", waiting_buffer_tail); - process_record(&waiting_buffer[waiting_buffer_tail]); + prev = cur; + prev_keycode = cur_keycode; } + return first_tap; +} - debug_waiting_buffer(); +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 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 000000000000..2ba155df7301 --- /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 000000000000..2b049cea3b4b --- /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 000000000000..8a6a2c59b0ae --- /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 000000000000..ac4edd08b237 --- /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 000000000000..dc0ef0dfe6ff --- /dev/null +++ b/tests/tap_hold_configurations/chordal_hold/default/test_tap_hold.cpp @@ -0,0 +1,237 @@ +// 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); +} 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 index edb34096c4c9..a590627a9aec 100644 --- 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 @@ -210,15 +210,13 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_same_hand_hold_til_timeout) EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); // Continue holding til the tapping term. - EXPECT_REPORT(driver, (KC_A, KC_RIGHT_SHIFT)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT)); idle_for(TAPPING_TERM); VERIFY_AND_CLEAR(driver); @@ -280,14 +278,12 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, two_mod_taps_nested_press_same_hand) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); 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); @@ -311,14 +307,8 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_roll) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A, KC_B)); mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); @@ -336,6 +326,8 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_same_hand_streak_roll) { // 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)); @@ -454,27 +446,25 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_two_left_one_right) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); + 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_A, KC_LEFT_CTRL, KC_C)); - EXPECT_REPORT(driver, (KC_A, 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_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); EXPECT_EMPTY_REPORT(driver); mod_tap_key2.release(); run_one_scan_loop(); @@ -486,21 +476,19 @@ TEST_F(ChordalHoldHoldOnOtherKeyPress, three_mod_taps_two_left_one_right) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); + 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_A, KC_LEFT_CTRL, KC_C)); - EXPECT_REPORT(driver, (KC_A, 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); 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 index b4385e5f6205..e48cba73e241 100644 --- 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 @@ -25,6 +25,31 @@ 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; 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 index c2a260e8837f..6f424e02e1ac 100644 --- 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 @@ -215,15 +215,13 @@ TEST_F(ChordalHoldPermissiveHold, two_mod_taps_same_hand_hold_til_timeout) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); // Continue holding til the tapping term. - EXPECT_REPORT(driver, (KC_A, KC_RIGHT_SHIFT)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL)); + EXPECT_REPORT(driver, (KC_RIGHT_CTRL, KC_RIGHT_SHIFT)); idle_for(TAPPING_TERM); VERIFY_AND_CLEAR(driver); @@ -281,14 +279,12 @@ TEST_F(ChordalHoldPermissiveHold, two_mod_taps_nested_press_same_hand) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); 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(); @@ -313,14 +309,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_roll) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A, KC_B)); mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); @@ -336,6 +326,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_roll) { // 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)); @@ -362,27 +354,27 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A, KC_B)); 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)); - EXPECT_REPORT(driver, (KC_A)); - EXPECT_EMPTY_REPORT(driver); 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); @@ -392,27 +384,27 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { idle_for(TAPPING_TERM); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A, KC_B)); 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)); - EXPECT_REPORT(driver, (KC_B)); - EXPECT_EMPTY_REPORT(driver); 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); @@ -422,14 +414,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { idle_for(TAPPING_TERM); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A, KC_B)); mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); @@ -445,6 +431,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_same_hand_streak_orders) { // 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); @@ -473,13 +461,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_opposite_hands_roll) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); @@ -496,9 +479,8 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_opposite_hands_roll) { // 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. - - // Release keys 1, 2, 3. - EXPECT_EMPTY_REPORT(driver); + 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)); @@ -525,28 +507,23 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_two_left_one_right) { EXPECT_NO_REPORT(driver); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_NO_REPORT(driver); mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); // Release key 3. - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C)); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); + 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_A)); + EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); mod_tap_key2.release(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); @@ -561,21 +538,17 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_two_left_one_right) { idle_for(TAPPING_TERM); mod_tap_key1.press(); run_one_scan_loop(); - VERIFY_AND_CLEAR(driver); - - EXPECT_REPORT(driver, (KC_A)); mod_tap_key2.press(); run_one_scan_loop(); - - EXPECT_NO_REPORT(driver); mod_tap_key3.press(); run_one_scan_loop(); VERIFY_AND_CLEAR(driver); // Release key 3. - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL, KC_C)); - EXPECT_REPORT(driver, (KC_A, KC_LEFT_CTRL)); + 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); @@ -612,27 +585,20 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_one_held_two_tapped) { VERIFY_AND_CLEAR(driver); // Release keys 3, 2, 1. - // - // NOTE: The correct order of events should be - // 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)); - // EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - // 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_LEFT_SHIFT)); EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_C)); - EXPECT_REPORT(driver, (KC_LEFT_SHIFT)); - EXPECT_EMPTY_REPORT(driver); + 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); @@ -649,27 +615,20 @@ TEST_F(ChordalHoldPermissiveHold, three_mod_taps_one_held_two_tapped) { VERIFY_AND_CLEAR(driver); // Release keys 3, 1, 2. - // - // NOTE: The correct order of events should be - // 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)); - // EXPECT_REPORT(driver, (KC_B)); - // 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_LEFT_SHIFT)); EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_B)); - EXPECT_REPORT(driver, (KC_B)); - EXPECT_REPORT(driver, (KC_B, KC_C)); - EXPECT_REPORT(driver, (KC_B)); - EXPECT_EMPTY_REPORT(driver); + 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);