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, FeatureEndpoint, event_payload},
12 nibble::U4,
13 protocol::v20::Hidpp20Error,
14};
15
16pub struct HiResWheelFeature {
21 endpoint: FeatureEndpoint,
23
24 emitter: Arc<EventEmitter<HiResWheelEvent>>,
26
27 msg_listener_hdl: u32,
31}
32
33impl CreatableFeature for HiResWheelFeature {
34 const ID: u16 = 0x2121;
35 const STARTING_VERSION: u8 = 0;
36
37 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
38 let emitter = Arc::new(EventEmitter::new());
39
40 let hdl = chan.add_msg_listener({
41 let emitter = Arc::clone(&emitter);
42
43 move |raw, matched| {
44 let Some((func, payload)) =
45 event_payload(raw, matched, device_index, feature_index)
46 else {
47 return;
48 };
49
50 let event = match func.to_lo() {
52 0 => {
53 let Ok(resolution) =
54 WheelResolution::try_from((payload[0] & (1 << 4)) >> 4)
55 else {
56 return;
57 };
58
59 HiResWheelEvent::WheelMovement(WheelMovementData {
60 resolution,
61 periods: U4::from_lo(payload[0]),
62 delta_vertical: i16::from_be_bytes(payload[1..=2].try_into().unwrap()),
63 })
64 }
65 1 => {
66 let Ok(state) = WheelRatchetState::try_from(payload[0] & 1) else {
67 return;
68 };
69
70 HiResWheelEvent::RatchetSwitch(state)
71 }
72 _ => return,
73 };
74
75 emitter.emit(event);
76 }
77 });
78
79 Self {
80 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
81 emitter,
82 msg_listener_hdl: hdl,
83 }
84 }
85}
86
87impl Feature for HiResWheelFeature {}
88
89impl EmittingFeature<HiResWheelEvent> for HiResWheelFeature {
90 fn listen(&self) -> async_channel::Receiver<HiResWheelEvent> {
91 self.emitter.create_receiver()
92 }
93}
94
95impl Drop for HiResWheelFeature {
96 fn drop(&mut self) {
97 self.endpoint
98 .chan()
99 .remove_msg_listener(self.msg_listener_hdl);
100 }
101}
102
103impl HiResWheelFeature {
104 pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
106 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
107
108 Ok(WheelCapabilities {
109 multiplier: payload[0],
110 has_invert: payload[1] & (1 << 3) != 0,
111 has_switch: payload[1] & (1 << 2) != 0,
112 ratches_per_rotation: payload[2],
113 wheel_diameter: payload[3],
114 })
115 }
116
117 pub async fn get_wheel_mode(&self) -> Result<WheelMode, Hidpp20Error> {
119 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
120
121 Ok(WheelMode {
122 inverted: payload[0] & (1 << 2) != 0,
123 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
124 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
125 target: WheelEventTarget::try_from(payload[0] & 1)
126 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
127 })
128 }
129
130 pub async fn set_wheel_mode(
138 &self,
139 target: WheelEventTarget,
140 resolution: WheelResolution,
141 inverted: bool,
142 ) -> Result<WheelMode, Hidpp20Error> {
143 let mut mode_byte = 0u8;
144 if inverted {
145 mode_byte |= 1 << 2;
146 }
147 mode_byte |= u8::from(resolution) << 1;
148 mode_byte |= u8::from(target);
149
150 let payload = self
151 .endpoint
152 .call(2, [mode_byte, 0x00, 0x00])
153 .await?
154 .extend_payload();
155
156 Ok(WheelMode {
157 inverted: payload[0] & (1 << 2) != 0,
158 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
159 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
160 target: WheelEventTarget::try_from(payload[0] & 1)
161 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
162 })
163 }
164
165 pub async fn get_ratchet_switch_state(&self) -> Result<WheelRatchetState, Hidpp20Error> {
167 let payload = self.endpoint.call(3, [0; 3]).await?.extend_payload();
168
169 WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
170 }
171}
172
173#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
176#[cfg_attr(feature = "serde", derive(serde::Serialize))]
177#[non_exhaustive]
178pub struct WheelCapabilities {
179 pub multiplier: u8,
183
184 pub has_invert: bool,
189
190 pub has_switch: bool,
192
193 pub ratches_per_rotation: u8,
196
197 pub wheel_diameter: u8,
199}
200
201#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
204#[cfg_attr(feature = "serde", derive(serde::Serialize))]
205#[non_exhaustive]
206pub struct WheelMode {
207 pub inverted: bool,
210
211 pub resolution: WheelResolution,
213
214 pub target: WheelEventTarget,
216}
217
218#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
220#[cfg_attr(feature = "serde", derive(serde::Serialize))]
221#[non_exhaustive]
222#[repr(u8)]
223pub enum WheelResolution {
224 Low = 0,
225 High = 1,
226}
227
228#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
230#[cfg_attr(feature = "serde", derive(serde::Serialize))]
231#[non_exhaustive]
232#[repr(u8)]
233pub enum WheelEventTarget {
234 Native = 0,
235 Diverted = 1,
236}
237
238#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
240#[cfg_attr(feature = "serde", derive(serde::Serialize))]
241#[non_exhaustive]
242#[repr(u8)]
243pub enum WheelRatchetState {
244 Freespin = 0,
245 Ratchet = 1,
246}
247
248#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
250#[cfg_attr(feature = "serde", derive(serde::Serialize))]
251#[non_exhaustive]
252pub enum HiResWheelEvent {
253 WheelMovement(WheelMovementData),
255
256 RatchetSwitch(WheelRatchetState),
260}
261
262#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
264#[cfg_attr(feature = "serde", derive(serde::Serialize))]
265#[non_exhaustive]
266pub struct WheelMovementData {
267 pub resolution: WheelResolution,
269
270 pub periods: U4,
272
273 pub delta_vertical: i16,
276}