[Core] Enhance Flow Tap to work better for rolls over multiple tap-hold keys. (#25200)

* Flow Tap revision for rolling press.

* Remove debugging cruft.

* Formatting fix.
This commit is contained in:
Pascal Getreuer 2025-04-28 00:52:20 -07:00 committed by GitHub
parent 7fa65aa877
commit c26449e64f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 1395 additions and 13 deletions

View File

@ -103,10 +103,11 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco
# endif
# if defined(FLOW_TAP_TERM)
static uint32_t flow_tap_prev_time = 0;
static uint16_t flow_tap_prev_keycode = KC_NO;
static uint16_t flow_tap_prev_time = 0;
static bool flow_tap_expired = true;
static bool flow_tap_key_if_within_term(keyrecord_t *record);
static bool flow_tap_key_if_within_term(keyrecord_t *record, uint16_t prev_time);
# endif // defined(FLOW_TAP_TERM)
static keyrecord_t tapping_key = {};
@ -159,6 +160,12 @@ void action_tapping_process(keyrecord_t record) {
}
if (IS_EVENT(record.event)) {
ac_dprintf("\n");
} else {
# ifdef FLOW_TAP_TERM
if (!flow_tap_expired && TIMER_DIFF_16(record.event.time, flow_tap_prev_time) >= INT16_MAX / 2) {
flow_tap_expired = true;
}
# endif // FLOW_TAP_TERM
}
}
@ -240,7 +247,7 @@ bool process_tapping(keyrecord_t *keyp) {
// into the "pressed" tapping key state
# if defined(FLOW_TAP_TERM)
if (flow_tap_key_if_within_term(keyp)) {
if (flow_tap_key_if_within_term(keyp, flow_tap_prev_time)) {
return true;
}
# endif // defined(FLOW_TAP_TERM)
@ -281,6 +288,27 @@ bool process_tapping(keyrecord_t *keyp) {
// copy tapping state
keyp->tap = tapping_key.tap;
# if defined(FLOW_TAP_TERM)
// Now that tapping_key has settled as tapped, check whether
// Flow Tap applies to following yet-unsettled keys.
uint16_t prev_time = tapping_key.event.time;
for (; waiting_buffer_tail != waiting_buffer_head; waiting_buffer_tail = (waiting_buffer_tail + 1) % WAITING_BUFFER_SIZE) {
keyrecord_t *record = &waiting_buffer[waiting_buffer_tail];
if (!record->event.pressed) {
break;
}
const int16_t next_time = record->event.time;
if (!is_tap_record(record)) {
process_record(record);
} else if (!flow_tap_key_if_within_term(record, prev_time)) {
break;
}
prev_time = next_time;
}
debug_waiting_buffer();
# endif // defined(FLOW_TAP_TERM)
// enqueue
return false;
}
@ -557,7 +585,7 @@ bool process_tapping(keyrecord_t *keyp) {
} else if (is_tap_record(keyp)) {
// Sequential tap can be interfered with other tap key.
# if defined(FLOW_TAP_TERM)
if (flow_tap_key_if_within_term(keyp)) {
if (flow_tap_key_if_within_term(keyp, flow_tap_prev_time)) {
tapping_key = (keyrecord_t){0};
debug_tapping_key();
return true;
@ -791,11 +819,11 @@ static void waiting_buffer_process_regular(void) {
# ifdef FLOW_TAP_TERM
void flow_tap_update_last_event(keyrecord_t *record) {
const uint16_t keycode = get_record_keycode(record, false);
// Don't update while a tap-hold key is unsettled.
if (waiting_buffer_tail != waiting_buffer_head || (tapping_key.event.pressed && tapping_key.tap.count == 0)) {
if (record->tap.count == 0 && (waiting_buffer_tail != waiting_buffer_head || (tapping_key.event.pressed && tapping_key.tap.count == 0))) {
return;
}
const uint16_t keycode = get_record_keycode(record, false);
// Ignore releases of modifiers and held layer switches.
if (!record->event.pressed) {
switch (keycode) {
@ -826,20 +854,25 @@ void flow_tap_update_last_event(keyrecord_t *record) {
}
flow_tap_prev_keycode = keycode;
flow_tap_prev_time = timer_read32();
flow_tap_prev_time = record->event.time;
flow_tap_expired = false;
}
static bool flow_tap_key_if_within_term(keyrecord_t *record) {
static bool flow_tap_key_if_within_term(keyrecord_t *record, uint16_t prev_time) {
const uint16_t idle_time = TIMER_DIFF_16(record->event.time, prev_time);
if (flow_tap_expired || idle_time >= 500) {
return false;
}
const uint16_t keycode = get_record_keycode(record, false);
if (is_mt_or_lt(keycode)) {
const uint32_t idle_time = timer_elapsed32(flow_tap_prev_time);
uint16_t term = get_flow_tap_term(keycode, record, flow_tap_prev_keycode);
uint16_t term = get_flow_tap_term(keycode, record, flow_tap_prev_keycode);
if (term > 500) {
term = 500;
}
if (idle_time < 500 && idle_time < term) {
if (idle_time < term) {
debug_event(record->event);
ac_dprintf(" within flow tap term (%u < %u) considered a tap\n", (int16_t)idle_time, term);
ac_dprintf(" within flow tap term (%u < %u) considered a tap\n", idle_time, term);
record->tap.count = 1;
registered_taps_add(record->event.key);
debug_registered_taps();

View File

@ -0,0 +1,23 @@
/* Copyright 2022 Vladislav Kucheriavykh
* Copyright 2024-2025 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 <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define PERMISSIVE_HOLD
#define FLOW_TAP_TERM 150

View File

@ -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 <http://www.gnu.org/licenses/>.
INTROSPECTION_KEYMAP_C = test_keymap.c

View File

@ -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'},
};

View File

@ -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 <http://www.gnu.org/licenses/>.
*/
#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<std::pair<KeymapKey, KeymapKey>>, 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);
}

View File

@ -0,0 +1,911 @@
// Copyright 2024-2025 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 ChordalHoldPermissiveHoldFlowTap : public TestFixture {};
TEST_F(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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.
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);
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(ChordalHoldPermissiveHoldFlowTap, 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.
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);
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(ChordalHoldPermissiveHoldFlowTap, 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.
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);
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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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(ChordalHoldPermissiveHoldFlowTap, 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_EQ(layer_state, 2);
VERIFY_AND_CLEAR(driver);
// Release layer-tap-hold key.
EXPECT_NO_REPORT(driver);
layer_tap_hold_key.release();
run_one_scan_loop();
EXPECT_EQ(layer_state, 0);
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHoldFlowTap, 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_EQ(layer_state, 2);
VERIFY_AND_CLEAR(driver);
// Release first layer-tap key.
EXPECT_NO_REPORT(driver);
first_layer_tap_key.release();
run_one_scan_loop();
EXPECT_EQ(layer_state, 0);
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHoldFlowTap, lt_mt_one_regular_key) {
TestDriver driver;
InSequence s;
auto lt_key = KeymapKey(0, 1, 0, LT(1, KC_A));
auto mt_key0 = KeymapKey(0, 2, 0, SFT_T(KC_B));
auto mt_key1 = KeymapKey(1, 2, 0, CTL_T(KC_C));
auto regular_key = KeymapKey(1, MATRIX_COLS - 1, 0, KC_X);
auto no_key0 = KeymapKey(0, MATRIX_COLS - 1, 0, XXXXXXX);
auto no_key1 = KeymapKey(1, 1, 0, XXXXXXX);
set_keymap({lt_key, mt_key0, mt_key1, regular_key, no_key0, no_key1});
// Press LT, MT, and regular key.
EXPECT_NO_REPORT(driver);
lt_key.press();
run_one_scan_loop();
mt_key1.press();
run_one_scan_loop();
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release the regular key.
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_REPORT(driver, (KC_LCTL, KC_X));
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
EXPECT_EQ(get_mods(), MOD_BIT_LCTRL);
EXPECT_EQ(layer_state, 2);
VERIFY_AND_CLEAR(driver);
// Release MT key.
EXPECT_EMPTY_REPORT(driver);
mt_key1.release();
run_one_scan_loop();
EXPECT_EQ(get_mods(), 0);
VERIFY_AND_CLEAR(driver);
// Release LT key.
EXPECT_NO_REPORT(driver);
lt_key.release();
run_one_scan_loop();
EXPECT_EQ(layer_state, 0);
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHoldFlowTap, nested_tap_of_layer_tap_keys) {
TestDriver driver;
InSequence s;
// The keys are layer-taps on all layers.
auto first_key_layer_0 = KeymapKey(0, 1, 0, LT(1, KC_A));
auto second_key_layer_0 = KeymapKey(0, MATRIX_COLS - 1, 0, LT(1, KC_P));
auto first_key_layer_1 = KeymapKey(1, 1, 0, LT(2, KC_B));
auto second_key_layer_1 = KeymapKey(1, MATRIX_COLS - 1, 0, LT(2, KC_Q));
auto first_key_layer_2 = KeymapKey(2, 1, 0, KC_TRNS);
auto second_key_layer_2 = KeymapKey(2, MATRIX_COLS - 1, 0, KC_TRNS);
set_keymap({first_key_layer_0, second_key_layer_0, first_key_layer_1, second_key_layer_1, first_key_layer_2, second_key_layer_2});
// Press first layer-tap key.
EXPECT_NO_REPORT(driver);
first_key_layer_0.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press second layer-tap key.
EXPECT_NO_REPORT(driver);
second_key_layer_0.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release second layer-tap key.
EXPECT_REPORT(driver, (KC_Q));
EXPECT_EMPTY_REPORT(driver);
second_key_layer_0.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release first layer-tap key.
EXPECT_NO_REPORT(driver);
first_key_layer_0.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldPermissiveHoldFlowTap, roll_layer_tap_key_with_regular_key) {
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});
// 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_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release layer-tap-hold key.
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_A));
layer_tap_hold_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(ChordalHoldPermissiveHoldFlowTap, two_mod_tap_keys_stuttered_press) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, LSFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, LCTL_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Hold first mod-tap key until the tapping term.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key1.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Press the second mod-tap key, then quickly release and press the first.
EXPECT_NO_REPORT(driver);
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
EXPECT_REPORT(driver, (KC_B));
EXPECT_REPORT(driver, (KC_B, KC_A));
mod_tap_key1.press();
run_one_scan_loop();
EXPECT_EQ(get_mods(), 0); // Verify that Shift was released.
VERIFY_AND_CLEAR(driver);
// Release both keys.
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);
}

View File

@ -98,7 +98,7 @@ TEST_F(FlowTapTest, distinct_taps) {
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
idle_for(TAPPING_TERM + 1);
mod_tap_key2.release();
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
@ -640,3 +640,205 @@ TEST_F(FlowTapTest, quick_tap) {
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, rolling_mt_mt) {
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));
set_keymap({mod_tap_key1, mod_tap_key2});
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_B));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Hold for longer than the tapping term.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, rolling_lt_mt_regular) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_B));
auto regular_key = KeymapKey(0, 2, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
layer_tap_key.press();
run_one_scan_loop();
mod_tap_key.press();
run_one_scan_loop();
regular_key.press();
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_B, KC_C));
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Hold for longer than the tapping term.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, rolling_lt_regular_mt) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto regular_key = KeymapKey(0, 1, 0, KC_B);
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
layer_tap_key.press();
run_one_scan_loop();
regular_key.press();
run_one_scan_loop();
mod_tap_key.press();
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_B, KC_C));
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Hold for longer than the tapping term.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release mod-tap keys.
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, rolling_mt_mt_mt) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
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 first mod-tap key.
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));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Hold for longer than the tapping term.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release other mod-tap keys.
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
mod_tap_key3.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, roll_release_132) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 0, 0, CTL_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 1, 0, GUI_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 2, 0, ALT_T(KC_C));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release first mod-tap key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_B));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release other mod-tap keys.
EXPECT_REPORT(driver, (KC_B, KC_C));
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key3.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}