[Core] Fix to make Repeat Key QK_REP work with Key Overrides. (#26312)

This commit is contained in:
Pascal Getreuer
2026-08-12 21:06:14 +01:00
committed by GitHub
parent bd823862c1
commit d7ad3bf8aa
5 changed files with 248 additions and 0 deletions
@@ -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"
@@ -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 <http://www.gnu.org/licenses/>.
REPEAT_KEY_ENABLE = yes
KEY_OVERRIDE_ENABLE = yes
INTROSPECTION_KEYMAP_C = 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};
@@ -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 <functional>
#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<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default;
std::function<bool(uint16_t, keyrecord_t *, uint8_t *)> 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<report_keyboard_t &> 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<report_keyboard_t &> 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