Skip to main content

starnix_modules_input_report_event_conversion/
lib.rs

1// Copyright 2026 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5pub mod consumer_control;
6pub mod keyboard;
7pub mod mouse;
8pub mod touch;
9
10pub use consumer_control::*;
11pub use keyboard::*;
12pub use mouse::*;
13pub use touch::*;
14
15use fidl_fuchsia_input_report as fir;
16use starnix_uapi::uapi;
17
18#[inline]
19pub(crate) fn clamp_i64_to_i32(val: i64) -> i32 {
20    i32::try_from(val).unwrap_or_else(|_| val.clamp(i32::MIN as i64, i32::MAX as i64) as i32)
21}
22
23pub(crate) fn emit_key_transitions(
24    previous: &mut sorted_vec_map::SortedVecSet<u16>,
25    current: sorted_vec_map::SortedVecSet<u16>,
26    time: uapi::timeval,
27    events: &mut Vec<uapi::input_event>,
28) {
29    for &key in previous.iter() {
30        if !current.contains(&key) {
31            events.push(uapi::input_event {
32                time,
33                type_: uapi::EV_KEY as u16,
34                code: key,
35                value: 0,
36            });
37        }
38    }
39    for &key in current.iter() {
40        if !previous.contains(&key) {
41            events.push(uapi::input_event {
42                time,
43                type_: uapi::EV_KEY as u16,
44                code: key,
45                value: 1,
46            });
47        }
48    }
49    *previous = current;
50}
51
52/// Unified converter translating any `fuchsia.input.report.InputReport` into Linux `uapi::input_event`s.
53#[derive(Debug, Default, PartialEq)]
54pub struct InputReportConverter {
55    pub touch: TouchReportConverter,
56    pub mouse: MouseReportConverter,
57    pub keyboard: KeyboardReportConverter,
58    pub consumer_control: ConsumerControlReportConverter,
59}
60
61impl InputReportConverter {
62    pub fn new() -> Self {
63        Self::default()
64    }
65
66    pub fn from_device_descriptor(descriptor: &fir::DeviceDescriptor) -> Self {
67        let touch = descriptor
68            .touch
69            .as_ref()
70            .map(TouchReportConverter::from_touch_descriptor)
71            .unwrap_or_default();
72        Self { touch, ..Default::default() }
73    }
74
75    pub fn handle_report(&mut self, report: &fir::InputReport) -> Vec<uapi::input_event> {
76        let event_time = report
77            .event_time
78            .filter(|&t| t > 0)
79            .map(zx::MonotonicInstant::from_nanos)
80            .unwrap_or_else(zx::MonotonicInstant::get);
81
82        let mut events = Vec::new();
83
84        if let Some(ref touch) = report.touch {
85            events.extend(self.touch.handle(touch, event_time));
86        }
87
88        if let Some(ref mouse) = report.mouse {
89            events.extend(self.mouse.handle(mouse, event_time));
90        }
91
92        if let Some(ref keyboard) = report.keyboard {
93            events.extend(self.keyboard.handle(keyboard, event_time));
94        }
95
96        if let Some(ref consumer_control) = report.consumer_control {
97            events.extend(self.consumer_control.handle(consumer_control, event_time));
98        }
99
100        events
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107    use fidl_fuchsia_input::Key;
108
109    #[::fuchsia::test]
110    fn test_unified_input_report_converter() {
111        let mut converter = InputReportConverter::new();
112        let time_nanos = 700_000_000;
113
114        let report = fir::InputReport {
115            event_time: Some(time_nanos),
116            keyboard: Some(fir::KeyboardInputReport {
117                pressed_keys3: Some(vec![Key::Escape]),
118                ..Default::default()
119            }),
120            mouse: Some(fir::MouseInputReport {
121                movement_x: Some(10),
122                movement_y: Some(-5),
123                ..Default::default()
124            }),
125            ..Default::default()
126        };
127
128        let events = converter.handle_report(&report);
129        assert!(events.iter().any(|e| e.code == uapi::KEY_ESC as u16 && e.value == 1));
130        assert!(events.iter().any(|e| e.code == uapi::REL_X as u16 && e.value == 10));
131        assert!(events.iter().any(|e| e.code == uapi::REL_Y as u16 && e.value == -5));
132        assert_eq!(events[0].time.tv_sec, 0);
133        assert_eq!(events[0].time.tv_usec, (time_nanos / 1_000) as i64);
134    }
135
136    #[::fuchsia::test]
137    fn test_empty_reports_return_empty_events() {
138        assert!(InputReportConverter::new().handle_report(&fir::InputReport::default()).is_empty());
139    }
140
141    #[::fuchsia::test]
142    fn test_event_time_filtering() {
143        // Report with event_time = 0
144        let mut converter_zero = InputReportConverter::new();
145        let report_zero = fir::InputReport {
146            event_time: Some(0),
147            keyboard: Some(fir::KeyboardInputReport {
148                pressed_keys3: Some(vec![Key::Escape]),
149                ..Default::default()
150            }),
151            ..Default::default()
152        };
153        let events_zero = converter_zero.handle_report(&report_zero);
154        assert!(!events_zero.is_empty());
155        assert!(events_zero[0].time.tv_sec > 0 || events_zero[0].time.tv_usec > 0);
156
157        // Report with event_time = -100
158        let mut converter_neg = InputReportConverter::new();
159        let report_neg = fir::InputReport {
160            event_time: Some(-100),
161            keyboard: Some(fir::KeyboardInputReport {
162                pressed_keys3: Some(vec![Key::Escape]),
163                ..Default::default()
164            }),
165            ..Default::default()
166        };
167        let events_neg = converter_neg.handle_report(&report_neg);
168        assert!(!events_neg.is_empty());
169        assert!(events_neg[0].time.tv_sec > 0 || events_neg[0].time.tv_usec > 0);
170        assert!(events_neg[0].time.tv_usec >= 0);
171    }
172
173    #[::fuchsia::test]
174    fn test_clamp_i64_to_i32() {
175        assert_eq!(clamp_i64_to_i32(0), 0);
176        assert_eq!(clamp_i64_to_i32(42), 42);
177        assert_eq!(clamp_i64_to_i32(-42), -42);
178        assert_eq!(clamp_i64_to_i32(i32::MAX as i64), i32::MAX);
179        assert_eq!(clamp_i64_to_i32(i32::MIN as i64), i32::MIN);
180        assert_eq!(clamp_i64_to_i32(i32::MAX as i64 + 100), i32::MAX);
181        assert_eq!(clamp_i64_to_i32(i32::MIN as i64 - 100), i32::MIN);
182        assert_eq!(clamp_i64_to_i32(i64::MAX), i32::MAX);
183        assert_eq!(clamp_i64_to_i32(i64::MIN), i32::MIN);
184    }
185
186    #[::fuchsia::test]
187    fn test_input_report_converter_from_device_descriptor() {
188        let desc = fir::DeviceDescriptor {
189            touch: Some(fir::TouchDescriptor {
190                input: Some(fir::TouchInputDescriptor {
191                    max_contacts: Some(7),
192                    ..Default::default()
193                }),
194                ..Default::default()
195            }),
196            ..Default::default()
197        };
198        let converter = InputReportConverter::from_device_descriptor(&desc);
199        assert_eq!(converter.touch.max_slots(), 7);
200
201        let desc_empty = fir::DeviceDescriptor::default();
202        let converter_empty = InputReportConverter::from_device_descriptor(&desc_empty);
203        assert_eq!(converter_empty.touch.max_slots(), 10);
204    }
205}