summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPascal Getreuer <50221757+getreuer@users.noreply.github.com>2025-03-19 12:45:56 -0700
committerGitHub <noreply@github.com>2025-03-19 20:45:56 +0100
commit3484f0a0dffd264ae3bef70ce2a8961489c16f50 (patch)
tree6c9611c3510dc970fd586ee7bc61d9e0265bfd2d
parent40a446a0ec1f2f7cb8c52538bfefb1b26c951dd4 (diff)
[Core] get_keycode_string(): function to format keycodes as strings, for more readable debug logging. (#24787)
* keycode_string(): Format keycodes as strings. This adds the `keycode_string()` function described in https://getreuer.info/posts/keyboards/keycode-string/index.html as a core feature. * Fix formatting. * keycode_string review revisions. * Rename keycode_string() -> get_keycode_string() for consistency with existing string utils like get_u8_str(). * Revise custom keycode names with separate _user and _kb tables. * Correct indent in builddefs/generic_features.mk. Co-authored-by: Ryan <fauxpark@gmail.com> * Add KC_NUHS, KC_NUBS, and KC_CAPS. * Fix linking error with custom names. * Attempt at simplifying interface. * Formatting fix. * Several fixes and revisions. * Don't use PSTR in KEYCODE_STRING_NAME, since this fails to build on AVR. Store custom names in RAM. * Revise the internal table of common keycode names to use its own storage representation, still in PROGMEM, and now more efficiently stored flat in 8 bytes per entry. * Support Swap Hands keycodes and a few other keycodes. * Revert "Formatting fix." This reverts commit 2a2771068c7ee545ffac4103aa07e847a9ec3816. * Revert "Attempt at simplifying interface." This reverts commit 8eaf67de76e75bc92d106a8b0decc893fbc65fa5. * Simplify custom names API by sigprof's suggestion. * Support more keycodes. * Add QK_LOCK keycode. * Add Secure keycodes. * Add Joystick keycodes. * Add Programmable Button keycodes. * Add macro MC_ keycodes. * For remaining keys in known code ranges, stringify them as "QK_<feature>+<number>". For instance, "QK_MIDI+7". * Bug fix and a few improvements. * Fix missing right-hand bit when displaying 5-bit mods numerically. * Support KC_HYPR, KC_MEH, HYPR_T(kc), MEH_T(kc). * Exclude one-shot keycodes when NO_ACTION_ONESHOT is defined. --------- Co-authored-by: Ryan <fauxpark@gmail.com>
-rw-r--r--builddefs/generic_features.mk1
-rw-r--r--docs/faq_debug.md11
-rw-r--r--docs/unit_testing.md28
-rw-r--r--quantum/keycode_string.c564
-rw-r--r--quantum/keycode_string.h134
-rw-r--r--quantum/quantum.h1
-rw-r--r--tests/keycode_string/config.h19
-rw-r--r--tests/keycode_string/test.mk22
-rw-r--r--tests/keycode_string/test_keycode_string.cpp153
9 files changed, 933 insertions, 0 deletions
diff --git a/builddefs/generic_features.mk b/builddefs/generic_features.mk
index f14f440877..015a804d91 100644
--- a/builddefs/generic_features.mk
+++ b/builddefs/generic_features.mk
@@ -34,6 +34,7 @@ GENERIC_FEATURES = \
34 DYNAMIC_TAPPING_TERM \ 34 DYNAMIC_TAPPING_TERM \
35 GRAVE_ESC \ 35 GRAVE_ESC \
36 HAPTIC \ 36 HAPTIC \
37 KEYCODE_STRING \
37 KEY_LOCK \ 38 KEY_LOCK \
38 KEY_OVERRIDE \ 39 KEY_OVERRIDE \
39 LAYER_LOCK \ 40 LAYER_LOCK \
diff --git a/docs/faq_debug.md b/docs/faq_debug.md
index 35a4160e27..269049afb8 100644
--- a/docs/faq_debug.md
+++ b/docs/faq_debug.md
@@ -77,6 +77,17 @@ KL: kc: 172, col: 2, row: 0, pressed: 1, time: 16303, int: 0, count: 0
77KL: kc: 172, col: 2, row: 0, pressed: 0, time: 16411, int: 0, count: 0 77KL: kc: 172, col: 2, row: 0, pressed: 0, time: 16411, int: 0, count: 0
78``` 78```
79 79
80### Which keycode is this keypress?
81
82Keycodes are logged in the example above as numerical codes, which may be difficult to interpret. For more readable logging, add `KEYCODE_STRING_ENABLE = yes` in your `rules.mk` and use `get_keycode_string(kc)`. For example:
83
84```c
85uprintf("kc: %s\n", get_keycode_string(keycode));
86```
87
88This logs the keycode as a human-readable string like "`LT(2,KC_D)`" rather than a numerical code like "`0x4207`." See the [Keycode String](unit_testing#keycode-string) section of the Unit Testing page for more information.
89
90
80### How long did it take to scan for a keypress? 91### How long did it take to scan for a keypress?
81 92
82When testing performance issues, it can be useful to know the frequency at which the switch matrix is being scanned. To enable logging for this scenario, add the following code to your keymaps `config.h` 93When testing performance issues, it can be useful to know the frequency at which the switch matrix is being scanned. To enable logging for this scenario, add the following code to your keymaps `config.h`
diff --git a/docs/unit_testing.md b/docs/unit_testing.md
index 3e4c914bf1..aec4ec8334 100644
--- a/docs/unit_testing.md
+++ b/docs/unit_testing.md
@@ -58,6 +58,34 @@ It's not yet possible to do a full integration test, where you would compile the
58 58
59In that model you would emulate the input, and expect a certain output from the emulated keyboard. 59In that model you would emulate the input, and expect a certain output from the emulated keyboard.
60 60
61# Keycode String {#keycode-string}
62
63It's much nicer to read keycodes as names like "`LT(2,KC_D)`" than numerical codes like "`0x4207`." To convert keycodes to human-readable strings, add `KEYCODE_STRING_ENABLE = yes` to the `rules.mk` file, then use the `get_keycode_string(kc)` function to convert a given 16-bit keycode to a string.
64
65```c
66const char *key_name = get_keycode_string(keycode);
67dprintf("kc: %s\n", key_name);
68```
69
70The stringified keycode may then be logged to console output with `dprintf()` or elsewhere.
71
72::: warning
73Use the result of `get_keycode_string()` immediately. Subsequent invocations reuse the same static buffer and overwrite the previous contents.
74:::
75
76Many common QMK keycodes are recognized by `get_keycode_string()`, but not all. These include some common basic keycodes, layer switch keycodes, mod-taps, one-shot keycodes, tap dance keycodes, and Unicode keycodes. As a fallback, an unrecognized keycode is written as a hex number.
77
78Optionally, `KEYCODE_STRING_NAMES_USER` may be defined to add names for additional keycodes. For example, supposing keymap.c defines `MYMACRO1` and `MYMACRO2` as custom keycodes, the following adds their names:
79
80```c
81KEYCODE_STRING_NAMES_USER(
82 KEYCODE_STRING_NAME(MYMACRO1),
83 KEYCODE_STRING_NAME(MYMACRO2),
84);
85```
86
87Similarly, `KEYCODE_STRING_NAMES_KB` may be defined to add names at the keyboard level.
88
61# Tracing Variables {#tracing-variables} 89# Tracing Variables {#tracing-variables}
62 90
63Sometimes you might wonder why a variable gets changed and where, and this can be quite tricky to track down without having a debugger. It's of course possible to manually add print statements to track it, but you can also enable the variable trace feature. This works for both variables that are changed by the code, and when the variable is changed by some memory corruption. 91Sometimes you might wonder why a variable gets changed and where, and this can be quite tricky to track down without having a debugger. It's of course possible to manually add print statements to track it, but you can also enable the variable trace feature. This works for both variables that are changed by the code, and when the variable is changed by some memory corruption.
diff --git a/quantum/keycode_string.c b/quantum/keycode_string.c
new file mode 100644
index 0000000000..18044f2ef6
--- /dev/null
+++ b/quantum/keycode_string.c
@@ -0,0 +1,564 @@
1// Copyright 2024-2025 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 "keycode_string.h"
16
17#include <string.h>
18#include "bitwise.h"
19#include "keycode.h"
20#include "progmem.h"
21#include "quantum_keycodes.h"
22#include "util.h"
23
24typedef int_fast8_t index_t;
25
26// clang-format off
27/** Packs a 7-char keycode name, ignoring the third char, as 3 words. */
28#define KEYCODE_NAME7(c0, c1, unused_c2, c3, c4, c5, c6) \
29 ((uint16_t)c0) | (((uint16_t)c1) << 8), \
30 ((uint16_t)c3) | (((uint16_t)c4) << 8), \
31 ((uint16_t)c5) | (((uint16_t)c6) << 8)
32
33/**
34 * @brief Names of some common keycodes.
35 *
36 * Each (keycode, name) entry is stored flat in 8 bytes in PROGMEM. Names in
37 * this table must be at most 7 chars long and have an underscore '_' for the
38 * third char. This underscore is assumed and not actually stored.
39 *
40 * To save memory, feature-specific key entries are ifdef'd to include them only
41 * when their feature is enabled.
42 */
43static const uint16_t common_names[] PROGMEM = {
44 KC_TRNS, KEYCODE_NAME7('K', 'C', '_', 'T', 'R', 'N', 'S'),
45 KC_ENT , KEYCODE_NAME7('K', 'C', '_', 'E', 'N', 'T', 0 ),
46 KC_ESC , KEYCODE_NAME7('K', 'C', '_', 'E', 'S', 'C', 0 ),
47 KC_BSPC, KEYCODE_NAME7('K', 'C', '_', 'B', 'S', 'P', 'C'),
48 KC_TAB , KEYCODE_NAME7('K', 'C', '_', 'T', 'A', 'B', 0 ),
49 KC_SPC , KEYCODE_NAME7('K', 'C', '_', 'S', 'P', 'C', 0 ),
50 KC_MINS, KEYCODE_NAME7('K', 'C', '_', 'M', 'I', 'N', 'S'),
51 KC_EQL , KEYCODE_NAME7('K', 'C', '_', 'E', 'Q', 'L', 0 ),
52 KC_LBRC, KEYCODE_NAME7('K', 'C', '_', 'L', 'B', 'R', 'C'),
53 KC_RBRC, KEYCODE_NAME7('K', 'C', '_', 'R', 'B', 'R', 'C'),
54 KC_BSLS, KEYCODE_NAME7('K', 'C', '_', 'B', 'S', 'L', 'S'),
55 KC_NUHS, KEYCODE_NAME7('K', 'C', '_', 'N', 'U', 'H', 'S'),
56 KC_SCLN, KEYCODE_NAME7('K', 'C', '_', 'S', 'C', 'L', 'N'),
57 KC_QUOT, KEYCODE_NAME7('K', 'C', '_', 'Q', 'U', 'O', 'T'),
58 KC_GRV , KEYCODE_NAME7('K', 'C', '_', 'G', 'R', 'V', 0 ),
59 KC_COMM, KEYCODE_NAME7('K', 'C', '_', 'C', 'O', 'M', 'M'),
60 KC_DOT , KEYCODE_NAME7('K', 'C', '_', 'D', 'O', 'T', 0 ),
61 KC_SLSH, KEYCODE_NAME7('K', 'C', '_', 'S', 'L', 'S', 'H'),
62 KC_CAPS, KEYCODE_NAME7('K', 'C', '_', 'C', 'A', 'P', 'S'),
63 KC_PSCR, KEYCODE_NAME7('K', 'C', '_', 'P', 'S', 'C', 'R'),
64 KC_PAUS, KEYCODE_NAME7('K', 'C', '_', 'P', 'A', 'U', 'S'),
65 KC_INS , KEYCODE_NAME7('K', 'C', '_', 'I', 'N', 'S', 0 ),
66 KC_HOME, KEYCODE_NAME7('K', 'C', '_', 'H', 'O', 'M', 'E'),
67 KC_PGUP, KEYCODE_NAME7('K', 'C', '_', 'P', 'G', 'U', 'P'),
68 KC_DEL , KEYCODE_NAME7('K', 'C', '_', 'D', 'E', 'L', 0 ),
69 KC_END , KEYCODE_NAME7('K', 'C', '_', 'E', 'N', 'D', 0 ),
70 KC_PGDN, KEYCODE_NAME7('K', 'C', '_', 'P', 'G', 'D', 'N'),
71 KC_RGHT, KEYCODE_NAME7('K', 'C', '_', 'R', 'G', 'H', 'T'),
72 KC_LEFT, KEYCODE_NAME7('K', 'C', '_', 'L', 'E', 'F', 'T'),
73 KC_DOWN, KEYCODE_NAME7('K', 'C', '_', 'D', 'O', 'W', 'N'),
74 KC_UP , KEYCODE_NAME7('K', 'C', '_', 'U', 'P', 0 , 0 ),
75 KC_NUBS, KEYCODE_NAME7('K', 'C', '_', 'N', 'U', 'B', 'S'),
76 KC_HYPR, KEYCODE_NAME7('K', 'C', '_', 'H', 'Y', 'P', 'R'),
77 KC_MEH , KEYCODE_NAME7('K', 'C', '_', 'M', 'E', 'H', 0 ),
78#ifdef EXTRAKEY_ENABLE
79 KC_WHOM, KEYCODE_NAME7('K', 'C', '_', 'W', 'H', 'O', 'M'),
80 KC_WBAK, KEYCODE_NAME7('K', 'C', '_', 'W', 'B', 'A', 'K'),
81 KC_WFWD, KEYCODE_NAME7('K', 'C', '_', 'W', 'F', 'W', 'D'),
82 KC_WSTP, KEYCODE_NAME7('K', 'C', '_', 'W', 'S', 'T', 'P'),
83 KC_WREF, KEYCODE_NAME7('K', 'C', '_', 'W', 'R', 'E', 'F'),
84 KC_MNXT, KEYCODE_NAME7('K', 'C', '_', 'M', 'N', 'X', 'T'),
85 KC_MPRV, KEYCODE_NAME7('K', 'C', '_', 'M', 'P', 'R', 'V'),
86 KC_MPLY, KEYCODE_NAME7('K', 'C', '_', 'M', 'P', 'L', 'Y'),
87 KC_MUTE, KEYCODE_NAME7('K', 'C', '_', 'M', 'U', 'T', 'E'),
88 KC_VOLU, KEYCODE_NAME7('K', 'C', '_', 'V', 'O', 'L', 'U'),
89 KC_VOLD, KEYCODE_NAME7('K', 'C', '_', 'V', 'O', 'L', 'D'),
90#endif // EXTRAKEY_ENABLE
91#ifdef MOUSEKEY_ENABLE
92 MS_LEFT, KEYCODE_NAME7('M', 'S', '_', 'L', 'E', 'F', 'T'),
93 MS_RGHT, KEYCODE_NAME7('M', 'S', '_', 'R', 'G', 'H', 'T'),
94 MS_UP , KEYCODE_NAME7('M', 'S', '_', 'U', 'P', 0 , 0 ),
95 MS_DOWN, KEYCODE_NAME7('M', 'S', '_', 'D', 'O', 'W', 'N'),
96 MS_WHLL, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'L'),
97 MS_WHLR, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'R'),
98 MS_WHLU, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'U'),
99 MS_WHLD, KEYCODE_NAME7('M', 'S', '_', 'W', 'H', 'L', 'D'),
100#endif // MOUSEKEY_ENABLE
101#ifdef SWAP_HANDS_ENABLE
102 SH_ON , KEYCODE_NAME7('S', 'H', '_', 'O', 'N', 0 , 0 ),
103 SH_OFF , KEYCODE_NAME7('S', 'H', '_', 'O', 'F', 'F', 0 ),
104 SH_MON , KEYCODE_NAME7('S', 'H', '_', 'M', 'O', 'N', 0 ),
105 SH_MOFF, KEYCODE_NAME7('S', 'H', '_', 'M', 'O', 'F', 'F'),
106 SH_TOGG, KEYCODE_NAME7('S', 'H', '_', 'T', 'O', 'G', 'G'),
107 SH_TT , KEYCODE_NAME7('S', 'H', '_', 'T', 'T', 0 , 0 ),
108# if !defined(NO_ACTION_ONESHOT)
109 SH_OS , KEYCODE_NAME7('S', 'H', '_', 'O', 'S', 0 , 0 ),
110# endif // !defined(NO_ACTION_ONESHOT)
111#endif // SWAP_HANDS_ENABLE
112#ifdef LEADER_ENABLE
113 QK_LEAD, KEYCODE_NAME7('Q', 'K', '_', 'L', 'E', 'A', 'D'),
114#endif // LEADER_ENABLE
115#ifdef KEY_LOCK_ENABLE
116 QK_LOCK, KEYCODE_NAME7('Q', 'K', '_', 'L', 'O', 'C', 'K'),
117#endif // KEY_LOCK_ENABLE
118#ifdef TRI_LAYER_ENABLE
119 TL_LOWR, KEYCODE_NAME7('T', 'L', '_', 'L', 'O', 'W', 'R'),
120 TL_UPPR, KEYCODE_NAME7('T', 'L', '_', 'U', 'P', 'P', 'R'),
121#endif // TRI_LAYER_ENABLE
122#ifdef GRAVE_ESC_ENABLE
123 QK_GESC, KEYCODE_NAME7('Q', 'K', '_', 'G', 'E', 'S', 'C'),
124#endif // GRAVE_ESC_ENABLE
125#ifdef CAPS_WORD_ENABLE
126 CW_TOGG, KEYCODE_NAME7('C', 'W', '_', 'T', 'O', 'G', 'G'),
127#endif // CAPS_WORD_ENABLE
128#ifdef SECURE_ENABLE
129 SE_LOCK, KEYCODE_NAME7('S', 'E', '_', 'L', 'O', 'C', 'K'),
130 SE_UNLK, KEYCODE_NAME7('S', 'E', '_', 'U', 'N', 'L', 'K'),
131 SE_TOGG, KEYCODE_NAME7('S', 'E', '_', 'T', 'O', 'G', 'G'),
132 SE_REQ , KEYCODE_NAME7('S', 'E', '_', 'R', 'E', 'Q', 0 ),
133#endif // SECURE_ENABLE
134#ifdef LAYER_LOCK_ENABLE
135 QK_LLCK, KEYCODE_NAME7('Q', 'K', '_', 'L', 'L', 'C', 'K'),
136#endif // LAYER_LOCK_ENABLE
137 EE_CLR , KEYCODE_NAME7('E', 'E', '_', 'C', 'L', 'R', 0 ),
138 QK_BOOT, KEYCODE_NAME7('Q', 'K', '_', 'B', 'O', 'O', 'T'),
139 DB_TOGG, KEYCODE_NAME7('D', 'B', '_', 'T', 'O', 'G', 'G'),
140};
141// clang-format on
142
143/** Users can override this to define names of additional keycodes. */
144__attribute__((weak)) const keycode_string_name_t* keycode_string_names_data_user = NULL;
145__attribute__((weak)) uint16_t keycode_string_names_size_user = 0;
146/** Keyboard vendors can override this to define names of additional keycodes. */
147__attribute__((weak)) const keycode_string_name_t* keycode_string_names_data_kb = NULL;
148__attribute__((weak)) uint16_t keycode_string_names_size_kb = 0;
149/** Names of the 4 mods on each hand. */
150static const char mod_names[] PROGMEM = "CTL\0SFT\0ALT\0GUI";
151/** Internal buffer for holding a stringified keycode. */
152static char buffer[32];
153#define BUFFER_MAX_LEN (sizeof(buffer) - 1)
154static index_t buffer_len;
155
156/** Finds the name of a keycode in `common_names` or returns NULL. */
157static const char* search_common_names(uint16_t keycode) {
158 static uint8_t buffer[8];
159
160 for (int_fast16_t offset = 0; offset < ARRAY_SIZE(common_names); offset += 4) {
161 if (keycode == pgm_read_word(common_names + offset)) {
162 const uint16_t w0 = pgm_read_word(common_names + offset + 1);
163 const uint16_t w1 = pgm_read_word(common_names + offset + 2);
164 const uint16_t w2 = pgm_read_word(common_names + offset + 3);
165 buffer[0] = (uint8_t)w0;
166 buffer[1] = (uint8_t)(w0 >> 8);
167 buffer[2] = '_';
168 buffer[3] = (uint8_t)w1;
169 buffer[4] = (uint8_t)(w1 >> 8);
170 buffer[5] = (uint8_t)w2;
171 buffer[6] = (uint8_t)(w2 >> 8);
172 buffer[7] = 0;
173 return (const char*)buffer;
174 }
175 }
176
177 return NULL;
178}
179
180/**
181 * @brief Finds the name of a keycode in table or returns NULL.
182 *
183 * @param data Pointer to table to be searched.
184 * @param size Numer of entries in the table.
185 * @return Name string for the keycode, or NULL if not found.
186 */
187static const char* search_table(const keycode_string_name_t* data, uint16_t size, uint16_t keycode) {
188 if (data != NULL) {
189 for (uint16_t i = 0; i < size; ++i) {
190 if (data[i].keycode == keycode) {
191 return data[i].name;
192 }
193 }
194 }
195 return NULL;
196}
197
198/** Formats `number` in `base`, either 10 or 16. */
199static char* number_string(uint16_t number, int8_t base) {
200 static char result[7];
201 result[sizeof(result) - 1] = '\0';
202 index_t i = sizeof(result) - 1;
203 do {
204 const uint8_t digit = number % base;
205 number /= base;
206 result[--i] = (digit < 10) ? (char)(digit + UINT8_C('0')) : (char)(digit + (UINT8_C('A') - 10));
207 } while (number > 0 && i > 0);
208
209 if (base == 16 && i >= 2) {
210 result[--i] = 'x';
211 result[--i] = '0';
212 }
213 return result + i;
214}
215
216/** Appends `str` to `buffer`, truncating if the result would overflow. */
217static void append(const char* str) {
218 char* dest = buffer + buffer_len;
219 index_t i;
220 for (i = 0; buffer_len + i < BUFFER_MAX_LEN && str[i]; ++i) {
221 dest[i] = str[i];
222 }
223 buffer_len += i;
224 buffer[buffer_len] = '\0';
225}
226
227/** Same as append(), but where `str` is a PROGMEM string. */
228static void append_P(const char* str) {
229 char* dest = buffer + buffer_len;
230 index_t i;
231 for (i = 0; buffer_len + i < BUFFER_MAX_LEN; ++i) {
232 const char c = pgm_read_byte(&str[i]);
233 if (c == '\0') {
234 break;
235 }
236 dest[i] = c;
237 }
238 buffer_len += i;
239 buffer[buffer_len] = '\0';
240}
241
242/** Appends a single char to `buffer` if there is space. */
243static void append_char(char c) {
244 if (buffer_len < BUFFER_MAX_LEN) {
245 buffer[buffer_len] = c;
246 buffer[++buffer_len] = '\0';
247 }
248}
249
250/** Formats `number` in `base`, either 10 or 16, and appends it to `buffer`. */
251static void append_number(uint16_t number, int8_t base) {
252 append(number_string(number, base));
253}
254
255/** Stringifies 5-bit mods and appends it to `buffer`. */
256static void append_5_bit_mods(uint8_t mods) {
257 const bool is_rhs = mods > 15;
258 const uint8_t csag = mods & 15;
259 if (csag != 0 && (csag & (csag - 1)) == 0) { // One mod is set.
260 append_P(PSTR("MOD_"));
261 append_char(is_rhs ? 'R' : 'L');
262 append_P(&mod_names[4 * biton(csag)]);
263 } else { // Fallback: write the mod as a hex value.
264 append_number(mods, 16);
265 }
266}
267
268/**
269 * @brief Writes a keycode of the format `name` + "(" + `param` + ")".
270 * @note `name` is a PROGMEM string, `param` is not.
271 */
272static void append_unary_keycode(const char* name, const char* param) {
273 append_P(name);
274 append_char('(');
275 append(param);
276 append_char(')');
277}
278
279/**
280 * @brief Writes a keycode of the format `name` + `number`.
281 * @note `name` is a PROGMEM string.
282 */
283static void append_numbered_keycode(const char* name, uint16_t number) {
284 append_P(name);
285 append_number(number, 10);
286}
287
288/** Stringifies `keycode` and appends it to `buffer`. */
289static void append_keycode(uint16_t keycode) {
290 // In case there is overlap among tables, search `keycode_string_names_user`
291 // first so that it takes precedence.
292 const char* keycode_name = search_table(keycode_string_names_data_user, keycode_string_names_size_user, keycode);
293 if (keycode_name) {
294 append(keycode_name);
295 return;
296 }
297 keycode_name = search_table(keycode_string_names_data_kb, keycode_string_names_size_kb, keycode);
298 if (keycode_name) {
299 append(keycode_name);
300 return;
301 }
302 keycode_name = search_common_names(keycode);
303 if (keycode_name) {
304 append(keycode_name);
305 return;
306 }
307
308 if (keycode <= 255) { // Basic keycodes.
309 switch (keycode) {
310 // Modifiers KC_LSFT, KC_RCTL, etc.
311 case MODIFIER_KEYCODE_RANGE: {
312 const uint8_t i = keycode - KC_LCTL;
313 const bool is_rhs = i > 3;
314 append_P(PSTR("KC_"));
315 append_char(is_rhs ? 'R' : 'L');
316 append_P(&mod_names[4 * (i & 3)]);
317 }
318 return;
319
320 // Letters A-Z.
321 case KC_A ... KC_Z:
322 append_P(PSTR("KC_"));
323 append_char((char)(keycode + (UINT8_C('A') - KC_A)));
324 return;
325
326 // Digits 0-9 (NOTE: Unlike the ASCII order, KC_0 comes *after* KC_9.)
327 case KC_1 ... KC_0:
328 append_numbered_keycode(PSTR("KC_"), (keycode - (KC_1 - 1)) % 10);
329 return;
330
331 // Keypad digits.
332 case KC_KP_1 ... KC_KP_0:
333 append_numbered_keycode(PSTR("KC_KP_"), (keycode - (KC_KP_1 - 1)) % 10);
334 return;
335
336 // Function keys. F1-F12 and F13-F24 are coded in separate ranges.
337 case KC_F1 ... KC_F12:
338 append_numbered_keycode(PSTR("KC_F"), keycode - (KC_F1 - 1));
339 return;
340
341 case KC_F13 ... KC_F24:
342 append_numbered_keycode(PSTR("KC_F"), keycode - (KC_F13 - 13));
343 return;
344 }
345 }
346
347 // clang-format off
348 switch (keycode) {
349 // A modified keycode, like S(KC_1) for Shift + 1 = !. This implementation
350 // only covers modified keycodes where one modifier is applied, e.g. a
351 // Ctrl + Shift + kc or Hyper + kc keycode is not formatted.
352 case QK_MODS ... QK_MODS_MAX: {
353 uint8_t mods = QK_MODS_GET_MODS(keycode);
354 const bool is_rhs = mods > 15;
355 mods &= 15;
356 if (mods != 0 && (mods & (mods - 1)) == 0) { // One mod is set.
357 const char* name = &mod_names[4 * biton(mods)];
358 if (is_rhs) {
359 append_char('R');
360 append_P(name);
361 } else {
362 append_char(pgm_read_byte(&name[0]));
363 }
364 append_char('(');
365 append_keycode(QK_MODS_GET_BASIC_KEYCODE(keycode));
366 append_char(')');
367 return;
368 }
369 } break;
370
371#if !defined(NO_ACTION_ONESHOT)
372 case QK_ONE_SHOT_MOD ... QK_ONE_SHOT_MOD_MAX: // One-shot mod OSM(mod) key.
373 append_P(PSTR("OSM("));
374 append_5_bit_mods(QK_ONE_SHOT_MOD_GET_MODS(keycode));
375 append_char(')');
376 return;
377#endif // !defined(NO_ACTION_ONESHOT)
378
379 // Various layer switch keys.
380 case QK_LAYER_TAP ... QK_LAYER_TAP_MAX: // Layer-tap LT(layer,kc) key.
381 append_P(PSTR("LT("));
382 append_number(QK_LAYER_TAP_GET_LAYER(keycode), 10);
383 append_char(',');
384 append_keycode(QK_LAYER_TAP_GET_TAP_KEYCODE(keycode));
385 append_char(')');
386 return;
387
388 case QK_LAYER_MOD ... QK_LAYER_MOD_MAX: // LM(layer,mod) key.
389 append_P(PSTR("LM("));
390 append_number(QK_LAYER_MOD_GET_LAYER(keycode), 10);
391 append_char(',');
392 append_5_bit_mods(QK_LAYER_MOD_GET_MODS(keycode));
393 append_char(')');
394 return;
395
396 case QK_TO ... QK_TO_MAX: // TO(layer) key.
397 append_unary_keycode(PSTR("TO"), number_string(QK_TO_GET_LAYER(keycode), 10));
398 return;
399
400 case QK_MOMENTARY ... QK_MOMENTARY_MAX: // MO(layer) key.
401 append_unary_keycode(PSTR("MO"), number_string(QK_MOMENTARY_GET_LAYER(keycode), 10));
402 return;
403
404 case QK_DEF_LAYER ... QK_DEF_LAYER_MAX: // DF(layer) key.
405 append_unary_keycode(PSTR("DF"), number_string(QK_DEF_LAYER_GET_LAYER(keycode), 10));
406 return;
407
408 case QK_TOGGLE_LAYER ... QK_TOGGLE_LAYER_MAX: // TG(layer) key.
409 append_unary_keycode(PSTR("TG"), number_string(QK_TOGGLE_LAYER_GET_LAYER(keycode), 10));
410 return;
411
412#if !defined(NO_ACTION_ONESHOT)
413 case QK_ONE_SHOT_LAYER ... QK_ONE_SHOT_LAYER_MAX: // OSL(layer) key.
414 append_unary_keycode(PSTR("OSL"), number_string(QK_ONE_SHOT_LAYER_GET_LAYER(keycode), 10));
415 return;
416#endif // !defined(NO_ACTION_ONESHOT)
417
418 case QK_LAYER_TAP_TOGGLE ... QK_LAYER_TAP_TOGGLE_MAX: // TT(layer) key.
419 append_unary_keycode(PSTR("TT"), number_string(QK_LAYER_TAP_TOGGLE_GET_LAYER(keycode), 10));
420 return;
421
422 case QK_PERSISTENT_DEF_LAYER ... QK_PERSISTENT_DEF_LAYER_MAX: // PDF(layer) key.
423 append_unary_keycode(PSTR("PDF"), number_string(QK_PERSISTENT_DEF_LAYER_GET_LAYER(keycode), 10));
424 return;
425
426 // Mod-tap MT(mod,kc) key. This implementation formats the MT keys where
427 // one modifier is applied. For MT keys with multiple modifiers, the mod
428 // arg is written numerically as a hex code.
429 case QK_MOD_TAP ... QK_MOD_TAP_MAX: {
430 uint8_t mods = QK_MOD_TAP_GET_MODS(keycode);
431 const bool is_rhs = mods > 15;
432 const uint8_t csag = mods & 15;
433 if (csag != 0 && (csag & (csag - 1)) == 0) { // One mod is set.
434 append_char(is_rhs ? 'R' : 'L');
435 append_P(&mod_names[4 * biton(csag)]);
436 append_P(PSTR("_T("));
437 } else if (mods == MOD_HYPR) {
438 append_P(PSTR("HYPR_T("));
439 } else if (mods == MOD_MEH) {
440 append_P(PSTR("MEH_T("));
441 } else {
442 append_P(PSTR("MT("));
443 append_number(mods, 16);
444 append_char(',');
445 }
446 append_keycode(QK_MOD_TAP_GET_TAP_KEYCODE(keycode));
447 append_char(')');
448 } return;
449
450 case QK_TAP_DANCE ... QK_TAP_DANCE_MAX: // Tap dance TD(i) key.
451 append_unary_keycode(PSTR("TD"), number_string(QK_TAP_DANCE_GET_INDEX(keycode), 10));
452 return;
453
454#ifdef UNICODE_ENABLE
455 case QK_UNICODE ... QK_UNICODE_MAX: // Unicode UC(codepoint) key.
456 append_unary_keycode(PSTR("UC"), number_string(QK_UNICODE_GET_CODE_POINT(keycode), 16));
457 return;
458#elif defined(UNICODEMAP_ENABLE)
459 case QK_UNICODEMAP ... QK_UNICODEMAP_MAX: // Unicode Map UM(i) key.
460 append_unary_keycode(PSTR("UM"), number_string(QK_UNICODEMAP_GET_INDEX(keycode), 10));
461 return;
462
463 case QK_UNICODEMAP_PAIR ... QK_UNICODEMAP_PAIR_MAX: { // UP(i,j) key.
464 const uint8_t i = QK_UNICODEMAP_PAIR_GET_UNSHIFTED_INDEX(keycode);
465 const uint8_t j = QK_UNICODEMAP_PAIR_GET_SHIFTED_INDEX(keycode);
466 append_P(PSTR("UP("));
467 append_number(i, 10);
468 append_char(',');
469 append_number(j, 10);
470 append_char(')');
471 } return;
472#endif
473#ifdef MOUSEKEY_ENABLE
474 case MS_BTN1 ... MS_BTN8: // Mouse button keycode.
475 append_numbered_keycode(PSTR("MS_BTN"), keycode - (MS_BTN1 - 1));
476 return;
477#endif // MOUSEKEY_ENABLE
478#ifdef SWAP_HANDS_ENABLE
479 case QK_SWAP_HANDS ... QK_SWAP_HANDS_MAX: // Swap Hands SH_T(kc) key.
480 if (!IS_SWAP_HANDS_KEYCODE(keycode)) {
481 append_P(PSTR("SH_T("));
482 append_keycode(QK_SWAP_HANDS_GET_TAP_KEYCODE(keycode));
483 append_char(')');
484 return;
485 }
486 break;
487#endif // SWAP_HANDS_ENABLE
488#ifdef JOYSTICK_ENABLE
489 case JOYSTICK_KEYCODE_RANGE: // Joystick JS_ key.
490 append_numbered_keycode(PSTR("JS_"), keycode - JS_0);
491 return;
492#endif // JOYSTICK_ENABLE
493#ifdef PROGRAMMABLE_BUTTON_ENABLE
494 case PROGRAMMABLE_BUTTON_KEYCODE_RANGE: // Programmable button PB_ key.
495 append_numbered_keycode(PSTR("PB_"), keycode - (PB_1 - 1));
496 return;
497#endif // PROGRAMMABLE_BUTTON_ENABLE
498
499 case MACRO_KEYCODE_RANGE: // Macro range MC_ keycode.
500 append_numbered_keycode(PSTR("MC_"), keycode - MC_0);
501 return;
502
503 case KB_KEYCODE_RANGE: // Keyboard range keycode.
504 append_numbered_keycode(PSTR("QK_KB_"), keycode - QK_KB_0);
505 return;
506
507 case USER_KEYCODE_RANGE: // User range keycode.
508 append_numbered_keycode(PSTR("QK_USER_"), keycode - QK_USER_0);
509 return;
510
511 // It would take a nontrivial amount of string data to cover some
512 // feature-specific keycodes, such as those for MIDI and lighting. As a
513 // fallback while still providing some information, we stringify
514 // remaining keys in known code ranges as "QK_<feature>+<number>".
515#ifdef MAGIC_ENABLE
516 case MAGIC_KEYCODE_RANGE:
517 append_numbered_keycode(PSTR("QK_MAGIC+"), keycode - QK_MAGIC);
518 return;
519#endif // MAGIC_ENABLE
520#ifdef MIDI_ENABLE
521 case MIDI_KEYCODE_RANGE:
522 append_numbered_keycode(PSTR("QK_MIDI+"), keycode - QK_MIDI);
523 return;
524#endif // MIDI_ENABLE
525#ifdef SEQUENCER_ENABLE
526 case SEQUENCER_KEYCODE_RANGE:
527 append_numbered_keycode(PSTR("QK_SEQUENCER+"), keycode - QK_SEQUENCER);
528 return;
529#endif // SEQUENCER_ENABLE
530#ifdef AUDIO_ENABLE
531 case AUDIO_KEYCODE_RANGE:
532 append_numbered_keycode(PSTR("QK_AUDIO+"), keycode - QK_AUDIO);
533 return;
534#endif // AUDIO_ENABLE
535#if defined(BACKLIGHT_ENABLE) || defined(LED_MATRIX_ENABLE) || defined(RGBLIGHT_ENABLED) || defined(RGB_MATRIX_ENABLE) // Lighting-related features.
536 case QK_LIGHTING ... QK_LIGHTING_MAX:
537 append_numbered_keycode(PSTR("QK_LIGHTING+"), keycode - QK_LIGHTING);
538 return;
539#endif // defined(BACKLIGHT_ENABLE) || defined(LED_MATRIX_ENABLE) || defined(RGBLIGHT_ENABLED) || defined(RGB_MATRIX_ENABLE)
540#ifdef STENO_ENABLE
541 case STENO_KEYCODE_RANGE:
542 append_numbered_keycode(PSTR("QK_STENO+"), keycode - QK_STENO);
543 return;
544#endif // AUDIO_ENABLE
545#ifdef BLUETOOTH_ENABLE
546 case CONNECTION_KEYCODE_RANGE:
547 append_numbered_keycode(PSTR("QK_CONNECTION+"), keycode - QK_CONNECTION);
548 return;
549#endif // BLUETOOTH_ENABLE
550 case QUANTUM_KEYCODE_RANGE:
551 append_numbered_keycode(PSTR("QK_QUANTUM+"), keycode - QK_QUANTUM);
552 return;
553 }
554 // clang-format on
555
556 append_number(keycode, 16); // Fallback: write keycode as hex value.
557}
558
559const char* get_keycode_string(uint16_t keycode) {
560 buffer_len = 0;
561 buffer[0] = '\0';
562 append_keycode(keycode);
563 return buffer;
564}
diff --git a/quantum/keycode_string.h b/quantum/keycode_string.h
new file mode 100644
index 0000000000..1315613a80
--- /dev/null
+++ b/quantum/keycode_string.h
@@ -0,0 +1,134 @@
1// Copyright 2024-2025 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 <stdint.h>
18
19#if KEYCODE_STRING_ENABLE
20
21/**
22 * @brief Formats a QMK keycode as a human-readable string.
23 *
24 * Given a keycode, like `KC_A`, this function returns a formatted string, like
25 * "KC_A". This is useful for debugging and diagnostics so that keys are more
26 * easily identified than they would be by raw numerical codes.
27 *
28 * @note The returned char* string should be used right away. The string memory
29 * is reused and will be overwritten by the next call to `keycode_string()`.
30 *
31 * Many common QMK keycodes are understood by this function, but not all.
32 * Recognized keycodes include:
33 *
34 * - Most basic keycodes, including letters `KC_A` - `KC_Z`, digits `KC_0` -
35 * `KC_9`, function keys `KC_F1` - `KC_F24`, and modifiers like `KC_LSFT`.
36 *
37 * - Modified basic keycodes, like `S(KC_1)` (Shift + 1 = !).
38 *
39 * - `MO`, `TO`, `TG`, `OSL`, `LM(layer,mod)`, `LT(layer,kc)` layer switches.
40 *
41 * - One-shot mod `OSM(mod)` keycodes.
42 *
43 * - Mod-tap `MT(mod, kc)` keycodes.
44 *
45 * - Tap dance keycodes `TD(i)`.
46 *
47 * - Swap hands keycodes `SH_T(kc)`, `SH_TOGG`, etc.
48 *
49 * - Joystick keycodes `JS_n`.
50 *
51 * - Programmable button keycodes `PB_n`.
52 *
53 * - Unicode `UC(codepoint)` and Unicode Map `UM(i)` and `UP(i,j)` keycodes.
54 *
55 * - Keyboard range keycodes `QK_KB_*`.
56 *
57 * - User range (SAFE_RANGE) keycodes `QK_USER_*`.
58 *
59 * Keycodes involving mods like `OSM`, `LM`, `MT` are fully supported only where
60 * a single mod is applied.
61 *
62 * Unrecognized keycodes are printed numerically as hex values like `0x1ABC`.
63 *
64 * Optionally, use `keycode_string_names_user` or `keycode_string_names_kb` to
65 * define names for additional keycodes or override how any of the above are
66 * formatted.
67 *
68 * @param keycode QMK keycode.
69 * @return Stringified keycode.
70 */
71const char* get_keycode_string(uint16_t keycode);
72
73/** Defines a human-readable name for a keycode. */
74typedef struct {
75 uint16_t keycode;
76 const char* name;
77} keycode_string_name_t;
78
79// clang-format off
80/**
81 * @brief Defines names for additional keycodes for `get_keycode_string()`.
82 *
83 * Define `KEYCODE_STRING_NAMES_USER` in your keymap.c to add names for
84 * additional keycodes to `keycode_string()`. This table may also be used to
85 * override how `keycode_string()` formats a keycode. For example, supposing
86 * keymap.c defines `MYMACRO1` and `MYMACRO2` as custom keycodes:
87 *
88 * KEYCODE_STRING_NAMES_USER(
89 * KEYCODE_STRING_NAME(MYMACRO1),
90 * KEYCODE_STRING_NAME(MYMACRO2),
91 * KEYCODE_STRING_NAME(KC_EXLM),
92 * );
93 *
94 * The above defines names for `MYMACRO1` and `MYMACRO2`, and overrides
95 * `KC_EXLM` to format as "KC_EXLM" instead of the default "S(KC_1)".
96 */
97# define KEYCODE_STRING_NAMES_USER(...) \
98 static const keycode_string_name_t keycode_string_names_user[] = {__VA_ARGS__}; \
99 uint16_t keycode_string_names_size_user = \
100 sizeof(keycode_string_names_user) / sizeof(keycode_string_name_t); \
101 const keycode_string_name_t* keycode_string_names_data_user = \
102 keycode_string_names_user
103
104/** Same as above, but defines keycode string names at the keyboard level. */
105# define KEYCODE_STRING_NAMES_KB(...) \
106 static const keycode_string_name_t keycode_string_names_kb[] = {__VA_ARGS__}; \
107 uint16_t keycode_string_names_size_kb = \
108 sizeof(keycode_string_names_kb) / sizeof(keycode_string_name_t); \
109 const keycode_string_name_t* keycode_string_names_data_kb = \
110 keycode_string_names_kb
111
112/** Helper to define a keycode_string_name_t. */
113# define KEYCODE_STRING_NAME(kc) \
114 { (kc), #kc }
115// clang-format on
116
117extern const keycode_string_name_t* keycode_string_names_data_user;
118extern uint16_t keycode_string_names_size_user;
119extern const keycode_string_name_t* keycode_string_names_data_kb;
120extern uint16_t keycode_string_names_size_kb;
121
122#else
123
124// When keycode_string is disabled, fall back to printing keycodes numerically
125// as decimal values, using get_u16_str() from quantum.c.
126# define get_keycode_string(kc) get_u16_str(kc, ' ')
127
128const char* get_u16_str(uint16_t curr_num, char curr_pad);
129
130# define KEYCODE_STRING_NAMES_USER(...)
131# define KEYCODE_STRING_NAMES_KB(...)
132# define KEYCODE_STRING_NAME(kc)
133
134#endif // KEYCODE_STRING_ENABLE
diff --git a/quantum/quantum.h b/quantum/quantum.h
index 59a415ead4..3a994e9a03 100644
--- a/quantum/quantum.h
+++ b/quantum/quantum.h
@@ -39,6 +39,7 @@
39#include "keymap_common.h" 39#include "keymap_common.h"
40#include "quantum_keycodes.h" 40#include "quantum_keycodes.h"
41#include "keycode_config.h" 41#include "keycode_config.h"
42#include "keycode_string.h"
42#include "action_layer.h" 43#include "action_layer.h"
43#include "eeconfig.h" 44#include "eeconfig.h"
44#include "bootloader.h" 45#include "bootloader.h"
diff --git a/tests/keycode_string/config.h b/tests/keycode_string/config.h
new file mode 100644
index 0000000000..7fc76d7c2e
--- /dev/null
+++ b/tests/keycode_string/config.h
@@ -0,0 +1,19 @@
1/* Copyright 2017 Fred Sundvik
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
17#pragma once
18
19#include "test_common.h"
diff --git a/tests/keycode_string/test.mk b/tests/keycode_string/test.mk
new file mode 100644
index 0000000000..aa255a1b6b
--- /dev/null
+++ b/tests/keycode_string/test.mk
@@ -0,0 +1,22 @@
1# Copyright 2025 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
15EXTRAKEY_ENABLE = yes
16KEYCODE_STRING_ENABLE = yes
17KEY_LOCK_ENABLE = yes
18MAGIC_ENABLE = yes
19MOUSEKEY_ENABLE = yes
20PROGRAMMABLE_BUTTON_ENABLE = yes
21SECURE_ENABLE = yes
22SWAP_HANDS_ENABLE = yes
diff --git a/tests/keycode_string/test_keycode_string.cpp b/tests/keycode_string/test_keycode_string.cpp
new file mode 100644
index 0000000000..e1dec70e7a
--- /dev/null
+++ b/tests/keycode_string/test_keycode_string.cpp
@@ -0,0 +1,153 @@
1// Copyright 2025 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 <iostream>
16
17#include "test_common.hpp"
18
19enum {
20 MYMACRO1 = SAFE_RANGE,
21 MYMACRO2,
22};
23
24// clang-format off
25extern "C" {
26
27KEYCODE_STRING_NAMES_KB(
28 KEYCODE_STRING_NAME(MYMACRO1),
29);
30
31KEYCODE_STRING_NAMES_USER(
32 KEYCODE_STRING_NAME(MYMACRO2),
33 KEYCODE_STRING_NAME(KC_EXLM),
34);
35
36const keypos_t PROGMEM hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = {
37 {{9, 0}, {8, 0}, {7, 0}, {6, 0}, {5, 0}, {4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}},
38 {{9, 1}, {8, 1}, {7, 1}, {6, 1}, {5, 1}, {4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}},
39 {{9, 2}, {8, 2}, {7, 2}, {6, 2}, {5, 2}, {4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}},
40 {{9, 3}, {8, 3}, {7, 3}, {6, 3}, {5, 3}, {4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}},
41};
42
43} // extern "C"
44// clang-format on
45
46class KeycodeStringTest : public TestFixture {};
47
48TEST_F(KeycodeStringTest, get_keycode_string) {
49 struct TestParams {
50 uint16_t keycode;
51 std::string expected;
52 };
53 for (const auto [keycode, expected] : std::vector<TestParams>({
54 {KC_TRNS, "KC_TRNS"},
55 {KC_ESC, "KC_ESC"},
56 {KC_A, "KC_A"},
57 {KC_Z, "KC_Z"},
58 {KC_0, "KC_0"},
59 {KC_9, "KC_9"},
60 {KC_KP_0, "KC_KP_0"},
61 {KC_KP_9, "KC_KP_9"},
62 {KC_LBRC, "KC_LBRC"},
63 {KC_NUHS, "KC_NUHS"},
64 {KC_NUBS, "KC_NUBS"},
65 {KC_CAPS, "KC_CAPS"},
66 {DB_TOGG, "DB_TOGG"},
67 {KC_LCTL, "KC_LCTL"},
68 {KC_LSFT, "KC_LSFT"},
69 {KC_RALT, "KC_RALT"},
70 {KC_RGUI, "KC_RGUI"},
71 {KC_UP, "KC_UP"},
72 {KC_HYPR, "KC_HYPR"},
73 {KC_MEH, "KC_MEH"},
74 // F1-F24 keycodes.
75 {KC_F1, "KC_F1"},
76 {KC_F12, "KC_F12"},
77 {KC_F13, "KC_F13"},
78 {KC_F24, "KC_F24"},
79 // Macro keycodes.
80 {MC_0, "MC_0"},
81 {MC_31, "MC_31"},
82 // Keyboard range keycodes.
83 {QK_KB_0, "QK_KB_0"},
84 {QK_KB_31, "QK_KB_31"},
85 // User range keycodes.
86 {QK_USER_2, "QK_USER_2"},
87 {QK_USER_31, "QK_USER_31"},
88 // Modified keycodes.
89 {KC_COLN, "S(KC_SCLN)"},
90 {C(KC_PGUP), "C(KC_PGUP)"},
91 {RALT(KC_BSPC), "RALT(KC_BSPC)"},
92 // One-shot mods.
93 {OSM(MOD_LSFT), "OSM(MOD_LSFT)"},
94 {OSM(MOD_RGUI), "OSM(MOD_RGUI)"},
95 {OSM(MOD_RCTL | MOD_RGUI), "OSM(0x19)"},
96 // Layer switch keycodes.
97 {DF(2), "DF(2)"},
98 {PDF(12), "PDF(12)"},
99 {MO(3), "MO(3)"},
100 {TO(0), "TO(0)"},
101 {TT(1), "TT(1)"},
102 {TG(3), "TG(3)"},
103 {OSL(3), "OSL(3)"},
104 {LM(3, MOD_RALT), "LM(3,MOD_RALT)"},
105 {LT(15, KC_QUOT), "LT(15,KC_QUOT)"},
106 // Tap dance keycodes.
107 {TD(0), "TD(0)"},
108 {TD(31), "TD(31)"},
109 // Mod-tap keycodes.
110 {LSFT_T(KC_ENT), "LSFT_T(KC_ENT)"},
111 {RCTL_T(KC_RGHT), "RCTL_T(KC_RGHT)"},
112 {HYPR_T(KC_GRV), "HYPR_T(KC_GRV)"},
113 {MEH_T(KC_EQL), "MEH_T(KC_EQL)"},
114 {RSA_T(KC_LBRC), "MT(0x16,KC_LBRC)"},
115 // Extrakey keycodes.
116 {KC_WBAK, "KC_WBAK"},
117 {KC_WFWD, "KC_WFWD"},
118 {KC_WREF, "KC_WREF"},
119 {KC_VOLU, "KC_VOLU"},
120 {KC_VOLD, "KC_VOLD"},
121 // Mouse Key keycodes.
122 {MS_LEFT, "MS_LEFT"},
123 {MS_RGHT, "MS_RGHT"},
124 {MS_UP, "MS_UP"},
125 {MS_WHLU, "MS_WHLU"},
126 {MS_WHLD, "MS_WHLD"},
127 {MS_BTN1, "MS_BTN1"},
128 {MS_BTN8, "MS_BTN8"},
129 // Swap Hands keycodes.
130 {SH_MON, "SH_MON"},
131 {SH_TOGG, "SH_TOGG"},
132 {SH_T(KC_PSCR), "SH_T(KC_PSCR)"},
133 // Secure keycodes.
134 {SE_LOCK, "SE_LOCK"},
135 {SE_UNLK, "SE_UNLK"},
136 {SE_TOGG, "SE_TOGG"},
137 {SE_REQ, "SE_REQ"},
138 // Programmable button keycodes.
139 {PB_1, "PB_1"},
140 {PB_32, "PB_32"},
141 // Magic button keycodes.
142 {QK_MAGIC + 7, "QK_MAGIC+7"},
143 // Quantum keycodes.
144 {QK_LOCK, "QK_LOCK"},
145 {QK_QUANTUM + 7, "QK_QUANTUM+7"},
146 // Custom keycode names.
147 {MYMACRO1, "MYMACRO1"},
148 {MYMACRO2, "MYMACRO2"},
149 {KC_EXLM, "KC_EXLM"},
150 })) {
151 EXPECT_EQ(get_keycode_string(keycode), expected) << "where keycode = 0x" << std::hex << keycode;
152 }
153}