Skip to main content

openlogi_hid/reprog_controls/
event.rs

1use hidpp::protocol::v20;
2
3use super::{ControlId, ReprogControlsEvent, decode_full_event};
4
5/// An unsolicited `0x1b04` event decoded for OpenLogi's gesture pipeline.
6#[derive(Debug, Clone, Copy, PartialEq, Eq)]
7pub enum RawControlEvent {
8    /// `divertedButtonsEvent`: the (up to four) CIDs currently held down. A
9    /// slot is `0` when fewer than four are pressed; an all-zero array means
10    /// every diverted control was released.
11    DivertedButtons([u16; 4]),
12    /// `rawXYEvent`: signed movement deltas reported while a raw-XY control is
13    /// held.
14    RawXy {
15        /// Horizontal delta (`+` = right, in the device's raw units).
16        dx: i16,
17        /// Vertical delta (`+` = down, in the device's raw units).
18        dy: i16,
19    },
20}
21
22impl RawControlEvent {
23    /// Whether `cid` is among the controls reported as currently pressed.
24    #[must_use]
25    pub fn is_pressed(&self, cid: u16) -> bool {
26        matches!(self, Self::DivertedButtons(cids) if cids.contains(&cid))
27    }
28}
29
30impl TryFrom<ReprogControlsEvent> for RawControlEvent {
31    type Error = ReprogControlsEvent;
32
33    fn try_from(event: ReprogControlsEvent) -> Result<Self, Self::Error> {
34        match event {
35            ReprogControlsEvent::DivertedButtons(cids) => {
36                Ok(Self::DivertedButtons(cids.map(ControlId::into)))
37            }
38            ReprogControlsEvent::DivertedRawMouseXy { dx, dy } => Ok(Self::RawXy { dx, dy }),
39            ReprogControlsEvent::AnalyticsKeyEvents(_)
40            | ReprogControlsEvent::DivertedRawWheel { .. } => Err(event),
41        }
42    }
43}
44
45/// Decode a channel message into a [`RawControlEvent`] when it is an unsolicited
46/// `0x1b04` event for `(device_index, feature_index)`.
47///
48/// Returns `None` for request responses (`software_id != 0`), messages from a
49/// different device or feature, and events outside OpenLogi's gesture-control
50/// pipeline.
51#[must_use]
52pub fn decode_event(
53    msg: &v20::Message,
54    device_index: u8,
55    feature_index: u8,
56) -> Option<RawControlEvent> {
57    decode_full_event(msg, device_index, feature_index)?
58        .try_into()
59        .ok()
60}
61
62#[cfg(test)]
63mod tests {
64    use hidpp::{nibble::U4, protocol::v20};
65
66    use super::*;
67    use crate::reprog_controls::GESTURE_BUTTON_CID;
68
69    fn event(function_id: u8, software_id: u8, payload: [u8; 16]) -> v20::Message {
70        v20::Message::Long(
71            v20::MessageHeader {
72                device_index: 2,
73                feature_index: 7,
74                function_id: U4::from_lo(function_id),
75                software_id: U4::from_lo(software_id),
76            },
77            payload,
78        )
79    }
80
81    #[test]
82    fn decodes_diverted_buttons() {
83        let mut p = [0u8; 16];
84        p[0..2].copy_from_slice(&GESTURE_BUTTON_CID.to_be_bytes());
85        let decoded = decode_event(&event(0, 0, p), 2, 7);
86        assert_eq!(
87            decoded,
88            Some(RawControlEvent::DivertedButtons([
89                GESTURE_BUTTON_CID,
90                0,
91                0,
92                0,
93            ]))
94        );
95        assert!(decoded.is_some_and(|e| e.is_pressed(GESTURE_BUTTON_CID)));
96    }
97
98    #[test]
99    fn decodes_signed_raw_xy() {
100        let mut p = [0u8; 16];
101        p[0..2].copy_from_slice(&(-5i16).to_be_bytes());
102        p[2..4].copy_from_slice(&12i16.to_be_bytes());
103        assert_eq!(
104            decode_event(&event(1, 0, p), 2, 7),
105            Some(RawControlEvent::RawXy { dx: -5, dy: 12 })
106        );
107    }
108
109    #[test]
110    fn ignores_responses_and_foreign_messages() {
111        let p = [0u8; 16];
112        // software_id != 0 marks a request response, not an event.
113        assert_eq!(decode_event(&event(0, 5, p), 2, 7), None);
114        // Right device + feature, but an event outside the gesture-control path.
115        assert_eq!(decode_event(&event(2, 0, p), 2, 7), None);
116        // Wrong feature index.
117        assert_eq!(decode_event(&event(0, 0, p), 2, 9), None);
118    }
119}