QMK Firmware 如何配置 Combos 组合键实现多键同按触发单个动作?
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
QMK Firmware 的 Combos(Combo)功能是一种 chording 方案:在规定的时限内同时按下多个按键时,触发一个预设的单个动作,而不是发出多个独立的键。例如同时按A和B发出ESC,或同时按C和D发出Ctrl+Z。本文介绍在 C 语言 keymap 中启用、定义和调参 Combo 的完整路径,以及结果验证方式。
准备条件
- 一个使用 C 语言 keymap(
rules.mk+keymap.c)的键盘固件目录。 - 能执行
qmk compile的构建环境(即已安装 QMK CLI 工具链)。
第一步:在 rules.mk 中启用功能
Combo 默认不编译进固件,需要在键盘的rules.mk中加入:
COMBO_ENABLE = yes第二步:在 keymap.c 中定义组合键
定义方式是:先为每个组合定义一个以COMBO_END结尾的按键数组,再用combo_t key_combos[]把按键数组和触发后的结果 keycode 对应起来。
const uint16_t PROGMEM test_combo1[] = {KC_A, KC_B, COMBO_END}; const uint16_t PROGMEM test_combo2[] = {KC_C, KC_D, COMBO_END}; combo_t key_combos[] = { COMBO(test_combo1, KC_ESC), COMBO(test_combo2, LCTL(KC_Z)), // 结果也可以是带修饰键的组合 };上面的定义表示:同按 A、B 发出 Escape;同按 C、D 发出 Ctrl+Z。结果可以是普通按键、带修饰键的组合(如LCTL(KC_Z))、MO(_LAYER)之类的 layer 切换键等。
组合较多时,推荐用enum管理,避免key_combos[]里无法定位条目:
enum combos { AB_ESC, JK_TAB, QW_SFT, SD_LAYER }; const uint16_t PROGMEM ab_combo[] = {KC_A, KC_B, COMBO_END}; const uint16_t PROGMEM jk_combo[] = {KC_J, KC_K, COMBO_END}; const uint16_t PROGMEM qw_combo[] = {KC_Q, KC_W, COMBO_END}; const uint16_t PROGMEM sd_combo[] = {KC_S, KC_D, COMBO_END}; combo_t key_combos[] = { [AB_ESC] = COMBO(ab_combo, KC_ESC), [JK_TAB] = COMBO(jk_combo, KC_TAB), [QW_SFT] = COMBO(qw_combo, KC_LSFT), [SD_LAYER] = COMBO(sd_combo, MO(_LAYER)), };支持高级 keycode
Mod-Tap、Layer-Tap、Tap Dance 等高级 keycode 可以直接出现在 combo 定义中,此时必须写完整的 keycode 形式:
const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(1, KC_B), COMBO_END}; const uint16_t PROGMEM test_combo2[] = {TD(TD_ESC_CAPS), KC_F1, COMBO_END};重叠组合
多个组合可以共享按键。文档说明:当存在重叠时,按键全部按下的只激活按键数更多的那个组合。例如:
const uint16_t PROGMEM test_combo1[] = {LSFT_T(KC_A), LT(1, KC_B), COMBO_END}; const uint16_t PROGMEM test_combo2[] = {LSFT_T(KC_A), LT(1, KC_B), KC_C, COMBO_END}; combo_t key_combos[] = { COMBO(test_combo1, KC_ESC) COMBO(test_combo2, KC_TAB) };三个键全部同时按下时,只有三键的test_combo2会激活,而不再同时触发两键的test_combo1。
可选分支:触发复杂动作
COMBO(x, KC_NO)等价于COMBO_ACTION(x),即组合命中后不直接发送 keycode,而是交由process_combo_event函数处理,可以发送字符串或执行一串按键序列:
enum combo_events { EM_EMAIL, BSPC_LSFT_CLEAR, }; const uint16_t PROGMEM email_combo[] = {KC_E, KC_M, COMBO_END}; const uint16_t PROGMEM clear_line_combo[] = {KC_BSPC, KC_LSFT, COMBO_END}; combo_t key_combos[] = { [EM_EMAIL] = COMBO_ACTION(email_combo), [BSPC_LSFT_CLEAR] = COMBO_ACTION(clear_line_combo), }; void process_combo_event(uint16_t combo_index, bool pressed) { switch(combo_index) { case EM_EMAIL: if (pressed) { SEND_STRING("john.doe@example.com"); } break; case BSPC_LSFT_CLEAR: if (pressed) { tap_code16(KC_END); tap_code16(S(KC_HOME)); tap_code16(KC_BSPC); } break; } }按 E+M 会输入示例邮箱地址,Backspace+Left Shift 会清空当前行(End → Shift+Home → Backspace)。注意文档特别指出:由于COMBO_ACTION已可用自定义 keycode 替代,如果你自己实现process_combo_event,就不能再与 gboards 字典管理方式(同样占用该函数)混用。
另一种更简洁的路径:定义自定义 keycode(用SAFE_RANGE起始),在process_record_user中实现功能,然后直接写COMBO(<key_array>, <your_custom_keycode>)。process_record_user的写法见 Macros 文档的 "SEND_STRING()&process_record_user" 一节,enum custom_keycodes必须声明在keymaps[]和process_record_user()之前。
编译与验证
修改完成后执行(<keyboard>、<keymap>替换为你的键盘名与 keymap 名):
qmk compile -kb <keyboard> -km <keymap>编译通过后将固件烧录到键盘,实际验证方式就是文档给出的行为本身:
- 在
COMBO_TERM(默认 50ms)窗口内同时按下 A 和 B,电脑应收到一次 Escape,而不是 A、B 两个字符; - 同按 C 和 D 收到 Ctrl+Z;
- 单独按下参与组合的任意一个键,应仍按原有 keymap 正常发送(Combo 只拦截"同按"情况)。
如果同按后仍发出两个独立按键,说明组合未被识别,按下一节检查时限与按键顺序设置。
高级配置(config.h)
以下配置写入config.h,用于处理误触发、识别窗口等实际问题:
Combo Term(识别窗口)
默认组合识别超时为 50ms。出现误触发,或难以把多个键按到一起时,可以调整:
#define COMBO_TERM 40 // 缩短窗口,减少误触发调大数值则放宽同按要求。
组合键数与缓冲区大小
使用很长的组合或大量重叠组合时,缓冲区可能不够,此时可按需配置。组合最大按键数:
| 按键数 | 配置 |
|---|---|
| 6 | #define EXTRA_SHORT_COMBOS |
| 8 | QMK 默认,无需配置 |
| 16 | #define EXTRA_LONG_COMBOS |
| 32 | #define EXTRA_EXTRA_LONG_COMBOS |
缓冲区大小:
| 配置 | 默认 |
|---|---|
#define COMBO_KEY_BUFFER_LENGTH 8 | 8(即组合最大按键数) |
#define COMBO_BUFFER_LENGTH 4 | 4 |
注意:更大的组合尺寸和缓冲区会增加内存占用。另外EXTRA_SHORT_COMBOS会把组合内部状态压缩为一字节以省内存,定义后组合不能超过 6 键。
修饰键组合、按键顺序
#define COMBO_MUST_HOLD_MODS:当组合结果是修饰键时,单独延长其处理窗口;窗口时长用#define COMBO_HOLD_TERM 150配置(默认为TAPPING_TERM)。启用后该组合不能再"点按"触发,可减少误触发。#define COMBO_MUST_PRESS_IN_ORDER:组合按键必须按定义顺序按下才激活。
逐组合自定义行为
通过以下配置宏 + 对应回调函数,可以为每个组合单独设置识别窗口、必须按住、仅点按、必须按序按下:
| 配置宏 | 回调函数 | 作用(默认值) |
|---|---|---|
COMBO_TERM_PER_COMBO | uint16_t get_combo_term(uint16_t combo_index, combo_t *combo) | 逐组合超时窗口(默认COMBO_TERM) |
COMBO_MUST_HOLD_PER_COMBO | bool get_combo_must_hold(uint16_t combo_index, combo_t *combo) | 命中后立即触发还是必须按住(默认false) |
COMBO_MUST_TAP_PER_COMBO | bool get_combo_must_tap(uint16_t combo_index, combo_t *combo) | 是否仅在COMBO_HOLD_TERM内点按才触发(默认false) |
COMBO_MUST_PRESS_IN_ORDER_PER_COMBO | bool get_combo_must_press_in_order(uint16_t combo_index, combo_t *combo) | 是否必须按序按下(默认true) |
在回调中可按combo_index、combo->keycode或combo->keys[]判断并返回对应值,最后return COMBO_TERM;等兜底。典型用途:组合里含 mod-tap / layer-tap 键时,把该组合设为仅点按(tap-only)——点按发出组合结果,长按则组合不激活、按键按普通键处理。
其他选项
#define COMBO_SHOULD_TRIGGER+bool combo_should_trigger(...):按条件允许或阻止组合激活,例如在某 layer 上禁用某个组合。#define COMBO_STRICT_TIMER:计时器只在第一个键按下时启动(默认行为是每个后续键都重置计时,更宽松但也更易误触发);严格模式下整个和弦必须在COMBO_TERM内完成。#define COMBO_NO_TIMER:完全关闭计时器,组合在第一个键释放时激活;这会同时使 "must hold" 功能失效。#define COMBO_ONLY_FROM_LAYER 0:无论当前处于哪个 layer,组合按键始终从 layer 0 的 keymap 读取,适合 QWERTY/Colemak 等多 base layer 共用同一套组合的场景;更细粒度的按 layer 映射可用combo_ref_from_layer钩子或COMBO_REF_LAYER()宏。#define COMBO_PROCESS_KEY_RELEASE/#define COMBO_PROCESS_KEY_REPRESS:在组合激活后的按键释放、再按下时执行自定义代码(如逐个注销修饰键),返回true可提前释放组合。
运行时开关与状态查询
Combo 功能可以在运行中开关,适合打游戏等场景临时禁用:
| Keycode | 别名 | 说明 |
|---|---|---|
QK_COMBO_ON | CM_ON | 开启 Combo |
QK_COMBO_OFF | CM_OFF | 关闭 Combo |
QK_COMBO_TOGGLE | CM_TOGG | 切换 Combo 开关 |
这些 keycode 可直接放进keymap.c的任意 keymap 中。代码中也可以用用户回调函数:combo_enable()开启,combo_disable()关闭并清空 combo 缓冲区,combo_toggle()切换,is_combo_enabled()查询当前状态(返回 true/false)。
已知限制
- 默认组合长度为 8 键;需要 6/16/32 键时必须显式定义对应宏,否则超长组合不可用。
- 增大
COMBO_KEY_BUFFER_LENGTH、COMBO_BUFFER_LENGTH或组合长度都会增加固件内存占用。 COMBO_ACTION与 gboards 字典管理(VPATH += keyboards/gboards+ 在 keymap.c 包含g/keymap_combo.h)共用同一个process_combo_event,二者不能同时自己实现。COMBO_NO_TIMER与 "must hold" 类功能互斥:关闭计时器后 must hold 不再生效。
完整行为细节与更多示例见 docs/features/combo.md;自定义 keycode 的写法参考 docs/feature_macros.md,Mod-Tap 语法参考 docs/mod_tap.md。
【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考