docs/plans/2026-05-03-logi-ble-hidpp-divert-postmortem.md
时间: 2026-05-01 ~ 2026-05-03 结论状态: 已收敛为产品策略;提交前已移除默认高频饱和日志,保留 DEBUG 自动日志框架与可选 verbose trace 相关代码:
Mos/Logi/*,Mos/ButtonCore/*,Mos/Windows/PreferencesWindow/ButtonsView/*
Mos 的 [按键] 模块通过 Logitech HID++ REPROG_CONTROLS_V4 接管部分 Logi 设备按键。用户报告:
最终确认: 主要问题不是 Mos 的普通事件分发,而是 BLE 直连下 Logi Options+ 会持续抢占或清除 HID++ 按键通知/接管状态。Bolt/Unifying 路径没有表现出同样的不稳定,因为 Mos 与设备的 HID++ 通信和按键通知经由接收器路径,不容易被 Logi Options+ 以同样方式清掉。
测试流程:
观察:
曾经在引入 HID++ 前,Back / Forward 会被录为系统鼠标键 3/4,触发稳定。
实验:
3/4。结论:
DPI Switch 没有标准 macOS 鼠标键回退,只能通过 HID++ 通知识别。
实验:
观察:
结论:
用户尝试卸载 Logi Options+:
结论:
Bolt/Unifying 通过 Logitech 接收器呈现设备和 HID++ 通道。Mos 接管按键时,主要面向接收器下的目标设备。
实测表现:
BLE 直连时,设备同时暴露:
3/4 到达。Logi Options+ 也会打开并操作同一 BLE HID++ 通道。当它清除或重写 control reporting 后:
尝试:
1006/1007 fallback 到系统鼠标键 3/4。问题:
3/4 CGEvent 不一定能匹配。处理:
尝试:
问题:
处理:
问题:
temporarilyDivertAll() 在录制时把标准侧键也拉进 HID++,导致用户无法录到 3/4。处理:
3/4,而不是 Logi MosCode。| 类型 | 示例 | BLE 策略 | Bolt/Unifying 策略 | UI |
|---|---|---|---|---|
| 标准鼠标键 | Middle / Back / Forward | 使用系统鼠标键 2/3/4,不争 HID++ | 保持 HID++ 可用 | 蓝色 branch 仅用于历史 Logi 绑定迁移 |
| HID++-only 按键 | DPI Switch / SmartShift 等 | 允许绑定,但提示 BLE HID++ 可能不稳定 | 保持 HID++ | 琥珀色无线/警告图标 |
| 真实接管冲突 | 外部应用清除 Mos divert | 不自动硬抢;按类型提示 | 提示冲突 | 黄色 branch |
含义:
2/3/4。2/3/4 绑定,避免列表重复。对应模块:
LogiStandardMouseButtonAliasButtonBindingReplacementButtonCapturePresentationStatus.standardMouseAliasAvailable含义:
对应模块:
ConflictStatusLogiButtonDeliveryMode.contendedButtonCapturePresentationStatus.contended含义:
对应模块:
LogiButtonCaptureDiagnosis.isBLEHIDPPOnlyControlButtonCapturePresentationStatus.bleHIDPPUnstablelogi_ble_hidpp_unstable_toast文件:
Mos/Logi/Divert/LogiButtonDeliveryPolicy.swiftMos/Logi/Divert/LogiButtonDeliveryMode.swiftMos/Logi/Core/LogiDeviceSession.swiftMos/Logi/Core/LogiSessionManager.swift核心概念:
LogiTransportIdentity: .bleDirect, .receiver, .unsupportedLogiButtonDeliveryPolicy: 决定某 CID 在某 transport/phase 下是否走 HID++。LogiButtonDeliveryMode: 当前接管模式,目前保留 .hidpp 与 .contended。LogiButtonCaptureDiagnosis: UI 读取的统一诊断对象,包含 ownership / delivery / transport / native alias 等。当前默认:
文件:
Mos/Logi/Core/LogiCIDDirectory.swiftMos/ButtonCore/LogiStandardMouseButtonAlias.swiftMos/Windows/PreferencesWindow/ButtonsView/RecordedEvent.swift映射:
| HID++ CID | Logi MosCode | macOS 鼠标键 |
|---|---|---|
0x0052 Middle Button | 1005 | 2 |
0x0053 Back Button | 1006 | 3 |
0x0056 Forward Button | 1007 | 4 |
原则:
InputProcessor 不再做 Logi MosCode -> native mouse runtime 翻译。3/4 的绑定就是普通鼠标绑定,不再依赖 HID++。文件:
Mos/Logi/Core/LogiDeviceSession.swiftMos/Logi/Divert/LogiButtonDeliveryMode.swift目的:
注意:
文件:
Mos/Windows/PreferencesWindow/ButtonsView/ButtonCapturePresentationStatus.swiftMos/Windows/PreferencesWindow/ButtonsView/ButtonTableCellView.swiftMos/Windows/PreferencesWindow/ButtonsView/PreferencesButtonsViewController.swiftMos/Localizable.xcstrings改动:
文件:
Mos/Logi/Debug/LogiDebugPanel.swiftMos/Logi/LogiCenter.swiftMosTests/LogiPersistenceCanaryTests.swift行为:
~/Library/Logs/Mos/hidpp-debug-latest.log。hidpp-debug-YYYY-MM-DD-HHMMSS.log。#if DEBUG 下,不会进入线上构建。LogiTrace 仍保留为 DEBUG 可选诊断通道,默认关闭;需要临时打开时设置 UserDefaults LogiVerboseTraceEnabled=true。用途:
| 中文概念 | 代码命名 | 含义 |
|---|---|---|
| BLE 直连 | LogiTransportIdentity.bleDirect | 不经 Bolt/Unifying 接收器的蓝牙连接 |
| 接收器连接 | LogiTransportIdentity.receiver | Bolt/Unifying/Lightspeed 等 receiver 路径 |
| HID++ 投递策略 | LogiButtonDeliveryPolicy | 决定某按键走 HID++ 还是 native CGEvent |
| 投递模式 | LogiButtonDeliveryMode | 当前 ownership 是否仍走 HID++ 或进入 contended |
| 接管诊断 | LogiButtonCaptureDiagnosis | UI 的统一状态输入 |
| 标准鼠标键别名 | LogiStandardMouseButtonAlias | Logi Back/Forward/Middle 到 3/4/2 的映射 |
| BLE 标准键 undivert guard | LogiBLEStandardButtonUndivertPlanner / StandardButtonGuard log tag | 防止 BLE 标准侧键残留 HID++ divert |
| 可改用系统鼠标键 | ButtonCapturePresentationStatus.standardMouseAliasAvailable | 蓝色 branch |
| 接管冲突 | ButtonCapturePresentationStatus.contended / ConflictStatus | 黄色 branch |
| BLE HID++ 不稳定 | ButtonCapturePresentationStatus.bleHIDPPUnstable | 琥珀无线/警告图标 |
Linux 内核社区曾尝试对所有 Logitech Bluetooth 设备默认启用 HID++:
hid-logitech-hidpp 会绑定所有 Bluetooth mice,但某些 corner case 下驱动会放弃设备,最终用户得到 dead mouse。参考: kernel revert commit 与 Phoronix 归纳。-ENODEV,导致鼠标不可用。参考: Software Heritage mirror。对 Mos 的启发:
Solaar 文档说明:
对 Mos 的启发:
社区整理的 0x1B04 Special Keys and Mouse Buttons 文档说明:
divertedButtonsEvent 报告给软件。getCidReporting 返回当前 divert / persist / rawXY / remap 状态。setCidReporting 的 divert + dvalid 控制临时 divert。remap=0 表示保持之前 remap 设置不变,不是清 remap;清 remap 应 remap 到自身 CID。对 Mos 的启发:
SetControlReporting 保持 remap=0 是保守选择。Solaar 当前实现中,setCidReporting 将 (cid, flags, remap) 打包为 struct.pack("!HBH", ...),其中 remap=0 的语义是保留当前 mapping。参考: Solaar hidpp20.py。
对 Mos 的启发:
当前策略保留 UserDefaults 级开关,便于灰度或回退:
LogiBLEStandardButtonsNativeFirstLogiBLEStandardUndivertGuardEnabledLogiBLEStandardUndivertGuardInterval建议:
提交前已经清理:
InputProcessor / ButtonCore 中仅用于本轮定位的按键级 verbose 日志。LogiTrace 默认关闭,避免常规 DEBUG 运行持续写入高频 trace。不建议删除:
LogiStandardMouseButtonAlias。ButtonCapturePresentationStatus 状态聚合。每次改 Logi 按键前应至少验证:
3/4,长按/连按稳定。3/4 且不重复。3/4 绑定不失效。本轮收尾前已执行:
xcodebuild test -scheme Debug -destination 'platform=macOS,arch=arm64' CODE_SIGNING_ALLOWED=NO
结果:
同时执行:
scripts/lint-logi-boundary.sh
jq empty Mos/Localizable.xcstrings
git diff --check
结果均通过。
BLE 问题的根因不是单一 "Mos 没有定时 keepalive",而是:
因此最终产品策略是: