diff --git a/quantum/process_keycode/process_key_override.c b/quantum/process_keycode/process_key_override.c index 2b6e0dcf3d0..85e42d28fc8 100644 --- a/quantum/process_keycode/process_key_override.c +++ b/quantum/process_keycode/process_key_override.c @@ -441,6 +441,10 @@ bool process_key_override(const uint16_t keycode, const keyrecord_t *const recor #ifdef KEY_OVERRIDE_INCLUDE_WEAK_MODS effective_mods |= get_weak_mods(); +#elif defined(REPEAT_KEY_ENABLE) + if (get_repeat_key_count()) { + effective_mods |= get_weak_mods(); + } #endif #ifndef NO_ACTION_ONESHOT diff --git a/tests/repeat_key/repeat_key_overrides/config.h b/tests/repeat_key/repeat_key_overrides/config.h new file mode 100644 index 00000000000..5a99fe50c28 --- /dev/null +++ b/tests/repeat_key/repeat_key_overrides/config.h @@ -0,0 +1,17 @@ +// Copyright 2026 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. + +#pragma once + +#include "test_common.h" diff --git a/tests/repeat_key/repeat_key_overrides/test.mk b/tests/repeat_key/repeat_key_overrides/test.mk new file mode 100644 index 00000000000..adda6c0b8c4 --- /dev/null +++ b/tests/repeat_key/repeat_key_overrides/test.mk @@ -0,0 +1,18 @@ +# Copyright 2026 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 . + +REPEAT_KEY_ENABLE = yes +KEY_OVERRIDE_ENABLE = yes +INTROSPECTION_KEYMAP_C = test_keymap.c diff --git a/tests/repeat_key/repeat_key_overrides/test_keymap.c b/tests/repeat_key/repeat_key_overrides/test_keymap.c new file mode 100644 index 00000000000..171f47e50e4 --- /dev/null +++ b/tests/repeat_key/repeat_key_overrides/test_keymap.c @@ -0,0 +1,19 @@ +// Copyright 2026 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "quantum.h" + +const key_override_t alt_slash_override = ko_make_basic(MOD_BIT(KC_LALT), KC_SLSH, KC_BSLS); + +const key_override_t *key_overrides[] = {&alt_slash_override, NULL}; diff --git a/tests/repeat_key/repeat_key_overrides/test_repeat_key_overrides.cpp b/tests/repeat_key/repeat_key_overrides/test_repeat_key_overrides.cpp new file mode 100644 index 00000000000..5058ff69551 --- /dev/null +++ b/tests/repeat_key/repeat_key_overrides/test_repeat_key_overrides.cpp @@ -0,0 +1,190 @@ +// Copyright 2026 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 + +#include "keyboard_report_util.hpp" +#include "keycode.h" +#include "test_common.hpp" +#include "test_fixture.hpp" +#include "test_keymap_key.hpp" + +using ::testing::AnyNumber; +using ::testing::Matcher; + +namespace { + +bool process_record_user_default(uint16_t keycode, keyrecord_t *record) { + return true; +} + +bool remember_last_key_user_default(uint16_t keycode, keyrecord_t *record, uint8_t *remembered_mods) { + return true; +} + +std::function process_record_user_fun = process_record_user_default; +std::function remember_last_key_user_fun = remember_last_key_user_default; + +extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) { + return process_record_user_fun(keycode, record); +} + +extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t *record, uint8_t *remembered_mods) { + return remember_last_key_user_fun(keycode, record, remembered_mods); +} + +class RepeatKeyOverrides : public TestFixture { + public: + bool process_record_user_was_called_; + + void SetUp() override { + reset_repeat_key_state(); + process_record_user_fun = process_record_user_default; + remember_last_key_user_fun = remember_last_key_user_default; + } + + void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) { + process_record_user_was_called_ = false; + process_record_user_fun = [=](uint16_t keycode, keyrecord_t *record) { + EXPECT_EQ(record->event.pressed, expected_press); + EXPECT_KEYCODE_EQ(keycode, expected_keycode); + EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count); + process_record_user_was_called_ = true; + return true; + }; + } +}; + +// Tests that pressing Left Alt + / triggers override to backslash \, +// and tapping Repeat Key subsequently outputs \ again. +TEST_F(RepeatKeyOverrides, AltSlashOverride) { + TestDriver driver; + Matcher empty_or_lalt = AnyOf(KeyboardReport(), KeyboardReport(KC_LALT)); + KeymapKey key_lalt(0, 0, 0, KC_LALT); + KeymapKey key_slsh(0, 1, 0, KC_SLSH); + KeymapKey key_repeat(0, 2, 0, QK_REP); + set_keymap({key_lalt, key_slsh, key_repeat}); + + // Hold Left Alt + EXPECT_REPORT(driver, (KC_LALT)); + key_lalt.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press / (triggers override -> backslash \, Left Alt suppressed) + EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_BSLS)); + key_slsh.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release / (Left Alt still held, so Left Alt is sent again) + EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LALT)); + key_slsh.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release Left Alt + EXPECT_EMPTY_REPORT(driver); + key_lalt.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap Repeat Key (sends \, since weak Left Alt + / triggers override) + EXPECT_CALL(driver, send_keyboard_mock(empty_or_lalt)).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_BSLS)); + ExpectProcessRecordUserCalledWith(true, KC_SLSH, 1); + key_repeat.press(); + run_one_scan_loop(); + EXPECT_TRUE(process_record_user_was_called_); + VERIFY_AND_CLEAR(driver); + + EXPECT_CALL(driver, send_keyboard_mock(empty_or_lalt)).Times(AnyNumber()); + ExpectProcessRecordUserCalledWith(false, KC_SLSH, 1); + key_repeat.release(); + run_one_scan_loop(); + EXPECT_TRUE(process_record_user_was_called_); + VERIFY_AND_CLEAR(driver); +} + +// Tests that pressing Left Alt + Left Shift + / triggers override to |, +// and tapping Repeat Key subsequently outputs | again. +TEST_F(RepeatKeyOverrides, AltShiftSlashOverride) { + TestDriver driver; + Matcher lsft_and_or_lalt = AnyOf(KeyboardReport(KC_LSFT), KeyboardReport(KC_LSFT, KC_LALT)); + KeymapKey key_lalt(0, 0, 0, KC_LALT); + KeymapKey key_lshift(0, 1, 0, KC_LSFT); + KeymapKey key_slsh(0, 2, 0, KC_SLSH); + KeymapKey key_repeat(0, 3, 0, QK_REP); + set_keymap({key_lalt, key_lshift, key_slsh, key_repeat}); + + // Hold Left Alt + EXPECT_REPORT(driver, (KC_LALT)); + key_lalt.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Hold Left Shift + EXPECT_REPORT(driver, (KC_LALT, KC_LSFT)); + key_lshift.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Press / (triggers override -> Shift + \, Alt suppressed, Shift remains) + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_BSLS)); + key_slsh.press(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release / + EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LALT, KC_LSFT)); + key_slsh.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release Left Shift + EXPECT_EMPTY_REPORT(driver).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LALT)); + key_lshift.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Release Left Alt + EXPECT_EMPTY_REPORT(driver); + key_lalt.release(); + run_one_scan_loop(); + VERIFY_AND_CLEAR(driver); + + // Tap Repeat Key (sends | -> Shift + \) + EXPECT_CALL(driver, send_keyboard_mock(lsft_and_or_lalt)).Times(AnyNumber()); + EXPECT_REPORT(driver, (KC_LSFT, KC_BSLS)); + ExpectProcessRecordUserCalledWith(true, KC_SLSH, 1); + key_repeat.press(); + run_one_scan_loop(); + EXPECT_TRUE(process_record_user_was_called_); + VERIFY_AND_CLEAR(driver); + + EXPECT_CALL(driver, send_keyboard_mock(lsft_and_or_lalt)).Times(AnyNumber()); + EXPECT_EMPTY_REPORT(driver); + ExpectProcessRecordUserCalledWith(false, KC_SLSH, 1); + key_repeat.release(); + run_one_scan_loop(); + EXPECT_TRUE(process_record_user_was_called_); + VERIFY_AND_CLEAR(driver); +} + +} // namespace