Skip to main content

hidpp/feature/mouse_pointer/
mod.rs

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