0x1C00 · persistentRemappableAction
读写按控件和主机保存的非易失性 HID 动作分配。
persistentRemappableAction(0x1c00)将控件的 HID 动作保存在设备非易失性
存储中。它支持为每个控件和每个主机槽保存独立映射。它的 ControlId 与
0x1b04 共用。
当两个功能同时存在时,实时 0x1b04 分流优先于此处的持久映射。这个功能与
键盘相关,但 v0.8.15 的 OpenLogi 没有设备层、
agent、CLI 或 GUI 使用者。
**规格:**没有公开 Logitech 规格。布局由 Logi Options+ 2.8(静态分析)逆向得出。**交叉核对:**Solaar。**用于:**OpenLogi 未使用。
函数参考
PersistentRemappableActionFeature 实现 0x1c00。
方法
| 函数 | HID++ fn | 签名 | 返回值 |
|---|---|---|---|
get_feature_info | 0 | () | RemappableCapabilities |
get_count | 1 | () | RemappableInfo |
get_cid_info | 2 | (index: u8, host: HostIndex) | ControlId |
get_persistent_action | 3 | (cid: ControlId, host: HostIndex) | PersistentAction |
set_persistent_action | 4 | (cid: ControlId, host: HostIndex, config: PersistentActionConfig) | () |
reset_persistent_action | 5 | (cid: ControlId, host: HostIndex) | () |
reset_to_factory_settings | 6 | (hosts: HostMask) | () |
所有方法都是 async。它们返回 Result<…, Hidpp20Error>。
类型
RemappableCapabilities
| 标志 | 位 | 说明 |
|---|---|---|
KEYBOARD_REPORT | 1 << 0 | 键盘或小键盘输出。 |
MOUSE_BUTTONS | 1 << 1 | 鼠标按钮输出。 |
X_DISPLACEMENT | 1 << 2 | 鼠标 X 位移。 |
Y_DISPLACEMENT | 1 << 3 | 鼠标 Y 位移。 |
VERTICAL_ROLLER | 1 << 4 | 垂直滚轮输出。 |
HORIZONTAL_ROLLER | 1 << 5 | 水平滚轮或 AC pan 输出。 |
CONSUMER_CONTROL | 1 << 6 | Consumer Control 输出。 |
INTERNAL_FUNCTION | 1 << 7 | 内部函数输出。 |
POWER_KEY | 1 << 8 | 电源键输出。 |
RemappableInfo 和 HostIndex
| 类型 | 字段或变体 | 说明 |
|---|---|---|
RemappableInfo | count: u8 | 控件 ID 数量。 |
RemappableInfo | host_count: u8 | 设备主机槽数量。 |
HostIndex | Current | 当前设备主机。编码为 0xff。 |
HostIndex | Slot(u8) | 从零开始的主机槽。 |
PersistentAction
| 字段 | 类型 | 说明 |
|---|---|---|
cid | ControlId | 已读取动作的控件。 |
host | HostIndex | 此映射的主机槽。 |
action_id | ActionId | 动作类型。 |
value | u16 | usage、位移或内部函数索引。 |
modifier_mask | ModifierMask | 键盘动作的修饰键。 |
status | PersistentActionStatus | 完整响应状态字节。 |
PersistentAction::is_remapped() 测试状态位 0。
PersistentActionConfig
| 字段 | 类型 | 说明 |
|---|---|---|
action_id | ActionId | 要保存的动作。 |
value | u16 | usage、位移或内部函数索引。 |
modifier_mask | ModifierMask | 键盘动作的修饰键。 |
ActionId
| 变体 | 值 | 说明 |
|---|---|---|
Empty | 0x00 | 无输出。读取响应报告零值和零修饰键。 |
SendKeyboard | 0x01 | 键盘或小键盘报告,HID usage page 7。 |
SendMouseButton | 0x02 | 鼠标按钮报告,HID usage page 9。 |
SendXDisplacement | 0x03 | 鼠标 X 位移,usage page 1 code 0x30。 |
SendYDisplacement | 0x04 | 鼠标 Y 位移,usage page 1 code 0x31。 |
SendVerticalRoller | 0x05 | 垂直滚轮,usage page 1 code 0x38。 |
SendHorizontalRoller | 0x06 | 水平滚轮或 AC pan,usage page 12 code 0x0238。 |
SendConsumerControl | 0x07 | Consumer Control 报告,HID usage page 12。 |
ExecuteInternalFunction | 0x08 | 内部函数。value 是函数索引。 |
SendPowerKey | 0x09 | 电源键报告,HID usage page 1。 |
ModifierMask
| 标志 | 位 | 说明 |
|---|---|---|
LEFT_CTRL | 1 << 0 | 左 Control。 |
LEFT_SHIFT | 1 << 1 | 左 Shift。 |
LEFT_ALT | 1 << 2 | 左 Alt。 |
LEFT_GUI | 1 << 3 | 左 GUI。 |
RIGHT_CTRL | 1 << 4 | 右 Control。 |
RIGHT_SHIFT | 1 << 5 | 右 Shift。 |
RIGHT_ALT | 1 << 6 | 右 Alt。 |
RIGHT_GUI | 1 << 7 | 右 GUI。 |
PersistentActionStatus
| 标志 | 位 | 说明 |
|---|---|---|
REMAPPED | 1 << 0 | 动作不同于出厂默认值。 |
PersistentActionStatus 保留位 1–7。这些位的含义未知。
HostMask
| 标志 | 位 | 说明 |
|---|---|---|
HOST_1 | 1 << 0 | 主机 1。 |
HOST_2 | 1 << 1 | 主机 2。 |
HOST_3 | 1 << 2 | 主机 3。 |
事件
PersistentRemappableActionFeature 不实现事件监听器。
线格式
表格中的字节是逻辑 HID++ payload 字节。OpenLogi 发送 3 字节短请求,只有 fn 4 发送 16 字节长请求。读取方法使用扩展后的 16 字节响应视图,并忽略没有建模字段 的字节。
get_feature_info(fn 0)
请求:[0x00, 0x00, 0x00]。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | RemappableCapabilities | BE u16 位字段。 |
| 2–15 | — | OpenLogi 不解析字段。 |
get_count(fn 1)
请求:[0x00, 0x00, 0x00]。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0 | count | 控件 ID 数量。 |
| 1 | host_count | 主机槽数量。 |
| 2–15 | — | OpenLogi 不解析字段。 |
get_cid_info(fn 2)
请求:[index, host, 0x00]。host 是 HostIndex 的 u8 值。Current 为
0xff,Slot(n) 为 n。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE ControlId。 |
| 2–15 | — | OpenLogi 不解析字段。 |
get_persistent_action(fn 3)
请求:[cid_hi, cid_lo, host]。cid_hi,cid_lo 是 BE u16 格式的 ControlId。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE ControlId。 |
| 2 | host | HostIndex 字节。 |
| 3 | action_id | ActionId,包括 Empty = 0x00。 |
| 4–5 | value | BE u16。 |
| 6 | modifier_mask | ModifierMask 位。 |
| 7 bit 0 | REMAPPED | 已知的 PersistentActionStatus 位。Options+ 2.8;Solaar。 |
| 7 bits 1–7 | unknown status | 由 PersistentActionStatus 保留,含义未确认。Options+ 2.8;Solaar。 |
| 8–15 | — | OpenLogi 不解析字段。 |
set_persistent_action(fn 4)
OpenLogi 发送 16 字节长请求。它在字节 7–15 写入零填充。
| 字节 | 字段 | 备注 |
|---|---|---|
| 0–1 | cid | BE ControlId。 |
| 2 | host | HostIndex 字节。 |
| 3 | action_id | ActionId。 |
| 4–5 | value | BE u16。 |
| 6 | modifier_mask | ModifierMask 位。 |
| 7–15 | — | OpenLogi 写入固定的零填充。 |
请求成功后,OpenLogi 返回 ()。它不解析响应体,也不读取动作来确认写入结果。
reset_persistent_action(fn 5)
请求:[cid_hi, cid_lo, host]。ControlId 使用 BE 顺序。OpenLogi 返回 (),且
不解析响应体。
reset_to_factory_settings(fn 6)
请求:[hosts_bits, 0x00, 0x00]。hosts_bits 是 HostMask。位 0、1 和 2 分别
选择 HOST_1、HOST_2 和 HOST_3。OpenLogi 返回 (),且不解析响应体。
OpenLogi 的用法
OpenLogi 为 0x1c00 注册类型化封装。v0.8.15 的 openlogi-device、agent、CLI 和
GUI 都不调用该封装。因此,OpenLogi 不会写入、重置、恢复或读回持久动作。
直接 Rust 调用者必须从 RemappableCapabilities 中选择受支持的动作。调用者也必须
选择目标 HostIndex。OpenLogi 不会在封装外增加能力门控、读改写层或写后读回。
未确认的细节
只有状态位 0 的含义已恢复。Options+ 2.8 和 Solaar 都保留完整状态字节,但没有
确定状态位 1–7 的含义。OpenLogi 在 PersistentActionStatus 中保留这些未知位。
协议证据来自静态分析。没有硬件 trace 验证状态位含义、0xff 当前主机 sentinel,
或设备如何处理 fn 4 填充。
Rust 用法
use hidpp::{
device::Device,
feature::{
hosts_info::HostIndex,
persistent_remappable_action::{
ActionId, ModifierMask, PersistentActionConfig,
PersistentRemappableActionFeature, RemappableCapabilities,
},
},
protocol::v20::Hidpp20Error,
};
async fn map_first_control(mut device: Device) -> Result<(), Hidpp20Error> {
device.enumerate_features().await?;
let Some(feature) = device.get_feature::<PersistentRemappableActionFeature>() else {
return Ok(());
};
let caps = feature.get_feature_info().await?;
if !caps.contains(RemappableCapabilities::KEYBOARD_REPORT) {
return Ok(());
}
let info = feature.get_count().await?;
if info.count == 0 {
return Ok(());
}
let host = HostIndex::Current;
let cid = feature.get_cid_info(0, host).await?;
let config = PersistentActionConfig {
action_id: ActionId::SendKeyboard,
value: 0x0006, // HID keycode for C.
modifier_mask: ModifierMask::LEFT_CTRL,
};
feature.set_persistent_action(cid, host, config).await?;
Ok(())
}