hidpp/feature/hires_wheel/
mod.rs1use std::{hash::Hash, sync::Arc};
5
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7
8use crate::{
9 channel::HidppChannel,
10 event::EventEmitter,
11 feature::{CreatableFeature, EmittingFeature, Feature},
12 nibble::U4,
13 protocol::v20::{self, Hidpp20Error},
14};
15
16pub struct HiResWheelFeature {
21 chan: Arc<HidppChannel>,
23
24 device_index: u8,
26
27 feature_index: u8,
29
30 emitter: Arc<EventEmitter<HiResWheelEvent>>,
32
33 msg_listener_hdl: u32,
37}
38
39impl CreatableFeature for HiResWheelFeature {
40 const ID: u16 = 0x2121;
41 const STARTING_VERSION: u8 = 0;
42
43 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
44 let emitter = Arc::new(EventEmitter::new());
45
46 let hdl = chan.add_msg_listener({
47 let emitter = Arc::clone(&emitter);
48
49 move |raw, matched| {
50 if matched {
51 return;
52 }
53
54 let msg = v20::Message::from(raw);
55
56 let header = msg.header();
57 if header.device_index != device_index
58 || header.feature_index != feature_index
59 || header.software_id.to_lo() != 0
60 {
61 return;
62 }
63
64 let payload = msg.extend_payload();
65
66 let event = match header.function_id.to_lo() {
67 0 => {
68 let Ok(resolution) =
69 WheelResolution::try_from((payload[0] & (1 << 4)) >> 4)
70 else {
71 return;
72 };
73
74 HiResWheelEvent::WheelMovement(WheelMovementData {
75 resolution,
76 periods: U4::from_lo(payload[0]),
77 delta_vertical: i16::from_be_bytes(payload[1..=2].try_into().unwrap()),
78 })
79 }
80 1 => {
81 let Ok(state) = WheelRatchetState::try_from(payload[0] & 1) else {
82 return;
83 };
84
85 HiResWheelEvent::RatchetSwitch(state)
86 }
87 _ => return,
88 };
89
90 emitter.emit(event);
91 }
92 });
93
94 Self {
95 chan,
96 device_index,
97 feature_index,
98 emitter,
99 msg_listener_hdl: hdl,
100 }
101 }
102}
103
104impl Feature for HiResWheelFeature {}
105
106impl EmittingFeature<HiResWheelEvent> for HiResWheelFeature {
107 fn listen(&self) -> async_channel::Receiver<HiResWheelEvent> {
108 self.emitter.create_receiver()
109 }
110}
111
112impl Drop for HiResWheelFeature {
113 fn drop(&mut self) {
114 self.chan.remove_msg_listener(self.msg_listener_hdl);
115 }
116}
117
118impl HiResWheelFeature {
119 pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
121 let response = self
122 .chan
123 .send_v20(v20::Message::Short(
124 v20::MessageHeader {
125 device_index: self.device_index,
126 feature_index: self.feature_index,
127 function_id: U4::from_lo(0),
128 software_id: self.chan.get_sw_id(),
129 },
130 [0x00, 0x00, 0x00],
131 ))
132 .await?;
133
134 let payload = response.extend_payload();
135
136 Ok(WheelCapabilities {
137 multiplier: payload[0],
138 has_invert: payload[1] & (1 << 3) != 0,
139 has_switch: payload[1] & (1 << 2) != 0,
140 ratches_per_rotation: payload[2],
141 wheel_diameter: payload[3],
142 })
143 }
144
145 pub async fn get_wheel_mode(&self) -> Result<WheelMode, Hidpp20Error> {
147 let response = self
148 .chan
149 .send_v20(v20::Message::Short(
150 v20::MessageHeader {
151 device_index: self.device_index,
152 feature_index: self.feature_index,
153 function_id: U4::from_lo(1),
154 software_id: self.chan.get_sw_id(),
155 },
156 [0x00, 0x00, 0x00],
157 ))
158 .await?;
159
160 let payload = response.extend_payload();
161
162 Ok(WheelMode {
163 inverted: payload[0] & (1 << 2) != 0,
164 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
165 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
166 target: WheelEventTarget::try_from(payload[0] & 1)
167 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
168 })
169 }
170
171 pub async fn set_wheel_mode(
179 &self,
180 target: WheelEventTarget,
181 resolution: WheelResolution,
182 inverted: bool,
183 ) -> Result<WheelMode, Hidpp20Error> {
184 let mut mode_byte = 0u8;
185 if inverted {
186 mode_byte |= 1 << 2;
187 }
188 mode_byte |= u8::from(resolution) << 1;
189 mode_byte |= u8::from(target);
190
191 let response = self
192 .chan
193 .send_v20(v20::Message::Short(
194 v20::MessageHeader {
195 device_index: self.device_index,
196 feature_index: self.feature_index,
197 function_id: U4::from_lo(2),
198 software_id: self.chan.get_sw_id(),
199 },
200 [mode_byte, 0x00, 0x00],
201 ))
202 .await?;
203
204 let payload = response.extend_payload();
205
206 Ok(WheelMode {
207 inverted: payload[0] & (1 << 2) != 0,
208 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
209 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
210 target: WheelEventTarget::try_from(payload[0] & 1)
211 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
212 })
213 }
214
215 pub async fn get_ratchet_switch_state(&self) -> Result<WheelRatchetState, Hidpp20Error> {
217 let response = self
218 .chan
219 .send_v20(v20::Message::Short(
220 v20::MessageHeader {
221 device_index: self.device_index,
222 feature_index: self.feature_index,
223 function_id: U4::from_lo(3),
224 software_id: self.chan.get_sw_id(),
225 },
226 [0x00, 0x00, 0x00],
227 ))
228 .await?;
229
230 let payload = response.extend_payload();
231
232 WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
233 }
234}
235
236#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
239#[cfg_attr(feature = "serde", derive(serde::Serialize))]
240#[non_exhaustive]
241pub struct WheelCapabilities {
242 pub multiplier: u8,
246
247 pub has_invert: bool,
252
253 pub has_switch: bool,
255
256 pub ratches_per_rotation: u8,
259
260 pub wheel_diameter: u8,
262}
263
264#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
267#[cfg_attr(feature = "serde", derive(serde::Serialize))]
268#[non_exhaustive]
269pub struct WheelMode {
270 pub inverted: bool,
273
274 pub resolution: WheelResolution,
276
277 pub target: WheelEventTarget,
279}
280
281#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
283#[cfg_attr(feature = "serde", derive(serde::Serialize))]
284#[non_exhaustive]
285#[repr(u8)]
286pub enum WheelResolution {
287 Low = 0,
288 High = 1,
289}
290
291#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
293#[cfg_attr(feature = "serde", derive(serde::Serialize))]
294#[non_exhaustive]
295#[repr(u8)]
296pub enum WheelEventTarget {
297 Native = 0,
298 Diverted = 1,
299}
300
301#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
303#[cfg_attr(feature = "serde", derive(serde::Serialize))]
304#[non_exhaustive]
305#[repr(u8)]
306pub enum WheelRatchetState {
307 Freespin = 0,
308 Ratchet = 1,
309}
310
311#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
313#[cfg_attr(feature = "serde", derive(serde::Serialize))]
314#[non_exhaustive]
315pub enum HiResWheelEvent {
316 WheelMovement(WheelMovementData),
318
319 RatchetSwitch(WheelRatchetState),
323}
324
325#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
327#[cfg_attr(feature = "serde", derive(serde::Serialize))]
328#[non_exhaustive]
329pub struct WheelMovementData {
330 pub resolution: WheelResolution,
332
333 pub periods: U4,
335
336 pub delta_vertical: i16,
339}