Skip to main content

hidpp/feature/reprog_controls/
event.rs

1use num_enum::{IntoPrimitive, TryFromPrimitive};
2
3use crate::{nibble::U4, protocol::v20};
4
5use super::ControlId;
6
7fn i16_from_be_payload(bytes: &[u8]) -> i16 {
8    i16::from_be_bytes(bytes.try_into().unwrap())
9}
10
11/// One analytics key event entry.
12#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
13#[cfg_attr(feature = "serde", derive(serde::Serialize))]
14pub struct AnalyticsKeyEvent {
15    /// Control ID associated with the analytics event.
16    pub cid: ControlId,
17    /// Device-defined analytics event code.
18    pub event: u8,
19}
20
21impl AnalyticsKeyEvent {
22    fn from_payload(bytes: &[u8]) -> Self {
23        Self {
24            cid: ControlId::from_payload(&bytes[0..=1]),
25            event: bytes[2],
26        }
27    }
28}
29
30/// Raw wheel movement resolution.
31#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, IntoPrimitive, TryFromPrimitive)]
32#[cfg_attr(feature = "serde", derive(serde::Serialize))]
33#[repr(u8)]
34pub enum RawWheelResolution {
35    /// Low-resolution wheel movement.
36    Low = 0,
37    /// High-resolution wheel movement.
38    High = 1,
39}
40
41/// Event emitted by `0x1b04`.
42#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
43#[cfg_attr(feature = "serde", derive(serde::Serialize))]
44pub enum ReprogControlsEvent {
45    /// Up to four currently pressed diverted controls.
46    DivertedButtons([ControlId; 4]),
47    /// Raw pointer movement reported while a diverted control is held.
48    DivertedRawMouseXy {
49        /// Horizontal delta.
50        dx: i16,
51        /// Vertical delta.
52        dy: i16,
53    },
54    /// Batch of analytics key event entries.
55    AnalyticsKeyEvents([AnalyticsKeyEvent; 5]),
56    /// Raw wheel movement reported while a diverted control is held.
57    DivertedRawWheel {
58        /// Wheel movement resolution.
59        resolution: RawWheelResolution,
60        /// Number of wheel periods encoded in the event header.
61        periods: U4,
62        /// Vertical wheel delta.
63        delta_vertical: i16,
64    },
65}
66
67impl ReprogControlsEvent {
68    /// Whether `cid` is currently reported as pressed in a diverted-buttons event.
69    #[must_use]
70    pub fn is_pressed(self, cid: ControlId) -> bool {
71        matches!(self, Self::DivertedButtons(cids) if cids.contains(&cid))
72    }
73}
74
75/// Decode an unsolicited `0x1b04` HID++ message.
76#[must_use]
77pub fn decode_event(
78    msg: &v20::Message,
79    device_index: u8,
80    feature_index: u8,
81) -> Option<ReprogControlsEvent> {
82    let header = msg.header();
83    if header.device_index != device_index
84        || header.feature_index != feature_index
85        || header.software_id.to_lo() != 0
86    {
87        return None;
88    }
89    decode_event_payload(header.function_id.to_lo(), &msg.extend_payload())
90}
91
92pub(super) fn decode_event_payload(
93    function_id: u8,
94    payload: &[u8; 16],
95) -> Option<ReprogControlsEvent> {
96    match function_id {
97        0 => Some(ReprogControlsEvent::DivertedButtons([
98            ControlId::from_payload(&payload[0..=1]),
99            ControlId::from_payload(&payload[2..=3]),
100            ControlId::from_payload(&payload[4..=5]),
101            ControlId::from_payload(&payload[6..=7]),
102        ])),
103        1 => Some(ReprogControlsEvent::DivertedRawMouseXy {
104            dx: i16_from_be_payload(&payload[0..=1]),
105            dy: i16_from_be_payload(&payload[2..=3]),
106        }),
107        2 => Some(ReprogControlsEvent::AnalyticsKeyEvents([
108            AnalyticsKeyEvent::from_payload(&payload[0..=2]),
109            AnalyticsKeyEvent::from_payload(&payload[3..=5]),
110            AnalyticsKeyEvent::from_payload(&payload[6..=8]),
111            AnalyticsKeyEvent::from_payload(&payload[9..=11]),
112            AnalyticsKeyEvent::from_payload(&payload[12..=14]),
113        ])),
114        4 => Some(ReprogControlsEvent::DivertedRawWheel {
115            resolution: RawWheelResolution::try_from((payload[0] >> 4) & 1).ok()?,
116            periods: U4::from_lo(payload[0]),
117            delta_vertical: i16_from_be_payload(&payload[1..=2]),
118        }),
119        _ => None,
120    }
121}