Chordal Hold: restrict what chords settle as hold

This commit is contained in:
Pascal Getreuer 2024-11-01 23:58:36 -07:00
parent 248a09d545
commit fa857db172
11 changed files with 756 additions and 1 deletions

View File

@ -49,6 +49,47 @@ __attribute__((weak)) bool get_permissive_hold(uint16_t keycode, keyrecord_t *re
}
# endif
# 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) {
uint8_t tap_hold_hand = chordal_hold_handedness_user(tap_hold_record->event.key);
if (tap_hold_hand == 0) {
return true;
}
uint8_t other_hand = chordal_hold_handedness_user(other_record->event.key);
return other_hand == 0 || tap_hold_hand != other_hand;
}
__attribute__((weak)) uint8_t chordal_hold_handedness_kb(keypos_t key) {
# if defined(SPLIT_KEYBOARD) || ((MATRIX_ROWS) > (MATRIX_COLS))
#pragma message "Inferred handedness rows"
// If the keyboard is split or if MATRIX_ROWS > MATRIX_COLS, assume that the
// first half of the rows are left and the latter half are right.
return (key.row < (MATRIX_ROWS) / 2) ? /*left*/ 1 : /*right*/ 2;
# else
#pragma message "Inferred handedness cols"
// Otherwise, assume the first half of the cols are left, others are right.
return (key.col < (MATRIX_COLS) / 2) ? /*left*/ 1 : /*right*/ 2;
# endif
}
__attribute__((weak)) uint8_t chordal_hold_handedness_user(keypos_t key) {
# if defined(CHORDAL_HOLD_LAYOUT)
# pragma message "Using chordal_hold_layout"
// If given, read handedness from `chordal_hold_layout` array.
return pgm_read_byte(&chordal_hold_layout[key.row][key.col]);
# else
return chordal_hold_handedness_kb(key);
# endif
}
# endif // CHORDAL_HOLD
# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
__attribute__((weak)) bool get_hold_on_other_key_press(uint16_t keycode, keyrecord_t *record) {
return false;
@ -188,7 +229,7 @@ bool process_tapping(keyrecord_t *keyp) {
return true;
}
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(CHORDAL_HOLD) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
TAP_DEFINE_KEYCODE;
# endif
@ -271,6 +312,22 @@ bool process_tapping(keyrecord_t *keyp) {
// set interrupted flag when other key pressed during tapping
if (event.pressed) {
tapping_key.tap.interrupted = true;
# if defined(CHORDAL_HOLD)
if (!is_tap_record(keyp) &&
!get_chordal_hold(tapping_keycode, &tapping_key,
get_record_keycode(keyp, true), keyp)) {
// Settle the tapping key as *tapped*, since it is
// not considered a held chord with keyp.
ac_dprintf("Tapping: End. Tap in non-chord\n");
tapping_key.tap.count = 1;
// In process_action(), HOLD_ON_OTHER_KEY_PRESS will
// revert interrupted events to holds, so this needs
// to be set false.
tapping_key.tap.interrupted = false;
process_record(&tapping_key);
} else
# endif
if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
// Auto Shift cannot evaluate this early
@ -278,6 +335,8 @@ bool process_tapping(keyrecord_t *keyp) {
&& !(MAYBE_RETRO_SHIFTING(event, keyp) && get_auto_shifted_key(get_record_keycode(keyp, false), keyp))
# endif
) {
// Settle the tapping key as *held*, since
// HOLD_ON_OTHER_KEY_PRESS is enabled for this key.
ac_dprintf("Tapping: End. No tap. Interfered by pressed key\n");
process_record(&tapping_key);
tapping_key = (keyrecord_t){0};

View File

@ -46,6 +46,82 @@ 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 CHORDAL_HOLD
/**
* Callback to say when a key chord before the tapping term is considered held.
*
* In keymap.c, define the callback
*
* bool get_chordal_hold(uint16_t tap_hold_keycode,
* keyrecord_t* tap_hold_record,
* uint16_t other_keycode,
* keyrecord_t* other_record) {
* // Conditions...
* }
*
* This callback is called when:
*
* 1. `tap_hold_keycode` is pressed.
* 2. `other_keycode` is pressed while `tap_hold_keycode` is still held,
* provided `other_keycode` is *not* also a tap-hold key and it is pressed
* before the tapping term.
*
* Returning true indicates that the tap-hold key should be considered held, or
* false to consider it tapped.
*
* @param tap_hold_keycode Keycode of the tap-hold key.
* @param tap_hold_record Record from the tap-hold press event.
* @param other_keycode Keycode of the other key.
* @param other_record Record from the other key's press event.
* @return True if the tap-hold key is considered held; false if tapped.
*/
bool get_chordal_hold(
uint16_t tap_hold_keycode, keyrecord_t* tap_hold_record,
uint16_t other_keycode, keyrecord_t* other_record);
/**
* Default "opposite hands rule" for whether a key chord should settle as held.
*
* This function returns true when the tap-hold key and other key are on
* "opposite hands." In detail, handedness of the two keys are compared. If
* handedness values differ, or if either handedness is zero, the function
* returns true, indicating a hold. Otherwise, it returns false, indicating that
* the tap-hold key should settle as tapped.
*
* "Handedness" is determined as follows, in order of decending precedence:
* 1. `chordal_hold_handedness_user()`, if defined.
* 2. `chordal_hold_layout`, if CHORDAL_HOLD_LAYOUT is defined.
* 3. `chordal_hold_handedness_kb()`, if defined.
* 4. fallback assumption based on keyboard matrix dimensions.
*
* @param tap_hold_record Record of the active tap-hold key press.
* @param other_record Record of the other, interrupting key press.
* @return True if the tap-hold key is considered held; false if tapped.
*/
bool get_chordal_hold_default(
keyrecord_t* tap_hold_record, keyrecord_t* other_record);
/**
* Keyboard-level callback to determine handedness of a key.
*
* This function should return:
* 1 for keys pressed by the left hand,
* 2 for keys on the right hand,
* 0 for keys exempt from the "opposite hands rule." This could be used
* perhaps on thumb keys or keys that might be pressed by either hand.
*
* @param key A key matrix position.
* @return Handedness value.
*/
uint8_t chordal_hold_handedness_kb(keypos_t key);
/** User callback to determine handedness of a key. */
uint8_t chordal_hold_handedness_user(keypos_t key);
# ifdef CHORDAL_HOLD_LAYOUT
extern const uint8_t chordal_hold_layout[MATRIX_ROWS][MATRIX_COLS] PROGMEM;
# endif
#endif
#ifdef DYNAMIC_TAPPING_TERM_ENABLE
extern uint16_t g_tapping_term;
#endif

View File

@ -0,0 +1,21 @@
/* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD

View File

@ -0,0 +1,18 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------
# Keep this file, even if it is empty, as a marker that this folder contains tests
# --------------------------------------------------------------------------------

View File

@ -0,0 +1,167 @@
/* 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/>.
*/
#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 ChordalHold : public TestFixture {};
TEST_F(ChordalHold, chord_with_mod_tap_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_hold_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_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
regular_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHold, 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_hold_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_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();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHold, 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(ChordalHold, 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(ChordalHold, chordal_hold_ignores_multiple_mod_taps) {
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-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press second mod-tap key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_RIGHT_SHIFT));
mod_tap_key2.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
/* Release keys. */
EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}

View File

@ -0,0 +1,22 @@
/* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define HOLD_ON_OTHER_KEY_PRESS

View File

@ -0,0 +1,18 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------
# Keep this file, even if it is empty, as a marker that this folder contains tests
# --------------------------------------------------------------------------------

View File

@ -0,0 +1,167 @@
/* 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/>.
*/
#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 ChordalHoldAndHoldOnOtherKeypress : public TestFixture {};
TEST_F(ChordalHoldAndHoldOnOtherKeypress, chord_with_mod_tap_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_hold_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_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
regular_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldAndHoldOnOtherKeypress, 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_hold_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_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();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldAndHoldOnOtherKeypress, 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(ChordalHoldAndHoldOnOtherKeypress, 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(ChordalHoldAndHoldOnOtherKeypress, chordal_hold_ignores_multiple_mod_taps) {
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-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press second mod-tap key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_RIGHT_SHIFT));
mod_tap_key2.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
/* Release keys. */
EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}

View File

@ -0,0 +1,22 @@
/* 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/>.
*/
#pragma once
#include "test_common.h"
#define CHORDAL_HOLD
#define PERMISSIVE_HOLD

View File

@ -0,0 +1,18 @@
# Copyright 2022 Vladislav Kucheriavykh
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# --------------------------------------------------------------------------------
# Keep this file, even if it is empty, as a marker that this folder contains tests
# --------------------------------------------------------------------------------

View File

@ -0,0 +1,167 @@
/* 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/>.
*/
#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 ChordalHoldAndPermissiveHold : public TestFixture {};
TEST_F(ChordalHoldAndPermissiveHold, chord_with_mod_tap_settled_as_hold) {
TestDriver driver;
InSequence s;
// Mod-tap key on the left hand.
auto mod_tap_hold_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_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_key.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_A));
regular_key.press();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release regular key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
regular_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldAndPermissiveHold, 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_hold_key = KeymapKey(0, 1, 0, SFT_T(KC_P));
auto regular_key = KeymapKey(0, 2, 0, KC_A);
set_keymap({mod_tap_hold_key, regular_key});
/* Press mod-tap-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_hold_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();
idle_for(TAPPING_TERM);
VERIFY_AND_CLEAR(driver);
/* Release mod-tap-hold key. */
EXPECT_EMPTY_REPORT(driver);
mod_tap_hold_key.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}
TEST_F(ChordalHoldAndPermissiveHold, 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(ChordalHoldAndPermissiveHold, 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(ChordalHoldAndPermissiveHold, chordal_hold_ignores_multiple_mod_taps) {
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-hold key. */
EXPECT_NO_REPORT(driver);
mod_tap_key1.press();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
/* Press second mod-tap key. */
EXPECT_REPORT(driver, (KC_LEFT_SHIFT));
EXPECT_REPORT(driver, (KC_LEFT_SHIFT, KC_RIGHT_SHIFT));
mod_tap_key2.press();
idle_for(TAPPING_TERM + 1);
VERIFY_AND_CLEAR(driver);
/* Release keys. */
EXPECT_REPORT(driver, (KC_RIGHT_SHIFT));
EXPECT_EMPTY_REPORT(driver);
mod_tap_key1.release();
run_one_scan_loop();
mod_tap_key2.release();
run_one_scan_loop();
VERIFY_AND_CLEAR(driver);
}