QMK 固件 US ANSI Shifted 符号键码:KC_TILDE 等 Shift 符号快捷键的实现原理与使用限制
2026/9/14 6:01:19 网站建设 项目流程

QMK 固件 US ANSI Shifted 符号键码:KC_TILDE 等 Shift 符号快捷键的实现原理与使用限制

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

QMK 固件为 US ANSI 布局提供了一组 "Shifted 符号" 键码(如KC_TILDEKC_EXCLAIMKC_AT),它们本身不是独立的 HID 按键,而是LSFT(kc)的语法糖——按下时自动同时发送左 Shift 与对应的基础键码,让键帽上的符号可以直接写入键位定义。本文基于仓库中的 US ANSI Shifted 符号键码文档 与 keymap_us.h 源码,完整梳理这 22 个键码及其别名、宏展开机制、底层注册流程,以及 Mod-Tap/Layer-Tap 中不可用、Windows 远程桌面丢字符这两个重要限制的原因与解决办法。

一、定位:这些键码"不是真正的键码"

文档首先给出的关键结论是:这些键码对应的是标准 US ANSI 键盘上需要 "shift" 才能打出的字符。它们没有自己的 HID 键码(US ANSI 布局中~!@#$%^&*()_+{}|:"<>?在底层就是基础键加 Shift),QMK 只是为它们提供了更直观的宏名,作为LSFT(kc)的快捷方式。

因此使用KC_EXCLAIM时,固件实际发送的是"左 Shift +1"这一组合,而不是某个独立的 "!" 键码。这个语义差异正是后文所有限制(Mod-Tap 中失效、远程桌面丢字)的根源。

在源码层面,这组键码统一定义在 keymap_us.h 中,全部展开为S(...)宏:

// quantum/keymap_extras/keymap_us.h(节选) #define KC_TILD S(KC_GRAVE) // ~ #define KC_EXLM S(KC_1) // ! #define KC_AT S(KC_2) // @ #define KC_HASH S(KC_3) // # #define KC_DLR S(KC_4) // $ #define KC_PERC S(KC_5) // % #define KC_CIRC S(KC_6) // ^ #define KC_AMPR S(KC_7) // & #define KC_ASTR S(KC_8) // * #define KC_LPRN S(KC_9) // ( #define KC_RPRN S(KC_0) // ) #define KC_UNDS S(KC_MINUS) // _ #define KC_PLUS S(KC_EQUAL) // + #define KC_LCBR S(KC_LEFT_BRACKET) // { #define KC_RCBR S(KC_RIGHT_BRACKET) // } #define KC_PIPE S(KC_BACKSLASH) // | #define KC_COLN S(KC_SEMICOLON) // : #define KC_DQUO S(KC_QUOTE) // " #define KC_LABK S(KC_COMMA) // < #define KC_RABK S(KC_DOT) // > #define KC_QUES S(KC_SLASH) // ?

二、完整键码与别名对照表

以下是 原文档 提供的完整键码表。QMK 同时提供两套命名风格:短别名(历史遗留,如KC_TILDKC_EXLM)和全拼名(如KC_TILDEKC_EXCLAIM)。两者在 keymap_us.h 中通过#define相互绑定,例如#define KC_TILDE KC_TILD,因此在 keymap 中混用完全等价。

键码(全拼名)别名对应符号等价于
KC_TILDEKC_TILD~S(KC_GRAVE)
KC_EXCLAIMKC_EXLM!S(KC_1)
KC_AT@S(KC_2)
KC_HASH#S(KC_3)
KC_DOLLARKC_DLR$S(KC_4)
KC_PERCENTKC_PERC%S(KC_5)
KC_CIRCUMFLEXKC_CIRC^S(KC_6)
KC_AMPERSANDKC_AMPR&S(KC_7)
KC_ASTERISKKC_ASTR*S(KC_8)
KC_LEFT_PARENKC_LPRN(S(KC_9)
KC_RIGHT_PARENKC_RPRN)S(KC_0)
KC_UNDERSCOREKC_UNDS_S(KC_MINUS)
KC_PLUS+S(KC_EQUAL)
KC_LEFT_CURLY_BRACEKC_LCBR{S(KC_LEFT_BRACKET)
KC_RIGHT_CURLY_BRACEKC_RCBR}S(KC_RIGHT_BRACKET)
KC_PIPE\|S(KC_BACKSLASH)
KC_COLONKC_COLN:S(KC_SEMICOLON)
KC_DOUBLE_QUOTEKC_DQUOKC_DQT"S(KC_QUOTE)
KC_LEFT_ANGLE_BRACKETKC_LABKKC_LT<S(KC_COMMA)
KC_RIGHT_ANGLE_BRACKETKC_RABKKC_GT>S(KC_DOT)
KC_QUESTIONKC_QUES?S(KC_SLASH)

其中KC_DOUBLE_QUOTEKC_LEFT_ANGLE_BRACKETKC_RIGHT_ANGLE_BRACKET各有两个别名,例如KC_DQTKC_DQUO都指向同一个宏(见 keymap_us.h 中的#define KC_DQT KC_DQUO#define KC_LT KC_LABK#define KC_GT KC_RABK),写 keymap 时选哪种拼法不影响行为。

三、宏展开链路:从 KC_AT 到发送 Left Shift + 2

理解这些键码的关键在于S()宏的定义链。在 quantum_keycodes.h 中:

#define S(kc) LSFT(kc)

LSFT在同文件 第 46 行 定义:

#define LSFT(kc) (QK_LSFT | (kc))

因此KC_AT在编译期就静态展开为QK_LSFT | KC_2这样的 16 位值:高 8 位携带 "左 Shift" 标志,低 8 位保留基础键码KC_2(注意QK_LSFT的值落在低 8 位键码区的高位上,宏直接按位或即可编码出组合语义)。这与KC_LSPO/KC_RSPC这类真正独立的 HID 键码完全不同——后者不需要 Shift 参与。

运行时的注册流程

按键被判定为按下后,会进入 quantum.c 中的 16 位键码注册入口:

__attribute__((weak)) void register_code16(uint16_t code) { if (IS_MODIFIER_KEYCODE(code) || code == KC_NO) { do_code16(code, register_mods); } else { do_code16(code, register_weak_mods); } register_code(code); }

其中do_code16调用extract_mod_bits(quantum.c 第 102 行)解析出要发送的修饰键:当code & QK_LSFT成立且不属于右修饰键区间时,执行

if (code & QK_LSFT) mods_to_send |= MOD_BIT(KC_LEFT_SHIFT);

随后才调用register_code(code)发送剥掉标志位后的基础键码。也就是说,KC_HASH按下时固件实际产生两个动作:先(弱)注册左 Shift 修饰键,再注册KC_3,松键时逆序释放——这就是"发送 Left Shift 与未 Shift 的键码,而非符号本身"的底层机制。

四、限制一:不能用于 Mod-Tap / Layer-Tap

原文档 的 Caveats 部分指出:这些键码不能用在 Mod-Tap 或 Layer-Tap 中,因为键码里携带的修饰键会被忽略

从源码结构看,这一限制可以直接验证。MT()LT()宏在 quantum_keycodes.h 中定义:

#define LT(layer, kc) (QK_LAYER_TAP | (((layer) & 0xF) << 8) | ((kc) & 0xFF)) #define MT(mod, kc) (QK_MOD_TAP | (((mod) & 0x1F) << 8) | ((kc) & 0xFF))

两个宏都只对kc做了& 0xFF掩码。而KC_EXCLAIM展开后的QK_LSFT | KC_1中,QK_LSFT的标志位正好位于低 8 位内被掩码掉的位置,组合语义随之丢失——tap 分支最终只会触发KC_1本身,而不是!

正确写法是在 Mod-Tap/Layer-Tap 中显式传入基础键,或者把整个组合交给外层宏,例如:

// 错误:KC_EXCLAIM 的 Shift 标志被 MT 掩码丢弃 MT(MOD_LSFT, KC_EXCLAIM) // 正确一:tap 部分直接用基础键码 MT(MOD_LSFT, KC_1) // 正确二:需要符号输出时,用 LT/MT 之外的方式组合,或在 tap 分支 // 里显式使用 S(kc) 展开后的完整键值(具体取决于想要的行为)

同理,任何"在 keymap 宏内嵌套组合宏"的场景都应先确认内层键值是否会被外层宏的位运算破坏,这是 QMK 键码位编码模型的通用心智负担。

五、限制二:Windows 远程桌面丢字符及修复方法

原文档 还记录了第二个坑:在 Windows 上使用 Remote Desktop Connection 时可能打不出这些符号。原因是这些键码触发的 Shift 按下与释放极快(弱修饰键在发送基础键码前后几乎瞬时完成),远程桌面的输入转发链路可能漏掉这对事件。

文档给出的官方修复步骤是:

  1. 打开 Remote Desktop Connection;
  2. 点击Show Options
  3. 进入Local Resources(本地资源)选项卡;
  4. 在 Keyboard 区域,把下拉框从默认值改为On this Computer

这样键盘事件在本地处理后再转发,字符即可正常输出。

需要说明的适用前提:该问题只出现在"本地 QMK 键盘 → 远程桌面 → 远程会话"这一特定链路,且与 Shifted 符号键码"发送速度极快"的特性直接相关;换成LCTL/LSFT长按组合或KC_LBRC等真实键码则不受影响。

六、使用建议小结

  1. 静态按键位(普通 keymap 层、KC_TILDE直接填进数组)是最常用且完全安全的场景,等价于手写S(KC_GRAVE),但可读性更好;
  2. 动态场景(Mod-Tap、Layer-Tap、自定义on_press回调中)避免直接传入这些 Shifted 键码,防止标志位被外层宏掩码丢弃;
  3. 跨平台注意:它们只编码 US ANSI 的符号位置(KC_LABK=S(KC_COMMA)等),在 US ISO 或其他布局下对应位置的实际字符会不同——这是布局特性,不是键码缺陷;
  4. 远程桌面用户按第五节步骤调整 RDP 键盘选项即可消除丢字。

七、相关文件索引

文件作用
docs/keycodes_us_ansi_shifted.md本文档:键码表、Caveats、RDP 修复步骤
quantum/keymap_extras/keymap_us.h全部 22 个 Shifted 符号键码及别名的#define定义
quantum/quantum_keycodes.hLSFT/S宏定义,LT/MT宏的 0xFF 掩码逻辑
quantum/quantum.cextract_mod_bitsregister_code16QK_LSFT标志到左 Shift 修饰键的解析与发送

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询