0x2121 · hiResWheel
读取并保留高分辨率滚轮状态,接收转发的移动和棘轮事件,并支持 OpenLogi 的按设备平滑滚轮。
hiResWheel 配置高分辨率滚轮报告及其原生或转发路由。它还报告滚轮移动和棘轮切换。OpenLogi v0.8.15 修改已定义设置时会保留完整的三字节模式。
**规格:**Logitech HID++ 2.0 —— x2121 hiResWheel(v1)。**交叉核对:**Logi Options+ 2.8(静态分析)、Solaar。用于:滚动。
函数参考
HiResWheelFeature(0x2121)支持 feature 版本 0,并提供以下方法。
方法
| 函数 | HID++ fn | 签名 | 返回值 |
|---|---|---|---|
get_wheel_capabilities | 0 | () | Result<WheelCapabilities, Hidpp20Error> |
get_wheel_mode | 1 | () | Result<WheelMode, Hidpp20Error> |
set_wheel_mode | 2 | (mode: WheelMode) | Result<WheelMode, Hidpp20Error> |
get_ratchet_switch_state | 3 | () | Result<WheelRatchetState, Hidpp20Error> |
listen | event | () | async_channel::Receiver<HiResWheelEvent> |
前四个方法是 async。listen 是 EmittingFeature trait 的方法。OpenLogi 没有对应 function 4 的 get_analytics_data 方法。
类型
WheelCapabilities
| 字段 | 类型 | 说明 |
|---|---|---|
multiplier | u8 | 每个棘齿距离对应的高分辨率移动单位数。 |
has_priority | bool | 模式包含字节 1。Options+ 2.8 将其命名为 priority。 |
has_rate | bool | 模式包含字节 2。Options+ 2.8 将其命名为 rate。 |
has_switch | bool | 滚轮具有物理棘轮模式切换开关。 |
has_invert | bool | 设备支持原生 HID 方向反转。它不适用于转发报告。 |
has_analytics_data | bool | feature 报告 analytics 数据。OpenLogi 不读取此数据。 |
ratches_per_rotation | u8 | 一整圈中的棘齿数。 |
wheel_diameter | u8 | 标称滚轮直径,单位为毫米。 |
WheelMode
| 字段 | 类型 | 说明 |
|---|---|---|
target | WheelEventTarget | 原生 HID 或转发 HID++ 移动路由。 |
resolution | WheelResolution | 当前报告分辨率。 |
inverted | bool | 原生 HID 方向反转。 |
| private extension | private | 字节 0 中 0~2 以外的位,以及字节 1~2。OpenLogi 会逐字节保留这些由固件管理的数据。 |
WheelMode 没有公开构造函数。你必须使用 get_wheel_mode 读取它,只修改其公开字段,然后传给 set_wheel_mode。
WheelResolution
| 变体 | 值 | 说明 |
|---|---|---|
Low | 0 | 低分辨率报告。 |
High | 1 | 高分辨率报告。 |
WheelEventTarget
| 变体 | 值 | 说明 |
|---|---|---|
Native | 0 | 移动发送到原生 HID 路径。 |
Diverted | 1 | 移动作为 HID++ 事件到达。 |
WheelRatchetState
| 变体 | 值 | 说明 |
|---|---|---|
Freespin | 0 | 滚轮处于自由滚动模式。 |
Ratchet | 1 | 滚轮处于棘轮模式。 |
事件
HiResWheelEvent
| 变体 | 载荷 | 说明 |
|---|---|---|
WheelMovement | WheelMovementData | 转发的滚轮移动。 |
RatchetSwitch | WheelRatchetState | 棘轮模式变化。 |
WheelMovementData
| 字段 | 类型 | 说明 |
|---|---|---|
resolution | WheelResolution | 此移动事件报告的分辨率。 |
periods | U4 | 采样周期计数,范围为 0~15。 |
delta_vertical | i16 | BE 有符号垂直增量。远离用户时为正。 |
delta_horizontal | i16 | BE 有符号水平增量。仅能垂直移动的滚轮报告零。Options+ 2.8。 |
线格式
所有 OpenLogi function 请求均使用三字节载荷。OpenLogi 会在解码前将回复和事件扩展为 16 字节缓冲区。多字节移动增量为 BE 有符号 i16 值。
get_wheel_capabilities(fn 0)
请求:[0x00, 0x00, 0x00]。
回复:
| 字节 | 字段 | 说明 |
|---|---|---|
| 0 | multiplier | 原始 u8。 |
| 1 bit 0 | has_priority | Options+ 2.8。 |
| 1 bit 1 | has_rate | Options+ 2.8。 |
| 1 bit 2 | has_switch | 棘轮切换能力。 |
| 1 bit 3 | has_invert | 原生反转能力。 |
| 1 bit 4 | has_analytics_data | Options+ 2.8。 |
| 1 bits 5~7 | 保留或未知 | OpenLogi 忽略这些位。 |
| 2 | ratches_per_rotation | 原始 u8。 |
| 3 | wheel_diameter | 原始 u8 毫米值。 |
get_wheel_mode(fn 1)
请求:[0x00, 0x00, 0x00]。
回复:
| 字节 | 字段 | 说明 |
|---|---|---|
| 0 bit 0 | target | 0 为 Native;1 为 Diverted。 |
| 0 bit 1 | resolution | 0 为 Low;1 为 High。 |
| 0 bit 2 | inverted | 适用于原生 HID 报告。 |
| 0 bit 3 | analytics | Options+ 2.8。OpenLogi 保留此位,但不公开它。 |
| 0 bits 4~7 | 保留或未知 | OpenLogi 在私有模式扩展中逐字节保留这些位。 |
| 1 | priority | Options+ 2.8。OpenLogi 保留此字节,但不公开或验证它。 |
| 2 | rate | Options+ 2.8。OpenLogi 保留此字节,但不公开或验证它。 |
set_wheel_mode(fn 2)
**请求:**已读取 WheelMode 中的三个字节。
| 字节 | 写入字段 | 说明 |
|---|---|---|
| 0 | target、resolution、inverted 和扩展标志 | OpenLogi 用公开字段替换位 0~2。它保留 bit 3 和位 4~7。 |
| 1 | priority | 从已读取模式中逐字节保留。Options+ 2.8。 |
| 2 | rate | 从已读取模式中逐字节保留。Options+ 2.8。 |
**回复:**与 get_wheel_mode 相同的三字节模式布局。
get_ratchet_switch_state(fn 3)
请求:[0x00, 0x00, 0x00]。
回复:
| 字节 | 字段 | 说明 |
|---|---|---|
| 0 bit 0 | WheelRatchetState | 0 为 Freespin;1 为 Ratchet。 |
| 0 bits 1~7 | 保留 | OpenLogi 忽略这些位。 |
get_analytics_data(fn 4)
OpenLogi 未实现此 function。Options+ 2.8 仅在 analytics 数据可用时读取 function 4,并接受至少 16 个原始字节的回复。请求布局和这 16 个字节的含义仍未确认。
事件
WheelMovement(event fn 0)
| 字节 | 字段 | 说明 |
|---|---|---|
| 0 bit 4 | resolution | 0 为 Low;1 为 High。 |
| 0 bits 3~0 | periods | U4,范围为 0~15。 |
| 1~2 | delta_vertical | BE 有符号 i16。 |
| 3~4 | delta_horizontal | BE 有符号 i16。Options+ 2.8 要求五字节移动载荷。 |
RatchetSwitch(event fn 1)
| 字节 | 字段 | 说明 |
|---|---|---|
| 0 bit 0 | WheelRatchetState | 0 为 Freespin;1 为 Ratchet。 |
OpenLogi 的用法
OpenLogi 将 0x2121 记录为 hires_wheel 能力。它会单独读取 has_invert,再公开原生滚轮反转。常规分辨率和反转写入会先读取完整模式。它们只修改 target、resolution 或 inverted,发送原始扩展字节,检查 setter 回显,然后读回并检查已定义字段。原生反转写入需要 has_invert。
每台设备的 smooth_wheel 选项默认值为 false。仅当设备具有 0x2121、设备已启用、鼠标捕获已启用且操作系统鼠标钩子可以发送输入时,此选项才会 arm。在 macOS 上,最后一个条件需要辅助功能访问权限。捕获会话读取能力和模式,然后以读-改-写方式写入 target = Diverted,并在可用时写入已配置的分辨率或反转值。该会话拥有滚轮期间,常规原生模式重放路径会停止写入。
会话将垂直 WheelMovement 报告路由给 agent。高分辨率移动会除以 multiplier,低分辨率移动每个棘齿使用一个单位。agent 应用已配置的反转和 macOS 自然滚动方向。即使全局平滑滚动选项关闭,agent 也会发送缓动滚动。如果滚动运行时无法接受输入,agent 会直接发送已缩放的垂直增量。OpenLogi 当前会丢弃 delta_horizontal,且只记录 RatchetSwitch;两者均不会成为用户可见事件。
正常停止、断开连接、捕获计划变化或 arm 失败时,OpenLogi 只以读-改-写方式写入 target = Native。此操作会保留分辨率、反转、analytics、priority、rate 和未知模式位。恢复失败会保持为待处理状态,并通过当前通道重试。如果 agent 异常退出,滚轮可能保持转发状态,直到 OpenLogi 重新连接或再次运行。
未确认的细节
Options+ 2.8 将字节 0 bit 3 命名为 analytics,并将字节 1~2 命名为 priority 和 rate。其静态分析未确定这些字段的合法范围或设备行为。OpenLogi 保留这三个字段,但不将其公开为设置。
Options+ 2.8 要求五字节 WheelMovement 载荷。OpenLogi 会解码扩展后的 16 字节缓冲区,且不会拒绝原始长度较短的载荷。截断事件的硬件行为仍未确认。
Options+ 2.8 从 function 4 接受至少 16 个字节。请求载荷及其字节含义仍未确认。OpenLogi 公开 analytics 能力标志,但没有 function 4 的原始 API。
Rust 用法
use std::sync::Arc;
use hidpp::{
channel::HidppChannel,
device::Device,
feature::{
EmittingFeature,
hires_wheel::{HiResWheelFeature, WheelEventTarget, WheelResolution},
},
};
async fn read_one_diverted_event(
channel: Arc<HidppChannel>,
device_index: u8,
) -> Result<(), Box<dyn std::error::Error>> {
let mut device = Device::new(channel, device_index).await?;
device.enumerate_features().await?;
let Some(feature) = device.get_feature::<HiResWheelFeature>() else {
return Ok(());
};
let mut mode = feature.get_wheel_mode().await?;
let original_target = mode.target;
mode.target = WheelEventTarget::Diverted;
mode.resolution = WheelResolution::High;
feature.set_wheel_mode(mode).await?;
let events = feature.listen();
if let Ok(event) = events.recv().await {
println!("{event:?}");
}
let mut restore = feature.get_wheel_mode().await?;
restore.target = original_target;
feature.set_wheel_mode(restore).await?;
Ok(())
}