mirror of
https://github.com/zsa/qmk_firmware.git
synced 2026-01-08 06:32:25 +00:00
Add Flow Tap to zsa/qmk_firmware. (#408)
* 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:
@@ -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"},
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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=");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'},
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
23
tests/tap_hold_configurations/flow_tap/config.h
Normal file
23
tests/tap_hold_configurations/flow_tap/config.h
Normal 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
|
||||
18
tests/tap_hold_configurations/flow_tap/test.mk
Normal file
18
tests/tap_hold_configurations/flow_tap/test.mk
Normal 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
|
||||
23
tests/tap_hold_configurations/flow_tap/test_keymap.c
Normal file
23
tests/tap_hold_configurations/flow_tap/test_keymap.c
Normal 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
|
||||
844
tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp
Normal file
844
tests/tap_hold_configurations/flow_tap/test_tap_hold.cpp
Normal 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);
|
||||
}
|
||||
Reference in New Issue
Block a user