[Core] Speculative Hold option for mod-taps: hold mods instantly while unsettled. (#25572)

This commit is contained in:
Pascal Getreuer 2025-11-11 03:27:12 -08:00 committed by GitHub
parent 2af9aac61c
commit efc5d63383
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 3662 additions and 1 deletions

View file

@ -224,6 +224,7 @@
"PERMISSIVE_HOLD_PER_KEY": {"info_key": "tapping.permissive_hold_per_key", "value_type": "flag"},
"RETRO_TAPPING": {"info_key": "tapping.retro", "value_type": "flag"},
"RETRO_TAPPING_PER_KEY": {"info_key": "tapping.retro_per_key", "value_type": "flag"},
"SPECULATIVE_HOLD": {"info_key": "tapping.speculative_hold", "value_type": "flag"},
"TAP_CODE_DELAY": {"info_key": "qmk.tap_keycode_delay", "value_type": "int"},
"TAP_HOLD_CAPS_DELAY": {"info_key": "qmk.tap_capslock_delay", "value_type": "int"},
"TAPPING_TERM": {"info_key": "tapping.term", "value_type": "int"},

View file

@ -779,6 +779,39 @@ Do not use `MOD_xxx` constants like `MOD_LSFT` or `MOD_RALT`, since they're 5-bi
[Auto Shift](features/auto_shift) has its own version of `retro tapping` called `retro shift`. It is extremely similar to `retro tapping`, but holding the key past `AUTO_SHIFT_TIMEOUT` results in the value it sends being shifted. Other configurations also affect it differently; see [here](features/auto_shift#retro-shift) for more information.
### Speculative Hold
Speculative Hold makes mod-tap keys more responsive by applying the modifier instantly on keydown, before the tap-hold decision is made. This is especially useful for actions like Shift+Click with a mouse, which can feel laggy with standard mod-taps.
The firmware holds the modifier speculatively. Once the key's behavior is settled:
* If held, the modifier remains active as expected until the key is released.
* If tapped, the speculative modifier is canceled just before the tapping keycode is sent.
Speculative Hold applies the modifier early but does not change the underlying tap-hold decision logic. Speculative Hold is compatible to use in combination with any other tap-hold options.
To enable Speculative Hold, add the following to your `config.h`:
```c
#define SPECULATIVE_HOLD
```
By default, Speculative Hold applies to mod-taps using Shift, Ctrl, or Shift + Ctrl. You can override this behavior by defining the `get_speculative_hold()` callback in your keymap, for instance:
```c
bool get_speculative_hold(uint16_t keycode, keyrecord_t* record) {
switch (keycode) { // These keys may be speculatively held.
case LCTL_T(KC_ESC):
case LSFT_T(KC_Z):
case RSFT_T(KC_SLSH):
return true;
}
return false; // Disable otherwise.
}
```
Some operating systems or applications assign actions to tapping a modifier key by itself, e.g., tapping GUI to open a start menu. Because Speculative Hold sends a lone modifier key press in some cases, it can falsely trigger these actions. To prevent this, set `DUMMY_MOD_NEUTRALIZER_KEYCODE` (and optionally `MODS_TO_NEUTRALIZE`) in your `config.h` in the same way as described above for [Retro Tapping](#retro-tapping).
## Why do we include the key record for the per key functions?
One thing that you may notice is that we include the key record for all of the "per key" functions, and may be wondering why we do that.

View file

@ -281,6 +281,11 @@ void process_record(keyrecord_t *record) {
if (IS_NOEVENT(record->event)) {
return;
}
#ifdef SPECULATIVE_HOLD
if (record->event.pressed) {
speculative_key_settled(record);
}
#endif // SPECULATIVE_HOLD
#ifdef FLOW_TAP_TERM
flow_tap_update_last_event(record);
#endif // FLOW_TAP_TERM

View file

@ -38,7 +38,7 @@ extern "C" {
/* tapping count and state */
typedef struct {
bool interrupted : 1;
bool reserved2 : 1;
bool speculated : 1;
bool reserved1 : 1;
bool reserved0 : 1;
uint8_t count : 4;

View file

@ -6,8 +6,10 @@
#include "action_tapping.h"
#include "action_util.h"
#include "keycode.h"
#include "keycode_config.h"
#include "quantum_keycodes.h"
#include "timer.h"
#include "wait.h"
#ifndef NO_ACTION_TAPPING
@ -51,6 +53,21 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
}
# endif
# ifdef SPECULATIVE_HOLD
typedef struct {
keypos_t key;
uint8_t mods;
} speculative_key_t;
# define SPECULATIVE_KEYS_SIZE 8
static speculative_key_t speculative_keys[SPECULATIVE_KEYS_SIZE] = {};
static uint8_t num_speculative_keys = 0;
static uint8_t prev_speculative_mods = 0;
static uint8_t speculative_mods = 0;
/** Handler to be called on incoming press events. */
static void speculative_key_press(keyrecord_t *record);
# endif // SPECULATIVE_HOLD
# 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.
@ -129,6 +146,13 @@ static void debug_waiting_buffer(void);
* FIXME: Needs doc
*/
void action_tapping_process(keyrecord_t record) {
# ifdef SPECULATIVE_HOLD
prev_speculative_mods = speculative_mods;
if (record.event.pressed) {
speculative_key_press(&record);
}
# endif // SPECULATIVE_HOLD
if (process_tapping(&record)) {
if (IS_EVENT(record.event)) {
ac_dprintf("processed: ");
@ -145,6 +169,12 @@ void action_tapping_process(keyrecord_t record) {
}
}
# ifdef SPECULATIVE_HOLD
if (speculative_mods != prev_speculative_mods) {
send_keyboard_report();
}
# endif // SPECULATIVE_HOLD
// process waiting_buffer
if (IS_EVENT(record.event) && waiting_buffer_head != waiting_buffer_tail) {
ac_dprintf("---- action_exec: process waiting_buffer -----\n");
@ -708,6 +738,147 @@ void waiting_buffer_scan_tap(void) {
}
}
# ifdef SPECULATIVE_HOLD
static void debug_speculative_keys(void) {
ac_dprintf("mods = { ");
for (int8_t i = 0; i < num_speculative_keys; ++i) {
ac_dprintf("%02X ", speculative_keys[i].mods);
}
ac_dprintf("}, keys = { ");
for (int8_t i = 0; i < num_speculative_keys; ++i) {
ac_dprintf("%02X%02X ", speculative_keys[i].key.row, speculative_keys[i].key.col);
}
ac_dprintf("}\n");
}
// Find key in speculative_keys. Returns num_speculative_keys if not found.
static int8_t speculative_keys_find(keypos_t key) {
uint8_t i;
for (i = 0; i < num_speculative_keys; ++i) {
if (KEYEQ(speculative_keys[i].key, key)) {
break;
}
}
return i;
}
static void speculative_key_press(keyrecord_t *record) {
if (num_speculative_keys >= SPECULATIVE_KEYS_SIZE) { // Overflow!
ac_dprintf("SPECULATIVE KEYS OVERFLOW: IGNORING EVENT\n");
return; // Don't trigger: speculative_keys is full.
}
if (speculative_keys_find(record->event.key) < num_speculative_keys) {
return; // Don't trigger: key is already in speculative_keys.
}
const uint16_t keycode = get_record_keycode(record, false);
if (!IS_QK_MOD_TAP(keycode)) {
return; // Don't trigger: not a mod-tap key.
}
uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode));
if ((mods & 0x10) != 0) { // Unpack 5-bit mods to 8-bit representation.
mods <<= 4;
}
if ((~(get_mods() | speculative_mods) & mods) == 0) {
return; // Don't trigger: mods are already active.
}
// Don't do Speculative Hold when there are non-speculated buffered events,
// since that could result in sending keys out of order.
for (uint8_t i = waiting_buffer_tail; i != waiting_buffer_head; i = (i + 1) % WAITING_BUFFER_SIZE) {
if (!waiting_buffer[i].tap.speculated) {
return;
}
}
if (get_speculative_hold(keycode, record)) {
record->tap.speculated = true;
speculative_mods |= mods;
// Remember the keypos and mods associated with this key.
speculative_keys[num_speculative_keys] = (speculative_key_t){
.key = record->event.key,
.mods = mods,
};
++num_speculative_keys;
ac_dprintf("Speculative Hold: ");
debug_speculative_keys();
}
}
uint8_t get_speculative_mods(void) {
return speculative_mods;
}
__attribute__((weak)) bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
const uint8_t mods = mod_config(QK_MOD_TAP_GET_MODS(keycode));
return (mods & (MOD_LCTL | MOD_LSFT)) == mods;
}
void speculative_key_settled(keyrecord_t *record) {
if (num_speculative_keys == 0) {
return; // Early return when there are no active speculative keys.
}
uint8_t i = speculative_keys_find(record->event.key);
const uint16_t keycode = get_record_keycode(record, false);
if (IS_QK_MOD_TAP(keycode) && record->tap.count == 0) { // MT hold press.
if (i < num_speculative_keys) {
--num_speculative_keys;
const uint8_t cleared_mods = speculative_keys[i].mods;
if (num_speculative_keys) {
speculative_mods &= ~cleared_mods;
// Don't call send_keyboard_report() here; allow default
// handling to reapply the mod before the next report.
// Remove the ith entry from speculative_keys.
for (uint8_t j = i; j < num_speculative_keys; ++j) {
speculative_keys[j] = speculative_keys[j + 1];
}
} else {
speculative_mods = 0;
}
ac_dprintf("Speculative Hold: settled %02x, ", cleared_mods);
debug_speculative_keys();
}
} else { // Tap press event; cancel speculatively-held mod.
if (i >= num_speculative_keys) {
i = 0;
}
// Clear mods for the ith key and all keys that follow.
uint8_t cleared_mods = 0;
for (uint8_t j = i; j < num_speculative_keys; ++j) {
cleared_mods |= speculative_keys[j].mods;
}
num_speculative_keys = i; // Remove ith and following entries.
if ((prev_speculative_mods & cleared_mods) != 0) {
# ifdef DUMMY_MOD_NEUTRALIZER_KEYCODE
neutralize_flashing_modifiers(get_mods() | prev_speculative_mods);
# endif // DUMMY_MOD_NEUTRALIZER_KEYCODE
}
if (num_speculative_keys) {
speculative_mods &= ~cleared_mods;
} else {
speculative_mods = 0;
}
send_keyboard_report();
wait_ms(TAP_CODE_DELAY);
ac_dprintf("Speculative Hold: canceled %02x, ", cleared_mods);
debug_speculative_keys();
}
}
# endif // SPECULATIVE_HOLD
# if defined(CHORDAL_HOLD) || defined(FLOW_TAP_TERM)
static void registered_taps_add(keypos_t key) {
if (num_registered_taps >= REGISTERED_TAPS_SIZE) {

View file

@ -46,6 +46,36 @@ bool get_permissive_hold(uint16_t keycode, keyrecord_t *record);
bool get_retro_tapping(uint16_t keycode, keyrecord_t *record);
bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record);
#ifdef SPECULATIVE_HOLD
/** Gets the currently active speculative mods. */
uint8_t get_speculative_mods(void);
/**
* Callback to say if a mod-tap key may be speculatively held.
*
* By default, speculative hold is enabled for mod-tap keys where the mod is
* Ctrl, Shift, and Ctrl+Shift for either hand.
*
* @param keycode Keycode of the mod-tap key.
* @param record Record associated with the mod-tap press event.
* @return True if the mod-tap key may be speculatively held.
*/
bool get_speculative_hold(uint16_t keycode, keyrecord_t *record);
/**
* Handler to be called on press events after tap-holds are settled.
*
* This function is to be called in process_record() in action.c, that is, just
* after tap-hold events are settled as either tapped or held. When `record`
* corresponds to a speculatively-held key, the speculative mod is cleared.
*
* @param record Record associated with the mod-tap press event.
*/
void speculative_key_settled(keyrecord_t *record);
#else
# define get_speculative_mods() 0
#endif // SPECULATIVE_HOLD
#ifdef CHORDAL_HOLD
/**
* Callback to say when a key chord before the tapping term may be held.

View file

@ -19,6 +19,7 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
#include "debug.h"
#include "action_util.h"
#include "action_layer.h"
#include "action_tapping.h"
#include "timer.h"
#include "keycode_config.h"
#include <string.h>
@ -284,6 +285,10 @@ static uint8_t get_mods_for_report(void) {
}
#endif
#ifdef SPECULATIVE_HOLD
mods |= get_speculative_mods();
#endif
#ifdef KEY_OVERRIDE_ENABLE
// These need to be last to be able to properly control key overrides
mods &= ~suppressed_mods;

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 SPECULATIVE_HOLD
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24

View file

@ -0,0 +1,21 @@
# 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/>.
KEY_OVERRIDE_ENABLE = yes
MAGIC_ENABLE = yes
INTROSPECTION_KEYMAP_C = test_keymap.c

View file

@ -0,0 +1,20 @@
// 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"
// Shift + Esc = Home
const key_override_t home_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, KC_HOME);
const key_override_t *key_overrides[] = {&home_esc_override};

View file

@ -0,0 +1,794 @@
// 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 <functional>
#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;
namespace {
// Gets the unpacked 8-bit mods corresponding to a given mod-tap keycode.
uint8_t unpack_mod_tap_mods(uint16_t keycode) {
const uint8_t mods5 = QK_MOD_TAP_GET_MODS(keycode);
return (mods5 & 0x10) != 0 ? (mods5 << 4) : mods5;
}
bool get_speculative_hold_all_keys(uint16_t keycode, keyrecord_t *record) {
return true; // Enable Speculative Hold for all mod-tap keys.
}
bool process_record_user_default(uint16_t keycode, keyrecord_t *record) {
return true;
}
// Indirection so that get_speculative_hold() and process_record_user() can be
// replaced with other functions in the test cases below.
std::function<bool(uint16_t, keyrecord_t *)> get_speculative_hold_fun = get_speculative_hold_all_keys;
std::function<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default;
extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
return get_speculative_hold_fun(keycode, record);
}
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) {
return process_record_user_fun(keycode, record);
}
class SpeculativeHoldDefault : public TestFixture {
public:
void SetUp() override {
get_speculative_hold_fun = get_speculative_hold_all_keys;
process_record_user_fun = process_record_user_default;
}
};
TEST_F(SpeculativeHoldDefault, tap_mod_tap) {
TestDriver driver;
InSequence s;
static int process_record_user_calls = 0;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
process_record_user_fun = [](uint16_t keycode, keyrecord_t *record) {
++process_record_user_calls;
return true;
};
// Press mod-tap-hold key. Mod is held speculatively.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
idle_for(10);
VERIFY_AND_CLEAR(driver);
EXPECT_EQ(get_speculative_mods(), MOD_BIT_LSHIFT);
// Speculative mod holds and releases are made directly, bypassing regular
// event processing. No calls have been made yet to process_record_user().
EXPECT_EQ(process_record_user_calls, 0);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver); // Speculative mod canceled.
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), 0);
// Two calls have now been made, for pressing and releasing KC_P.
EXPECT_EQ(process_record_user_calls, 2);
// Idle for tapping term of mod tap hold key.
idle_for(TAPPING_TERM - 10);
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, tap_mod_tap_neutralized) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, GUI_T(KC_P));
set_keymap({mod_tap_key});
// Press mod-tap-hold key. Mod is held speculatively.
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_key.press();
idle_for(10);
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key. Speculative mod is neutralized and canceled.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Idle for tapping term of mod tap hold key.
idle_for(TAPPING_TERM - 10);
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, hold_two_mod_taps) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, LCTL_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, RALT_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press first mod-tap key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL);
// Press second mod-tap key.
EXPECT_REPORT(driver, (KC_LCTL, KC_RALT));
mod_tap_key2.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EQ(get_speculative_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), MOD_BIT_LCTRL | MOD_BIT_RALT);
// Release first mod-tap key.
EXPECT_REPORT(driver, (KC_RALT));
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release second mod-tap key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, two_mod_taps_same_mods) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 1, 0, GUI_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 2, 0, GUI_T(KC_B));
set_keymap({mod_tap_key1, mod_tap_key2});
// Press first mod-tap key.
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap second mod-tap key.
EXPECT_NO_REPORT(driver);
mod_tap_key2.press();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release first mod-tap key.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_REPORT(driver, (KC_A, KC_B));
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, respects_get_speculative_hold_callback) {
TestDriver driver;
InSequence s;
auto mod_tap_key1 = KeymapKey(0, 0, 0, LSFT_T(KC_A));
auto mod_tap_key2 = KeymapKey(0, 1, 0, LSFT_T(KC_B));
auto mod_tap_key3 = KeymapKey(0, 2, 0, LCTL_T(KC_C));
auto mod_tap_key4 = KeymapKey(0, 3, 0, LCTL_T(KC_D));
auto mod_tap_key5 = KeymapKey(0, 4, 0, RSFT_T(KC_E));
auto mod_tap_key6 = KeymapKey(0, 5, 0, RSFT_T(KC_F));
set_keymap({mod_tap_key1, mod_tap_key2, mod_tap_key3, mod_tap_key4, mod_tap_key5, mod_tap_key6});
// Enable Speculative Hold selectively for some of the keys.
get_speculative_hold_fun = [](uint16_t keycode, keyrecord_t *record) {
switch (keycode) {
case LSFT_T(KC_B):
case LCTL_T(KC_D):
case RSFT_T(KC_F):
return true;
}
return false;
};
for (KeymapKey *mod_tap_key : {&mod_tap_key2, &mod_tap_key4, &mod_tap_key6}) {
SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
// Long press and release mod_tap_key.
// For these keys where Speculative Hold is enabled, then the mod should
// activate immediately on keydown.
EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
mod_tap_key->press();
run_one_scan_loop();
EXPECT_EQ(get_speculative_mods(), mods);
EXPECT_EQ(get_mods(), 0);
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM + 1);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), mods);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key->release();
idle_for(TAPPING_TERM + 1);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), 0);
VERIFY_AND_CLEAR(driver);
}
for (KeymapKey *mod_tap_key : {&mod_tap_key1, &mod_tap_key3, &mod_tap_key5}) {
SCOPED_TRACE(std::string("mod_tap_key = ") + mod_tap_key->name);
const uint8_t mods = unpack_mod_tap_mods(mod_tap_key->code);
// Long press and release mod_tap_key.
// For these keys where Speculative Hold is disabled, the mod should
// activate when the key has settled after the tapping term.
EXPECT_NO_REPORT(driver);
mod_tap_key->press();
run_one_scan_loop();
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), 0);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL + biton(mods)));
idle_for(TAPPING_TERM + 1);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), mods);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key->release();
idle_for(TAPPING_TERM + 1);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), 0);
VERIFY_AND_CLEAR(driver);
}
}
TEST_F(SpeculativeHoldDefault, respects_magic_mod_config) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, CTL_T(KC_P));
set_keymap({mod_tap_key});
keymap_config.swap_lctl_lgui = true;
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
keymap_config.swap_lctl_lgui = false;
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, key_overrides) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A));
auto esc_key = KeymapKey(0, 3, 0, KC_ESC);
set_keymap({mod_tap_key, esc_key});
// Press mod-tap Shift key.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press Esc key.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_HOME));
esc_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
// Release Esc key.
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT));
esc_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap Shift key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, tap_regular_key_while_mod_tap_key_is_held) {
TestDriver driver;
InSequence s;
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_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Tap regular key.
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Idle for tapping term of mod tap hold key.
idle_for(TAPPING_TERM - 3);
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, tap_a_mod_tap_key_while_another_mod_tap_key_is_held) {
TestDriver driver;
InSequence s;
auto first_mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto second_mod_tap_key = KeymapKey(0, 2, 0, RSFT_T(KC_A));
set_keymap({first_mod_tap_key, second_mod_tap_key});
// Press first mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LSFT));
first_mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press second tap-hold key.
EXPECT_REPORT(driver, (KC_LSFT, KC_RSFT));
second_mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release second tap-hold key.
EXPECT_NO_REPORT(driver);
second_mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release first mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_P, KC_A));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
first_mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldDefault, tap_mod_tap_key_two_times) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-hold key again.
EXPECT_REPORT(driver, (KC_P));
mod_tap_key.press();
idle_for(TAPPING_TERM);
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(SpeculativeHoldDefault, tap_mod_tap_key_twice_and_hold_on_second_time) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-hold key again.
EXPECT_REPORT(driver, (KC_P));
mod_tap_key.press();
idle_for(TAPPING_TERM);
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(SpeculativeHoldDefault, tap_and_hold_mod_tap_key) {
TestDriver driver;
InSequence s;
static int process_record_user_calls = 0;
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
set_keymap({mod_tap_key});
process_record_user_fun = [](uint16_t keycode, keyrecord_t *record) {
++process_record_user_calls;
return true;
};
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
idle_for(TAPPING_TERM - 1);
EXPECT_EQ(get_speculative_mods(), MOD_BIT_LSHIFT);
EXPECT_EQ(get_mods(), 0);
// Speculative mod holds and releases are made directly, bypassing regular
// event processing. No calls have been made yet to process_record_user().
EXPECT_EQ(process_record_user_calls, 0);
idle_for(2);
// Now that the key has settled, one call has been made for the hold event.
EXPECT_EQ(process_record_user_calls, 1);
EXPECT_EQ(get_speculative_mods(), 0);
EXPECT_EQ(get_mods(), MOD_BIT_LSHIFT);
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
EXPECT_EQ(process_record_user_calls, 2);
VERIFY_AND_CLEAR(driver);
}
// Test with layer tap and speculative mod tap keys on the same layer,
// rolling from LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldDefault, lt_mt_same_layer_roll) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap key, after flow tap term but within tapping term. The
// speculative mod activates.
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Wait for the layer tap key to settle.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying a
// nested press:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(SpeculativeHoldDefault, lt_mt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
run_one_scan_loop();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys: MT first, LT second.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying a
// nested press with the MT first:
// "MT down, LT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldDefault, mt_lt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, SFT_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, rolling from
// LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
// Press mod tap key.
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LSFT));
layer_tap_key.release();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, slowly
// rolling from LT to MT key:
// "LT down, (wait), MT down, (wait), LT up, MT up."
TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_slow_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, trying a
// nested press:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, trying a
// slow nested press:
// "LT down, (wait), MT down, MT up, LT up."
TEST_F(SpeculativeHoldDefault, lt_mt_different_layer_slow_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, SFT_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
} // namespace

View file

@ -0,0 +1,25 @@
/* 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 SPECULATIVE_HOLD
#define FLOW_TAP_TERM 150
#define PERMISSIVE_HOLD
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24

View file

@ -0,0 +1,15 @@
# 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/>.

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,30 @@
/* Copyright 2022 Isaac Elenbaas
* 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 SPECULATIVE_HOLD
#define PERMISSIVE_HOLD
#define RETRO_SHIFT 2 * TAPPING_TERM
// releases between AUTO_SHIFT_TIMEOUT and TAPPING_TERM are not tested
#define AUTO_SHIFT_TIMEOUT TAPPING_TERM
#define AUTO_SHIFT_MODIFIERS
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24

View file

@ -0,0 +1,17 @@
# Copyright 2022 Isaac Elenbaas
# 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/>.
AUTO_SHIFT_ENABLE = yes

View file

@ -0,0 +1,689 @@
// Copyright 2022 Isaac Elenbaas
// 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/>.
#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"
extern "C" {
bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
return true;
}
bool get_auto_shifted_key(uint16_t keycode, keyrecord_t *record) {
return true;
}
} // extern "C"
using testing::_;
using testing::AnyNumber;
using testing::AnyOf;
using testing::InSequence;
class RetroShiftPermissiveHold : public TestFixture {};
TEST_F(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_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_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
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(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A));
set_keymap({mod_tap_key, mod_tap_regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL, KC_LALT));
mod_tap_regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
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(RetroShiftPermissiveHold, tap_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_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_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
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(RetroShiftPermissiveHold, tap_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A));
set_keymap({mod_tap_key, mod_tap_regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL, KC_LALT));
mod_tap_regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-regular key.
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LCTL))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
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(RetroShiftPermissiveHold, hold_regular_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press regular key.
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
VERIFY_AND_CLEAR(driver);
// Release regular key.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL));
regular_key.release();
run_one_scan_loop();
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(RetroShiftPermissiveHold, hold_mod_tap_key_while_mod_tap_key_is_held_over_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A));
set_keymap({mod_tap_key, mod_tap_regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL, KC_LALT));
mod_tap_regular_key.press();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
VERIFY_AND_CLEAR(driver);
// Release mod-tap-regular key.
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT),
KeyboardReport(KC_LCTL))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
// clang-format off
EXPECT_CALL(driver, send_keyboard_mock(AnyOf(
KeyboardReport(KC_LCTL, KC_LSFT),
KeyboardReport(KC_LSFT))))
.Times(AnyNumber());
// clang-format on
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_regular_key.release();
run_one_scan_loop();
idle_for(TAPPING_TERM);
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(RetroShiftPermissiveHold, roll_tap_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_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 mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(RetroShiftPermissiveHold, roll_tap_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A));
set_keymap({mod_tap_key, mod_tap_regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL, KC_LALT));
mod_tap_regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-regular key.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(RetroShiftPermissiveHold, roll_hold_regular_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, KC_A);
set_keymap({mod_tap_key, regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_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 mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
idle_for(AUTO_SHIFT_TIMEOUT);
VERIFY_AND_CLEAR(driver);
// Release regular key.
EXPECT_NO_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(RetroShiftPermissiveHold, roll_hold_mod_tap_key_while_mod_tap_key_is_held_under_tapping_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 0, 0, LCTL_T(KC_P));
auto mod_tap_regular_key = KeymapKey(0, MATRIX_COLS - 1, 0, LALT_T(KC_A));
set_keymap({mod_tap_key, mod_tap_regular_key});
// Press mod-tap-hold key.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap-regular key.
EXPECT_REPORT(driver, (KC_LCTL, KC_LALT));
mod_tap_regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-hold key.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Release mod-tap-regular key.
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
EXPECT_EMPTY_REPORT(driver);
idle_for(AUTO_SHIFT_TIMEOUT);
mod_tap_regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
// Test with layer tap and speculative mod tap keys on the same layer, rolling
// from LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_same_layer_roll) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap key, after flow tap term but within tapping term. The
// speculative mod activates.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Wait for the layer tap key to settle.
EXPECT_NO_REPORT(driver);
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying
// a nested press from LT to MT key:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
run_one_scan_loop();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys: MT first, LT second.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_C));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying
// a nested press with the MT first:
// "MT down, LT down, (wait out tapping term), LT up, MT up."
TEST_F(RetroShiftPermissiveHold, mt_lt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT, KC_A));
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT));
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, rolling from
// LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
// Press mod tap key.
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, slowly
// rolling from LT to MT key:
// "LT down, (wait), MT down, (wait), LT up, MT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_slow_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_A));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, try a nested
// press:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LSFT, KC_C));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, try a slow
// nested press:
// "LT down, (wait), MT down, MT up, LT up."
TEST_F(RetroShiftPermissiveHold, lt_mt_different_layer_slow_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}

View file

@ -0,0 +1,24 @@
/* 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 SPECULATIVE_HOLD
#define RETRO_TAPPING
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_F24

View file

@ -0,0 +1,15 @@
# 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/>.

View file

@ -0,0 +1,629 @@
// 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::InSequence;
extern "C" bool get_speculative_hold(uint16_t keycode, keyrecord_t *record) {
return true;
}
class SpeculativeHoldRetroTappingTest : public TestFixture {};
TEST_F(SpeculativeHoldRetroTappingTest, roll_regular_to_lgui_mod) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_B);
set_keymap({mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_B));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B, KC_LGUI));
mod_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LGUI));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Neutralizer invoked by Speculative Hold.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, regular_to_mod_under_tap_term) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A));
auto regular_key = KeymapKey(0, 2, 0, KC_B);
set_keymap({mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_B));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B, KC_LSFT));
mod_tap_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);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_regular) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_B);
set_keymap({mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Neutralizer invoked by Speculative Hold.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_REPORT(driver, (KC_B, KC_P));
EXPECT_REPORT(driver, (KC_B));
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_regular) {
TestDriver driver;
InSequence s;
auto mod_tap_key = KeymapKey(0, 1, 0, LSFT_T(KC_A));
auto regular_key = KeymapKey(0, 2, 0, KC_B);
set_keymap({mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_key.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_B));
regular_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_B));
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_under_tap_term) {
TestDriver driver;
InSequence s;
auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A));
set_keymap({mod_tap_lgui, mod_tap_lsft});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lsft.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI));
mod_tap_lgui.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lgui.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_mod_under_tap_term) {
TestDriver driver;
InSequence s;
auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A));
set_keymap({mod_tap_lgui, mod_tap_lsft});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lsft.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI));
mod_tap_lgui.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_NO_REPORT(driver);
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_REPORT(driver, (KC_LSFT, KC_P));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lgui.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_over_tap_term) {
TestDriver driver;
InSequence s;
auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A));
set_keymap({mod_tap_lgui, mod_tap_lsft});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lsft.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI));
mod_tap_lgui.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Neutralizer invoked by Retro Tapping.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_REPORT(driver, (KC_P, KC_LSFT));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lgui.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_under_tap_term_to_mod_over_tap_term_offset) {
TestDriver driver;
InSequence s;
auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A));
set_keymap({mod_tap_lgui, mod_tap_lsft});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lsft.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI));
mod_tap_lgui.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_A));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LGUI));
// Neutralizer invoked by Retro Tapping.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
idle_for(TAPPING_TERM);
mod_tap_lgui.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_over_tap_term_to_mod_over_tap_term) {
TestDriver driver;
InSequence s;
auto mod_tap_lgui = KeymapKey(0, 1, 0, LGUI_T(KC_P));
auto mod_tap_lsft = KeymapKey(0, 2, 0, LSFT_T(KC_A));
set_keymap({mod_tap_lgui, mod_tap_lsft});
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lsft.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LGUI));
mod_tap_lgui.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LGUI));
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Neutralizer invoked by Retro Tapping.
EXPECT_REPORT(driver, (KC_LGUI, DUMMY_MOD_NEUTRALIZER_KEYCODE));
EXPECT_REPORT(driver, (KC_LGUI));
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_REPORT(driver, (KC_P, KC_LSFT));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lgui.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(SpeculativeHoldRetroTappingTest, mod_to_mod_to_mod) {
TestDriver driver;
InSequence s;
auto mod_tap_lalt = KeymapKey(0, 1, 0, LALT_T(KC_R));
auto mod_tap_lsft = KeymapKey(0, 2, 0, SFT_T(KC_A));
auto mod_tap_lctl = KeymapKey(0, 3, 0, LCTL_T(KC_C));
set_keymap({mod_tap_lalt, mod_tap_lsft, mod_tap_lctl});
EXPECT_REPORT(driver, (KC_LALT));
mod_tap_lalt.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT, KC_LALT));
mod_tap_lsft.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LSFT));
mod_tap_lalt.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL, KC_LSFT));
mod_tap_lctl.press();
idle_for(TAPPING_TERM);
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_lsft.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_REPORT(driver, (KC_C, KC_LSFT));
EXPECT_REPORT(driver, (KC_LSFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_lctl.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
// Test with layer tap and speculative mod tap keys on the same layer, rolling
// from LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_same_layer_roll) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Press mod-tap key, after flow tap term but within tapping term. The
// speculative mod activates.
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// Wait for the layer tap key to settle.
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying a
// nested press:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys: MT first, LT second.
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with layer tap and speculative mod tap keys on the same layer, trying a
// nested press with the MT first:
// "MT down, LT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldRetroTappingTest, mt_lt_same_layer_nested_press) {
TestDriver driver;
InSequence s;
auto layer_tap_key = KeymapKey(0, 0, 0, LT(1, KC_A));
auto mod_tap_key = KeymapKey(0, 1, 0, LCTL_T(KC_B));
auto regular_key = KeymapKey(1, 1, 0, KC_C);
set_keymap({layer_tap_key, mod_tap_key, regular_key});
EXPECT_REPORT(driver, (KC_LCTL));
mod_tap_key.press();
run_one_scan_loop();
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
EXPECT_REPORT(driver, (KC_LCTL, KC_A));
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, rolling from
// LT to MT key:
// "LT down, MT down, (wait out tapping term), LT up, MT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
// Press layer tap key.
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
// Press mod tap key.
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LCTL));
layer_tap_key.release();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, slowly
// rolling from LT to MT key:
// "LT down, (wait), MT down, (wait), LT up, MT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_slow_roll) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_REPORT(driver, (KC_LCTL));
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_B));
EXPECT_EMPTY_REPORT(driver);
layer_tap_key.release();
run_one_scan_loop();
mod_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, trying a
// nested press:
// "LT down, MT down, (wait out tapping term), MT up, LT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_NO_REPORT(driver);
layer_tap_key.press();
run_one_scan_loop();
mod_tap_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
// Release keys.
EXPECT_REPORT(driver, (KC_LCTL));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}
// Test with a speculative mod tap key reached by a layer tap key, slowly making
// a nested press from LT to MT key:
// "LT down, (wait), MT down, MT up, LT up."
TEST_F(SpeculativeHoldRetroTappingTest, lt_mt_different_layer_slow_nested_press) {
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 placeholder_key = KeymapKey(1, 0, 0, KC_NO);
auto mod_tap_key = KeymapKey(1, 1, 0, LCTL_T(KC_C));
set_keymap({layer_tap_key, regular_key, placeholder_key, mod_tap_key});
EXPECT_REPORT(driver, (KC_LCTL));
layer_tap_key.press();
idle_for(TAPPING_TERM + 1);
mod_tap_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
EXPECT_EMPTY_REPORT(driver);
EXPECT_REPORT(driver, (KC_C));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key.release();
run_one_scan_loop();
layer_tap_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
// All mods are released.
EXPECT_EQ(get_mods() | get_speculative_mods(), 0);
}