Skip to main content

hidpp/feature/
mouse_pointer.rs

1//! Implements the `MousePointer` feature (ID `0x2200`) that reports a mouse's
2//! basic optical-sensor properties and pointer-tuning hints.
3
4use num_enum::{IntoPrimitive, TryFromPrimitive};
5use openlogi_hidpp_derive::Feature;
6
7use crate::{feature::FeatureEndpoint, protocol::v20::Hidpp20Error};
8
9/// The pointer-acceleration ("ballistics") curve a device suggests, based on its
10/// physical characteristics.
11///
12/// A host that provides multiple ballistics curves can pick a default from this
13/// hint; a host without its own ballistics ignores it.
14#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
15#[cfg_attr(feature = "serde", derive(serde::Serialize))]
16#[non_exhaustive]
17#[repr(u8)]
18pub enum PointerAcceleration {
19    /// No acceleration suggested.
20    None = 0,
21    /// A low acceleration curve.
22    Low = 1,
23    /// A medium acceleration curve.
24    Medium = 2,
25    /// A high acceleration curve.
26    High = 3,
27}
28
29/// Mouse-pointer information returned by
30/// [`MousePointerFeature::get_mouse_pointer_info`].
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33#[non_exhaustive]
34pub struct MousePointerInfo {
35    /// Typical sensor resolution on a standard surface, in 1-DPI steps.
36    ///
37    /// Real-world resolution may differ from this value by up to ±20% depending
38    /// on the surface.
39    pub sensor_resolution: u16,
40
41    /// The acceleration curve the device suggests.
42    pub pointer_acceleration: PointerAcceleration,
43
44    /// Whether the device suggests using the OS-native ballistics.
45    ///
46    /// `false` means the host may override the OS ballistics if it can; `true`
47    /// means the device suggests keeping the OS-native ballistics.
48    pub suggest_os_ballistics: bool,
49
50    /// Whether the device suggests offering vertical-orientation tuning.
51    ///
52    /// `true` for devices such as trackballs, where the host can let the user
53    /// fine-tune X/Y movement relative to cursor movement.
54    pub suggest_vertical_tuning: bool,
55}
56
57/// Implements the `MousePointer` / `0x2200` feature.
58#[derive(Clone, Feature)]
59#[creatable(id = 0x2200, version = 0)]
60pub struct MousePointerFeature {
61    /// The endpoint this feature talks to.
62    endpoint: FeatureEndpoint,
63}
64
65impl MousePointerFeature {
66    /// Retrieves the sensor resolution and pointer-tuning hints of the mouse.
67    pub async fn get_mouse_pointer_info(&self) -> Result<MousePointerInfo, Hidpp20Error> {
68        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
69        MousePointerInfo::from_payload(payload)
70    }
71}
72
73impl MousePointerInfo {
74    /// Decodes a `getMousePointerInfo` response payload.
75    fn from_payload(payload: [u8; 16]) -> Result<Self, Hidpp20Error> {
76        let flags = payload[2];
77        Ok(Self {
78            sensor_resolution: u16::from_be_bytes([payload[0], payload[1]]),
79            // Acceleration occupies the low two bits; all four values are valid
80            // so this conversion cannot actually fail.
81            pointer_acceleration: PointerAcceleration::try_from(flags & 0b11)
82                .map_err(|_| Hidpp20Error::UnsupportedResponse)?,
83            suggest_os_ballistics: flags & (1 << 2) != 0,
84            suggest_vertical_tuning: flags & (1 << 3) != 0,
85        })
86    }
87}
88
89#[cfg(test)]
90#[allow(clippy::unwrap_used, reason = "expect/unwrap are idiomatic in tests")]
91mod tests {
92    use super::{MousePointerInfo, PointerAcceleration};
93
94    #[test]
95    fn decodes_resolution_and_flags() {
96        let mut payload = [0; 16];
97        payload[0..2].copy_from_slice(&1600u16.to_be_bytes());
98        // High acceleration (0b11) + suggest OS ballistics (bit 2).
99        payload[2] = 0b0000_0111;
100
101        let info = MousePointerInfo::from_payload(payload).unwrap();
102        assert_eq!(info.sensor_resolution, 1600);
103        assert_eq!(info.pointer_acceleration, PointerAcceleration::High);
104        assert!(info.suggest_os_ballistics);
105        assert!(!info.suggest_vertical_tuning);
106    }
107
108    #[test]
109    fn decodes_trackball_vertical_tuning() {
110        let mut payload = [0; 16];
111        payload[0..2].copy_from_slice(&400u16.to_be_bytes());
112        // Suggest vertical tuning (bit 3), acceleration none.
113        payload[2] = 0b0000_1000;
114
115        let info = MousePointerInfo::from_payload(payload).unwrap();
116        assert_eq!(info.pointer_acceleration, PointerAcceleration::None);
117        assert!(!info.suggest_os_ballistics);
118        assert!(info.suggest_vertical_tuning);
119    }
120}