[Core] get_keycode_string(): function to format keycodes as strings, for more readable debug logging. (#24787)
Update API Data / api_data (push) Waiting to run
CI Build Major Branch / Determine concurrency (push) Waiting to run
CI Build Major Branch / Compile keymap default (push) Blocked by required conditions
CI Build Major Branch / Consolidation (push) Blocked by required conditions
CLI CI / test (push) Waiting to run
Update feature branches after develop merge / feature_branch_update (riot) (push) Waiting to run
Update feature branches after develop merge / feature_branch_update (xap) (push) Waiting to run
Lint Format / lint (push) Waiting to run
Regenerate Files / regen (push) Waiting to run
Unit Tests / test (push) Waiting to run

* 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 2a2771068c.

* Revert "Attempt at simplifying interface."

This reverts commit 8eaf67de76.

* 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>
This commit is contained in:
Pascal Getreuer
2025-03-19 20:45:56 +01:00
committed by GitHub
co-authored by Ryan
parent 40a446a0ec
commit 3484f0a0df
9 changed files with 933 additions and 0 deletions
+1
View File
@@ -34,6 +34,7 @@ GENERIC_FEATURES = \
DYNAMIC_TAPPING_TERM \
GRAVE_ESC \
HAPTIC \
KEYCODE_STRING \
KEY_LOCK \
KEY_OVERRIDE \
LAYER_LOCK \
+11
View File
@@ -77,6 +77,17 @@ KL: kc: 172, col: 2, row: 0, pressed: 1, time: 16303, int: 0, count: 0
KL: kc: 172, col: 2, row: 0, pressed: 0, time: 16411, int: 0, count: 0
```
### Which keycode is this keypress?
Keycodes 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:
```c
uprintf("kc: %s\n", get_keycode_string(keycode));
```
This 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.
### How long did it take to scan for a keypress?
When 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`
+28
View File
@@ -58,6 +58,34 @@ It's not yet possible to do a full integration test, where you would compile the
In that model you would emulate the input, and expect a certain output from the emulated keyboard.
# Keycode String {#keycode-string}
It'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.
```c
const char *key_name = get_keycode_string(keycode);
dprintf("kc: %s\n", key_name);
```
The stringified keycode may then be logged to console output with `dprintf()` or elsewhere.
::: warning
Use the result of `get_keycode_string()` immediately. Subsequent invocations reuse the same static buffer and overwrite the previous contents.
:::
Many 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.
Optionally, `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:
```c
KEYCODE_STRING_NAMES_USER(
KEYCODE_STRING_NAME(MYMACRO1),
KEYCODE_STRING_NAME(MYMACRO2),
);
```
Similarly, `KEYCODE_STRING_NAMES_KB` may be defined to add names at the keyboard level.
# Tracing Variables {#tracing-variables}
Sometimes 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.
File diff suppressed because it is too large Load Diff
+134
View File
@@ -0,0 +1,134 @@
// Copyright 2024-2025 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 <stdint.h>
#if KEYCODE_STRING_ENABLE
/**
* @brief Formats a QMK keycode as a human-readable string.
*
* Given a keycode, like `KC_A`, this function returns a formatted string, like
* "KC_A". This is useful for debugging and diagnostics so that keys are more
* easily identified than they would be by raw numerical codes.
*
* @note The returned char* string should be used right away. The string memory
* is reused and will be overwritten by the next call to `keycode_string()`.
*
* Many common QMK keycodes are understood by this function, but not all.
* Recognized keycodes include:
*
* - Most basic keycodes, including letters `KC_A` - `KC_Z`, digits `KC_0` -
* `KC_9`, function keys `KC_F1` - `KC_F24`, and modifiers like `KC_LSFT`.
*
* - Modified basic keycodes, like `S(KC_1)` (Shift + 1 = !).
*
* - `MO`, `TO`, `TG`, `OSL`, `LM(layer,mod)`, `LT(layer,kc)` layer switches.
*
* - One-shot mod `OSM(mod)` keycodes.
*
* - Mod-tap `MT(mod, kc)` keycodes.
*
* - Tap dance keycodes `TD(i)`.
*
* - Swap hands keycodes `SH_T(kc)`, `SH_TOGG`, etc.
*
* - Joystick keycodes `JS_n`.
*
* - Programmable button keycodes `PB_n`.
*
* - Unicode `UC(codepoint)` and Unicode Map `UM(i)` and `UP(i,j)` keycodes.
*
* - Keyboard range keycodes `QK_KB_*`.
*
* - User range (SAFE_RANGE) keycodes `QK_USER_*`.
*
* Keycodes involving mods like `OSM`, `LM`, `MT` are fully supported only where
* a single mod is applied.
*
* Unrecognized keycodes are printed numerically as hex values like `0x1ABC`.
*
* Optionally, use `keycode_string_names_user` or `keycode_string_names_kb` to
* define names for additional keycodes or override how any of the above are
* formatted.
*
* @param keycode QMK keycode.
* @return Stringified keycode.
*/
const char* get_keycode_string(uint16_t keycode);
/** Defines a human-readable name for a keycode. */
typedef struct {
uint16_t keycode;
const char* name;
} keycode_string_name_t;
// clang-format off
/**
* @brief Defines names for additional keycodes for `get_keycode_string()`.
*
* Define `KEYCODE_STRING_NAMES_USER` in your keymap.c to add names for
* additional keycodes to `keycode_string()`. This table may also be used to
* override how `keycode_string()` formats a keycode. For example, supposing
* keymap.c defines `MYMACRO1` and `MYMACRO2` as custom keycodes:
*
* KEYCODE_STRING_NAMES_USER(
* KEYCODE_STRING_NAME(MYMACRO1),
* KEYCODE_STRING_NAME(MYMACRO2),
* KEYCODE_STRING_NAME(KC_EXLM),
* );
*
* The above defines names for `MYMACRO1` and `MYMACRO2`, and overrides
* `KC_EXLM` to format as "KC_EXLM" instead of the default "S(KC_1)".
*/
# define KEYCODE_STRING_NAMES_USER(...) \
static const keycode_string_name_t keycode_string_names_user[] = {__VA_ARGS__}; \
uint16_t keycode_string_names_size_user = \
sizeof(keycode_string_names_user) / sizeof(keycode_string_name_t); \
const keycode_string_name_t* keycode_string_names_data_user = \
keycode_string_names_user
/** Same as above, but defines keycode string names at the keyboard level. */
# define KEYCODE_STRING_NAMES_KB(...) \
static const keycode_string_name_t keycode_string_names_kb[] = {__VA_ARGS__}; \
uint16_t keycode_string_names_size_kb = \
sizeof(keycode_string_names_kb) / sizeof(keycode_string_name_t); \
const keycode_string_name_t* keycode_string_names_data_kb = \
keycode_string_names_kb
/** Helper to define a keycode_string_name_t. */
# define KEYCODE_STRING_NAME(kc) \
{ (kc), #kc }
// clang-format on
extern const keycode_string_name_t* keycode_string_names_data_user;
extern uint16_t keycode_string_names_size_user;
extern const keycode_string_name_t* keycode_string_names_data_kb;
extern uint16_t keycode_string_names_size_kb;
#else
// When keycode_string is disabled, fall back to printing keycodes numerically
// as decimal values, using get_u16_str() from quantum.c.
# define get_keycode_string(kc) get_u16_str(kc, ' ')
const char* get_u16_str(uint16_t curr_num, char curr_pad);
# define KEYCODE_STRING_NAMES_USER(...)
# define KEYCODE_STRING_NAMES_KB(...)
# define KEYCODE_STRING_NAME(kc)
#endif // KEYCODE_STRING_ENABLE
+1
View File
@@ -39,6 +39,7 @@
#include "keymap_common.h"
#include "quantum_keycodes.h"
#include "keycode_config.h"
#include "keycode_string.h"
#include "action_layer.h"
#include "eeconfig.h"
#include "bootloader.h"
+19
View File
@@ -0,0 +1,19 @@
/* Copyright 2017 Fred Sundvik
*
* 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/>.
*/
#pragma once
#include "test_common.h"
+22
View File
@@ -0,0 +1,22 @@
# Copyright 2025 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.
EXTRAKEY_ENABLE = yes
KEYCODE_STRING_ENABLE = yes
KEY_LOCK_ENABLE = yes
MAGIC_ENABLE = yes
MOUSEKEY_ENABLE = yes
PROGRAMMABLE_BUTTON_ENABLE = yes
SECURE_ENABLE = yes
SWAP_HANDS_ENABLE = yes
@@ -0,0 +1,153 @@
// Copyright 2025 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 <iostream>
#include "test_common.hpp"
enum {
MYMACRO1 = SAFE_RANGE,
MYMACRO2,
};
// clang-format off
extern "C" {
KEYCODE_STRING_NAMES_KB(
KEYCODE_STRING_NAME(MYMACRO1),
);
KEYCODE_STRING_NAMES_USER(
KEYCODE_STRING_NAME(MYMACRO2),
KEYCODE_STRING_NAME(KC_EXLM),
);
const keypos_t PROGMEM hand_swap_config[MATRIX_ROWS][MATRIX_COLS] = {
{{9, 0}, {8, 0}, {7, 0}, {6, 0}, {5, 0}, {4, 0}, {3, 0}, {2, 0}, {1, 0}, {0, 0}},
{{9, 1}, {8, 1}, {7, 1}, {6, 1}, {5, 1}, {4, 1}, {3, 1}, {2, 1}, {1, 1}, {0, 1}},
{{9, 2}, {8, 2}, {7, 2}, {6, 2}, {5, 2}, {4, 2}, {3, 2}, {2, 2}, {1, 2}, {0, 2}},
{{9, 3}, {8, 3}, {7, 3}, {6, 3}, {5, 3}, {4, 3}, {3, 3}, {2, 3}, {1, 3}, {0, 3}},
};
} // extern "C"
// clang-format on
class KeycodeStringTest : public TestFixture {};
TEST_F(KeycodeStringTest, get_keycode_string) {
struct TestParams {
uint16_t keycode;
std::string expected;
};
for (const auto [keycode, expected] : std::vector<TestParams>({
{KC_TRNS, "KC_TRNS"},
{KC_ESC, "KC_ESC"},
{KC_A, "KC_A"},
{KC_Z, "KC_Z"},
{KC_0, "KC_0"},
{KC_9, "KC_9"},
{KC_KP_0, "KC_KP_0"},
{KC_KP_9, "KC_KP_9"},
{KC_LBRC, "KC_LBRC"},
{KC_NUHS, "KC_NUHS"},
{KC_NUBS, "KC_NUBS"},
{KC_CAPS, "KC_CAPS"},
{DB_TOGG, "DB_TOGG"},
{KC_LCTL, "KC_LCTL"},
{KC_LSFT, "KC_LSFT"},
{KC_RALT, "KC_RALT"},
{KC_RGUI, "KC_RGUI"},
{KC_UP, "KC_UP"},
{KC_HYPR, "KC_HYPR"},
{KC_MEH, "KC_MEH"},
// F1-F24 keycodes.
{KC_F1, "KC_F1"},
{KC_F12, "KC_F12"},
{KC_F13, "KC_F13"},
{KC_F24, "KC_F24"},
// Macro keycodes.
{MC_0, "MC_0"},
{MC_31, "MC_31"},
// Keyboard range keycodes.
{QK_KB_0, "QK_KB_0"},
{QK_KB_31, "QK_KB_31"},
// User range keycodes.
{QK_USER_2, "QK_USER_2"},
{QK_USER_31, "QK_USER_31"},
// Modified keycodes.
{KC_COLN, "S(KC_SCLN)"},
{C(KC_PGUP), "C(KC_PGUP)"},
{RALT(KC_BSPC), "RALT(KC_BSPC)"},
// One-shot mods.
{OSM(MOD_LSFT), "OSM(MOD_LSFT)"},
{OSM(MOD_RGUI), "OSM(MOD_RGUI)"},
{OSM(MOD_RCTL | MOD_RGUI), "OSM(0x19)"},
// Layer switch keycodes.
{DF(2), "DF(2)"},
{PDF(12), "PDF(12)"},
{MO(3), "MO(3)"},
{TO(0), "TO(0)"},
{TT(1), "TT(1)"},
{TG(3), "TG(3)"},
{OSL(3), "OSL(3)"},
{LM(3, MOD_RALT), "LM(3,MOD_RALT)"},
{LT(15, KC_QUOT), "LT(15,KC_QUOT)"},
// Tap dance keycodes.
{TD(0), "TD(0)"},
{TD(31), "TD(31)"},
// Mod-tap keycodes.
{LSFT_T(KC_ENT), "LSFT_T(KC_ENT)"},
{RCTL_T(KC_RGHT), "RCTL_T(KC_RGHT)"},
{HYPR_T(KC_GRV), "HYPR_T(KC_GRV)"},
{MEH_T(KC_EQL), "MEH_T(KC_EQL)"},
{RSA_T(KC_LBRC), "MT(0x16,KC_LBRC)"},
// Extrakey keycodes.
{KC_WBAK, "KC_WBAK"},
{KC_WFWD, "KC_WFWD"},
{KC_WREF, "KC_WREF"},
{KC_VOLU, "KC_VOLU"},
{KC_VOLD, "KC_VOLD"},
// Mouse Key keycodes.
{MS_LEFT, "MS_LEFT"},
{MS_RGHT, "MS_RGHT"},
{MS_UP, "MS_UP"},
{MS_WHLU, "MS_WHLU"},
{MS_WHLD, "MS_WHLD"},
{MS_BTN1, "MS_BTN1"},
{MS_BTN8, "MS_BTN8"},
// Swap Hands keycodes.
{SH_MON, "SH_MON"},
{SH_TOGG, "SH_TOGG"},
{SH_T(KC_PSCR), "SH_T(KC_PSCR)"},
// Secure keycodes.
{SE_LOCK, "SE_LOCK"},
{SE_UNLK, "SE_UNLK"},
{SE_TOGG, "SE_TOGG"},
{SE_REQ, "SE_REQ"},
// Programmable button keycodes.
{PB_1, "PB_1"},
{PB_32, "PB_32"},
// Magic button keycodes.
{QK_MAGIC + 7, "QK_MAGIC+7"},
// Quantum keycodes.
{QK_LOCK, "QK_LOCK"},
{QK_QUANTUM + 7, "QK_QUANTUM+7"},
// Custom keycode names.
{MYMACRO1, "MYMACRO1"},
{MYMACRO2, "MYMACRO2"},
{KC_EXLM, "KC_EXLM"},
})) {
EXPECT_EQ(get_keycode_string(keycode), expected) << "where keycode = 0x" << std::hex << keycode;
}
}