用途
该技能用于在 macOS 上查明未知 USB 或蓝牙 HID 设备(宏键盘、鼠标按键、脚踏板、HID 小工具等)的每个按键在操作系统层面实际发出的键码,而不是根据标签、厂商文档或照片猜测。廉价 HID 键盘常带有任意或错误标注的键码,例如 Jieli/Free3-P 的 top/middle/bottom 按键实际发出 Ctrl+C/Ctrl+V/Ctrl+X,并非符合剪切/复制/粘贴惯例,而是硬件随机分配。
何时使用
- 新 HID 设备到货,不知道其按键发出什么。
- 键盘有多个固件模式,需要映射每个模式的键码。
- 某条规则未触发,怀疑猜错了 from.key_code。
- 需要为可复现的配置记录设备信息。
三工具工作流
| 工具 | 用途 |
|---|---|
| Karabiner ignore: true | 让 Karabiner 观察设备而不抓取它 |
| Karabiner-EventViewer | 以文本形式显示原始 HID 事件 |
| Quartz screencapture -l | 在不抢占焦点的情况下捕获 EventViewer 窗口 |
关键点在于 ignore: true:启用后 Karabiner 不重映射任何内容,但仍记录设备事件,因此可以看到固件发出的原始键码。
工作流
步骤 1 — 识别设备的 VID/PID
# USB
ioreg -p IOUSB -l -w 0 | grep -B 2 -A 6 "<product name or partial>"
# Bluetooth (after pairing)
system_profiler SPBluetoothDataType | grep -A 15 "<pad name>"
以十进制记录 VID/PID(Karabiner 的 JSON 格式)。
步骤 2 — 添加 no-op 诊断规则(强制 Karabiner 抓取设备)
为什么不在 devices[] 中直接使用 "ignore": true?那会告诉 Karabiner 完全不管该设备,EventViewer 也就看不到它的事件。ignore: true 用于“不要动这个设备”,而不是“检查这个设备”。
正确做法:添加一条限定到该设备的惰性 complex_modifications 规则。Karabiner 会抓取设备(因此 EventViewer 能捕获每个 HID 报告),但规则本身不做任何事。编辑 ~/.config/karabiner/karabiner.json → profile 0 → complex_modifications.rules 并插入:
{
"description": "[DIAGNOSTIC] Grab <pad> (no remap)",
"manipulators": [
{
"type": "basic",
"from": { "key_code": "vk_none" },
"to": [{ "key_code": "vk_none" }],
"conditions": [
{
"type": "device_if",
"identifiers": [{ "vendor_id": 19530, "product_id": 16725 }]
}
]
}
]
}
vk_none 是 Karabiner 的虚拟键,永远不会匹配真实输入,因此该 manipulator 是惰性的。device_if 限定使 Karabiner 抓取设备以供检查。
重新加载 Karabiner:Karabiner-Elements 菜单栏图标 → Restart Karabiner-Elements。
步骤 3 — 打开 EventViewer 并按下每个按键
open -a "Karabiner-EventViewer"
- Main 标签页:显示 key_down / key_up 及解码后的键码名称(c、left_control、page_up 等)。
- Devices 标签页:显示每个事件来自哪个设备,确认抓取的是正确的 VID/PID。
- Unknown Events 标签页:显示 Karabiner 无法解码的事件,适用于消费者按键或自定义 HID 描述符。
缓慢按下每个按键。对于在一个 HID 报告中发出的修饰键组合(廉价键盘常见),会看到多个 key_down 事件紧密连续出现:
13:44:02.123 key_down left_control
13:44:02.123 key_down c
13:44:02.198 key_up c
13:44:02.198 key_up left_control
left_control + c 具有相同的微秒时间戳 = 在一个 HID 报告中发出,因此需要同时匹配器。
步骤 4 — 在不抢占焦点的情况下捕获
如果把 EventViewer 带到前台来读取,就会失去在测试窗口上按键的能力。变通方法——按窗口 ID 捕获:
# List windows; find EventViewer's window ID
python3 -c '
from Quartz import CGWindowListCopyWindowInfo, kCGWindowListOptionAll, kCGNullWindowID
for w in CGWindowListCopyWindowInfo(kCGWindowListOptionAll, kCGNullWindowID):
if "EventViewer" in w.get("kCGWindowOwnerName", "") or "EventViewer" in w.get("kCGWindowName", ""):
print(w["kCGWindowNumber"], w.get("kCGWindowName"))
'
# Screenshot that window without focusing it
screencapture -l <WID> -o -x /tmp/eventviewer.png
-l <WID> 捕获特定窗口,-o 排除阴影,-x 抑制捕获声音。窗口无需处于前台。
步骤 5 — 对每个固件模式重复(蓝牙键盘)
许多廉价蓝牙键盘有未记录的固件模式,由按键组合触发(按住所有 3 个键 5 秒、按住顶部键 10 秒等)。每个模式可能发出完全不同的键码。对于发现的每个模式:
- 将键盘切换到该模式。
- 重复步骤 3——记录每个按键的键码。
- 记录到表格中。
示例(Jieli/Free3-P):
| 模式 | Top | Middle | Bottom |
|---|---|---|---|
| 1 | volume_increment | volume_decrement | spacebar (play/pause) |
| 4 | page_up | page_down | equal_sign |
步骤 6 — 清理
从 complex_modifications.rules 中移除 [DIAGNOSTIC] 规则并重新加载 Karabiner。或者通过将 vk_none 替换为实际的 from / to 绑定,将其转换为真正的重映射规则。
避免触发 Touch ID 的审计
在此工作流中,不要查询 TCC.db 或 /Library/Application Support/com.apple.TCC/ 下的 SQLite 文件来“审计权限”——这些查询需要 sudo,并且每次调用都会触发 Touch ID 提示。改用:
# Non-sudo audit: is Karabiner actually grabbing the device?
karabiner_cli --list-connected-devices | jq '.[] | select(.product == "<pad-name>")'
# Returns { ..., "is_grabbed": true/false } — same info, no biometric prompt
可用的工具本身就是审计。这是通过艰难方式发现的;参见 ../configure-macro-keyboard/references/04-anti-patterns.md → “Sudo-based TCC.db audits trigger Touch ID”。
深入参考
- ../configure-macro-keyboard/references/03-patterns.md — “ignore: true diagnostic” + “Quartz window-ID capture” 模式的完整内容。
- ../configure-macro-keyboard/references/04-anti-patterns.md — 顶层 {"any": "key_code"} 会静默失败;位置推断错误。
- ./references/diagnostic-workflow.md — 带截图的扩展分步说明。
兄弟技能
- configure-macro-keyboard — 一旦知道按键发出什么,就用它编写限定到设备的 Karabiner 规则。此处步骤 2 的 vk_none no-op 规则可通过交换 from / to 绑定直接转换为真正的规则。
- emit-fn-key-on-macos — 如果发现的某个键码应重映射为真正的 Fn(用于 Typeless、听写、地球键),该兄弟技能解释了唯一正确的 Karabiner 咒语。
执行后反思
该技能完成后,在关闭任务前反思:
- 定位自己。 — 在任何编辑之前确认此 SKILL.md 是规范文件。
- 什么失败了? — 修复导致失败的指令。
- 什么比预期更好? — 提升为推荐实践。
- 什么漂移了? — 如果现实与文档不符,更新厂商 ID、键码或 FOSS 工具版本。
- 记录它。 — 添加一条 evolution-log 条目(或 04-anti-patterns.md 行),包含触发条件、修复、证据。
不要推迟。下一次调用会继承你留下的一切。