Skip to main content

ustreamer_proto/
input.rs

1/// Input event types sent from browser to server.
2///
3/// Binary wire format: 1-byte type tag followed by type-specific payload.
4
5#[derive(Debug, Clone, Copy)]
6pub enum InputEvent {
7    /// Continuous pointer movement (sent as unreliable datagram).
8    PointerMove {
9        /// Normalized X coordinate (0.0–1.0).
10        x: f32,
11        /// Normalized Y coordinate (0.0–1.0).
12        y: f32,
13        /// Button bitmask (same as PointerEvent.buttons).
14        buttons: u8,
15        /// Client-side timestamp (ms) for RTT measurement.
16        timestamp_ms: u32,
17    },
18
19    /// Discrete pointer button press (sent reliably).
20    PointerDown { button: u8, x: f32, y: f32 },
21
22    /// Discrete pointer button release (sent reliably).
23    PointerUp { button: u8, x: f32, y: f32 },
24
25    /// Scroll / wheel event (unreliable).
26    Scroll {
27        delta_x: f32,
28        delta_y: f32,
29        mode: ScrollMode,
30    },
31
32    /// Key press (reliable).
33    KeyDown { code: u16 },
34
35    /// Key release (reliable).
36    KeyUp { code: u16 },
37}
38
39#[derive(Debug, Clone, Copy)]
40pub enum ScrollMode {
41    Pixels = 0,
42    Lines = 1,
43    Pages = 2,
44}
45
46// Wire format type tags
47const TAG_POINTER_MOVE: u8 = 0x01;
48const TAG_POINTER_DOWN: u8 = 0x02;
49const TAG_POINTER_UP: u8 = 0x03;
50const TAG_SCROLL: u8 = 0x04;
51const TAG_KEY_DOWN: u8 = 0x10;
52const TAG_KEY_UP: u8 = 0x11;
53
54impl InputEvent {
55    /// Serialize to compact binary format.
56    pub fn to_bytes(&self) -> Vec<u8> {
57        match self {
58            InputEvent::PointerMove {
59                x,
60                y,
61                buttons,
62                timestamp_ms,
63            } => {
64                let mut buf = Vec::with_capacity(14);
65                buf.push(TAG_POINTER_MOVE);
66                buf.push(*buttons);
67                buf.extend_from_slice(&x.to_le_bytes());
68                buf.extend_from_slice(&y.to_le_bytes());
69                buf.extend_from_slice(&timestamp_ms.to_le_bytes());
70                buf
71            }
72            InputEvent::PointerDown { button, x, y } => {
73                let mut buf = Vec::with_capacity(10);
74                buf.push(TAG_POINTER_DOWN);
75                buf.push(*button);
76                buf.extend_from_slice(&x.to_le_bytes());
77                buf.extend_from_slice(&y.to_le_bytes());
78                buf
79            }
80            InputEvent::PointerUp { button, x, y } => {
81                let mut buf = Vec::with_capacity(10);
82                buf.push(TAG_POINTER_UP);
83                buf.push(*button);
84                buf.extend_from_slice(&x.to_le_bytes());
85                buf.extend_from_slice(&y.to_le_bytes());
86                buf
87            }
88            InputEvent::Scroll {
89                delta_x,
90                delta_y,
91                mode,
92            } => {
93                let mut buf = Vec::with_capacity(10);
94                buf.push(TAG_SCROLL);
95                buf.extend_from_slice(&delta_x.to_le_bytes());
96                buf.extend_from_slice(&delta_y.to_le_bytes());
97                buf.push(*mode as u8);
98                buf
99            }
100            InputEvent::KeyDown { code } => {
101                vec![TAG_KEY_DOWN, (*code >> 8) as u8, *code as u8]
102            }
103            InputEvent::KeyUp { code } => {
104                vec![TAG_KEY_UP, (*code >> 8) as u8, *code as u8]
105            }
106        }
107    }
108
109    /// Deserialize from compact binary format.
110    pub fn from_bytes(data: &[u8]) -> Result<Self, InputEventError> {
111        if data.is_empty() {
112            return Err(InputEventError::Empty);
113        }
114
115        match data[0] {
116            TAG_POINTER_MOVE if data.len() >= 14 => Ok(InputEvent::PointerMove {
117                buttons: data[1],
118                x: f32::from_le_bytes(data[2..6].try_into().unwrap()),
119                y: f32::from_le_bytes(data[6..10].try_into().unwrap()),
120                timestamp_ms: u32::from_le_bytes(data[10..14].try_into().unwrap()),
121            }),
122            TAG_POINTER_DOWN if data.len() >= 10 => Ok(InputEvent::PointerDown {
123                button: data[1],
124                x: f32::from_le_bytes(data[2..6].try_into().unwrap()),
125                y: f32::from_le_bytes(data[6..10].try_into().unwrap()),
126            }),
127            TAG_POINTER_UP if data.len() >= 10 => Ok(InputEvent::PointerUp {
128                button: data[1],
129                x: f32::from_le_bytes(data[2..6].try_into().unwrap()),
130                y: f32::from_le_bytes(data[6..10].try_into().unwrap()),
131            }),
132            TAG_SCROLL if data.len() >= 10 => Ok(InputEvent::Scroll {
133                delta_x: f32::from_le_bytes(data[1..5].try_into().unwrap()),
134                delta_y: f32::from_le_bytes(data[5..9].try_into().unwrap()),
135                mode: match data[9] {
136                    1 => ScrollMode::Lines,
137                    2 => ScrollMode::Pages,
138                    _ => ScrollMode::Pixels,
139                },
140            }),
141            TAG_KEY_DOWN if data.len() >= 3 => Ok(InputEvent::KeyDown {
142                code: ((data[1] as u16) << 8) | data[2] as u16,
143            }),
144            TAG_KEY_UP if data.len() >= 3 => Ok(InputEvent::KeyUp {
145                code: ((data[1] as u16) << 8) | data[2] as u16,
146            }),
147            tag => Err(InputEventError::UnknownTag(tag)),
148        }
149    }
150}
151
152#[derive(Debug, thiserror::Error)]
153pub enum InputEventError {
154    #[error("empty input buffer")]
155    Empty,
156    #[error("unknown input event tag: 0x{0:02x}")]
157    UnknownTag(u8),
158}
159
160#[cfg(test)]
161mod tests {
162    use super::*;
163
164    #[test]
165    fn roundtrip_pointer_move() {
166        let event = InputEvent::PointerMove {
167            x: 0.5,
168            y: 0.75,
169            buttons: 1,
170            timestamp_ms: 12345,
171        };
172        let bytes = event.to_bytes();
173        let decoded = InputEvent::from_bytes(&bytes).unwrap();
174        match decoded {
175            InputEvent::PointerMove {
176                x,
177                y,
178                buttons,
179                timestamp_ms,
180            } => {
181                assert!((x - 0.5).abs() < f32::EPSILON);
182                assert!((y - 0.75).abs() < f32::EPSILON);
183                assert_eq!(buttons, 1);
184                assert_eq!(timestamp_ms, 12345);
185            }
186            _ => panic!("wrong variant"),
187        }
188    }
189
190    #[test]
191    fn roundtrip_key_down() {
192        let event = InputEvent::KeyDown { code: 0x0041 };
193        let bytes = event.to_bytes();
194        let decoded = InputEvent::from_bytes(&bytes).unwrap();
195        match decoded {
196            InputEvent::KeyDown { code } => assert_eq!(code, 0x0041),
197            _ => panic!("wrong variant"),
198        }
199    }
200}