FEATURED · 精选文章

QMK 固件 One Shot 键详解:OSM/OSL 粘性修饰键与粘性层的完整用法及源码实现剖析

发布时间 / 2026/9/14 12:21:15
来源 / 创域科博编辑部
栏目 / 资讯中心
QMK 固件 One Shot 键详解:OSM/OSL 粘性修饰键与粘性层的完整用法及源码实现剖析 QMK 固件 One Shot 键详解OSM/OSL 粘性修饰键与粘性层的完整用法及源码实现剖析【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware本文以 QMK 官方文档 One Shot Keys 为核心系统讲解 One Shot一次性/粘性键的完整用法OSM()与OSL()键码的用法与区别、OS_TOGG/OS_ON/OS_OFF开关状态、ONESHOT_TAP_TOGGLE与ONESHOT_TIMEOUT两个配置宏的取值与含义、在宏与 Tap Dance 中以编程方式激活 One Shot 状态的 API以及oneshot_mods_changed_user等全部回调函数。读完后你可以直接在自己的 keymap 中配置并调试验 One Shot 行为并能理解 QMK 底层是如何通过状态机管理粘性修饰键的生命周期的。一、什么是 One Shot 键One shot 键是指按下并释放后仍然保持激活直到下一个按键按下或使用完毕后自动释放的键。这类键在键盘圈常被称为 Sticky keys粘性键或 Dead keys死键。它的价值在于让你无需同时按住多个键就能输入需要组合修饰的按键。例如把某个键定义为OSM(MOD_LSFT)后你可以先按下并释放 Shift再按下并释放 A即可输入大写 A。从计算机的视角看按下 Shift 的瞬间它就收到了 Shift 被按住而 Shift 会在 A 释放之后立即释放——两次击键之间 Shift 始终保持有效。One Shot 键同时具备普通修饰键的行为如果你按住不放一个 One Shot 键再打其他键它就和普通修饰键一样工作在你松手后 One Shot 状态会被立即清除。此外QMK 还支持连点锁定在很短时间内连续敲击某个 One Shot 键次数由ONESHOT_TAP_TOGGLE决定会把它锁定锁定后再按一次解锁。该机制对 One Shot ModifiersOSM和 One Shot LayersOSL都生效。二、配置项ONESHOT_TAP_TOGGLE 与 ONESHOT_TIMEOUT在 keymap 的config.h中定义以下两个宏可以控制 One Shot 键的行为#define ONESHOT_TAP_TOGGLE 5 /* Tapping this number of times holds the key until tapped once again. */ #define ONESHOT_TIMEOUT 5000 /* Time (in ms) before the one shot key is released */ONESHOT_TAP_TOGGLE连续快速敲击 N 次后该 One Shot 键进入锁定状态相当于把它当作常开修饰键/常开层再次单独敲击一次即解除锁定。取值1表示不启用连点锁定此时ONESHOT_TAP_TOGGLE与TAPPING_TOGGLE无关取值2即最常见的双击锁定。ONESHOT_TIMEOUTOne Shot 状态激活后若在该毫秒数内没有任何新按键One Shot 自动超时释放OSL层或OSM修饰键均受此控制。设为0或负值表示不启用超时即 One Shot 状态一直持续到下一个按键为止。从源码结构看One Shot 的总开关存储于持久化的键码配置位keymap_config.oneshot_enable中见 keymap_config.h 的keymap_config_t位域其中第 12 个 bit 就是oneshot_enable。在 action.c 的action_exec()入口处每次事件到来时都会检查该标志并在启用ONESHOT_TIMEOUT的情况下调用has_oneshot_layer_timed_out()/has_oneshot_mods_timed_out()判断是否超时// quantum/action.c (action_exec) if (keymap_config.oneshot_enable) { #if (defined(ONESHOT_TIMEOUT) (ONESHOT_TIMEOUT 0)) if (has_oneshot_layer_timed_out()) { clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED); } if (has_oneshot_mods_timed_out()) { clear_oneshot_mods(); }三、One Shot 键码完整清单Key别名说明QK_ONE_SHOT_TOGGLEOS_TOGG切换 One Shot 功能开关状态QK_ONE_SHOT_ONOS_ON打开 One Shot 功能QK_ONE_SHOT_OFFOS_OFF关闭 One Shot 功能OSL(layer)下一次按键使用layer层粘性层OSM(mod)下一次按键按住mod粘性修饰键OS_LCTL下一次按键按住左 ControlOS_LSFT下一次按键按住左 ShiftOS_LALT下一次按键按住左 AltOS_LGUI下一次按键按住左 GUIWin/CmdOS_LCS下一次按键按住左 Control 左 ShiftOS_LCA下一次按键按住左 Control 左 AltOS_LCG下一次按键按住左 Control 左 GUIOS_LSA下一次按键按住左 Shift 左 AltOS_LSG下一次按键按住左 Shift 左 GUIOS_LAG下一次按键按住左 Alt 左 GUIOS_LCSG下一次按键按住左 Control 左 Shift 左 GUIOS_LCAG下一次按键按住左 Control 左 Alt 左 GUIOS_LSAG下一次按键按住左 Shift 左 Alt 左 GUIOS_RCTL下一次按键按住右 ControlOS_RSFT下一次按键按住右 ShiftOS_RALT下一次按键按住右 AltOS_RGUI下一次按键按住右 GUIOS_RCS下一次按键按住右 Control 右 ShiftOS_RCA下一次按键按住右 Control 右 AltOS_RCG下一次按键按住右 Control 右 GUIOS_RSA下一次按键按住右 Shift 右 AltOS_RSG下一次按键按住右 Shift 右 GUIOS_RAG下一次按键按住右 Alt 右 GUIOS_RCSG下一次按键按住右 Control 右 Shift 右 GUIOS_RCAG下一次按键按住右 Control 右 Alt 右 GUIOS_RSAG下一次按键按住右 Shift 右 Alt 右 GUIOS_MEH下一次按键按住左 Control 左 Shift 左 AltOS_HYPR下一次按键按住左 Control 左 Shift 左 Alt 左 GUI以上所有组合键都可以通过 quantum_keycodes.h 中的宏定义核对它们本质都是OSM(MOD_*)的展开#define OSL(layer) (QK_ONE_SHOT_LAYER | ((layer) 0x1F)) #define OSM(mod) (QK_ONE_SHOT_MOD | ((mod) 0x1F)) #define OS_MEH OSM(MOD_LCTL | MOD_LSFT | MOD_LALT) #define OS_HYPR OSM(MOD_LCTL | MOD_LSFT | MOD_LALT | MOD_LGUI)需要注意的关键行为当 One Shot 功能被OS_OFF关闭时OSM()退化为普通修饰键Hold ModOSL()退化为普通的MO()。这正是OS_TOGG/OS_ON/OS_OFF三个键码存在的意义——允许你在一块键盘上动态切换 One Shot 是否生效。从源码看这三个开关键码在 process_oneshot.c 中被专门拦截处理分别调用oneshot_toggle()、oneshot_enable()、oneshot_disable()声明于 action_util.h从而修改keymap_config.oneshot_enable标志位。使用注意mod 参数必须使用MOD_*前缀OSM()的mod参数必须使用MOD_*前缀而不是KC_*例如OSM(MOD_LCTL | MOD_LSFT) /* 正确 */ OSM(KC_LCTL | KC_LSFT) /* 错误 */从键码编码看OSL/OSM的低 5 位直接保存修饰键位0x1F掩码对应MOD_*位图而不是完整键码值因此传KC_*会得到错误的修饰组合。四、底层实现One Shot 状态机与 tap_count 判定QMK 的 One Shot 机制与 action_tapping.c 的按键判定tap/hold 判定深度耦合。理解状态定义有助于理解行为// quantum/action_util.h typedef enum { ONESHOT_PRESSED 0b01, ONESHOT_OTHER_KEY_PRESSED 0b10, ONESHOT_START 0b11, ONESHOT_TOGGLED 0b100 } oneshot_fullfillment_t;ONESHOT_STARTOne Shot 状态刚刚建立修饰键刚被松开、层刚被切入ONESHOT_PRESSEDOne Shot 键自身仍被按住此时它就是普通修饰键/普通层ONESHOT_OTHER_KEY_PRESSED已有一个其他按键消费了这个 One Shot 状态ONESHOT_TOGGLED被连点锁定后进入的锁存状态。在 action.c 的process_action()中可以看到 OSL 的消耗逻辑当有 One Shot 层处于激活状态时若新按下的键是ACT_USAGE普通键或修饰键之外的动作就调用clear_oneshot_layer_state(ONESHOT_OTHER_KEY_PRESSED)将其清除而层操作类键ACT_LAYER等会被显式豁免do_release_oneshot false避免连续切层时状态互相干扰。对于OSM其处理位于 action.c 中ACT_LMODS_TAP/ACT_RMODS_TAP的MODS_ONESHOT分支依赖 tapping 模块统计出的tap_countcase MODS_ONESHOT: ... if (event.pressed) { if (tap_count 0) { // 判定为长按按普通修饰键处理 register_mods(mods); } else if (tap_count 1) { // 判定为轻点进入 One Shot 状态 add_oneshot_mods(mods); #if defined(ONESHOT_TAP_TOGGLE) ONESHOT_TAP_TOGGLE 1 } else if (tap_count ONESHOT_TAP_TOGGLE) { // 连点次数达到阈值锁定修饰键 register_mods(mods); del_oneshot_mods(mods); add_oneshot_locked_mods(mods); #endif } } else { // 释放分支分别清除 hold / oneshot / locked 三种状态 ... }这段代码解释了文档中描述的全部行为轻点一次产生粘性修饰键长按时行为如同普通修饰键以及连点ONESHOT_TAP_TOGGLE次后进入add_oneshot_locked_mods()建立的锁定态对应回调oneshot_locked_mods_changed_user。OSL的OP_ONESHOT分支action.c逻辑同构tap_count ONESHOT_TAP_TOGGLE时set_oneshot_layer(..., ONESHOT_START)达到阈值时切换为ONESHOT_TOGGLED锁存层。One Shot 状态管理的一组 API 全部声明在 action_util.hget_oneshot_mods()/add_oneshot_mods()/set_oneshot_mods()/clear_oneshot_mods()、get_oneshot_layer()/set_oneshot_layer()/clear_oneshot_layer_state()/reset_oneshot_layer()/is_oneshot_layer_active()等。五、在宏或 Tap Dance 中激活 One Shot有时你希望在宏macro或 Tap Dance 例程里程序化地激活一个 One Shot 键。QMK 提供了如下 APIOne Shot 层在 key down 时调用set_oneshot_layer(LAYER, ONESHOT_START)在 key up 时调用clear_oneshot_layer_state(ONESHOT_PRESSED)如果想彻底取消该 One Shot调用reset_oneshot_layer()。One Shot 修饰键调用set_oneshot_mods(MOD_BIT(KC_*))设置MOD_BIT()把KC_*键码转换为MOD_*位图调用clear_oneshot_mods()取消。例如在用户的user_task()或某个宏回调中临时打开第 1 层 One Shotset_oneshot_layer(1, ONESHOT_START);这与 action.c 中OP_ONESHOT分支对OSL按键的默认处理完全一致可视为手动复刻固件行为的官方入口。六、One Shot 回调Callbacks当需要在 One Shot 状态变化时执行自定义逻辑例如闪烁 LED、发出提示音时可以覆写以下三个回调均为__attribute__((weak))弱符号声明于 action_util.h1.oneshot_mods_changed_userOSM 状态变化在任何 One Shot 修饰键状态改变时调用——包括打开和关闭两种方向void oneshot_mods_changed_user(uint8_t mods) { if (mods MOD_MASK_SHIFT) { println(Oneshot mods SHIFT); } if (mods MOD_MASK_CTRL) { println(Oneshot mods CTRL); } if (mods MOD_MASK_ALT) { println(Oneshot mods ALT); } if (mods MOD_MASK_GUI) { println(Oneshot mods GUI); } if (!mods) { println(Oneshot mods off); } }mods参数是变化发生后当前生效的 One Shot 修饰键位图因此它反映的是最新状态全部清零时可用!mods判断已关闭。2.oneshot_locked_mods_changed_user连点锁定的状态变化启用了 One Shot Tap Toggle例如#define ONESHOT_TAP_TOGGLE 2后按键达到阈值被锁定时会触发void oneshot_locked_mods_changed_user(uint8_t mods) { if (mods MOD_MASK_SHIFT) { println(Oneshot locked mods SHIFT); } if (mods MOD_MASK_CTRL) { println(Oneshot locked mods CTRL); } if (mods MOD_MASK_ALT) { println(Oneshot locked mods ALT); } if (mods MOD_MASK_GUI) { println(Oneshot locked mods GUI); } if (!mods) { println(Oneshot locked mods off); } }3.oneshot_layer_changed_userOSL 层变化void oneshot_layer_changed_user(uint8_t layer) { if (layer 1) { println(Oneshot layer 1 on); } if (!layer) { println(Oneshot layer off); } }任何 One Shot 层被关闭时layer参数为 0。注意如果你关心的是所有层变化包括MO()、DF()等应使用更通用的layer_state_set_user回调oneshot_layer_changed_user只覆盖 One Shot 层的开/关。_kb变体与调用约定如果你是在为整块键盘而非单个 keymap 用户编写固件还存在对应的_kb版本void oneshot_locked_mods_changed_kb(uint8_t mods); void oneshot_mods_changed_kb(uint8_t mods); void oneshot_layer_changed_kb(uint8_t layer);按照 QMK 回调约定从 action_util.c 的默认实现可以看到_kb版本的默认行为是转发调用_user版本。因此你在_kb回调中实现键盘级逻辑后务必调用_user版本保留下游 keymap 用户的自定义能力。七、远程桌面下的 OSM 问题及解决方法如果你的 One Shot 修饰键在远程桌面Remote Desktop Connection中不生效可以在 RDC 的设置中打开 Local Resources 选项卡在键盘一节的下拉框中选择 On this Computer。这样远程会话会正确使用本机键盘的修饰键时序OSM 即可正常工作。八、小结One Shot 键OSM()/OSL()让先按修饰键、再按目标键成为可能同时长按时保持普通修饰键/普通层的行为通过OS_TOGG/OS_ON/OS_OFF可在运行时开关 One Shot 功能关闭后OSM/OSL退化为普通 Hold Mod 和MO()ONESHOT_TAP_TOGGLE控制连点锁定次数ONESHOT_TIMEOUT控制超时释放毫秒编程式激活使用set_oneshot_layer()/clear_oneshot_layer_state()/reset_oneshot_layer()层与set_oneshot_mods()/clear_oneshot_mods()修饰键三个回调oneshot_mods_changed_user、oneshot_locked_mods_changed_user、oneshot_layer_changed_user覆盖全部状态变化点配合_kb变体可实现键盘级 用户级两层定制底层实现位于 quantum/action.c、quantum/action_util.c 与 quantum/process_keycode/process_oneshot.c键码编码与宏定义见 quantum/quantum_keycodes.h 和 quantum/keycodes.h。【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻