Skip to main content

hidpp/feature/touchpad_raw_xy/
mod.rs

1//! Implements the `TouchpadRawXy` feature (ID `0x6100`) that exposes a
2//! touchpad's raw multi-touch data: pad characteristics, the raw-report mode,
3//! and a per-frame [`DualXyData`] event.
4
5pub mod event;
6
7#[cfg(test)]
8mod tests;
9
10use std::sync::Arc;
11
12use num_enum::{IntoPrimitive, TryFromPrimitive};
13
14pub use event::{DualXyData, TouchPoint, TouchpadRawEvent};
15
16use crate::{
17    channel::{HidppChannel, MessageListenerGuard},
18    event::EventEmitter,
19    feature::{CreatableFeature, EmittingFeature, Feature, FeatureEndpoint, event_payload},
20    protocol::v20::Hidpp20Error,
21};
22
23bitflags::bitflags! {
24    /// Raw-report mode flags from
25    /// [`get_raw_report_state`](TouchpadRawXyFeature::get_raw_report_state).
26    ///
27    /// Some combinations are mutually exclusive; common valid bitmaps are `0x00`
28    /// (off), `0x05`, `0x09`, `0x21` and `0x41` (see the feature spec).
29    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
30    #[cfg_attr(feature = "serde", derive(serde::Serialize))]
31    pub struct RawReportFlags: u8 {
32        /// Raw reporting enabled.
33        const RAW = 1 << 0;
34        /// Add force data to 16-bit reporting (deprecated).
35        const FORCE_ADD = 1 << 1;
36        /// Enhanced reporting enabled.
37        const ENHANCED = 1 << 2;
38        /// Report width/height instead of area.
39        const WIDTH_HEIGHT = 1 << 3;
40        /// Report native gestures.
41        const NATIVE_GESTURE = 1 << 4;
42        /// Report major/minor/orientation.
43        const MAJOR_MINOR = 1 << 5;
44        /// Report 8-bit width and height bytes instead of area.
45        const WIDTH_HEIGHT_8BIT = 1 << 6;
46    }
47}
48
49/// The position of a touchpad's coordinate origin, viewed from above.
50#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
51#[cfg_attr(feature = "serde", derive(serde::Serialize))]
52#[non_exhaustive]
53#[repr(u8)]
54pub enum Origin {
55    /// Lower-left corner.
56    LowerLeft = 1,
57    /// Lower-right corner.
58    LowerRight = 2,
59    /// Upper-left corner.
60    UpperLeft = 3,
61    /// Upper-right corner.
62    UpperRight = 4,
63}
64
65/// Touchpad characteristics from
66/// [`get_touchpad_info`](TouchpadRawXyFeature::get_touchpad_info).
67#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
68#[cfg_attr(feature = "serde", derive(serde::Serialize))]
69#[non_exhaustive]
70pub struct TouchpadInfo {
71    /// Pad width in native coordinate units.
72    pub x_size: u16,
73    /// Pad height in native coordinate units.
74    pub y_size: u16,
75    /// Z-data range (`0x00` = none, `0x0f` = 16-bit).
76    pub z_data_range: u8,
77    /// Area-data range (`0x0f` = 16-bit).
78    pub area_data_range: u8,
79    /// Timestamp increment, in units of 0.1 ms.
80    pub timestamp_units: u8,
81    /// Maximum number of fingers that can be tracked.
82    pub max_finger_count: u8,
83    /// Position of the coordinate origin.
84    pub origin: Origin,
85    /// Whether pen input is supported.
86    pub pen_support: bool,
87    /// Raw-report mapping version.
88    pub raw_report_mapping_version: u8,
89    /// Native sensor DPI.
90    pub dpi: u16,
91}
92
93impl TouchpadInfo {
94    fn from_payload(payload: &[u8; 16]) -> Result<Self, Hidpp20Error> {
95        Ok(Self {
96            x_size: u16::from_be_bytes([payload[0], payload[1]]),
97            y_size: u16::from_be_bytes([payload[2], payload[3]]),
98            z_data_range: payload[4],
99            area_data_range: payload[5],
100            timestamp_units: payload[6],
101            max_finger_count: payload[7],
102            origin: Origin::try_from(payload[8]).map_err(|_| Hidpp20Error::UnsupportedResponse)?,
103            pen_support: payload[9] != 0,
104            raw_report_mapping_version: payload[12],
105            dpi: u16::from_be_bytes([payload[13], payload[14]]),
106        })
107    }
108}
109
110/// Implements the `TouchpadRawXy` / `0x6100` feature.
111pub struct TouchpadRawXyFeature {
112    /// The endpoint this feature talks to.
113    endpoint: FeatureEndpoint,
114
115    /// The emitter used to publish decoded events.
116    emitter: Arc<EventEmitter<TouchpadRawEvent>>,
117
118    /// Removes the message listener when the feature is dropped.
119    _msg_listener: MessageListenerGuard,
120}
121
122impl CreatableFeature for TouchpadRawXyFeature {
123    const ID: u16 = 0x6100;
124    const STARTING_VERSION: u8 = 0;
125
126    fn new(chan: Arc<HidppChannel>, device_index: u8, feature_index: u8) -> Self {
127        let emitter = Arc::new(EventEmitter::new());
128
129        let listener = chan.add_msg_listener_guarded({
130            let emitter = Arc::clone(&emitter);
131
132            move |raw, matched| {
133                let Some((func, payload)) =
134                    event_payload(raw, matched, device_index, feature_index)
135                else {
136                    return;
137                };
138                if let Some(event) = event::decode_event(func.to_lo(), &payload) {
139                    emitter.emit(event);
140                }
141            }
142        });
143
144        Self {
145            endpoint: FeatureEndpoint::new(chan, device_index, feature_index),
146            emitter,
147            _msg_listener: listener,
148        }
149    }
150}
151
152impl Feature for TouchpadRawXyFeature {}
153
154impl EmittingFeature<TouchpadRawEvent> for TouchpadRawXyFeature {
155    fn listen(&self) -> async_channel::Receiver<TouchpadRawEvent> {
156        self.emitter.create_receiver()
157    }
158}
159
160impl TouchpadRawXyFeature {
161    /// Retrieves the touchpad's characteristics.
162    pub async fn get_touchpad_info(&self) -> Result<TouchpadInfo, Hidpp20Error> {
163        let payload = self.endpoint.call(0, [0; 3]).await?.extend_payload();
164        TouchpadInfo::from_payload(&payload)
165    }
166
167    /// Retrieves the current raw-report mode.
168    pub async fn get_raw_report_state(&self) -> Result<RawReportFlags, Hidpp20Error> {
169        let payload = self.endpoint.call(1, [0; 3]).await?.extend_payload();
170        Ok(RawReportFlags::from_bits_retain(payload[0]))
171    }
172
173    /// Sets the raw-report mode.
174    ///
175    /// Enable [`RawReportFlags::RAW`] for [`TouchpadRawEvent`]s to be emitted.
176    pub async fn set_raw_report_state(&self, flags: RawReportFlags) -> Result<(), Hidpp20Error> {
177        self.endpoint.call(2, [flags.bits(), 0, 0]).await?;
178        Ok(())
179    }
180}