Skip to content

Commit d7ad3bf

Browse files
authored
[Core] Fix to make Repeat Key QK_REP work with Key Overrides. (qmk#26312)
1 parent bd82386 commit d7ad3bf

5 files changed

Lines changed: 248 additions & 0 deletions

File tree

quantum/process_keycode/process_key_override.c

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -441,6 +441,10 @@ bool process_key_override(const uint16_t keycode, const keyrecord_t *const recor
441441

442442
#ifdef KEY_OVERRIDE_INCLUDE_WEAK_MODS
443443
effective_mods |= get_weak_mods();
444+
#elif defined(REPEAT_KEY_ENABLE)
445+
if (get_repeat_key_count()) {
446+
effective_mods |= get_weak_mods();
447+
}
444448
#endif
445449

446450
#ifndef NO_ACTION_ONESHOT
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#pragma once
16+
17+
#include "test_common.h"
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Copyright 2026 Google LLC
2+
#
3+
# This program is free software: you can redistribute it and/or modify
4+
# it under the terms of the GNU General Public License as published by
5+
# the Free Software Foundation, either version 2 of the License, or
6+
# (at your option) any later version.
7+
#
8+
# This program is distributed in the hope that it will be useful,
9+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
# GNU General Public License for more details.
12+
#
13+
# You should have received a copy of the GNU General Public License
14+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
15+
16+
REPEAT_KEY_ENABLE = yes
17+
KEY_OVERRIDE_ENABLE = yes
18+
INTROSPECTION_KEYMAP_C = test_keymap.c
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include "quantum.h"
16+
17+
const key_override_t alt_slash_override = ko_make_basic(MOD_BIT(KC_LALT), KC_SLSH, KC_BSLS);
18+
19+
const key_override_t *key_overrides[] = {&alt_slash_override, NULL};
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
// Copyright 2026 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
#include <functional>
16+
17+
#include "keyboard_report_util.hpp"
18+
#include "keycode.h"
19+
#include "test_common.hpp"
20+
#include "test_fixture.hpp"
21+
#include "test_keymap_key.hpp"
22+
23+
using ::testing::AnyNumber;
24+
using ::testing::Matcher;
25+
26+
namespace {
27+
28+
bool process_record_user_default(uint16_t keycode, keyrecord_t *record) {
29+
return true;
30+
}
31+
32+
bool remember_last_key_user_default(uint16_t keycode, keyrecord_t *record, uint8_t *remembered_mods) {
33+
return true;
34+
}
35+
36+
std::function<bool(uint16_t, keyrecord_t *)> process_record_user_fun = process_record_user_default;
37+
std::function<bool(uint16_t, keyrecord_t *, uint8_t *)> remember_last_key_user_fun = remember_last_key_user_default;
38+
39+
extern "C" bool process_record_user(uint16_t keycode, keyrecord_t *record) {
40+
return process_record_user_fun(keycode, record);
41+
}
42+
43+
extern "C" bool remember_last_key_user(uint16_t keycode, keyrecord_t *record, uint8_t *remembered_mods) {
44+
return remember_last_key_user_fun(keycode, record, remembered_mods);
45+
}
46+
47+
class RepeatKeyOverrides : public TestFixture {
48+
public:
49+
bool process_record_user_was_called_;
50+
51+
void SetUp() override {
52+
reset_repeat_key_state();
53+
process_record_user_fun = process_record_user_default;
54+
remember_last_key_user_fun = remember_last_key_user_default;
55+
}
56+
57+
void ExpectProcessRecordUserCalledWith(bool expected_press, uint16_t expected_keycode, int8_t expected_repeat_key_count) {
58+
process_record_user_was_called_ = false;
59+
process_record_user_fun = [=](uint16_t keycode, keyrecord_t *record) {
60+
EXPECT_EQ(record->event.pressed, expected_press);
61+
EXPECT_KEYCODE_EQ(keycode, expected_keycode);
62+
EXPECT_EQ(get_repeat_key_count(), expected_repeat_key_count);
63+
process_record_user_was_called_ = true;
64+
return true;
65+
};
66+
}
67+
};
68+
69+
// Tests that pressing Left Alt + / triggers override to backslash \,
70+
// and tapping Repeat Key subsequently outputs \ again.
71+
TEST_F(RepeatKeyOverrides, AltSlashOverride) {
72+
TestDriver driver;
73+
Matcher<report_keyboard_t &> empty_or_lalt = AnyOf(KeyboardReport(), KeyboardReport(KC_LALT));
74+
KeymapKey key_lalt(0, 0, 0, KC_LALT);
75+
KeymapKey key_slsh(0, 1, 0, KC_SLSH);
76+
KeymapKey key_repeat(0, 2, 0, QK_REP);
77+
set_keymap({key_lalt, key_slsh, key_repeat});
78+
79+
// Hold Left Alt
80+
EXPECT_REPORT(driver, (KC_LALT));
81+
key_lalt.press();
82+
run_one_scan_loop();
83+
VERIFY_AND_CLEAR(driver);
84+
85+
// Press / (triggers override -> backslash \, Left Alt suppressed)
86+
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
87+
EXPECT_REPORT(driver, (KC_BSLS));
88+
key_slsh.press();
89+
run_one_scan_loop();
90+
VERIFY_AND_CLEAR(driver);
91+
92+
// Release / (Left Alt still held, so Left Alt is sent again)
93+
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
94+
EXPECT_REPORT(driver, (KC_LALT));
95+
key_slsh.release();
96+
run_one_scan_loop();
97+
VERIFY_AND_CLEAR(driver);
98+
99+
// Release Left Alt
100+
EXPECT_EMPTY_REPORT(driver);
101+
key_lalt.release();
102+
run_one_scan_loop();
103+
VERIFY_AND_CLEAR(driver);
104+
105+
// Tap Repeat Key (sends \, since weak Left Alt + / triggers override)
106+
EXPECT_CALL(driver, send_keyboard_mock(empty_or_lalt)).Times(AnyNumber());
107+
EXPECT_REPORT(driver, (KC_BSLS));
108+
ExpectProcessRecordUserCalledWith(true, KC_SLSH, 1);
109+
key_repeat.press();
110+
run_one_scan_loop();
111+
EXPECT_TRUE(process_record_user_was_called_);
112+
VERIFY_AND_CLEAR(driver);
113+
114+
EXPECT_CALL(driver, send_keyboard_mock(empty_or_lalt)).Times(AnyNumber());
115+
ExpectProcessRecordUserCalledWith(false, KC_SLSH, 1);
116+
key_repeat.release();
117+
run_one_scan_loop();
118+
EXPECT_TRUE(process_record_user_was_called_);
119+
VERIFY_AND_CLEAR(driver);
120+
}
121+
122+
// Tests that pressing Left Alt + Left Shift + / triggers override to |,
123+
// and tapping Repeat Key subsequently outputs | again.
124+
TEST_F(RepeatKeyOverrides, AltShiftSlashOverride) {
125+
TestDriver driver;
126+
Matcher<report_keyboard_t &> lsft_and_or_lalt = AnyOf(KeyboardReport(KC_LSFT), KeyboardReport(KC_LSFT, KC_LALT));
127+
KeymapKey key_lalt(0, 0, 0, KC_LALT);
128+
KeymapKey key_lshift(0, 1, 0, KC_LSFT);
129+
KeymapKey key_slsh(0, 2, 0, KC_SLSH);
130+
KeymapKey key_repeat(0, 3, 0, QK_REP);
131+
set_keymap({key_lalt, key_lshift, key_slsh, key_repeat});
132+
133+
// Hold Left Alt
134+
EXPECT_REPORT(driver, (KC_LALT));
135+
key_lalt.press();
136+
run_one_scan_loop();
137+
VERIFY_AND_CLEAR(driver);
138+
139+
// Hold Left Shift
140+
EXPECT_REPORT(driver, (KC_LALT, KC_LSFT));
141+
key_lshift.press();
142+
run_one_scan_loop();
143+
VERIFY_AND_CLEAR(driver);
144+
145+
// Press / (triggers override -> Shift + \, Alt suppressed, Shift remains)
146+
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
147+
EXPECT_REPORT(driver, (KC_LSFT, KC_BSLS));
148+
key_slsh.press();
149+
run_one_scan_loop();
150+
VERIFY_AND_CLEAR(driver);
151+
152+
// Release /
153+
EXPECT_CALL(driver, send_keyboard_mock(KeyboardReport(KC_LSFT))).Times(AnyNumber());
154+
EXPECT_REPORT(driver, (KC_LALT, KC_LSFT));
155+
key_slsh.release();
156+
run_one_scan_loop();
157+
VERIFY_AND_CLEAR(driver);
158+
159+
// Release Left Shift
160+
EXPECT_EMPTY_REPORT(driver).Times(AnyNumber());
161+
EXPECT_REPORT(driver, (KC_LALT));
162+
key_lshift.release();
163+
run_one_scan_loop();
164+
VERIFY_AND_CLEAR(driver);
165+
166+
// Release Left Alt
167+
EXPECT_EMPTY_REPORT(driver);
168+
key_lalt.release();
169+
run_one_scan_loop();
170+
VERIFY_AND_CLEAR(driver);
171+
172+
// Tap Repeat Key (sends | -> Shift + \)
173+
EXPECT_CALL(driver, send_keyboard_mock(lsft_and_or_lalt)).Times(AnyNumber());
174+
EXPECT_REPORT(driver, (KC_LSFT, KC_BSLS));
175+
ExpectProcessRecordUserCalledWith(true, KC_SLSH, 1);
176+
key_repeat.press();
177+
run_one_scan_loop();
178+
EXPECT_TRUE(process_record_user_was_called_);
179+
VERIFY_AND_CLEAR(driver);
180+
181+
EXPECT_CALL(driver, send_keyboard_mock(lsft_and_or_lalt)).Times(AnyNumber());
182+
EXPECT_EMPTY_REPORT(driver);
183+
ExpectProcessRecordUserCalledWith(false, KC_SLSH, 1);
184+
key_repeat.release();
185+
run_one_scan_loop();
186+
EXPECT_TRUE(process_record_user_was_called_);
187+
VERIFY_AND_CLEAR(driver);
188+
}
189+
190+
} // namespace

0 commit comments

Comments
 (0)