hidpp/feature/
pointer_motion_scaling.rs1use std::num::NonZeroU16;
10
11use openlogi_hidpp_derive::Feature;
12
13use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
14
15#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize))]
21pub struct PointerScaling(NonZeroU16);
22
23impl PointerScaling {
24 pub const ONE: Self = match NonZeroU16::new(0x0100) {
26 Some(raw) => Self(raw),
27 None => panic!("0x0100 is non-zero"),
28 };
29
30 #[must_use]
32 pub const fn from_raw(raw: u16) -> Option<Self> {
33 match NonZeroU16::new(raw) {
34 Some(raw) => Some(Self(raw)),
35 None => None,
36 }
37 }
38
39 #[must_use]
41 pub const fn raw(self) -> u16 {
42 self.0.get()
43 }
44
45 #[must_use]
47 pub fn multiplier(self) -> f32 {
48 f32::from(self.raw()) / 256.0
49 }
50
51 fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
52 Self::from_raw(u16::from_be_bytes([payload[0], payload[1]]))
53 .ok_or(Hidpp20Error::UnsupportedResponse)
54 }
55}
56
57#[derive(Clone, Feature)]
59#[creatable(id = 0x2205, version = 0)]
60pub struct PointerMotionScalingFeature {
61 endpoint: FeatureEndpoint,
63}
64
65impl PointerMotionScalingFeature {
66 pub async fn get_pointer_scaling(&self) -> Result<PointerScaling, Hidpp20Error> {
68 let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
69 PointerScaling::from_payload(&payload)
70 }
71
72 pub async fn set_pointer_scaling(
76 &self,
77 scaling: PointerScaling,
78 ) -> Result<PointerScaling, Hidpp20Error> {
79 let [msb, lsb] = scaling.raw().to_be_bytes();
80 let payload = self
81 .endpoint
82 .call(1, [msb, lsb, 0x00])
83 .await?
84 .extend_payload();
85 PointerScaling::from_payload(&payload)
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use std::assert_matches;
92
93 use super::PointerScaling;
94 use crate::protocol::v20::Hidpp20Error;
95
96 fn payload(msb: u8, lsb: u8) -> [u8; 16] {
97 let mut payload = [0; 16];
98 payload[0] = msb;
99 payload[1] = lsb;
100 payload
101 }
102
103 #[test]
104 fn parses_scaling_most_significant_byte_first() {
105 let scaling = PointerScaling::from_payload(&payload(0x01, 0x80)).unwrap();
106
107 assert_eq!(scaling.raw(), 0x0180);
108 assert!((scaling.multiplier() - 1.5).abs() < f32::EPSILON);
109 }
110
111 #[test]
112 fn rejects_a_zero_scaling() {
113 assert_matches!(
114 PointerScaling::from_payload(&payload(0x00, 0x00)),
115 Err(Hidpp20Error::UnsupportedResponse)
116 );
117 assert_eq!(PointerScaling::from_raw(0), None);
118 }
119
120 #[test]
121 fn one_is_the_unscaled_default() {
122 assert_eq!(PointerScaling::ONE.raw(), 0x0100);
123 assert!((PointerScaling::ONE.multiplier() - 1.0).abs() < f32::EPSILON);
124 }
125}