diff --git a/quantum/action.c b/quantum/action.c index dd82c9ec99f..f05816b7ae4 100644 --- a/quantum/action.c +++ b/quantum/action.c @@ -112,7 +112,7 @@ void action_exec(keyevent_t event) { if (has_oneshot_layer_timed_out()) { clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); } - if (has_oneshot_mods_timed_out()) { + if (has_oneshot_mods_timed_out() && !get_oneshot_mods_fired()) { clear_oneshot_mods(); } # ifdef SWAP_HANDS_ENABLE diff --git a/quantum/action_util.c b/quantum/action_util.c index e821e113ef3..e4e4566ade3 100644 --- a/quantum/action_util.c +++ b/quantum/action_util.c @@ -46,9 +46,13 @@ extern inline void clear_keys(void); #ifndef NO_ACTION_ONESHOT static uint8_t oneshot_mods = 0; static uint8_t oneshot_locked_mods = 0; +static bool oneshot_mods_fired = false; uint8_t get_oneshot_locked_mods(void) { return oneshot_locked_mods; } +bool get_oneshot_mods_fired(void) { + return oneshot_mods_fired; +} void add_oneshot_locked_mods(uint8_t mods) { if ((oneshot_locked_mods & mods) != mods) { oneshot_locked_mods |= mods; @@ -260,16 +264,15 @@ static uint8_t get_mods_for_report(void) { #ifndef NO_ACTION_ONESHOT if (oneshot_mods) { -# if (defined(ONESHOT_TIMEOUT) && (ONESHOT_TIMEOUT > 0)) - if (has_oneshot_mods_timed_out()) { - dprintf("Oneshot: timeout\n"); + // Fire oneshots when other keys are pressed + // Release oneshots when everything else is released + if (has_anykey() || mods) { + oneshot_mods_fired = true; + } else if (oneshot_mods_fired) { + oneshot_mods_fired = false; clear_oneshot_mods(); } -# endif mods |= oneshot_mods; - if (has_anykey()) { - clear_oneshot_mods(); - } } #endif diff --git a/quantum/action_util.h b/quantum/action_util.h index d2ecb145bed..967d9a1c5e1 100644 --- a/quantum/action_util.h +++ b/quantum/action_util.h @@ -68,6 +68,7 @@ void clear_oneshot_mods(void); bool has_oneshot_mods_timed_out(void); uint8_t get_oneshot_locked_mods(void); +bool get_oneshot_mods_fired(void); void add_oneshot_locked_mods(uint8_t mods); void set_oneshot_locked_mods(uint8_t mods); void clear_oneshot_locked_mods(void); diff --git a/tests/basic/config.h b/tests/basic/config.h index 7fc76d7c2e7..9b00608b12a 100644 --- a/tests/basic/config.h +++ b/tests/basic/config.h @@ -17,3 +17,5 @@ #pragma once #include "test_common.h" + +#define ONESHOT_TAP_TOGGLE 5 diff --git a/tests/basic/test_one_shot_keys.cpp b/tests/basic/test_one_shot_keys.cpp index 4784c866940..fdae638b7cd 100644 --- a/tests/basic/test_one_shot_keys.cpp +++ b/tests/basic/test_one_shot_keys.cpp @@ -254,7 +254,7 @@ TEST_F(OneShot, OSMHoldNotLockingOSMs) { /* Press and release regular key */ EXPECT_REPORT(driver, (osm_key1.report_code, osm_key2.report_code, regular_key.report_code)).Times(1); - EXPECT_REPORT(driver, (osm_key2.report_code)).Times(1); + EXPECT_REPORT(driver, (osm_key1.report_code, osm_key2.report_code)).Times(1); tap_key(regular_key); VERIFY_AND_CLEAR(driver); @@ -277,6 +277,118 @@ TEST_F(OneShot, OSMHoldNotLockingOSMs) { VERIFY_AND_CLEAR(driver); } +TEST_F(OneShot, OSMHoldRelease) { + TestDriver driver; + InSequence s; + KeymapKey osm_key = KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}; + + set_keymap({osm_key}); + + /* Press and release OSM */ + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + /* Press and hold OSM */ + EXPECT_REPORT(driver, (osm_key.report_code)).Times(1); + osm_key.press(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + /* Release OSM */ + EXPECT_EMPTY_REPORT(driver); + osm_key.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSMHoldReleaseTwoOSMs) { + TestDriver driver; + InSequence s; + KeymapKey osm_key1 = KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}; + KeymapKey osm_key2 = KeymapKey{0, 0, 1, OSM(MOD_LCTL), KC_LCTL}; + + set_keymap({osm_key1, osm_key2}); + + /* Press and release OSM1 */ + EXPECT_NO_REPORT(driver); + tap_key(osm_key1); + VERIFY_AND_CLEAR(driver); + + /* Press and release OSM2 */ + EXPECT_NO_REPORT(driver); + tap_key(osm_key2); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + /* Press and hold OSM1 */ + EXPECT_REPORT(driver, (osm_key1.report_code, osm_key2.report_code)).Times(1); + osm_key1.press(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + /* Release OSM1 */ + EXPECT_EMPTY_REPORT(driver); + osm_key1.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSMNonOSMMod) { + TestDriver driver; + InSequence s; + KeymapKey osm_key = KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}; + KeymapKey regular_mod = KeymapKey{0, 1, 0, KC_LCTL}; + + set_keymap({osm_key, regular_mod}); + + /* Press and release OSM */ + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + VERIFY_AND_CLEAR(driver); + + /* Press regular mod */ + EXPECT_REPORT(driver, (osm_key.report_code, regular_mod.report_code)).Times(1); + regular_mod.press(); + run_one_scan_loop(); + idle_for(TAPPING_TERM); + VERIFY_AND_CLEAR(driver); + + /* Release regular mod */ + EXPECT_EMPTY_REPORT(driver); + regular_mod.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); +} + +TEST_F(OneShot, OSMLockRelease) { + TestDriver driver; + InSequence s; + KeymapKey osm_key = KeymapKey{0, 0, 0, OSM(MOD_LSFT), KC_LSFT}; + + set_keymap({osm_key}); + + EXPECT_NO_REPORT(driver); + for (int i = 1; i < ONESHOT_TAP_TOGGLE; i++) { + /* Press and release OSM */ + tap_key(osm_key); + } + VERIFY_AND_CLEAR(driver); + + /* Lock OSM */ + EXPECT_REPORT(driver, (osm_key.report_code)).Times(1); + tap_key(osm_key); + VERIFY_AND_CLEAR(driver); + + /* Unlock OSM */ + EXPECT_NO_REPORT(driver); + tap_key(osm_key); + VERIFY_AND_CLEAR(driver); +} + TEST_F(OneShot, OSLWithAdditionalKeypress) { TestDriver driver; InSequence s;