hidpp/feature/hires_wheel/
mod.rs1use std::{hash::Hash, sync::Arc};
5
6use num_enum::{IntoPrimitive, TryFromPrimitive};
7
8use crate::{
9 channel::{HidppChannel, MessageListenerGuard},
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: MessageListenerGuard,
29}
30
31impl CreatableFeature for HiResWheelFeature {
32 const ID: u16 = 0x2121;
33 const STARTING_VERSION: u8 = 0;
34
35 fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
36 let emitter = Arc::new(EventEmitter::new());
37
38 let listener = chan.add_msg_listener_guarded({
39 let emitter = Arc::clone(&emitter);
40
41 move |raw, matched| {
42 let Some((func, payload)) =
43 event_payload(raw, matched, device_index, feature_index)
44 else {
45 return;
46 };
47
48 let event = match func.to_lo() {
50 0 => {
51 let Ok(resolution) =
52 WheelResolution::try_from((payload[0] & (1 << 4)) >> 4)
53 else {
54 return;
55 };
56
57 HiResWheelEvent::WheelMovement(WheelMovementData {
58 resolution,
59 periods: U4::from_lo(payload[0]),
60 delta_vertical: i16::from_be_bytes(payload[1..=2].try_into().unwrap()),
61 })
62 }
63 1 => {
64 let Ok(state) = WheelRatchetState::try_from(payload[0] & 1) else {
65 return;
66 };
67
68 HiResWheelEvent::RatchetSwitch(state)
69 }
70 _ => return,
71 };
72
73 emitter.emit(event);
74 }
75 });
76
77 Self {
78 endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
79 emitter,
80 _msg_listener: listener,
81 }
82 }
83}
84
85impl Feature for HiResWheelFeature {}
86
87impl EmittingFeature<HiResWheelEvent> for HiResWheelFeature {
88 fn listen(&self) -> async_channel::Receiver<HiResWheelEvent> {
89 self.emitter.create_receiver()
90 }
91}
92
93impl HiResWheelFeature {
94 pub async fn get_wheel_capabilities(&self) -> Result<WheelCapabilities, Hidpp20Error> {
96 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
97
98 Ok(WheelCapabilities {
99 multiplier: payload[0],
100 has_invert: payload[1] & (1 << 3) != 0,
101 has_switch: payload[1] & (1 << 2) != 0,
102 ratches_per_rotation: payload[2],
103 wheel_diameter: payload[3],
104 })
105 }
106
107 pub async fn get_wheel_mode(&self) -> Result<WheelMode, Hidpp20Error> {
109 let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
110
111 Ok(WheelMode {
112 inverted: payload[0] & (1 << 2) != 0,
113 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
114 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
115 target: WheelEventTarget::try_from(payload[0] & 1)
116 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
117 })
118 }
119
120 pub async fn set_wheel_mode(
128 &self,
129 target: WheelEventTarget,
130 resolution: WheelResolution,
131 inverted: bool,
132 ) -> Result<WheelMode, Hidpp20Error> {
133 let mut mode_byte = 0u8;
134 if inverted {
135 mode_byte |= 1 << 2;
136 }
137 mode_byte |= u8::from(resolution) << 1;
138 mode_byte |= u8::from(target);
139
140 let payload = self
141 .endpoint
142 .call(2, [mode_byte, 0x00, 0x00])
143 .await?
144 .extend_payload();
145
146 Ok(WheelMode {
147 inverted: payload[0] & (1 << 2) != 0,
148 resolution: WheelResolution::try_from((payload[0] & (1 << 1)) >> 1)
149 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
150 target: WheelEventTarget::try_from(payload[0] & 1)
151 .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
152 })
153 }
154
155 pub async fn get_ratchet_switch_state(&self) -> Result<WheelRatchetState, Hidpp20Error> {
157 let payload = self.endpoint.call(3, [0; 3]).await?.extend_payload();
158
159 WheelRatchetState::try_from(payload[0] & 1).map_err(|_| Hidpp20Error::UnsupportedResponse)
160 }
161}
162
163#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
166#[cfg_attr(feature = "serde", derive(serde::Serialize))]
167#[non_exhaustive]
168pub struct WheelCapabilities {
169 pub multiplier: u8,
173
174 pub has_invert: bool,
179
180 pub has_switch: bool,
182
183 pub ratches_per_rotation: u8,
186
187 pub wheel_diameter: u8,
189}
190
191#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
194#[cfg_attr(feature = "serde", derive(serde::Serialize))]
195#[non_exhaustive]
196pub struct WheelMode {
197 pub inverted: bool,
200
201 pub resolution: WheelResolution,
203
204 pub target: WheelEventTarget,
206}
207
208#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
210#[cfg_attr(feature = "serde", derive(serde::Serialize))]
211#[non_exhaustive]
212#[repr(u8)]
213pub enum WheelResolution {
214 Low = 0,
216 High = 1,
218}
219
220#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
222#[cfg_attr(feature = "serde", derive(serde::Serialize))]
223#[non_exhaustive]
224#[repr(u8)]
225pub enum WheelEventTarget {
226 Native = 0,
228 Diverted = 1,
230}
231
232#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
234#[cfg_attr(feature = "serde", derive(serde::Serialize))]
235#[non_exhaustive]
236#[repr(u8)]
237pub enum WheelRatchetState {
238 Freespin = 0,
240 Ratchet = 1,
242}
243
244#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
246#[cfg_attr(feature = "serde", derive(serde::Serialize))]
247#[non_exhaustive]
248pub enum HiResWheelEvent {
249 WheelMovement(WheelMovementData),
251
252 RatchetSwitch(WheelRatchetState),
256}
257
258#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
260#[cfg_attr(feature = "serde", derive(serde::Serialize))]
261#[non_exhaustive]
262pub struct WheelMovementData {
263 pub resolution: WheelResolution,
265
266 pub periods: U4,
268
269 pub delta_vertical: i16,
272}