Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug17281 - Retain momentary layers until the end of tapping #17282

Merged
merged 3 commits into from
Nov 28, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 84 additions & 77 deletions quantum/action_tapping.c
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,66 @@ void action_tapping_process(keyrecord_t record) {
}
}

/* Some conditionally defined helper macros to keep process_tapping more
* readable. The conditional definition of tapping_keycode and all the
* conditional uses of it are hidden inside macros named TAP_...
*/
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(TAPPING_FORCE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
# define TAP_DEFINE_KEYCODE uint16_t tapping_keycode = get_record_keycode(&tapping_key, false)
# else
# define TAP_DEFINE_KEYCODE
# endif

# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
# ifdef RETRO_TAPPING_PER_KEY
# define TAP_GET_RETRO_TAPPING get_retro_tapping(tapping_keycode, &tapping_key)
# else
# define TAP_GET_RETRO_TAPPING true
# endif
# define MAYBE_RETRO_SHIFTING(ev) (TAP_GET_RETRO_TAPPING && (RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16((ev).time, tapping_key.event.time) < (RETRO_SHIFT + 0))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't fully understand why did you prefix the macro name with "maybe"?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are more conditions that are checked before the actual retry-shifting code (e.g. tapping_key.tap.count == 0). The condition here is necessary but not sufficient for retro shifting.

# define TAP_IS_LT IS_LT(tapping_keycode)
# define TAP_IS_MT IS_MT(tapping_keycode)
# define TAP_IS_RETRO IS_RETRO(tapping_keycode)
# else
# define TAP_GET_RETRO_TAPPING false
# define MAYBE_RETRO_SHIFTING(ev) false
# define TAP_IS_LT false
# define TAP_IS_MT false
# define TAP_IS_RETRO false
# endif

# ifdef PERMISSIVE_HOLD_PER_KEY
# define TAP_GET_PERMISSIVE_HOLD get_permissive_hold(tapping_keycode, &tapping_key)
# elif defined(PERMISSIVE_HOLD)
# define TAP_GET_PERMISSIVE_HOLD true
# else
# define TAP_GET_PERMISSIVE_HOLD false
# endif

# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
# define TAP_GET_HOLD_ON_OTHER_KEY_PRESS get_hold_on_other_key_press(tapping_keycode, &tapping_key)
# elif defined(HOLD_ON_OTHER_KEY_PRESS)
# define TAP_GET_HOLD_ON_OTHER_KEY_PRESS true
# else
# define TAP_GET_HOLD_ON_OTHER_KEY_PRESS false
# endif

# ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
# define TAP_GET_IGNORE_MOD_TAP_INTERRUPT get_ignore_mod_tap_interrupt(tapping_keycode, &tapping_key)
# elif defined(IGNORE_MOD_TAP_INTERRUPT)
# define TAP_GET_IGNORE_MOD_TAP_INTERRUPT true
# else
# define TAP_GET_IGNORE_MOD_TAP_INTERRUPT false
# endif

# ifdef TAPPING_FORCE_HOLD_PER_KEY
# define TAP_GET_TAPPING_FORCE_HOLD get_tapping_force_hold(tapping_keycode, &tapping_key)
# elif defined(TAPPING_FORCE_HOLD)
# define TAP_GET_TAPPING_FORCE_HOLD true
# else
# define TAP_GET_TAPPING_FORCE_HOLD false
# endif

/** \brief Tapping
*
* Rule: Tap key is typed(pressed and released) within TAPPING_TERM.
Expand All @@ -125,24 +185,11 @@ void action_tapping_process(keyrecord_t record) {
/* return true when key event is processed or consumed. */
bool process_tapping(keyrecord_t *keyp) {
keyevent_t event = keyp->event;
# if (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)) || defined(PERMISSIVE_HOLD_PER_KEY) || defined(TAPPING_FORCE_HOLD_PER_KEY) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
uint16_t tapping_keycode = get_record_keycode(&tapping_key, false);
# endif
TAP_DEFINE_KEYCODE;

// if tapping
if (IS_TAPPING_PRESSED()) {
// clang-format off
if (WITHIN_TAPPING_TERM(event)
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, &tapping_key) &&
# endif
(RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16(event.time, tapping_key.event.time) < (RETRO_SHIFT + 0)
)
# endif
) {
// clang-format on
if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event)) {
if (tapping_key.tap.count == 0) {
if (IS_TAPPING_RECORD(keyp) && !event.pressed) {
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
Expand All @@ -164,57 +211,31 @@ bool process_tapping(keyrecord_t *keyp) {
* useful for long TAPPING_TERM but may prevent fast typing.
*/
// clang-format off
# if defined(PERMISSIVE_HOLD) || defined(PERMISSIVE_HOLD_PER_KEY) || (defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT))
else if (
(
IS_RELEASED(event) && waiting_buffer_typed(event)
# ifdef PERMISSIVE_HOLD_PER_KEY
&& get_permissive_hold(tapping_keycode, &tapping_key)
# elif defined(PERMISSIVE_HOLD)
&& true
# endif
IS_RELEASED(event) && waiting_buffer_typed(event) &&
TAP_GET_PERMISSIVE_HOLD
)
// Causes nested taps to not wait past TAPPING_TERM/RETRO_SHIFT
// unnecessarily and fixes them for Layer Taps.
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, &tapping_key) &&
# endif
|| (TAP_GET_RETRO_TAPPING &&
(
// Rolled over the two keys.
(
(
false
# if defined(HOLD_ON_OTHER_KEY_PRESS) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
|| (
IS_LT(tapping_keycode)
# ifdef HOLD_ON_OTHER_KEY_PRESS_PER_KEY
&& get_hold_on_other_key_press(tapping_keycode, &tapping_key)
# endif
)
# endif
# if !defined(IGNORE_MOD_TAP_INTERRUPT) || defined(IGNORE_MOD_TAP_INTERRUPT_PER_KEY)
|| (
IS_MT(tapping_keycode)
# ifdef IGNORE_MOD_TAP_INTERRUPT_PER_KEY
&& !get_ignore_mod_tap_interrupt(tapping_keycode, &tapping_key)
# endif
)
# endif
) && tapping_key.tap.interrupted == true
(tapping_key.tap.interrupted == true && (
(TAP_IS_LT && TAP_GET_HOLD_ON_OTHER_KEY_PRESS) ||
(TAP_IS_MT && !TAP_GET_IGNORE_MOD_TAP_INTERRUPT)
)
)
// Makes Retro Shift ignore [IGNORE_MOD_TAP_INTERRUPT's
// effects on nested taps for MTs and the default
// behavior of LTs] below TAPPING_TERM or RETRO_SHIFT.
|| (
IS_RETRO(tapping_keycode)
TAP_IS_RETRO
&& (event.key.col != tapping_key.event.key.col || event.key.row != tapping_key.event.key.row)
&& IS_RELEASED(event) && waiting_buffer_typed(event)
)
)
)
# endif
) {
// clang-format on
debug("Tapping: End. No tap. Interfered by typing key\n");
Expand All @@ -224,13 +245,12 @@ bool process_tapping(keyrecord_t *keyp) {
// enqueue
return false;
}
# endif
/* Process release event of a key pressed before tapping starts
* Without this unexpected repeating will occur with having fast repeating setting
* https://github.com/tmk/tmk_keyboard/issues/60
*/
else if (IS_RELEASED(event) && !waiting_buffer_typed(event)) {
// Modifier should be retained till end of this tapping.
// Modifier/Layer should be retained till end of this tapping.
action_t action = layer_switch_get_action(event.key);
switch (action.kind.id) {
case ACT_LMODS:
Expand All @@ -243,6 +263,16 @@ bool process_tapping(keyrecord_t *keyp) {
if (action.key.mods && keyp->tap.count == 0) return false;
if (IS_MOD(action.key.code)) return false;
break;
case ACT_LAYER_TAP:
case ACT_LAYER_TAP_EXT:
switch (action.layer_tap.code) {
case 0 ...(OP_TAP_TOGGLE - 1):
case OP_ON_OFF:
case OP_OFF_ON:
case OP_SET_CLEAR:
return false;
}
break;
}
// Release of key should be process immediately.
debug("Tapping: release event of a key pressed before tapping\n");
Expand All @@ -252,19 +282,14 @@ bool process_tapping(keyrecord_t *keyp) {
// set interrupted flag when other key preesed during tapping
if (event.pressed) {
tapping_key.tap.interrupted = true;
# if defined(HOLD_ON_OTHER_KEY_PRESS) || defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
# if defined(HOLD_ON_OTHER_KEY_PRESS_PER_KEY)
if (get_hold_on_other_key_press(tapping_keycode, &tapping_key))
# endif
{
if (TAP_GET_HOLD_ON_OTHER_KEY_PRESS) {
debug("Tapping: End. No tap. Interfered by pressed key\n");
process_record(&tapping_key);
tapping_key = (keyrecord_t){};
debug_tapping_key();
// enqueue
return false;
}
# endif
}
// enqueue
return false;
Expand Down Expand Up @@ -357,27 +382,10 @@ bool process_tapping(keyrecord_t *keyp) {
}
}
} else if (IS_TAPPING_RELEASED()) {
// clang-format off
if (WITHIN_TAPPING_TERM(event)
# if defined(AUTO_SHIFT_ENABLE) && defined(RETRO_SHIFT)
|| (
# ifdef RETRO_TAPPING_PER_KEY
get_retro_tapping(tapping_keycode, &tapping_key) &&
# endif
(RETRO_SHIFT + 0) != 0 && TIMER_DIFF_16(event.time, tapping_key.event.time) < (RETRO_SHIFT + 0)
)
# endif
) {
// clang-format on
if (WITHIN_TAPPING_TERM(event) || MAYBE_RETRO_SHIFTING(event)) {
if (event.pressed) {
if (IS_TAPPING_RECORD(keyp)) {
//# ifndef TAPPING_FORCE_HOLD
# if !defined(TAPPING_FORCE_HOLD) || defined(TAPPING_FORCE_HOLD_PER_KEY)
if (
# ifdef TAPPING_FORCE_HOLD_PER_KEY
!get_tapping_force_hold(tapping_keycode, &tapping_key) &&
# endif
!tapping_key.tap.interrupted && tapping_key.tap.count > 0) {
if (!TAP_GET_TAPPING_FORCE_HOLD && !tapping_key.tap.interrupted && tapping_key.tap.count > 0) {
// sequential tap.
keyp->tap = tapping_key.tap;
if (keyp->tap.count < 15) keyp->tap.count += 1;
Expand All @@ -389,7 +397,6 @@ bool process_tapping(keyrecord_t *keyp) {
debug_tapping_key();
return true;
}
# endif
// FIX: start new tap again
tapping_key = *keyp;
return true;
Expand Down
69 changes: 69 additions & 0 deletions tests/basic/test_tapping.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,72 @@ TEST_F(Tapping, ANewTapWithinTappingTermIsBuggy) {
key_shift_hold_p_tap.release();
run_one_scan_loop();
}

TEST_F(Tapping, TapA_CTL_T_KeyWhileReleasingShift) {
TestDriver driver;
InSequence s;
auto shift_key = KeymapKey(0, 7, 0, KC_LSFT);
auto mod_tap_hold_key = KeymapKey(0, 8, 0, CTL_T(KC_P));

set_keymap({shift_key, mod_tap_hold_key});

shift_key.press();
// Shift is reported
EXPECT_REPORT(driver, (KC_LSFT));
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);

mod_tap_hold_key.press();
// Tapping keys does nothing on press
EXPECT_NO_REPORT(driver);
run_one_scan_loop();

shift_key.release();
// Releasing shift is delayed while tapping is in progress
EXPECT_NO_REPORT(driver);
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);

mod_tap_hold_key.release();
// Releasing mod-tap key reports the tap and releases shift
EXPECT_REPORT(driver, (KC_LSFT, KC_P));
EXPECT_REPORT(driver, (KC_P));
EXPECT_EMPTY_REPORT(driver);
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}

TEST_F(Tapping, TapA_CTL_T_KeyWhileReleasingLayer) {
TestDriver driver;
InSequence s;
auto layer_key = KeymapKey(0, 7, 0, MO(1));
auto trans_key = KeymapKey(1, 7, 0, KC_TRNS);
auto mod_tap_hold_key0 = KeymapKey(0, 8, 0, CTL_T(KC_P));
auto mod_tap_hold_key1 = KeymapKey(1, 8, 0, CTL_T(KC_Q));

set_keymap({layer_key, trans_key, mod_tap_hold_key0, mod_tap_hold_key1});

layer_key.press();
// Pressing the layer key does nothing
EXPECT_NO_REPORT(driver);
run_one_scan_loop();

mod_tap_hold_key1.press();
// Tapping layer 1 mod-tap key does nothing on press
EXPECT_NO_REPORT(driver);
run_one_scan_loop();

layer_key.release();
// Releasing layer is delayed while tapping is in progress
EXPECT_NO_REPORT(driver);
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);

mod_tap_hold_key1.release();
// Releasing mod-tap key reports the tap of the layer 1 key
// If delayed layer release is broken, this reports the layer 0 key
EXPECT_REPORT(driver, (KC_Q));
EXPECT_EMPTY_REPORT(driver);
run_one_scan_loop();
testing::Mock::VerifyAndClearExpectations(&driver);
}