QMK Key Overrides 完全指南:用自定义组合键改写修饰键行为
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
Key Overrides(按键覆写)是 QMK 固件提供的一项高级功能:当用户按下某个“修饰键 + 普通键”组合时,固件可以在键盘报告中用另一组“修饰键 + 按键”甚至完全自定义的动作来替换它。本指南以docs/features/key_overrides.md为核心,结合quantum/process_keycode/process_key_override.c等源码实现,完整讲解 Key Overrides 的启用方式、四种初始化宏、全部结构体与选项位含义、三层激活/去激活原理、按键重复延迟机制,以及“中性化闪烁修饰键”等实战技巧。读完本文,你将能够为任意键盘编写精确到层、修饰键组合和激活时机的自定义快捷键。
Key Overrides 是什么
Key Overrides 允许你覆写修饰键组合,将其替换为另一组“修饰键 + 按键”,或者执行完全自定义的动作。例如,不想让Shift+1在你的电脑上打出!?可以用一个 Key Override 让键盘在你按下Shift+1时输出别的字符。其通用行为可以概括为:如果按下了修饰键 w+键 x,就在键盘报告中用修饰键 y+键 z替换这些按键(源码见 quantum/process_keycode/process_key_override.c)。
你可以像使用临时层/Fn 键一样用 Key Overrides 激活自定义键码或快捷键,同时享受若干额外好处:
- 完全保留修饰键的原有功能:修饰键按下时本身仍是修饰键,只是组合触发时被改写;
- 省掉 Fn 键:不必为键盘专门分配一个 Fn 层切换键,节省键位空间;
- 可配置“修饰键组合”:多个修饰键同时按下可以触发与单个修饰键不同的动作。
一些入门示例(后续有完整代码):
- 按下
Ctrl+音量加减时发送屏幕亮度加减; - 按下
Shift+退格时发送删除; - 创建自定义快捷键或改写已有快捷键,例如按下
Ctrl+Y时发送Ctrl+Shift+Z; - 按下
Ctrl+Alt+Esc时运行自定义代码。
启用与基础配置
启用该功能需要两步:
在键盘的
rules.mk中添加:KEY_OVERRIDE_ENABLE = yes在
keymap.c中定义全局的key_overrides配置数组(下文详述)。
源码在编译期通过#if defined(KEY_OVERRIDE_ENABLE)决定是否编译该功能,见 quantum/keymap_introspection.c;数组大小由key_override_count_raw()通过ARRAY_SIZE(key_overrides)取得,并有静态断言防止数组异常过大。
创建 Key Overrides:四个初始化宏
key_override_t结构体拥有众多可精细调节的字段,完整参考见下文。官方建议优先使用以下四个专用初始化宏(定义见 quantum/process_keycode/process_key_override.h),而不是手动构造结构体。
ko_make_basic(modifiers, key, replacement)
返回一个key_override_t:当key与modifiers全部按下时,发送replacement(可以是“键 + 修饰键”组合)。注意:此宏生成的 override 在额外按住其他未指定的修饰键时依然会激活;如需限制,请使用带 negative_mods 的变体。内部实现等价于ko_make_with_layers(modifiers, key, replacement, ~0),即默认在所有层上生效。
ko_make_with_layers(modifiers, key, replacement, layers)
在上一宏的基础上,额外接受一个layer_state_t类型的位掩码layers,用于指定该 override 在哪些层上生效(第i层对应第i位,即1 << i)。内部等价于ko_make_with_layers_and_negmods(..., 0),即无负向修饰键限制。
ko_make_with_layers_and_negmods(modifiers, key, replacement, layers, negative_mods)
再增加一个位掩码negative_mods,用于定义哪些修饰键按下时该 override 不得激活。
ko_make_with_layers_negmods_and_options(modifiers, key, replacement, layers, negative_mods, options)
在上一宏基础上再增加options位掩码,用于指定额外行为选项(见下文ko_option_t参考)。该宏是所有便捷宏的最终落地实现,它把suppressed_mods默认设为trigger_mods(即默认抑制触发修饰键),custom_action与context置为NULL,enabled置为NULL(始终启用)。
当上述宏仍不满足需求时,可以直接构造key_override_t结构体,实现更复杂的定制(如“修饰键当作层键”示例)。
简单示例
示例一:Shift + Backspace = Delete
const key_override_t delete_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_BSPC, KC_DEL); // 全局定义所有 key overrides const key_override_t *key_overrides[] = { &delete_key_override };示例二:分号与冒号互换(ANSI 及多数布局)
这个示例反转分号与冒号:单独按键发送Shift+分号(即S(KP_SCLN),别名KC_COLN,输出:);而按下 Shift 时 Shift 被抑制(见下文suppressed_mods),只发送分号(KC_SCLN):
const key_override_t semicolon_colon_key_override = ko_make_basic(MOD_MASK_SHIFT, KC_COLN, KC_SCLN); // 全局定义所有 key overrides const key_override_t *key_overrides[] = { &semicolon_colon_key_override };中级示例
媒体控制与屏幕亮度
下面的例子让一个按键同时承担媒体、音量和亮度控制:键位本身发送播放/暂停,配合不同的修饰键组合改写为其他功能。
| 组合 | 结果 |
|---|---|
Ctrl+播放/暂停 | 下一曲 |
Ctrl+Shift+播放/暂停 | 上一曲 |
Alt+播放/暂停 | 音量加 |
Alt+Shift+播放/暂停 | 音量减 |
Ctrl+Alt+播放/暂停 | 亮度加 |
Ctrl+Alt+Shift+播放/暂停 | 亮度减 |
const key_override_t next_track_override = ko_make_with_layers_negmods_and_options( MOD_MASK_CTRL, // 触发修饰键:ctrl KC_MPLY, // 触发键:播放/暂停 KC_MNXT, // 替换键 ~0, // 在所有层上生效 MOD_MASK_SA, // shift 或 alt 按下时不激活 ko_option_no_reregister_trigger); // 松开 ctrl 后不再重新注册播放键 const key_override_t prev_track_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CS, KC_MPLY, KC_MPRV, ~0, MOD_MASK_ALT, ko_option_no_reregister_trigger); const key_override_t vol_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_ALT, KC_MPLY, KC_VOLU, ~0, MOD_MASK_CS, ko_option_no_reregister_trigger); const key_override_t vol_down_override = ko_make_with_layers_negmods_and_options(MOD_MASK_SA, KC_MPLY, KC_VOLD, ~0, MOD_MASK_CTRL, ko_option_no_reregister_trigger); const key_override_t brightness_up_override = ko_make_with_layers_negmods_and_options(MOD_MASK_CA, KC_MPLY, KC_BRIU, ~0, MOD_MASK_SHIFT, ko_option_no_reregister_trigger); const key_override_t brightness_down_override = ko_make_basic(MOD_MASK_CSA, KC_MPLY, KC_BRID); // 全局定义所有 key overrides const key_override_t *key_overrides[] = { &next_track_override, &prev_track_override, &vol_up_override, &vol_down_override, &brightness_up_override, &brightness_down_override };注意每个组合都通过negative_mod_mask排除了相邻组合(例如“下一曲”排除了 Shift 和 Alt,避免被“上一曲”“音量加”等误触发),并大量使用ko_option_no_reregister_trigger,保证松开修饰键后播放/暂停不会再次输出。
灵活的 macOS 友好型 Grave Escape
Grave Escape 功能 可配置性有限,且 在 macOS 上存在已知缺陷。用 Key Overrides 可以实现类似功能,且无 macOS 兼容问题:
// Shift + esc = ~ const key_override_t tilde_esc_override = ko_make_basic(MOD_MASK_SHIFT, KC_ESC, S(KC_GRV)); // GUI + esc = ` const key_override_t grave_esc_override = ko_make_basic(MOD_MASK_GUI, KC_ESC, KC_GRV); const key_override_t *key_overrides[] = { &tilde_esc_override, &grave_esc_override };除避开 macOS 的意外缺陷外,行为也可任意调整:比如把GUI+ESC=`改为Ctrl+ESC=`,只需替换触发修饰键即可。
高级示例:把修饰键当作层键
是否真的需要一个专用键来切换 fn 层?借助 Key Overrides 也许不需要。下面的例子用rGUI+rAlt(右 GUI + 右 Alt)临时进入一个 fn 层,从而完全省去专用层键。修饰键的选择可按需更换,rGUI+rAlt仅作示例。
// 当 override 激活/去激活时被调用:激活时打开 fn 层,去激活时关闭 bool momentary_layer(bool key_down, void *layer) { if (key_down) { layer_on((uint8_t)(uintptr_t)layer); } else { layer_off((uint8_t)(uintptr_t)layer); } return false; } const key_override_t fn_override = {.trigger_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RALT), // .layers = ~(1 << LAYER_FN), // .suppressed_mods = MOD_BIT(KC_RGUI) | MOD_BIT(KC_RALT), // .options = ko_option_no_unregister_on_other_key_down, // .negative_mod_mask = (uint8_t) ~(MOD_BIT(KC_RGUI) | MOD_BIT(KC_RALT)), // .custom_action = momentary_layer, // .context = (void *)LAYER_FN, // .trigger = KC_NO, // .replacement = KC_NO, // .enabled = NULL};这里custom_action返回false,表示不再注册/注销替换键——因为本示例根本不需要发送任何按键,只需切换层;trigger与replacement均为KC_NO,即“无触发键、无替换键”,仅靠修饰键组合驱动。
控制 Key Overrides 的键码
| 键码 | 别名 | 说明 |
|---|---|---|
QK_KEY_OVERRIDE_TOGGLE | KO_TOGG | 切换 Key Overrides 开/关 |
QK_KEY_OVERRIDE_ON | KO_ON | 开启 Key Overrides |
QK_KEY_OVERRIDE_OFF | KO_OFF | 关闭 Key Overrides |
这些键码定义于 quantum/keycodes.h(别名见 quantum/keycodes.h),在 process_key_override.c 的process_key_override()中处理,分别调用key_override_toggle()、key_override_on()、key_override_off()。开关状态保存在静态变量enabled中(默认开启,当前未持久化到 EEPROM,见 process_key_override.c)。
key_override_t结构体完整参考
高级用户需要比ko_make宏更细的控制时,可直接构造key_override_t并设置全部成员。各成员含义如下(源码定义见 process_key_override.h):
| 成员 | 说明 |
|---|---|
uint16_t trigger | 触发 override 的非修饰键键码。该键码与必需修饰键(trigger_mods)必须被按下才会激活。设为KC_NO表示只需按下必需的修饰键、无需非修饰键即可激活。 |
uint8_t trigger_mods | 激活所需按下的修饰键。若同时设置了左右两侧(如左 Ctrl 与右 Ctrl),则只需其一被按下(例如左 Ctrl 即可)。请使用MOD_MASK_XXX与MOD_BIT()宏。 |
layer_state_t layers | 位掩码(!),定义该 override 适用的层。要在第i层使用,则设置第i位(1 << i)。 |
uint8_t negative_mod_mask | 不得按下的修饰键。必须满足(active_modifiers & negative_mod_mask) == 0,否则 override 不会激活;一旦已激活的 override 不再满足该条件,会被立刻去激活。 |
uint8_t suppressed_mods | 激活期间要“抑制”的修饰键。抑制意味着即使该修饰键被按住,主机系统也认为它未被按下。一个简单例子就是抑制触发修饰键本身。 |
uint16_t replacement | 触发时发送的复合键码。可以是简单键码、键 + 修饰键组合(如C(KC_A)),或KC_NO(不注册任何替换键码)。可配合suppressed_mods得到正确的修饰键输出。 |
ko_option_t options | 控制 override 行为的选项位(见下节)。 |
bool (*custom_action)(bool activated, void *context) | 若非 NULL,在替换键注册之前调用,传入提供的 context 与一个表示 override 被激活还是去激活的布尔值。用于为特定 override 执行自定义动作。返回false时不按常规注册/注销替换键;返回true则正常注册与注销。 |
void *context | 传给 custom_action 函数的上下文。 |
bool *enabled | 若指向false则该 override 不生效。设为NULL表示始终启用。 |
ko_option_t选项位完整参考
ko_option_t是一个位域枚举,定义见 process_key_override.h:
| 值 | 说明 |
|---|---|
ko_option_activation_trigger_down | 允许在触发键按下时激活。 |
ko_option_activation_required_mod_down | 允许在必需修饰键按下时激活。 |
ko_option_activation_negative_mod_up | 允许在负向修饰键释放时激活。 |
ko_option_one_mod | 若设置,trigger_mods中任意一个修饰键按下即可激活(逻辑 OR);若未设置,trigger_mods中所有修饰键都必须按下(逻辑 AND)。 |
ko_option_no_unregister_on_other_key_down | 若设置,另一个键按下时 override 不会被去激活。仅在你确实需要时才使用。 |
ko_option_no_reregister_trigger | 若设置,override 去激活后触发键将永远不会被重新注册。 |
ko_options_default | ko_make_xxx系列函数使用的默认选项,即ko_options_all_activations(允许上述三种激活事件中的全部三种)。 |
源码中 check_activation_event() 依据这些激活相关选项判断当前按键事件是否允许触发激活:修饰键按下对应ko_option_activation_required_mod_down,修饰键释放对应ko_option_activation_negative_mod_up,非修饰键按下对应ko_option_activation_trigger_down。若 options 中三种激活位全为 0,实现会回退为默认的全部激活方式。
进阶原理:Key Overrides 的内部工作机制
透彻理解各成员在何时发挥作用,是充分驾驭全部选项的前提。本部分结合 process_key_override.c 的实现展开。
激活
当必需的按键(trigger_mods+trigger)被按下时,override 被“激活”,替换键replacement被注册进键盘报告,同时trigger键从报告中移除;suppressed_mods指定的触发修饰键也可在激活时从报告中移除。若任一negative_modifiers被按下,override 不会激活。
override 可以在三种情况下激活:
- 触发键被按下,且必需修饰键已经在按下状态;
- 某个必需修饰键被按下,而触发键与其他必需修饰键已经在按下状态;
- 某个负向修饰键被释放,而所有必需修饰键与触发键都已在按下状态。
用options成员可定制上述哪些事件允许激活(默认三种都允许)。
无论哪种情况,override 只在trigger键是“最后按下的非修饰键”时才会激活。这是为了模拟主流操作系统(macOS、Windows、Linux)处理普通按键输入的行为——例如:按住a,再按住b,然后按住shift,会打出B而不会打出A。对应实现中,process_key_override()用last_key_down记录最后按下的非修饰键,try_activating_override() 中last_key_down == override->trigger是激活的必要条件之一。
去激活
当以下任一情况发生时,override 被“去激活”:
- 触发键(
trigger_mods或trigger)被松开; - 另一个非修饰键被按下;
- 某个
negative_modifiers被按下。
去激活时,replacement键从键盘报告中移除,仍按住的suppressed_mods被重新加入报告。默认情况下,若触发键仍被按住且自那以后没有按下其他非修饰键,trigger键会被重新加入报告。这同样模拟了操作系统行为——例如:按住a,再按住b,再按住shift,然后松开b,即使仍按住a和shift也不会打出A。可用ko_option_no_reregister_trigger选项在一切情况下阻止重新注册触发键。
去激活逻辑在 clear_active_override() 中实现:先清除抑制修饰键,再注销弱覆写修饰键,按需调用custom_action(false, ...),最后按reregister_trigger的判定条件(允许重注册、未设置no_reregister_trigger、触发键仍按下、非KC_NO、小于SAFE_RANGE)决定是否把触发键排入延迟注册。
按键重复延迟(Key Repeat Delay)
Key Overrides 模拟标准 OS 处理修饰键输入的第三种方式就是“按键重复延迟”。再次回顾普通输入:若按住a后再按shift,会先打出a,然后短暂停顿,再开始重复输出A。虽然shift在a之后很快按下,但A要过一会儿才出现——这正是按键重复延迟,用于防止误输出重复字符。
释放修饰键同理:按住shift再按a会打出A;若先松开shift、稍后松开a,你不会看到a被输出,即便短暂时间里你确实只按着a——因为未经过按键重复延迟就不会输出被修饰的字符。
Key Overrides 完整实现了这一行为:若存在Shift+a=b的 override,按住a后再按shift,b不会立即输出,而是从触发键a按下的时刻起,等待按键重复延迟结束后才延迟输出。
延迟时长由KEY_OVERRIDE_REPEAT_DELAY宏控制,在config.h中定义即可修改,默认 500ms。源码中该宏默认值见 process_key_override.c;延迟逻辑在 schedule_deferred_register() 中实现:若自触发键按下未满KEY_OVERRIDE_REPEAT_DELAY,则等待至满;否则仅延迟 50ms(防止修饰键事件紧邻非修饰键事件时误激活),实际注册由周期调用的 key_override_task() 完成。
与 Combos 的区别
注意 Key Overrides 与 Combos(组合键) 有本质区别:
- Combos要求几乎同时按下多个键,且可用于任意非修饰键组合;
- Key Overrides像键盘快捷键(如
Ctrl+Z):由多个修饰键 + 一个非修饰键组成,然后执行自定义动作。
Key Overrides 在按键顺序、时机与其它按键的交互上被精心实现,行为与普通键盘快捷键一致,并且还有一系列可选设置用于微调每个 override。另外,使用 Key Overrides不会像 Combos 那样延迟普通按键的输入(Combos 天然存在这种延迟,可能不受欢迎)。
解决“闪烁修饰键”(Flashing Modifiers)问题
如果你使用的程序把“点按修饰键”绑定为动作(例如点按左 GUI 打开应用程序菜单、点按左 Alt 聚焦菜单栏),那么使用带suppressed_mods的 Key Overrides 时可能会误触发这些动作。对策是在config.h中定义DUMMY_MOD_NEUTRALIZER_KEYCODE:固件会在被抑制修饰键的注册与注销事件之间发送该“哑键码”,从而让宿主程序不再把 Key Overrides 造成的修饰抑制误判为一次单独的修饰键点按。
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_RIGHT_CTRL使用技巧:
- 必须选择一个未绑定任何键盘快捷键的键码,推荐
KC_RIGHT_CTRL或KC_F18; DUMMY_MOD_NEUTRALIZER_KEYCODE必须是基础的、无修饰的 HID 键码,因此KC_NO、KC_TRANSPARENT、KC_PIPE(即S(KC_BACKSLASH))这类值都不允许。编译期在 quantum/action_util.h 中通过静态断言强制校验(要求处于KC_A至QK_BASIC_MAX之间);- 默认仅对左 Alt 与左 GUI 生效。若要修改适用的修饰键掩码列表,在
config.h中定义:
#define MODS_TO_NEUTRALIZE { <mod_mask_1>, <mod_mask_2>, ... }示例:
#define DUMMY_MOD_NEUTRALIZER_KEYCODE KC_RIGHT_CTRL // 中和左 alt 和左 GUI(默认值) #define MODS_TO_NEUTRALIZE { MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI) } // 中和左 alt、左 GUI、右 GUI 和 左 Control+Shift #define MODS_TO_NEUTRALIZE { MOD_BIT(KC_LEFT_ALT), MOD_BIT(KC_LEFT_GUI), MOD_BIT(KC_RIGHT_GUI), MOD_BIT(KC_LEFT_CTRL)|MOD_BIT(KC_LEFT_SHIFT) }::: warning 不要使用MOD_xxx常量(如MOD_LSFT、MOD_RALT),因为它们是 5 位打包位数组,而MODS_TO_NEUTRALIZE期望 8 位打包位数组。请使用MOD_BIT(<kc>)或MOD_MASK_xxx。 :::
该机制的核心实现在 quantum/action_util.c 的neutralize_flashing_modifiers()中:将当前激活修饰键与MODS_TO_NEUTRALIZE列表逐一比对,命中则tap_code(DUMMY_MOD_NEUTRALIZER_KEYCODE)。除了 Key Overrides(process_key_override.c 在激活前调用),它同样被 Retro Tapping 等需要先注销再注册修饰键的功能复用(见 quantum/action.c 与 quantum/action_tapping.c)。
源码与测试佐证
- 全部实现位于 quantum/process_keycode/process_key_override.c 与 quantum/process_keycode/process_key_override.h;
- 全局数组
key_overrides的遍历与大小通过弱函数key_override_count()/key_override_get()由 quantum/keymap_introspection.c 提供,便于其他模块或测试注入自定义实现; - 测试工程 tests/tap_hold_configurations/speculative_hold/default/test.mk 开启了
KEY_OVERRIDE_ENABLE = yes,其 test_keymap.c 中定义了ko_make_basic(MOD_MASK_SHIFT, KC_ESC, KC_HOME)的 Home/Esc override,并在 test_tap_hold.cpp 中以key_overrides测试用例验证了该功能与 Tap-Hold 配置的协同行为。
小结
Key Overrides 为 QMK 键盘提供了接近操作系统快捷键级别的按键改写能力:四个ko_make_*宏覆盖绝大多数场景,key_override_t与ko_option_t的完整字段/选项则支撑从“修饰键组合触发”“负向修饰排除”“修饰键抑制”到“自定义动作回调”“按层/按开关启用”的全部精细控制;激活、去激活与按键重复延迟三重机制确保其行为与主流操作系统对快捷键的处理高度一致。配合DUMMY_MOD_NEUTRALIZER_KEYCODE可规避宿主程序的修饰键点按误触发,使该功能在真实桌面环境中稳定可用。
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考