1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
//! Implements the `HiResWheel` feature (ID `0x2121`) that allows configuring
//! and using high-resolution scrolling.
use std::hash::Hash;
use num_enum::{IntoPrimitive, TryFromPrimitive};
use openlogi_hidpp_derive::Feature;
use crate::{
feature::{DecodeEvent, EventSource, FeatureEndpoint},
nibble::U4,
protocol::v20::Hidpp20Error,
};
/// Implements the `HiResWheel` / `0x2121` feature.
///
/// The analytics part of the feature is not implemented here as its data
/// structure lacks any documentation.
#[derive(Feature)]
#[creatable(id = 0x2121, version = 0)]
pub struct HiResWheelFeature {
/// The endpoint this feature talks to.
endpoint: FeatureEndpoint,
/// Publishes decoded events to listeners.
events: EventSource<HiResWheelEvent>,
}
impl DecodeEvent for HiResWheelEvent {
fn decode(sub_id: u8, payload: &[u8; 16]) -> Option<Self> {
// HiResWheel dispatches on the sub-id: 0 = movement, 1 = ratchet switch.
match sub_id {
0 => {
let resolution = WheelResolution::try_from((payload[0] & (1 << 4)) >> 4).ok()?;
Some(HiResWheelEvent::WheelMovement(WheelMovementData {
resolution,
periods: U4::from_lo(payload[0]),
delta_vertical: i16::from_be_bytes([payload[1], payload[2]]),
}))
}
1 => {
let state = WheelRatchetState::try_from(payload[0] & 1).ok()?;
Some(HiResWheelEvent::RatchetSwitch(state))
}
_ => None,
}
}
}
impl HiResWheelFeature {
/// Retrieves the capabilities of the hi-res wheel and this feature.
pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
Ok(WheelCapabilities {
multiplier: payload[0],
has_invert: payload[1] & (1 << 3) != 0,
has_switch: payload[1] & (1 << 2) != 0,
ratches_per_rotation: payload[2],
wheel_diameter: payload[3],
})
}
/// Retrieves the current mode of the hi-res wheel.
pub async fn get_wheel_mode(&self) -> Result<WheelMode, Hidpp20Error> {
let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
Ok(WheelMode {
inverted: payload[0] & (1 << 2) != 0,
resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
target: WheelEventTarget::try_from(payload[0] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
/// Sets the mode of the hi-res wheel.
///
/// Setting the bit to control analytics collection is not supported in this
/// feature implementation as the analytics data structure is completely
/// undocumented.\
/// If this is implemented in the future, a new implementation will do so to
/// not break this one.
pub async fn set_wheel_mode(
&self,
target: WheelEventTarget,
resolution: WheelResolution,
inverted: bool,
) -> Result<WheelMode, Hidpp20Error> {
let mut mode_byte = 0u8;
if inverted {
mode_byte |= 1 << 2;
}
mode_byte |= u8::from(resolution) << 1;
mode_byte |= u8::from(target);
let payload = self
.endpoint
.call(2, [mode_byte, 0x00, 0x00])
.await?
.extend_payload();
Ok(WheelMode {
inverted: payload[0] & (1 << 2) != 0,
resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
target: WheelEventTarget::try_from(payload[0] & 1)
.map_err(|_| Hidpp20Error::UnsupportedResponse)?,
})
}
/// Retrieves the current state of the ratchet switch.
pub async fn get_ratchet_switch_state(&self) -> Result<WheelRatchetState, Hidpp20Error> {
let payload = self.endpoint.call(3, [0; 3]).await?.extend_payload();
WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
}
}
/// Represents the capabilities of the hi-res wheel and this feature as reported
/// by [`HiResWheelFeature::get_wheel_capabilities`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelCapabilities {
/// The report multiplier for the high-resolution mode. A single ratchet
/// distance will produce this amount of wheel movement reports in hi-res
/// mode.
pub multiplier: u8,
/// Whether the device supports inverting the scrolling direction when in
/// native HID reporting mode.
///
/// Inverting is never supported in diverted HID++ mode.
pub has_invert: bool,
/// Whether the device has a switch to control the ratchet mode.
pub has_switch: bool,
/// The amount of ratches that would be generated by a whole rotation of the
/// scroll wheel.
pub ratches_per_rotation: u8,
/// The nominal wheel diameter in millimeters.
pub wheel_diameter: u8,
}
/// Represents the wheel mode as reported by
/// [`HiResWheelFeature::get_wheel_mode`].
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelMode {
/// Whether the scrolling direction is inverted.
/// Only applies when in native HID mode.
pub inverted: bool,
/// The current scrolling resolution.
pub resolution: WheelResolution,
/// The target of wheel movement reports (native or diverted).
pub target: WheelEventTarget,
}
/// Represents the resolution of the hi-res wheel.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelResolution {
/// Low-resolution wheel reporting.
Low = 0,
/// High-resolution wheel reporting.
High = 1,
}
/// Represents the target of wheel movement reports.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelEventTarget {
/// Wheel reports go to the native HID path.
Native = 0,
/// Wheel reports are diverted to HID++ events.
Diverted = 1,
}
/// Represents the state of the wheel ratchet.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
#[repr(u8)]
pub enum WheelRatchetState {
/// Wheel is in free-spin mode.
Freespin = 0,
/// Wheel is in ratchet mode.
Ratchet = 1,
}
/// Represents an event emitted by the [`HiResWheelFeature`] feature.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub enum HiResWheelEvent {
/// Is emitted whenever the scroll wheel is moved in diverted HID++ mode.
WheelMovement(WheelMovementData),
/// Is emitted whenever the wheel ratchet mode is changed.
///
/// This event is always enabled.
RatchetSwitch(WheelRatchetState),
}
/// Represents the data of the [`HiResWheelEvent::WheelMovement`] event.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize))]
#[non_exhaustive]
pub struct WheelMovementData {
/// The current resolution of the wheel.
pub resolution: WheelResolution,
/// The amount of sampling periods for this event. Maxes at 15.
pub periods: U4,
/// The vertical movement delta. Moving away from the user produces positive
/// values.
pub delta_vertical: i16,
}