Add Flow Tap to zsa/qmk_firmware. (#408)
Some checks failed
Build firmware / build-firmware (default) (push) Has been cancelled
Build firmware / build-firmware (oryx) (push) Has been cancelled

* Add Flow Tap to zsa/qmk_firmware.

* Flow Tap bug fixes and enhancements.

Adds Tap Flow bug fix https://github.com/qmk/qmk_firmware/pull/25175 and
enhancement https://github.com/qmk/qmk_firmware/pull/25200.
This commit is contained in:
Pascal Getreuer
2025-04-29 02:06:33 -07:00
committed by GitHub
parent 12c7a1f4b1
commit 873247739e
15 changed files with 2324 additions and 36 deletions

View File

@@ -196,6 +196,7 @@
// Tapping
"CHORDAL_HOLD": {"info_key": "tapping.chordal_hold", "value_type": "flag"},
"FLOW_TAP_TERM": {"info_key": "tapping.flow_tap_term", "value_type": "int"},
"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"},

View File

@@ -270,6 +270,9 @@ void process_record(keyrecord_t *record) {
if (IS_NOEVENT(record->event)) {
return;
}
#ifdef FLOW_TAP_TERM
flow_tap_update_last_event(record);
#endif // FLOW_TAP_TERM
if (!process_record_quantum(record)) {
#ifndef NO_ACTION_ONESHOT
@@ -1153,6 +1156,23 @@ bool is_tap_action(action_t action) {
return false;
}
uint16_t get_tap_keycode(uint16_t keycode) {
switch (keycode) {
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
return QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
return QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX:
// IS_SWAP_HANDS_KEYCODE() tests for the special action keycodes
// like SH_TOGG, SH_TT, ..., which overlap the SH_T(kc) range.
if (!IS_SWAP_HANDS_KEYCODE(keycode)) {
return QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode);
}
break;
}
return keycode;
}
/** \brief Debug print (FIXME: Needs better description)
*
* FIXME: Needs documentation.

View File

@@ -128,6 +128,12 @@ void layer_switch(uint8_t new_layer);
bool is_tap_record(keyrecord_t *record);
bool is_tap_action(action_t action);
/**
* Given an MT or LT keycode, returns the tap keycode. Otherwise returns the
* original keycode unchanged.
*/
uint16_t get_tap_keycode(uint16_t keycode);
#ifndef NO_ACTION_TAPPING
void process_record_tap_hint(keyrecord_t *record);
#endif

View File

@@ -4,7 +4,9 @@
#include "action.h"
#include "action_layer.h"
#include "action_tapping.h"
#include "action_util.h"
#include "keycode.h"
#include "quantum_keycodes.h"
#include "timer.h"
#ifndef NO_ACTION_TAPPING
@@ -49,9 +51,7 @@ __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;
# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
# 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] = {};
@@ -66,6 +66,14 @@ static void registered_taps_del_index(uint8_t i);
/** Logs the registered_taps array for debugging. */
static void debug_registered_taps(void);
static bool is_mt_or_lt(uint16_t keycode) {
return IS_QK_MOD_TAP(keycode) || IS_QK_LAYER_TAP(keycode);
}
# endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
# if defined(CHORDAL_HOLD)
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
/** \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
@@ -82,10 +90,6 @@ static void waiting_buffer_chordal_hold_taps_until(keypos_t key);
/** \brief Processes and pops buffered events until the first tap-hold event. */
static void waiting_buffer_process_regular(void);
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
@@ -98,6 +102,14 @@ __attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyreco
# include "process_auto_shift.h"
# endif
# if defined(FLOW_TAP_TERM)
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, uint16_t prev_time);
# endif // defined(FLOW_TAP_TERM)
static keyrecord_t tapping_key = {};
static keyrecord_t waiting_buffer[WAITING_BUFFER_SIZE] = {};
static uint8_t waiting_buffer_head = 0;
@@ -148,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
}
}
@@ -205,7 +223,7 @@ void action_tapping_process(keyrecord_t record) {
bool process_tapping(keyrecord_t *keyp) {
const keyevent_t event = keyp->event;
# if defined(CHORDAL_HOLD)
# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
if (!event.pressed) {
const int8_t i = registered_tap_find(event.key);
if (i != -1) {
@@ -217,7 +235,7 @@ bool process_tapping(keyrecord_t *keyp) {
debug_registered_taps();
}
}
# endif // CHORDAL_HOLD
# endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
// state machine is in the "reset" state, no tapping key is to be
// processed
@@ -227,6 +245,13 @@ bool process_tapping(keyrecord_t *keyp) {
} else if (event.pressed && is_tap_record(keyp)) {
// the currently pressed key is a tapping key, therefore transition
// into the "pressed" tapping key state
# if defined(FLOW_TAP_TERM)
if (flow_tap_key_if_within_term(keyp, flow_tap_prev_time)) {
return true;
}
# endif // defined(FLOW_TAP_TERM)
ac_dprintf("Tapping: Start(Press tap key).\n");
tapping_key = *keyp;
process_record_tap_hint(&tapping_key);
@@ -263,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;
}
@@ -538,6 +584,13 @@ bool process_tapping(keyrecord_t *keyp) {
return true;
} 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, flow_tap_prev_time)) {
tapping_key = (keyrecord_t){0};
debug_tapping_key();
return true;
}
# endif // defined(FLOW_TAP_TERM)
ac_dprintf("Tapping: Start with interfering other tap.\n");
tapping_key = *keyp;
waiting_buffer_scan_tap();
@@ -655,28 +708,7 @@ void waiting_buffer_scan_tap(void) {
}
}
# 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]);
}
# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
static void registered_taps_add(keypos_t key) {
if (num_registered_taps >= REGISTERED_TAPS_SIZE) {
ac_dprintf("TAPS OVERFLOW: CLEAR ALL STATES\n");
@@ -714,6 +746,30 @@ static void debug_registered_taps(void) {
ac_dprintf("}\n");
}
# endif // defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
# 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 uint8_t waiting_buffer_find_chordal_hold_tap(void) {
keyrecord_t *prev = &tapping_key;
uint16_t prev_keycode = get_record_keycode(&tapping_key, false);
@@ -761,6 +817,100 @@ static void waiting_buffer_process_regular(void) {
}
# endif // CHORDAL_HOLD
# 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 (record->tap.count == 0 && (waiting_buffer_tail != waiting_buffer_head || (tapping_key.event.pressed && tapping_key.tap.count == 0))) {
return;
}
// Ignore releases of modifiers and held layer switches.
if (!record->event.pressed) {
switch (keycode) {
case MODIFIER_KEYCODE_RANGE:
case QK_MOMENTARY ... QK_MOMENTARY_MAX:
case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX:
# ifndef NO_ACTION_ONESHOT // Ignore one-shot keys.
case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX:
case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX:
# endif // NO_ACTION_ONESHOT
# ifdef TRI_LAYER_ENABLE // Ignore Tri Layer keys.
case QK_TRI_LAYER_LOWER:
case QK_TRI_LAYER_UPPER:
# endif // TRI_LAYER_ENABLE
return;
case QK_MODS ... QK_MODS_MAX:
if (QK_MODS_GET_BASIC_KEYCODE(keycode) == KC_NO) {
return;
}
break;
case QK_MOD_TAP ... QK_MOD_TAP_MAX:
case QK_LAYER_TAP ... QK_LAYER_TAP_MAX:
if (record->tap.count == 0) {
return;
}
break;
}
}
flow_tap_prev_keycode = keycode;
flow_tap_prev_time = record->event.time;
flow_tap_expired = false;
}
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)) {
uint16_t term = get_flow_tap_term(keycode, record, flow_tap_prev_keycode);
if (term > 500) {
term = 500;
}
if (idle_time < term) {
debug_event(record->event);
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();
process_record(record);
return true;
}
}
return false;
}
// By default, enable Flow Tap for the keys in the main alphas area and Space.
// This should work reasonably even if the layout is remapped on the host to an
// alt layout or international layout (e.g. Dvorak or AZERTY), where these same
// key positions are mostly used for typing letters.
__attribute__((weak)) bool is_flow_tap_key(uint16_t keycode) {
if ((get_mods() & (MOD_MASK_CG | MOD_BIT_LALT)) != 0) {
return false; // Disable Flow Tap on hotkeys.
}
switch (get_tap_keycode(keycode)) {
case KC_SPC:
case KC_A ... KC_Z:
case KC_DOT:
case KC_COMM:
case KC_SCLN:
case KC_SLSH:
return true;
}
return false;
}
__attribute__((weak)) uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode) {
if (is_flow_tap_key(keycode) && is_flow_tap_key(prev_keycode)) {
return FLOW_TAP_TERM;
}
return 0;
}
# endif // FLOW_TAP_TERM
/** \brief Logs tapping key if ACTION_DEBUG is enabled. */
static void debug_tapping_key(void) {
ac_dprintf("TAPPING_KEY=");

View File

@@ -111,6 +111,66 @@ char chordal_hold_handedness(keypos_t key);
extern const char chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
#endif
#ifdef FLOW_TAP_TERM
/**
* Callback to specify the keys where Flow Tap is enabled.
*
* Flow Tap is constrained to certain keys by the following rule: this callback
* is called for both the tap-hold key *and* the key press immediately preceding
* it. If the callback returns true for both keycodes, Flow Tap is enabled.
*
* The default implementation of this callback corresponds to
*
* bool is_flow_tap_key(uint16_t keycode) {
* switch (get_tap_keycode(keycode)) {
* case KC_SPC:
* case KC_A ... KC_Z:
* case KC_DOT:
* case KC_COMM:
* case KC_SCLN:
* case KC_SLSH:
* return true;
* }
* return false;
* }
*
* @param keycode Keycode of the key.
* @return Whether to enable Flow Tap for this key.
*/
bool is_flow_tap_key(uint16_t keycode);
/**
* Callback to customize Flow Tap filtering.
*
* Flow Tap acts only when key events are closer together than this time.
*
* Return a time of 0 to disable filtering. In this way, Flow Tap may be
* disabled for certain tap-hold keys, or when following certain previous keys.
*
* The default implementation of this callback is
*
* uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t* record,
* uint16_t prev_keycode) {
* if (is_flow_tap_key(keycode) && is_flow_tap_key(prev_keycode)) {
* return g_flow_tap_term;
* }
* return 0;
* }
*
* NOTE: If both `is_flow_tap_key()` and `get_flow_tap_term()` are defined, then
* `get_flow_tap_term()` takes precedence.
*
* @param keycode Keycode of the tap-hold key.
* @param record keyrecord_t of the tap-hold event.
* @param prev_keycode Keycode of the previously pressed key.
* @return Time in milliseconds.
*/
uint16_t get_flow_tap_term(uint16_t keycode, keyrecord_t *record, uint16_t prev_keycode);
/** Updates the Flow Tap last key and timer. */
void flow_tap_update_last_event(keyrecord_t *record);
#endif // FLOW_TAP_TERM
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
extern uint16_t g_tapping_term;
#endif

View File

@@ -22,11 +22,7 @@ bool process_leader(uint16_t keycode, keyrecord_t *record) {
if (record->event.pressed) {
if (leader_sequence_active() && !leader_sequence_timed_out()) {
#ifndef LEADER_KEY_STRICT_KEY_PROCESSING
if (IS_QK_MOD_TAP(keycode)) {
keycode = QK_MOD_TAP_GET_TAP_KEYCODE(keycode);
} else if (IS_QK_LAYER_TAP(keycode)) {
keycode = QK_LAYER_TAP_GET_TAP_KEYCODE(keycode);
}
keycode = get_tap_keycode(keycode);
#endif
if (!leader_sequence_add(keycode)) {

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

@@ -0,0 +1,23 @@
/* Copyright 2022 Vladislav Kucheriavykh
* Copyright 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 FLOW_TAP_TERM 150
#define PERMISSIVE_HOLD

View File

@@ -0,0 +1,18 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# 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/>.
COMBO_ENABLE = yes
INTROSPECTION_KEYMAP_C = test_keymap.c

View File

@@ -0,0 +1,23 @@
// Copyright 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 "quantum.h"
uint16_t const mt_lt_combo[] = {SFT_T(KC_X), LT(1, KC_Y), COMBO_END};
// clang-format off
combo_t key_combos[] = {
COMBO(mt_lt_combo, KC_Z),
};
// clang-format on

View File

@@ -0,0 +1,844 @@
// Copyright 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::AnyNumber;
using testing::InSequence;
class FlowTapTest : public TestFixture {};
// Test an input of quick distinct taps. All should be settled as tapped.
TEST_F(FlowTapTest, distinct_taps) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
auto mod_tap_key3 = KeymapKey(0, 3, 0, ALT_T(KC_D));
set_keymap({regular_key, mod_tap_key1, mod_tap_key2, mod_tap_key3});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key, FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Tap mod-tap 1.
EXPECT_REPORT(driver, (KC_B));
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap mod-tap 2.
EXPECT_REPORT(driver, (KC_C));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap mod-tap 3.
EXPECT_REPORT(driver, (KC_D));
mod_tap_key3.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key3.release();
idle_for(FLOW_TAP_TERM + 1); // Pause between taps.
VERIFY_AND_CLEAR(driver);
// Tap mod-tap 1.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap mod-tap 2.
EXPECT_REPORT(driver, (KC_C));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(TAPPING_TERM + 1);
mod_tap_key2.release();
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
}
// By default, Flow Tap is disabled when mods other than Shift and AltGr are on.
TEST_F(FlowTapTest, hotkey_taps) {
TestDriver driver;
InSequence s;
auto ctrl_key = KeymapKey(0, 0, 0, KC_LCTL);
auto shft_key = KeymapKey(0, 1, 0, KC_LSFT);
auto alt_key = KeymapKey(0, 2, 0, KC_LALT);
auto gui_key = KeymapKey(0, 3, 0, KC_LGUI);
auto regular_key = KeymapKey(0, 4, 0, KC_A);
auto mod_tap_key = KeymapKey(0, 5, 0, RCTL_T(KC_B));
set_keymap({ctrl_key, shft_key, alt_key, gui_key, regular_key, mod_tap_key});
for (KeymapKey* mod_key : {&ctrl_key, &alt_key, &gui_key}) {
// Hold mod key.
EXPECT_REPORT(driver, (mod_key->code));
mod_key->press();
run_one_scan_loop();
// Tap regular key.
EXPECT_REPORT(driver, (mod_key->code, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (mod_key->code));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap, where Flow Tap is disabled due to the held mod.
EXPECT_REPORT(driver, (mod_key->code, KC_RCTL));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release mod-tap.
EXPECT_REPORT(driver, (mod_key->code));
mod_tap_key.release();
run_one_scan_loop();
// Release mod key.
EXPECT_EMPTY_REPORT(driver);
mod_key->release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
// Hold Shift key.
EXPECT_REPORT(driver, (KC_LSFT));
shft_key.press();
run_one_scan_loop();
// Tap regular key.
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap, where Flow Tap applies to settle as tapped.
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release mod-tap.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.release();
run_one_scan_loop();
// Release Shift key.
EXPECT_EMPTY_REPORT(driver);
shft_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
// Test input with two mod-taps in a rolled press quickly after a regular key.
TEST_F(FlowTapTest, rolled_press) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
set_keymap({regular_key, mod_tap_key1, mod_tap_key2});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Press mod-tap key 1 quickly after regular key. The mod-tap should settle
// immediately as tapped, sending `KC_B`.
EXPECT_REPORT(driver, (KC_B));
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap key 2 quickly.
EXPECT_REPORT(driver, (KC_B, KC_C));
mod_tap_key2.press();
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_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, long_flow_tap_settled_as_held) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B));
set_keymap({regular_key, mod_tap_key});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Press mod-tap key.
EXPECT_NO_REPORT(driver);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Hold for the tapping term.
EXPECT_REPORT(driver, (KC_LSFT));
idle_for(TAPPING_TERM);
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(FlowTapTest, holding_multiple_mod_taps) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_tap_key1 = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto mod_tap_key2 = KeymapKey(0, 2, 0, CTL_T(KC_C));
set_keymap({regular_key, mod_tap_key1, mod_tap_key2});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Press mod-tap keys.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
mod_tap_key2.press();
idle_for(TAPPING_TERM - 5); // Hold almost until tapping term.
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL, KC_A));
regular_key.press();
idle_for(10);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_EMPTY_REPORT(driver);
regular_key.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(FlowTapTest, holding_mod_tap_with_regular_mod) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_key = KeymapKey(0, 1, 0, KC_LSFT);
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_C));
set_keymap({regular_key, mod_key, mod_tap_key});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Press mod and mod-tap keys.
EXPECT_REPORT(driver, (KC_LSFT));
mod_key.press();
run_one_scan_loop();
mod_tap_key.press();
idle_for(TAPPING_TERM - 5); // Hold almost until tapping term.
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL, KC_A));
regular_key.press();
idle_for(10);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LSFT, KC_LCTL));
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
mod_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, layer_tap_key) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_B));
auto regular_key2 = KeymapKey(1, 0, 0, KC_C);
set_keymap({regular_key, layer_tap_key, regular_key2});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Press layer-tap key, quickly after the regular key.
EXPECT_REPORT(driver, (KC_B));
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release layer-tap key.
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Press layer-tap key, slowly after the regular key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
EXPECT_EQ(layer_state, 1 << 1);
VERIFY_AND_CLEAR(driver);
// Tap regular key2.
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Release layer-tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, layer_tap_ignored_with_disabled_key) {
TestDriver driver;
InSequence s;
auto no_key = KeymapKey(0, 0, 0, KC_NO);
auto regular_key = KeymapKey(1, 0, 0, KC_ESC);
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
EXPECT_REPORT(driver, (KC_ESC));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
tap_key(regular_key);
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL));
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(FlowTapTest, layer_tap_ignored_with_disabled_key_complex) {
TestDriver driver;
InSequence s;
auto regular_key1 = KeymapKey(0, 0, 0, KC_Q);
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_SPC));
auto mod_tap_key1 = KeymapKey(0, 2, 0, CTL_T(KC_T));
// Place RALT_T(KC_I), where Flow Tap is enabled, in the same position on
// layer 0 as KC_RGHT, where Flow Tap is disabled. This tests that Flow Tap
// tracks the keycode from the correct layer.
auto mod_tap_key2 = KeymapKey(0, 3, 0, RALT_T(KC_I));
auto regular_key2 = KeymapKey(1, 3, 0, KC_RGHT);
set_keymap({regular_key1, layer_tap_key, mod_tap_key1, mod_tap_key2, regular_key2});
// Tap regular key 1.
EXPECT_REPORT(driver, (KC_Q));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key1);
idle_for(FLOW_TAP_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Hold layer-tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
// idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Tap regular key 2.
EXPECT_REPORT(driver, (KC_RGHT));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key2);
VERIFY_AND_CLEAR(driver);
// Release layer-tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Quickly hold mod-tap key 1.
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_REPORT(driver, (KC_LCTL, KC_Q));
EXPECT_REPORT(driver, (KC_LCTL));
tap_key(regular_key1);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, layer_tap_ignored_with_enabled_key) {
TestDriver driver;
InSequence s;
auto no_key = KeymapKey(0, 0, 0, KC_NO);
auto regular_key = KeymapKey(1, 0, 0, KC_C);
auto layer_tap_key = KeymapKey(0, 1, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 2, 0, CTL_T(KC_B));
set_keymap({no_key, regular_key, layer_tap_key, mod_tap_key});
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
tap_key(regular_key);
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
idle_for(TAPPING_TERM + 1);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, combo_key) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_X));
auto layer_tap_key = KeymapKey(0, 2, 0, LT(1, KC_Y));
set_keymap({regular_key, mod_tap_key, layer_tap_key});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Press combo keys quickly after regular key.
EXPECT_REPORT(driver, (KC_Z));
EXPECT_EMPTY_REPORT(driver);
tap_combo({mod_tap_key, layer_tap_key});
VERIFY_AND_CLEAR(driver);
// Press mod-tap key quickly.
EXPECT_REPORT(driver, (KC_X));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
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(FlowTapTest, oneshot_mod_key) {
TestDriver driver;
InSequence s;
auto regular_key = KeymapKey(0, 0, 0, KC_A);
auto osm_key = KeymapKey(0, 1, 0, OSM(MOD_LSFT));
set_keymap({regular_key, osm_key});
// Tap regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Tap OSM, tap regular key.
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(osm_key);
tap_key(regular_key);
VERIFY_AND_CLEAR(driver);
// Nested press of OSM and regular keys.
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);
osm_key.press();
run_one_scan_loop();
tap_key(regular_key);
osm_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(FlowTapTest, quick_tap) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_A));
set_keymap({mod_tap_key});
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
tap_key(mod_tap_key);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_A));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
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(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);
}