Skip to main content

starnix_modules_input_report_event_conversion/
consumer_control.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
5use fidl_fuchsia_input_report as fir;
6use sorted_vec_map::SortedVecSet;
7use starnix_types::time::timeval_from_time;
8use starnix_uapi::uapi;
9
10/// Maps a Fuchsia `ConsumerControlButton` to a Linux keycode.
11pub fn consumer_control_button_to_linux_key(button: fir::ConsumerControlButton) -> Option<u32> {
12    match button {
13        fir::ConsumerControlButton::VolumeUp => Some(uapi::KEY_VOLUMEUP),
14        fir::ConsumerControlButton::VolumeDown => Some(uapi::KEY_VOLUMEDOWN),
15        fir::ConsumerControlButton::Power => Some(uapi::KEY_POWER),
16        fir::ConsumerControlButton::Pause => Some(uapi::KEY_PAUSE),
17        fir::ConsumerControlButton::MicMute => Some(uapi::KEY_MICMUTE),
18        fir::ConsumerControlButton::Reboot => Some(uapi::KEY_RESTART),
19        fir::ConsumerControlButton::CameraDisable => Some(uapi::KEY_CAMERA),
20        fir::ConsumerControlButton::Function => Some(uapi::KEY_VOLUMEDOWN),
21        _ => None,
22    }
23}
24
25/// Converts `ConsumerControlInputReport` events into Linux button `uapi::input_event`s.
26#[derive(Debug, Default, PartialEq)]
27pub struct ConsumerControlReportConverter {
28    pressed_buttons: SortedVecSet<u16>,
29}
30
31impl ConsumerControlReportConverter {
32    pub fn new() -> Self {
33        Self::default()
34    }
35
36    pub fn handle(
37        &mut self,
38        report: &fir::ConsumerControlInputReport,
39        event_time: zx::MonotonicInstant,
40    ) -> Vec<uapi::input_event> {
41        let mut events = Vec::new();
42        let time = timeval_from_time(event_time);
43
44        let pressed_buttons = report.pressed_buttons.as_deref().unwrap_or(&[]);
45        let mut current_buttons = SortedVecSet::new();
46        for &btn in pressed_buttons.iter() {
47            if let Some(lkey) = consumer_control_button_to_linux_key(btn) {
48                current_buttons.insert(lkey as u16);
49            }
50        }
51
52        crate::emit_key_transitions(&mut self.pressed_buttons, current_buttons, time, &mut events);
53
54        if !events.is_empty() {
55            events.push(uapi::input_event {
56                time,
57                type_: uapi::EV_SYN as u16,
58                code: uapi::SYN_REPORT as u16,
59                value: 0,
60            });
61        }
62
63        events
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[::fuchsia::test]
72    fn test_consumer_control_report_buttons() {
73        let mut converter = ConsumerControlReportConverter::new();
74        let time = zx::MonotonicInstant::from_nanos(600_000_000);
75
76        // Press VolumeUp + Power
77        let report1 = fir::ConsumerControlInputReport {
78            pressed_buttons: Some(vec![
79                fir::ConsumerControlButton::VolumeUp,
80                fir::ConsumerControlButton::Power,
81            ]),
82            ..Default::default()
83        };
84        let events1 = converter.handle(&report1, time);
85        assert!(events1.iter().any(|e| e.code == uapi::KEY_VOLUMEUP as u16 && e.value == 1));
86        assert!(events1.iter().any(|e| e.code == uapi::KEY_POWER as u16 && e.value == 1));
87        assert!(events1.iter().any(|e| e.code == uapi::SYN_REPORT as u16));
88
89        // Release VolumeUp, press VolumeDown
90        let report2 = fir::ConsumerControlInputReport {
91            pressed_buttons: Some(vec![
92                fir::ConsumerControlButton::Power,
93                fir::ConsumerControlButton::VolumeDown,
94            ]),
95            ..Default::default()
96        };
97        let events2 = converter.handle(&report2, time);
98        assert!(events2.iter().any(|e| e.code == uapi::KEY_VOLUMEUP as u16 && e.value == 0));
99        assert!(events2.iter().any(|e| e.code == uapi::KEY_VOLUMEDOWN as u16 && e.value == 1));
100        // Power is still pressed, so no Power event
101        assert!(!events2.iter().any(|e| e.code == uapi::KEY_POWER as u16));
102
103        // Release all
104        let report3 =
105            fir::ConsumerControlInputReport { pressed_buttons: Some(vec![]), ..Default::default() };
106        let events3 = converter.handle(&report3, time);
107        assert!(events3.iter().any(|e| e.code == uapi::KEY_POWER as u16 && e.value == 0));
108        assert!(events3.iter().any(|e| e.code == uapi::KEY_VOLUMEDOWN as u16 && e.value == 0));
109    }
110
111    #[::fuchsia::test]
112    fn test_empty_consumer_control_reports_return_empty_events() {
113        let time = zx::MonotonicInstant::from_nanos(400_000_000);
114        assert!(
115            ConsumerControlReportConverter::new()
116                .handle(&fir::ConsumerControlInputReport::default(), time)
117                .is_empty()
118        );
119    }
120
121    #[::fuchsia::test]
122    fn test_consumer_control_function_button_maps_to_volumedown() {
123        let mut converter = ConsumerControlReportConverter::new();
124        let time = zx::MonotonicInstant::from_nanos(500_000_000);
125
126        let report = fir::ConsumerControlInputReport {
127            pressed_buttons: Some(vec![fir::ConsumerControlButton::Function]),
128            ..Default::default()
129        };
130        let events = converter.handle(&report, time);
131        assert!(events.iter().any(|e| e.code == uapi::KEY_VOLUMEDOWN as u16 && e.value == 1));
132        assert!(events.iter().any(|e| e.code == uapi::SYN_REPORT as u16));
133    }
134
135    #[::fuchsia::test]
136    fn test_consumer_control_report_none_pressed_buttons_releases_held_buttons() {
137        let mut converter = ConsumerControlReportConverter::new();
138        let time = zx::MonotonicInstant::from_nanos(300_000_000);
139
140        // Press VolumeUp
141        let report1 = fir::ConsumerControlInputReport {
142            pressed_buttons: Some(vec![fir::ConsumerControlButton::VolumeUp]),
143            ..Default::default()
144        };
145        let events1 = converter.handle(&report1, time);
146        assert_eq!(
147            events1.iter().map(|e| (e.type_, e.code, e.value)).collect::<Vec<_>>(),
148            vec![
149                (uapi::EV_KEY as u16, uapi::KEY_VOLUMEUP as u16, 1),
150                (uapi::EV_SYN as u16, uapi::SYN_REPORT as u16, 0),
151            ]
152        );
153
154        // Subsequent report with pressed_buttons: None releases VolumeUp
155        let report2 =
156            fir::ConsumerControlInputReport { pressed_buttons: None, ..Default::default() };
157        let events2 = converter.handle(&report2, time);
158        assert_eq!(
159            events2.iter().map(|e| (e.type_, e.code, e.value)).collect::<Vec<_>>(),
160            vec![
161                (uapi::EV_KEY as u16, uapi::KEY_VOLUMEUP as u16, 0),
162                (uapi::EV_SYN as u16, uapi::SYN_REPORT as u16, 0),
163            ]
164        );
165
166        // Subsequent report with pressed_buttons: None when no buttons held emits nothing
167        let report3 =
168            fir::ConsumerControlInputReport { pressed_buttons: None, ..Default::default() };
169        let events3 = converter.handle(&report3, time);
170        assert!(events3.is_empty());
171    }
172}