0x1B04 · specialKeysMSEButtons
枚举可编程控件、修改控件上报状态,并接收已分流按钮、指针、analytics 和原始滚轮事件。
specialKeysMSEButtons(0x1b04)描述鼠标和键盘上的可编程物理控件与虚拟
控件。它枚举按钮、功能键和热键。主机软件可以分流控件、设置持久分流、修改
上报流,或选择重映射目标。
OpenLogi 将这个功能注册为 ReprogControls5。它的类型化 Rust 封装是
ReprogControlsFeature。0x1b04 是
0x1b00 · reprogControls 的较新版本。
**规格:**Logitech HID++ 2.0 —— specialKeysMseButtons(v2)。**交叉核对:**Logi Options+ 2.8(静态分析)、Solaar。用于:按键重映射。
函数参考
ReprogControlsFeature 实现 0x1b04。
方法
| 函数 | HID++ fn | 签名 | 返回值 |
|---|---|---|---|
get_count | 0 | () | u8 |
get_cid_info | 1 | (index: u8) | CidInfo |
get_cid_reporting | 2 | (cid: ControlId) | CidReporting |
set_cid_reporting | 3 | (cid: ControlId, change: CidReportingChange) | CidReportingChangeEcho |
get_capabilities | 4 | () | ReprogControlsCapabilities |
reset_all_cid_report_settings | 5 | () | () |
listen | event | () | async_channel::Receiver<ReprogControlsEvent> |
前六个方法都是 async。它们返回 Result<…, Hidpp20Error>。
listen 是同步的 EmittingFeature trait 方法。
类型
ControlId、TaskId 和 GroupMask
| 类型 | 内部类型 | 说明 |
|---|---|---|
ControlId | u16 | HID++ 控件 ID。 |
TaskId | u16 | 与控件关联的默认任务。 |
GroupMask | u8 | get_cid_info 返回的 g1..g8 成员掩码。 |
CidInfo
| 字段 | 类型 | 说明 |
|---|---|---|
cid | ControlId | 控件 ID。 |
task_id | TaskId | 默认任务 ID。 |
flags | CidFlags | 能力和分类标志。 |
position | u8 | 设备报告的物理位置值。 |
group | u8 | 设备报告的分组号。 |
group_mask | GroupMask | 分组成员掩码。 |
CidFlags
| 标志 | 位 | 说明 |
|---|---|---|
MOUSE | 1 << 0 | 鼠标或指针控件。 |
FUNCTION_KEY | 1 << 1 | 键盘功能键。 |
HOTKEY | 1 << 2 | 热键。 |
FN_TOGGLE | 1 << 3 | Fn 切换控件。 |
REPROGRAMMABLE | 1 << 4 | 可重编程控件。 |
DIVERTABLE | 1 << 5 | 可临时分流控件。 |
PERSISTENTLY_DIVERTABLE | 1 << 6 | 可持久分流控件。 |
VIRTUAL_CONTROL | 1 << 7 | 虚拟而非物理控件。 |
RAW_XY | 1 << 8 | 支持原始 XY 上报。 |
FORCE_RAW_XY | 1 << 9 | 支持强制原始 XY 上报。 |
ANALYTICS_KEY_EVENTS | 1 << 10 | 支持 analytics 按键事件。 |
RAW_WHEEL | 1 << 11 | 支持原始滚轮上报。 |
CidFlags::raw() 返回组合后的 u16。is_* 和 supports_* 方法公开对应的
具名标志。
CidReporting
| 字段 | 类型 | 说明 |
|---|---|---|
cid | ControlId | 已读取状态的控件。 |
diverted | bool | 临时分流状态。 |
persistently_diverted | bool | 持久分流状态。 |
force_raw_xy | bool | 强制原始 XY 状态。 |
raw_xy | bool | 原始 XY 状态。 |
remap | Option<ControlId> | 目标控件;设备报告 0x0000 时为 None。 |
analytics_key_events | bool | analytics 按键事件状态。 |
raw_wheel | bool | 原始滚轮状态。 |
CidReportingChange
每个 Option<bool> 仅在值为 Some 时修改对应设置。值为 None 时,设备保持
该设置不变。
| 字段 | 类型 | 说明 |
|---|---|---|
diverted | Option<bool> | 临时分流变更。 |
persistently_diverted | Option<bool> | 持久分流变更。 |
force_raw_xy | Option<bool> | 强制原始 XY 变更。 |
raw_xy | Option<bool> | 原始 XY 变更。 |
remap | Option<ControlId> | 目标控件。None 发送 0x0000 并保持当前重映射。 |
analytics_key_events | Option<bool> | analytics 按键事件变更。 |
raw_wheel | Option<bool> | 原始滚轮变更。 |
如要清除重映射,请为正在写入的控件设置 remap: Some(cid)。不要使用 None:
对于 set_cid_reporting,0x0000 表示保持之前的重映射。
CidReportingChangeEcho
| 字段 | 类型 | 说明 |
|---|---|---|
cid | ControlId | 响应中的控件 ID。 |
diverted | Option<bool> | 回显的临时分流变更。 |
persistently_diverted | Option<bool> | 回显的持久分流变更。 |
force_raw_xy | Option<bool> | 回显的强制原始 XY 变更。 |
raw_xy | Option<bool> | 回显的原始 XY 变更。 |
remap | Option<ControlId> | 非零时的回显重映射目标。 |
analytics_key_events | Option<bool> | 回显的 analytics 按键事件变更。 |
raw_wheel | Option<bool> | 回显的原始滚轮变更。 |
ReprogControlsCapabilities
| 字段 | 类型 | 说明 |
|---|---|---|
reset_all_cid_report_settings | bool | reset_all_cid_report_settings 可用。 |
事件
ReprogControlsFeature 实现 EmittingFeature<ReprogControlsEvent>。
| 变体 | Event fn | 字段 | 说明 |
|---|---|---|---|
DivertedButtons | 0 | [ControlId; 4] | 当前已分流控件。 |
DivertedRawMouseXy | 1 | dx: i16、dy: i16 | 原始指针增量。 |
AnalyticsKeyEvents | 2 | [AnalyticsKeyEvent; 5] | 五条 packed analytics 条目。 |
DivertedRawWheel | 4 | resolution、periods、delta_vertical | 原始垂直滚轮增量。 |
AnalyticsKeyEvent 包含 cid: ControlId 和设备定义的 event: u8。
RawWheelResolution 为 Low = 0 或 High = 1。periods 是无符号四位值 U4。
线格式
表格中的字节是逻辑 HID++ payload 字节。OpenLogi 发送 3 字节短请求,fn 1 和 fn 3 则发送 16 字节长请求。读取方法使用扩展后的 16 字节响应视图,并忽略没有 建模字段的字节。
get_count(fn 0)
请求:[0x00, 0x00, 0x00]。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0 | count | 控件表的条目数。 |
| 1–15 | — | OpenLogi 不解析字段。 |
get_cid_info(fn 1)
请求为长 payload,index 位于字节 0。OpenLogi 在字节 1–15 写入 0x00。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE u16 的 ControlId。 |
| 2–3 | task_id | BE u16 的 TaskId。 |
| 4 | primary flags | CidFlags 的位 0–7。 |
| 5 | position | 设备报告的位置。 |
| 6 | group | 设备报告的分组。 |
| 7 | group_mask | GroupMask 的 g1..g8 位。 |
| 8 | additional flags | CidFlags 的位 8–11。OpenLogi 将此字节左移 8 位。 |
| 9–15 | — | 没有已知字段。OpenLogi 忽略这些字节。 |
get_cid_reporting(fn 2)
请求:[cid_hi, cid_lo, 0x00]。cid_hi,cid_lo 是 BE u16 格式的 ControlId。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE u16。 |
| 2 bit 0 | diverted | 1 表示临时分流。 |
| 2 bit 2 | persistently_diverted | 1 表示持久分流。 |
| 2 bit 4 | raw_xy | 1 表示原始 XY。 |
| 2 bit 6 | force_raw_xy | 1 表示强制原始 XY。 |
| 3–4 | remap | BE u16 目标。0x0000 表示控件映射到自身,并变为 None。 |
| 5 bit 0 | analytics_key_events | 1 启用 analytics 事件。 |
| 5 bit 2 | raw_wheel | 1 启用原始滚轮事件。 |
| 6–15 | — | 没有已知字段。OpenLogi 忽略这些字节。 |
set_cid_reporting(fn 3)
OpenLogi 发送 16 字节长请求。它在字节 6–15 写入 0x00。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE u16。 |
| 2 bit 1 / bit 0 | diverted valid / value | 仅当 valid 为 1 时修改值。 |
| 2 bit 3 / bit 2 | persistently_diverted valid / value | 仅当 valid 为 1 时修改值。 |
| 2 bit 5 / bit 4 | raw_xy valid / value | 仅当 valid 为 1 时修改值。 |
| 2 bit 7 / bit 6 | force_raw_xy valid / value | 仅当 valid 为 1 时修改值。 |
| 3–4 | remap | BE u16。0x0000 保持之前的重映射。写入此 cid 可清除重映射。 |
| 5 bit 1 / bit 0 | analytics_key_events valid / value | 仅当 valid 为 1 时修改值。 |
| 5 bit 3 / bit 2 | raw_wheel valid / value | 仅当 valid 为 1 时修改值。 |
| 6–15 | — | OpenLogi 写入固定的零填充。 |
响应具有相同的已建模字节 0–5。CidReportingChangeEcho 仅在 valid 位已设置时
公开布尔值。它将零重映射字段变为 None,并忽略字节 6–15。
get_capabilities(fn 4)
请求:[0x00, 0x00, 0x00]。此函数适用于版本 6 的设备。较旧固件可能返回
InvalidFunctionId。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0 bit 0 | reset_all_cid_report_settings | 1 允许 fn 5。Options+ 2.8。 |
| 0 bits 1–7 | — | 未知位。OpenLogi 忽略。 |
| 1–15 | — | OpenLogi 不解析字段。 |
reset_all_cid_report_settings(fn 5)
请求:[0x00, 0x00, 0x00]。仅当 fn 4 报告支持时调用它。它重置每个控件的分流
和重映射状态。OpenLogi 返回 (),且不解析响应体。
事件
仅当 software ID 为 0 时,OpenLogi 才接受 unsolicited 消息。OpenLogi 不建模
下表中的未识别字节。
DivertedButtons(event fn 0)
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | slot 0 | BE ControlId;0x0000 表示此 slot 没有控件。 |
| 2–3 | slot 1 | BE ControlId。 |
| 4–5 | slot 2 | BE ControlId。 |
| 6–7 | slot 3 | BE ControlId。 |
| 8–15 | — | 没有已知字段。OpenLogi 忽略这些字节。 |
DivertedRawMouseXy(event fn 1)
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | dx | BE i16 水平增量。 |
| 2–3 | dy | BE i16 垂直增量。 |
| 4–15 | — | 没有已知字段。OpenLogi 忽略这些字节。 |
AnalyticsKeyEvents(event fn 2)
事件在字节 0–14 中包含五条 3 字节条目。
| 偏移量 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE ControlId。 |
| 2 | event | 设备定义的事件代码。 |
字节 15 没有已知字段。OpenLogi 忽略此字节。
DivertedRawWheel(event fn 4)
| 字节 | 字段 | 备注 |
|---|---|---|
| 0 bits 7–5 | — | 未找到公开含义。OpenLogi 忽略这些位。Options+ 2.8。 |
| 0 bit 4 | resolution | 0 = Low;1 = High。Options+ 2.8。 |
| 0 bits 3–0 | periods | U4 周期计数。Options+ 2.8。 |
| 1–2 | delta_vertical | BE i16 垂直增量。Options+ 2.8。 |
| 3–15 | — | 没有已知字段。OpenLogi 忽略这些字节。 |
OpenLogi 的用法
OpenLogi 使用 0x1b04 捕获鼠标控件和切换键盘主机。它在 arm 控件前枚举控件。
它要求对应的 CidFlags,例如 DIVERTABLE、RAW_XY 或 RAW_WHEEL。
每次捕获写入前,OpenLogi 读取并记录 CidReporting。它只写入会话拥有的上报
字段。它将读取到的重映射目标带入变更,因此不会清除已有重映射。
OpenLogi 会在重连后重新 arm 已捕获的控件。它在正常停止时恢复控件,并在 arm 失败后回滚控件。恢复失败会成为待处理的恢复操作,并在当前设备通道上重试。
自 v0.8.15 起,滚轮按住绑定仅会为带 RAW_WHEEL 的控件启用 raw_wheel。它会
在停止、重连失败和 arm 失败时清除该位。它不会恢复已有的 raw_wheel: true 状态。
没有该标志的控件仍是普通分流点击。
原始滚轮报告不包含源 CID。OpenLogi 将报告归属到最早按下的滚轮按住控件。 如果没有按住控件,或者高分辨率倍数无法缩放,OpenLogi 会丢弃该报告。
主机切换会话处理 DivertedButtons 和 AnalyticsKeyEvents。它不会将原始 XY 或
原始滚轮事件视为主机切换。OpenLogi 不调用全局 fn 5 重置,因为该重置可能清除
会话不拥有的设置。
未确认的细节
Logi Options+ 2.8 将带非零 software ID 的未匹配 fn 3 响应作为已分流按键状态 变化处理。OpenLogi 会从事件路径中过滤非零 software ID。因此,它不会发布这类 迟到或外来响应。
静态证据无法显示该响应在硬件上的出现频率。OpenLogi 仍向调用者返回正常匹配的
fn 3 响应,类型为 CidReportingChangeEcho。触发条件和频率需要设备 trace。
Rust 用法
use hidpp::{
device::Device,
feature::{
EmittingFeature,
reprog_controls::{CidReportingChange, ReprogControlsFeature},
},
protocol::v20::Hidpp20Error,
};
async fn divert_first_control(mut device: Device) -> Result<(), Hidpp20Error> {
device.enumerate_features().await?;
let Some(feature) = device.get_feature::<ReprogControlsFeature>() else {
return Ok(());
};
for index in 0..feature.get_count().await? {
let info = feature.get_cid_info(index).await?;
if !info.flags.is_divertable() {
continue;
}
let before = feature.get_cid_reporting(info.cid).await?;
let _events = feature.listen();
feature
.set_cid_reporting(
info.cid,
CidReportingChange {
diverted: Some(true),
..Default::default()
},
)
.await?;
// Restore the reporting bit this example changed before returning.
feature
.set_cid_reporting(
info.cid,
CidReportingChange {
diverted: Some(before.diverted),
..Default::default()
},
)
.await?;
break;
}
Ok(())
}