Capslock 如何工作:F18、事件钩子,以及只归还自己改动的映射
Caps Lock 占着键盘上最好的位置,干着最没用的活。Capslock 给它换了一份工作:轻按是 Escape,按住是 Hyper,再与四个修饰键组合,得到十六个控制平面。本文解释一个原生应用如何在 macOS 上做到这一点,而不需要内核扩展、驱动或特权 Helper。
Caps Lock 不是修饰键
macOS 并不把 Caps Lock 报告为「按下、抬起」的一对事件,而是报告一个来回翻转的锁定状态,还加了一小段延迟,防止误触切换大小写。这对输入大写字母没问题,对一个需要按住的修饰键却毫无用处。
所以 Capslock 先做一次映射:启用时通过 hidutil 让物理 Caps Lock 发送 F18——
一个苹果键盘上根本不存在的键。F18 以普通的按下、抬起事件到达,应用可以据此判断「按住」和「松开」。
F18 还有第二个好处:它不是系统修饰键。按住 Caps 和 Shift 再按 H,到达的是「F18 按住、 Shift 按住、H」。macOS 不会把 F18 与 Shift 合成别的东西,于是四个修饰键都可以零成本地叠加在 Caps 之上。
用事件钩子读取按键
Capslock 在 HID 层级、队列最前端安装一个主动的 CGEventTap。「主动」意味着它可以修改或丢弃事件,而不只是旁观。macOS 用辅助功能权限管控这一能力;它不需要输入监控权限,
那只适用于只读的监听钩子。
如果启动时还没有授权,应用会持续重试;你一授权,钩子立即安装,无需重启应用。钩子运行在独立线程上,回调里几乎只做整数比较,不会拖慢打字。
解析组合键
F18 按下时,应用记录「Caps 已按住」并吞掉这个事件;F18 抬起时,它判断这次按键是哪一种:
- 轻按:期间没有按其他键,执行轻按动作。默认配置里是 Escape。
- 组合键:Caps 加另一个键,执行该键在当前平面上映射的动作,原始按键不会到达前台应用。
- 双击:如果某个映射定义了双击,就执行双击动作。单击会等过双击窗口,两者不会同时触发。
平面由你与 Caps 一起按住的修饰键决定。每个修饰键是一位:Command 1、Option 2、 Control 4、Shift 8,它们的和对应十六个平面之一,从单独按 Caps 的 0 号到四键全按的 15 号。匹配是精确的:多按了一个修饰键的组合,绝不会退回到更宽泛的平面。因此给导航键加上 Shift 可以表示「选择」,而不会在别处带来意外。
Capslock 合成的每个事件都带有标记,钩子会跳过带标记的事件。否则 Caps+H 发出的方向键会立刻又被钩子截回来。
两种 Hyper
早期的 Capslock 布局通过按住右侧四个修饰键来实现 Hyper。Alfred、Moom 等启动器和窗口管理工具会监听这个组合,很多人的快捷键也建立在它之上。
Capslock 把它做成一个选项,而不是实现细节:
- 经典兼容:Caps 按住期间持续按住右侧 Command、Option、Control 与 Shift,其他应用照旧能看到 Hyper。
- 原生纯净:Hyper 只在 Capslock 内部生效。按住 Caps 时其他应用什么也看不到,只能看到它产生的动作。
两种模式下,平面都由你实际按下的修饰键计算;Capslock 为兼容而按住的右侧修饰键会被排除,否则每个组合键都会落到 15 号平面。
只拥有自己改动的部分
hidutil property --set 会整体替换用户键位映射。最直接的实现是启动时写入自己的列表、退出时清空,这会删掉其他工具或用户自己设置的映射。Capslock 把已有映射当作别人的东西:
- 读取当前映射;如果已有条目重映射了 Caps Lock,就拒绝启动。
- 改动之前,先写一份恢复日志,记录原来的映射和即将添加的那一个条目。
- 只合并自己的条目,并回读结果确认写入成功。
- 暂停或退出时,只移除这一条并恢复原映射——除非期间有别人改过它。
如果运行期间有其他程序修改了映射,Capslock 会停止并报告冲突,而不是去抢最后一次写入。睡眠唤醒、插入键盘、切换用户会话之后,它都会重新核对。这也是为什么「暂停」会还给你一个真正的 Caps Lock,而不是一个被透传的 F18。
应用没有正常退出时
崩溃或强制退出会跳过第 4 步的清理。有两道保障:应用内嵌一个小型看门狗,它读取恢复日志并盯住父进程,如果应用退出时没有释放映射,就恢复原来的映射;下次启动时,应用也会在安装钩子之前先核对遗留的日志。
如果需要手动清除,请使用应用自带的命令,它只移除自己添加的 Caps Lock 条目:
不要用空列表重置映射:那是全局操作,会把其他工具的映射一并删掉。何况这份映射本来就不会在重启后保留。
一切留在本地
以上所有处理都在你的 Mac 上完成。Capslock 不含分析或广告 SDK,不会把按键、映射、剪贴板内容或使用数据发送到任何地方。诊断日志不记录输入的文字、剪贴板内容、Shell 输出和应用历史。存储了什么、何时联网,见隐私说明。