Skip to main content

starnix_modules_input/
input_file.rs

1// Copyright 2023 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 crate::input_device::InputDeviceInfoHandle;
6use crossbeam::queue::SegQueue;
7use fuchsia_inspect::Inspector;
8use futures::FutureExt;
9use starnix_core::fileops_impl_nonseekable;
10use starnix_core::mm::{MemoryAccessor, MemoryAccessorExt};
11use starnix_core::task::{CurrentTask, EventHandler, WaitCanceler, WaitQueue, Waiter};
12use starnix_core::vfs::buffers::{InputBuffer, OutputBuffer};
13use starnix_core::vfs::{FileObject, FileOps, fileops_impl_noop_sync};
14use starnix_logging::{log_info, track_stub};
15use starnix_sync::{InputFileInputFileLock, LockDepMutex};
16use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult};
17use starnix_types::time::duration_from_timeval;
18use starnix_uapi::errors::Errno;
19use starnix_uapi::open_flags::OpenFlags;
20use starnix_uapi::user_address::{ArchSpecific, MultiArchUserRef, UserAddress, UserRef};
21use starnix_uapi::vfs::FdEvents;
22use starnix_uapi::{
23    ABS_CNT, ABS_MT_POSITION_X, ABS_MT_POSITION_Y, ABS_MT_PRESSURE, ABS_MT_SLOT,
24    ABS_MT_TOUCH_MAJOR, ABS_MT_TOUCH_MINOR, ABS_MT_TRACKING_ID, BTN_EXTRA, BTN_LEFT, BTN_MIDDLE,
25    BTN_MISC, BTN_RIGHT, BTN_SIDE, BTN_TOUCH, EV_CNT, FF_CNT, INPUT_PROP_CNT, INPUT_PROP_DIRECT,
26    INPUT_PROP_POINTER, KEY_CNT, KEY_DOWN, KEY_LEFT, KEY_OK, KEY_POWER, KEY_RIGHT, KEY_SLEEP,
27    KEY_UP, KEY_VOLUMEDOWN, KEY_VOLUMEUP, LED_CNT, MSC_CNT, REL_CNT, REL_HWHEEL, REL_WHEEL, REL_X,
28    REL_Y, SW_CNT, errno, error, uapi,
29};
30use std::sync::atomic::{AtomicBool, AtomicI64, AtomicU64, Ordering};
31use std::sync::{Arc, Weak};
32use zerocopy::IntoBytes as _; // for `as_bytes()`
33
34uapi::check_arch_independent_layout! {
35    input_id {}
36    input_absinfo {}
37}
38
39type InputEventPtr = MultiArchUserRef<uapi::input_event, uapi::arch32::input_event>;
40
41pub struct InputFileStatus {
42    /// The number of FIDL events received by this file from Fuchsia input system.
43    ///
44    /// We expect:
45    /// fidl_events_received_count = fidl_events_ignored_count +
46    ///                              fidl_events_unexpected_count +
47    ///                              fidl_events_converted_count
48    /// otherwise starnix ignored events unexpectedly.
49    ///
50    /// fidl_events_unexpected_count should be 0, if not it hints issues from upstream of ui stack.
51    pub fidl_events_received_count: AtomicU64,
52
53    /// The number of FIDL events ignored to this module’s representation of TouchEvent.
54    pub fidl_events_ignored_count: AtomicU64,
55
56    /// The unexpected number of FIDL events reached to this module should be filtered out
57    /// earlier in the UI stack.
58    /// It maybe unexpected format or unexpected order.
59    pub fidl_events_unexpected_count: AtomicU64,
60
61    /// The number of FIDL events converted to this module’s representation of TouchEvent.
62    pub fidl_events_converted_count: AtomicU64,
63
64    /// The number of uapi::input_events generated from TouchEvents.
65    pub uapi_events_generated_count: AtomicU64,
66
67    /// The event time of the last generated uapi::input_event.
68    pub last_generated_uapi_event_timestamp_ns: AtomicI64,
69
70    /// The number of uapi::input_events read from this input file by external process.
71    pub uapi_events_read_count: AtomicU64,
72
73    /// The event time of the last uapi::input_event read by external process.
74    pub last_read_uapi_event_timestamp_ns: AtomicI64,
75
76    /// Number of read calls.
77    pub fd_read_count: AtomicU64,
78
79    /// Number of notify calls.
80    pub fd_notify_count: AtomicU64,
81
82    /// Whether the file was opened without the NONBLOCK flag.
83    pub opened_without_nonblock: AtomicBool,
84
85    /// The timestamp when the file was opened.
86    pub open_timestamp_ns: AtomicI64,
87
88    /// Whether the file was closed (dropped).
89    pub closed: AtomicBool,
90
91    /// The timestamp when the file was closed.
92    pub close_timestamp_ns: AtomicI64,
93
94    /// The weak pointer to the InputFile.
95    pub input_file: LockDepMutex<Weak<InputFile>, InputFileInputFileLock>,
96}
97
98impl InputFileStatus {
99    fn new(node: &fuchsia_inspect::Node) -> Arc<Self> {
100        let status = Arc::new(Self {
101            fidl_events_received_count: AtomicU64::new(0),
102            fidl_events_ignored_count: AtomicU64::new(0),
103            fidl_events_unexpected_count: AtomicU64::new(0),
104            fidl_events_converted_count: AtomicU64::new(0),
105            uapi_events_generated_count: AtomicU64::new(0),
106            last_generated_uapi_event_timestamp_ns: AtomicI64::new(0),
107            uapi_events_read_count: AtomicU64::new(0),
108            last_read_uapi_event_timestamp_ns: AtomicI64::new(0),
109            fd_read_count: AtomicU64::new(0),
110            fd_notify_count: AtomicU64::new(0),
111            opened_without_nonblock: AtomicBool::new(false),
112            open_timestamp_ns: AtomicI64::new(0),
113            closed: AtomicBool::new(false),
114            close_timestamp_ns: AtomicI64::new(0),
115            input_file: Default::default(),
116        });
117
118        let cloned_status = status.clone();
119        node.record_lazy_values("status", move || {
120            let cloned_cloned_status = cloned_status.clone();
121            async move {
122                let is_dropped = cloned_cloned_status.input_file.lock().upgrade().is_none();
123                if is_dropped && !cloned_cloned_status.closed.load(Ordering::Relaxed) {
124                    cloned_cloned_status.closed.store(true, Ordering::Relaxed);
125                }
126
127                let inspector = Inspector::default();
128                let root = inspector.root();
129                root.record_uint(
130                    "fd_read_count",
131                    cloned_cloned_status.fd_read_count.load(Ordering::Relaxed),
132                );
133                root.record_uint(
134                    "fd_notify_count",
135                    cloned_cloned_status.fd_notify_count.load(Ordering::Relaxed),
136                );
137                root.record_bool(
138                    "opened_without_nonblock",
139                    cloned_cloned_status.opened_without_nonblock.load(Ordering::Relaxed),
140                );
141                root.record_int(
142                    "open_timestamp_ns",
143                    cloned_cloned_status.open_timestamp_ns.load(Ordering::Relaxed),
144                );
145                root.record_bool("closed", cloned_cloned_status.closed.load(Ordering::Relaxed));
146                root.record_int(
147                    "close_timestamp_ns",
148                    cloned_cloned_status.close_timestamp_ns.load(Ordering::Relaxed),
149                );
150                root.record_uint(
151                    "fidl_events_received_count",
152                    cloned_cloned_status.fidl_events_received_count.load(Ordering::Relaxed),
153                );
154                root.record_uint(
155                    "fidl_events_ignored_count",
156                    cloned_cloned_status.fidl_events_ignored_count.load(Ordering::Relaxed),
157                );
158                root.record_uint(
159                    "fidl_events_unexpected_count",
160                    cloned_cloned_status.fidl_events_unexpected_count.load(Ordering::Relaxed),
161                );
162                root.record_uint(
163                    "fidl_events_converted_count",
164                    cloned_cloned_status.fidl_events_converted_count.load(Ordering::Relaxed),
165                );
166                root.record_uint(
167                    "uapi_events_generated_count",
168                    cloned_cloned_status.uapi_events_generated_count.load(Ordering::Relaxed),
169                );
170                root.record_int(
171                    "last_generated_uapi_event_timestamp_ns",
172                    cloned_cloned_status
173                        .last_generated_uapi_event_timestamp_ns
174                        .load(Ordering::Relaxed),
175                );
176                root.record_uint(
177                    "uapi_events_read_count",
178                    cloned_cloned_status.uapi_events_read_count.load(Ordering::Relaxed),
179                );
180                root.record_int(
181                    "last_read_uapi_event_timestamp_ns",
182                    cloned_cloned_status.last_read_uapi_event_timestamp_ns.load(Ordering::Relaxed),
183                );
184                Ok(inspector)
185            }
186            .boxed()
187        });
188
189        status
190    }
191
192    pub fn count_received_events(&self, count: u64) {
193        self.fidl_events_received_count.fetch_add(count, Ordering::Relaxed);
194    }
195
196    pub fn count_ignored_events(&self, count: u64) {
197        self.fidl_events_ignored_count.fetch_add(count, Ordering::Relaxed);
198    }
199
200    pub fn count_unexpected_events(&self, count: u64) {
201        self.fidl_events_unexpected_count.fetch_add(count, Ordering::Relaxed);
202    }
203
204    pub fn count_converted_events(&self, count: u64) {
205        self.fidl_events_converted_count.fetch_add(count, Ordering::Relaxed);
206    }
207
208    pub fn count_generated_events(&self, count: u64, event_time_ns: i64) {
209        self.uapi_events_generated_count.fetch_add(count, Ordering::Relaxed);
210        self.last_generated_uapi_event_timestamp_ns.store(event_time_ns, Ordering::Relaxed);
211    }
212
213    pub fn count_read_events(&self, count: u64, event_time_ns: i64) {
214        self.uapi_events_read_count.fetch_add(count, Ordering::Relaxed);
215        self.last_read_uapi_event_timestamp_ns.store(event_time_ns, Ordering::Relaxed);
216    }
217
218    pub fn count_fd_read_calls(&self) {
219        self.fd_read_count.fetch_add(1, Ordering::Relaxed);
220    }
221
222    pub fn count_fd_notify_calls(&self) {
223        self.fd_notify_count.fetch_add(1, Ordering::Relaxed);
224    }
225
226    pub fn set_opened_without_nonblock(&self) {
227        self.opened_without_nonblock.store(true, Ordering::Relaxed);
228    }
229
230    pub fn set_open_timestamp(&self, timestamp: i64) {
231        self.open_timestamp_ns.store(timestamp, Ordering::Relaxed);
232    }
233
234    pub fn set_closed(&self) {
235        self.closed.store(true, Ordering::Relaxed);
236    }
237
238    pub fn set_closed_timestamp(&self, timestamp: i64) {
239        self.close_timestamp_ns.store(timestamp, Ordering::Relaxed);
240    }
241}
242
243pub struct InputFile {
244    driver_version: u32,
245    info: InputDeviceInfoHandle,
246    pub config: InputFileConfig,
247    events: SegQueue<LinuxEventWithTraceId>,
248    waiters: WaitQueue,
249    // InputFile will be initialized with an InputFileStatus that holds Inspect data
250    // `None` for Uinput InputFiles
251    pub inspect_status: Option<Arc<InputFileStatus>>,
252}
253
254pub struct LinuxEventWithTraceId {
255    pub event: uapi::input_event,
256    pub trace_id: Option<fuchsia_trace::Id>,
257}
258
259impl LinuxEventWithTraceId {
260    pub fn new(event: uapi::input_event) -> Self {
261        match event.type_ as u32 {
262            uapi::EV_SYN => {
263                let trace_id = fuchsia_trace::Id::new();
264                fuchsia_trace::duration!("input", "linux_event_create");
265                fuchsia_trace::flow_begin!("input", "linux_event", trace_id);
266                LinuxEventWithTraceId { event: event, trace_id: Some(trace_id) }
267            }
268            // EV_SYN marks the end of a complete input event. Other event types are its properties,
269            // so they don't initiate a trace.
270            _ => LinuxEventWithTraceId { event: event, trace_id: None },
271        }
272    }
273}
274
275/// Returns the minimum number of bytes required to store `n_bits` bits.
276pub const fn min_bytes(n_bits: u32) -> usize {
277    ((n_bits as usize) + 7) / 8
278}
279
280/// Parses a Fuchsia `Axis` into Linux `uapi::input_absinfo`.
281pub fn parse_axis_info(axis: &fidl_fuchsia_input_report::Axis) -> uapi::input_absinfo {
282    let minimum = axis.range.min.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
283    let maximum = axis.range.max.clamp(i32::MIN as i64, i32::MAX as i64) as i32;
284    let resolution = match axis.unit.type_ {
285        fidl_fuchsia_input_report::UnitType::Meters => {
286            // Linux evdev resolution is in units per mm (10^-3 m).
287            // resolution = 10^(-3 - exponent).
288            match (-3i32).checked_sub(axis.unit.exponent) {
289                Some(p) if (0..=9).contains(&p) => 10i32.pow(p as u32),
290                _ => 0,
291            }
292        }
293        _ => 0,
294    };
295    uapi::input_absinfo { minimum, maximum, resolution, ..Default::default() }
296}
297
298/// Dynamic configuration of capabilities and axis ranges for an [`InputFile`].
299#[derive(Clone, Debug)]
300pub struct InputFileConfig {
301    pub supported_event_types: BitSet<{ min_bytes(EV_CNT) }>,
302    pub supported_keys: BitSet<{ min_bytes(KEY_CNT) }>,
303    pub supported_position_attributes: BitSet<{ min_bytes(ABS_CNT) }>,
304    pub supported_motion_attributes: BitSet<{ min_bytes(REL_CNT) }>,
305    pub supported_switches: BitSet<{ min_bytes(SW_CNT) }>,
306    pub supported_leds: BitSet<{ min_bytes(LED_CNT) }>,
307    pub supported_haptics: BitSet<{ min_bytes(FF_CNT) }>,
308    pub supported_misc_features: BitSet<{ min_bytes(MSC_CNT) }>,
309    pub properties: BitSet<{ min_bytes(INPUT_PROP_CNT) }>,
310    pub mt_slot_axis_info: uapi::input_absinfo,
311    pub mt_tracking_id_axis_info: uapi::input_absinfo,
312    pub mt_pressure_axis_info: uapi::input_absinfo,
313    pub mt_touch_major_axis_info: uapi::input_absinfo,
314    pub mt_touch_minor_axis_info: uapi::input_absinfo,
315    pub x_axis_info: uapi::input_absinfo,
316    pub y_axis_info: uapi::input_absinfo,
317}
318
319fn absinfo_eq(a: &uapi::input_absinfo, b: &uapi::input_absinfo) -> bool {
320    a.value == b.value
321        && a.minimum == b.minimum
322        && a.maximum == b.maximum
323        && a.fuzz == b.fuzz
324        && a.flat == b.flat
325        && a.resolution == b.resolution
326}
327
328impl PartialEq for InputFileConfig {
329    fn eq(&self, other: &Self) -> bool {
330        self.supported_event_types == other.supported_event_types
331            && self.supported_keys == other.supported_keys
332            && self.supported_position_attributes == other.supported_position_attributes
333            && self.supported_motion_attributes == other.supported_motion_attributes
334            && self.supported_switches == other.supported_switches
335            && self.supported_leds == other.supported_leds
336            && self.supported_haptics == other.supported_haptics
337            && self.supported_misc_features == other.supported_misc_features
338            && self.properties == other.properties
339            && absinfo_eq(&self.mt_slot_axis_info, &other.mt_slot_axis_info)
340            && absinfo_eq(&self.mt_tracking_id_axis_info, &other.mt_tracking_id_axis_info)
341            && absinfo_eq(&self.mt_pressure_axis_info, &other.mt_pressure_axis_info)
342            && absinfo_eq(&self.mt_touch_major_axis_info, &other.mt_touch_major_axis_info)
343            && absinfo_eq(&self.mt_touch_minor_axis_info, &other.mt_touch_minor_axis_info)
344            && absinfo_eq(&self.x_axis_info, &other.x_axis_info)
345            && absinfo_eq(&self.y_axis_info, &other.y_axis_info)
346    }
347}
348
349impl Default for InputFileConfig {
350    fn default() -> Self {
351        Self {
352            supported_event_types: BitSet::new(),
353            supported_keys: BitSet::new(),
354            supported_position_attributes: BitSet::new(),
355            supported_motion_attributes: BitSet::new(),
356            supported_switches: BitSet::new(),
357            supported_leds: BitSet::new(),
358            supported_haptics: BitSet::new(),
359            supported_misc_features: BitSet::new(),
360            properties: BitSet::new(),
361            mt_slot_axis_info: uapi::input_absinfo::default(),
362            mt_tracking_id_axis_info: uapi::input_absinfo::default(),
363            mt_pressure_axis_info: uapi::input_absinfo::default(),
364            mt_touch_major_axis_info: uapi::input_absinfo::default(),
365            mt_touch_minor_axis_info: uapi::input_absinfo::default(),
366            x_axis_info: uapi::input_absinfo::default(),
367            y_axis_info: uapi::input_absinfo::default(),
368        }
369    }
370}
371
372impl InputFileConfig {
373    pub fn from_device_descriptor(
374        descriptor: &fidl_fuchsia_input_report::DeviceDescriptor,
375    ) -> Self {
376        let mut config = Self::default();
377
378        if let Some(touch) = &descriptor.touch {
379            config.supported_event_types.set(uapi::EV_ABS);
380            config.supported_event_types.set(uapi::EV_KEY);
381
382            let touch_type = touch.input.as_ref().and_then(|i| i.touch_type);
383            match touch_type {
384                Some(fidl_fuchsia_input_report::TouchType::Touchpad) => {
385                    config.properties.set(INPUT_PROP_POINTER);
386                    config.supported_keys.set(uapi::BTN_TOOL_FINGER);
387                }
388                _ => {
389                    config.properties.set(INPUT_PROP_DIRECT);
390                }
391            }
392
393            // Standard touch keys: only BTN_TOUCH unconditionally.
394            config.supported_keys.set(BTN_TOUCH);
395
396            // Multitouch and absolute coordinates matching touch_position_attributes().
397            config.supported_position_attributes = touch_position_attributes();
398
399            let mut max_contacts = 10;
400            if let Some(input) = &touch.input {
401                if let Some(contacts) = &input.contacts {
402                    if let Some(first) = contacts.first() {
403                        if let Some(x) = &first.position_x {
404                            config.x_axis_info = parse_axis_info(x);
405                        }
406                        if let Some(y) = &first.position_y {
407                            config.y_axis_info = parse_axis_info(y);
408                        }
409                        if let Some(pressure) = &first.pressure {
410                            config.supported_position_attributes.set(ABS_MT_PRESSURE);
411                            config.mt_pressure_axis_info = parse_axis_info(pressure);
412                        }
413                        match (&first.contact_width, &first.contact_height) {
414                            (Some(w), Some(h)) => {
415                                config.supported_position_attributes.set(ABS_MT_TOUCH_MAJOR);
416                                config.supported_position_attributes.set(ABS_MT_TOUCH_MINOR);
417                                if w.range.max >= h.range.max {
418                                    config.mt_touch_major_axis_info = parse_axis_info(w);
419                                    config.mt_touch_minor_axis_info = parse_axis_info(h);
420                                } else {
421                                    config.mt_touch_major_axis_info = parse_axis_info(h);
422                                    config.mt_touch_minor_axis_info = parse_axis_info(w);
423                                }
424                            }
425                            (Some(w), None) => {
426                                config.supported_position_attributes.set(ABS_MT_TOUCH_MAJOR);
427                                config.mt_touch_major_axis_info = parse_axis_info(w);
428                            }
429                            (None, Some(h)) => {
430                                config.supported_position_attributes.set(ABS_MT_TOUCH_MAJOR);
431                                config.mt_touch_major_axis_info = parse_axis_info(h);
432                            }
433                            (None, None) => {}
434                        }
435                    }
436                }
437                if let Some(contacts_limit) = input.max_contacts {
438                    if contacts_limit > 0 {
439                        max_contacts = contacts_limit;
440                    }
441                }
442                if let Some(buttons) = &input.buttons {
443                    for &button in buttons {
444                        if let Some(key) =
445                            starnix_modules_input_report_event_conversion::touch_button_to_linux_key(
446                                button,
447                            )
448                        {
449                            config.supported_keys.set(key);
450                        }
451                    }
452                }
453            }
454
455            config.mt_slot_axis_info = uapi::input_absinfo {
456                minimum: 0,
457                maximum: i32::try_from(
458                    max_contacts
459                        .min(fidl_fuchsia_input_report::TOUCH_MAX_CONTACTS)
460                        .saturating_sub(1),
461                )
462                .unwrap_or(i32::MAX),
463                ..Default::default()
464            };
465            config.mt_tracking_id_axis_info =
466                uapi::input_absinfo { minimum: 0, maximum: i32::MAX, ..Default::default() };
467        }
468
469        if let Some(keyboard) = &descriptor.keyboard {
470            config.supported_event_types.set(uapi::EV_KEY);
471
472            let mut has_keys = false;
473            if let Some(input) = &keyboard.input {
474                if let Some(keys3) = &input.keys3 {
475                    for key in keys3 {
476                        let keycode = starnix_modules_input_app_event_conversion::keymap::KEY_MAP
477                            .fuchsia_input_key_to_linux_keycode(*key);
478                        if keycode >= BTN_MISC && keycode < KEY_OK {
479                            continue;
480                        }
481                        if keycode != uapi::KEY_RESERVED && (keycode as usize) < KEY_CNT as usize {
482                            config.supported_keys.set(keycode);
483                            has_keys = true;
484                        }
485                    }
486                }
487            }
488            if !has_keys {
489                config.supported_keys |= keyboard_key_attributes();
490            }
491
492            if let Some(output) = &keyboard.output {
493                if let Some(leds) = &output.leds {
494                    for led in leds {
495                        let led_code = match led {
496                            fidl_fuchsia_input_report::LedType::NumLock => Some(uapi::LED_NUML),
497                            fidl_fuchsia_input_report::LedType::CapsLock => Some(uapi::LED_CAPSL),
498                            fidl_fuchsia_input_report::LedType::ScrollLock => {
499                                Some(uapi::LED_SCROLLL)
500                            }
501                            fidl_fuchsia_input_report::LedType::Compose => Some(uapi::LED_COMPOSE),
502                            fidl_fuchsia_input_report::LedType::Kana => Some(uapi::LED_KANA),
503                            _ => None,
504                        };
505                        if let Some(c) = led_code {
506                            config.supported_event_types.set(uapi::EV_LED);
507                            config.supported_leds.set(c);
508                        }
509                    }
510                }
511            }
512        }
513
514        if let Some(mouse) = &descriptor.mouse {
515            config.supported_event_types.set(uapi::EV_KEY);
516            config.supported_event_types.set(uapi::EV_REL);
517            if !config.properties.get(INPUT_PROP_DIRECT) {
518                config.properties.set(INPUT_PROP_POINTER);
519            }
520
521            let mut has_buttons = false;
522            let mut has_rel = false;
523            if let Some(input) = &mouse.input {
524                if let Some(buttons) = &input.buttons {
525                    for &button in buttons {
526                        if let Some(btn) =
527                            starnix_modules_input_report_event_conversion::mouse_button_to_linux_button(
528                                button,
529                            )
530                        {
531                            config.supported_keys.set(btn);
532                            has_buttons = true;
533                        }
534                    }
535                }
536                if input.movement_x.is_some() {
537                    config.supported_motion_attributes.set(REL_X);
538                    has_rel = true;
539                }
540                if input.movement_y.is_some() {
541                    config.supported_motion_attributes.set(REL_Y);
542                    has_rel = true;
543                }
544                if input.scroll_v.is_some() {
545                    config.supported_motion_attributes.set(REL_WHEEL);
546                    has_rel = true;
547                }
548                if input.scroll_h.is_some() {
549                    config.supported_motion_attributes.set(REL_HWHEEL);
550                    has_rel = true;
551                }
552                if input.scroll_v.is_some() || input.scroll_h.is_some() {
553                    config.supported_motion_attributes.set(REL_X);
554                    config.supported_motion_attributes.set(REL_Y);
555                }
556            }
557
558            if !has_buttons {
559                config.supported_keys |= mouse_key_attributes();
560            }
561
562            if !has_rel {
563                config.supported_motion_attributes |= mouse_motion_attributes();
564            }
565        }
566
567        if let Some(consumer_control) = &descriptor.consumer_control {
568            config.supported_event_types.set(uapi::EV_KEY);
569            let mut has_buttons = false;
570            if let Some(input) = &consumer_control.input {
571                if let Some(buttons) = &input.buttons {
572                    for button in buttons {
573                        if let Some(key) =
574                            starnix_modules_input_report_event_conversion::consumer_control_button_to_linux_key(
575                                *button,
576                            )
577                        {
578                            config.supported_keys.set(key);
579                            has_buttons = true;
580                        }
581                    }
582                }
583            }
584            if !has_buttons {
585                config.supported_keys.set(KEY_POWER);
586                config.supported_keys.set(KEY_VOLUMEUP);
587                config.supported_keys.set(KEY_VOLUMEDOWN);
588            }
589        }
590
591        config
592    }
593}
594
595/// Returns appropriate `INPUT_PROP`-erties for a keyboard device.
596fn keyboard_properties() -> BitSet<{ min_bytes(INPUT_PROP_CNT) }> {
597    let mut attrs = BitSet::new();
598    attrs.set(INPUT_PROP_DIRECT);
599    attrs
600}
601
602/// Returns appropriate `KEY`-board related flags for a touchscreen device.
603fn touch_key_attributes() -> BitSet<{ min_bytes(KEY_CNT) }> {
604    let mut attrs = BitSet::new();
605    attrs.set(BTN_TOUCH);
606    attrs.set(BTN_MISC); // Include BTN_MISC as a catchall key event.
607    attrs.set(KEY_SLEEP);
608    attrs.set(KEY_UP);
609    attrs.set(KEY_LEFT);
610    attrs.set(KEY_RIGHT);
611    attrs.set(KEY_DOWN);
612
613    attrs
614}
615
616/// Returns appropriate `ABS`-olute position related flags for a touchscreen device.
617fn touch_position_attributes() -> BitSet<{ min_bytes(ABS_CNT) }> {
618    let mut attrs = BitSet::new();
619    attrs.set(ABS_MT_SLOT);
620    attrs.set(ABS_MT_TRACKING_ID);
621    attrs.set(ABS_MT_POSITION_X);
622    attrs.set(ABS_MT_POSITION_Y);
623    attrs
624}
625
626/// Returns appropriate `INPUT_PROP`-erties for a touchscreen device.
627fn touch_properties() -> BitSet<{ min_bytes(INPUT_PROP_CNT) }> {
628    let mut attrs = BitSet::new();
629    attrs.set(INPUT_PROP_DIRECT);
630    attrs
631}
632
633/// Returns appropriate `KEY`-board related flags for a keyboard device.
634fn keyboard_key_attributes() -> BitSet<{ min_bytes(KEY_CNT) }> {
635    let mut attrs = BitSet::new();
636    for keycode in starnix_modules_input_app_event_conversion::keymap::KEY_MAP.all_linux_keycodes()
637    {
638        // `KEY_MAP` also carries a block of test-only keycodes (see b/311425670), 31 of which
639        // fall in the `BTN_*` range. Advertising those would make evdev clients classify this
640        // device as a gamepad in addition to a keyboard, which is the opposite of what this
641        // capability list is for. No real `KEY_*` constant lives in `BTN_MISC..KEY_OK`.
642        if keycode >= BTN_MISC && keycode < KEY_OK {
643            continue;
644        }
645        if (keycode as usize) < KEY_CNT as usize {
646            attrs.set(keycode);
647        }
648    }
649    attrs.set(BTN_MISC);
650    attrs.set(KEY_POWER);
651    attrs.set(KEY_VOLUMEUP);
652    attrs.set(KEY_VOLUMEDOWN);
653    attrs
654}
655
656/// Returns appropriate `ABS`-olute position related flags for a keyboard device.
657fn keyboard_position_attributes() -> BitSet<{ min_bytes(ABS_CNT) }> {
658    BitSet::new()
659}
660
661/// Returns appropriate `KEY`-board/button related flags for a mouse device.
662fn mouse_key_attributes() -> BitSet<{ min_bytes(KEY_CNT) }> {
663    let mut attrs = BitSet::new();
664    // In Linux UAPI, BTN_LEFT has the numeric value 0x110, which is also aliased to BTN_MOUSE.
665    // Evdev and libevdev treat BTN_MOUSE as the primary indicator that a device is a mouse:
666    // https://cs.opensource.google/fuchsia/fuchsia/+/main:third_party/android/platform/external/libevdev/libevdev/libevdev.c;l=1134-1140;drc=7007dd6442654da8be96df23cf632ae0e87d7b30
667    attrs.set(BTN_LEFT);
668    attrs.set(BTN_RIGHT);
669    attrs.set(BTN_MIDDLE);
670    attrs.set(BTN_SIDE);
671    attrs.set(BTN_EXTRA);
672    attrs
673}
674
675/// Returns appropriate `REL`-ative motion related flags for a mouse device.
676fn mouse_motion_attributes() -> BitSet<{ min_bytes(REL_CNT) }> {
677    let mut attrs = BitSet::new();
678    attrs.set(REL_X);
679    attrs.set(REL_Y);
680    attrs.set(REL_WHEEL);
681    attrs.set(REL_HWHEEL);
682    attrs
683}
684
685/// Returns appropriate `INPUT_PROP`-erties for a mouse device.
686fn mouse_properties() -> BitSet<{ min_bytes(INPUT_PROP_CNT) }> {
687    let mut attrs = BitSet::new();
688    // INPUT_PROP_POINTER indicates a mouse/pointer device.
689    // INPUT_PROP_DIRECT is for direct touchscreens and must NOT be set for a mouse.
690    attrs.set(INPUT_PROP_POINTER);
691    attrs
692}
693
694impl InputFile {
695    // Per https://www.linuxjournal.com/article/6429, the driver version is 32-bits wide,
696    // and interpreted as:
697    // * [31-16]: version
698    // * [15-08]: minor
699    // * [07-00]: patch level
700    const DRIVER_VERSION: u32 = 0;
701
702    /// Creates an `InputFile` instance suitable for emulating a touchscreen.
703    ///
704    /// # Parameters
705    /// - `info`: input device information.
706    /// - `width`: width of screen.
707    /// - `height`: height of screen.
708    /// - `node`: The inspect node for the parent device of "touch_input_file".
709    pub fn new_touch(
710        info: InputDeviceInfoHandle,
711        width: i32,
712        height: i32,
713        node: &fuchsia_inspect::Node,
714    ) -> Self {
715        // Fuchsia scales the position reported by the touch sensor to fit view coordinates.
716        // Hence, the range of touch positions is exactly the same as the range of view
717        // coordinates.
718        let config = InputFileConfig {
719            supported_event_types: BitSet::list([uapi::EV_ABS]),
720            supported_keys: touch_key_attributes(),
721            supported_position_attributes: touch_position_attributes(),
722            properties: touch_properties(),
723            mt_slot_axis_info: uapi::input_absinfo {
724                minimum: 0,
725                maximum: 10,
726                ..uapi::input_absinfo::default()
727            },
728            mt_tracking_id_axis_info: uapi::input_absinfo {
729                minimum: 0,
730                maximum: i32::MAX,
731                ..uapi::input_absinfo::default()
732            },
733            x_axis_info: uapi::input_absinfo {
734                minimum: 0,
735                maximum: i32::from(width),
736                // TODO(https://fxbug.dev/42075436): `value` field should contain the most recent
737                // X position.
738                ..uapi::input_absinfo::default()
739            },
740            y_axis_info: uapi::input_absinfo {
741                minimum: 0,
742                maximum: i32::from(height),
743                // TODO(https://fxbug.dev/42075436): `value` field should contain the most recent
744                // Y position.
745                ..uapi::input_absinfo::default()
746            },
747            ..Default::default()
748        };
749        Self::new_from_config(info, config, node)
750    }
751
752    /// Creates an `InputFile` instance suitable for emulating a keyboard.
753    ///
754    /// # Parameters
755    /// - `info`: input device information.
756    /// - `node`: The inspect node for the parent device of "keyboard_input_file".
757    pub fn new_keyboard(info: InputDeviceInfoHandle, node: &fuchsia_inspect::Node) -> Self {
758        let config = InputFileConfig {
759            supported_event_types: BitSet::list([uapi::EV_KEY]),
760            supported_keys: keyboard_key_attributes(),
761            supported_position_attributes: keyboard_position_attributes(),
762            properties: keyboard_properties(),
763            ..Default::default()
764        };
765        Self::new_from_config(info, config, node)
766    }
767
768    /// Creates an `InputFile` instance suitable for emulating a mouse.
769    ///
770    /// # Parameters
771    /// - `info`: input device information.
772    /// - `node`: The inspect node for the parent device of "mouse_input_file".
773    pub fn new_mouse(info: InputDeviceInfoHandle, node: &fuchsia_inspect::Node) -> Self {
774        let config = InputFileConfig {
775            // Mice report relative motion via EV_REL and buttons via EV_KEY.
776            // Absolute motion (EV_ABS) is not supported to avoid misclassification
777            // as a touch digitizer by libinput/Android EventHub.
778            supported_event_types: BitSet::list([uapi::EV_KEY, uapi::EV_REL]),
779            supported_keys: mouse_key_attributes(),
780            supported_motion_attributes: mouse_motion_attributes(),
781            properties: mouse_properties(),
782            ..Default::default()
783        };
784        Self::new_from_config(info, config, node)
785    }
786
787    /// Creates an `InputFile` instance from a pre-configured [`InputFileConfig`].
788    pub fn new_from_config(
789        info: InputDeviceInfoHandle,
790        config: InputFileConfig,
791        node: &fuchsia_inspect::Node,
792    ) -> Self {
793        Self {
794            driver_version: Self::DRIVER_VERSION,
795            info,
796            config,
797            events: SegQueue::new(),
798            waiters: WaitQueue::default(),
799            inspect_status: Some(InputFileStatus::new(node)),
800        }
801    }
802
803    /// Creates an `InputFile` instance by parsing a Fuchsia [`fidl_fuchsia_input_report::DeviceDescriptor`].
804    #[cfg(test)]
805    pub fn from_device_descriptor(
806        info: InputDeviceInfoHandle,
807        descriptor: &fidl_fuchsia_input_report::DeviceDescriptor,
808        node: &fuchsia_inspect::Node,
809    ) -> Self {
810        let config = InputFileConfig::from_device_descriptor(descriptor);
811        Self::new_from_config(info, config, node)
812    }
813
814    pub fn init_inspect_status(self: &Arc<Self>) {
815        if let Some(inspect) = &self.inspect_status {
816            *inspect.input_file.lock() = Arc::downgrade(self);
817        }
818    }
819
820    pub fn add_events(&self, events: Vec<uapi::input_event>) {
821        if events.is_empty() {
822            return;
823        }
824        if let Some(inspect) = &self.inspect_status {
825            inspect.count_fd_notify_calls();
826        }
827        for event in events {
828            self.events.push(LinuxEventWithTraceId::new(event));
829        }
830        self.waiters.notify_fd_events(FdEvents::POLLIN);
831    }
832
833    pub fn read_events(&self, limit: usize) -> Vec<LinuxEventWithTraceId> {
834        if let Some(inspect) = &self.inspect_status {
835            inspect.count_fd_read_calls();
836        }
837        let mut events = vec![];
838        for _ in 0..limit {
839            if let Some(event) = self.events.pop() {
840                events.push(event);
841            } else {
842                break;
843            }
844        }
845        // We do not notify if the buffer was not enough to read all events.
846        // `query_events` will still return `FdEvents::POLLIN` if there are remaining events,
847        // so the caller can continue reading or poll again.
848        events
849    }
850}
851
852// Remove the variable part of the request with params, so we can identify it.
853// Lowest 14 bits of the top 16 bits encode the buffer length in bytes.
854// See https://cs.opensource.google/fuchsia/fuchsia/+/main:third_party/android/platform/bionic/libc/kernel/uapi/linux/input.h;l=82;drc=0f0c18f695543b15b852f68f297744d03d642a26
855pub(crate) const EVIOC_VAR_LEN_MASK: u32 = !(uapi::_IOC_SIZEMASK << uapi::_IOC_SIZESHIFT);
856
857pub(crate) const EVIOCGNAME_BASE: u32 = uapi::EVIOCGNAME_0 & EVIOC_VAR_LEN_MASK;
858pub(crate) const EVIOCGPHYS_BASE: u32 = uapi::EVIOCGPHYS_0 & EVIOC_VAR_LEN_MASK;
859pub(crate) const EVIOCGUNIQ_BASE: u32 = uapi::EVIOCGUNIQ_0 & EVIOC_VAR_LEN_MASK;
860pub(crate) const EVIOCGKEY_BASE: u32 = uapi::EVIOCGKEY_0 & EVIOC_VAR_LEN_MASK;
861pub(crate) const EVIOCGLED_BASE: u32 = uapi::EVIOCGLED_0 & EVIOC_VAR_LEN_MASK;
862pub(crate) const EVIOCGSND_BASE: u32 = uapi::EVIOCGSND_0 & EVIOC_VAR_LEN_MASK;
863pub(crate) const EVIOCGSW_BASE: u32 = uapi::EVIOCGSW_0 & EVIOC_VAR_LEN_MASK;
864pub(crate) const EVIOCGPROP_BASE: u32 = uapi::EVIOCGPROP & EVIOC_VAR_LEN_MASK;
865pub(crate) const EVIOCGBIT_0_BASE: u32 = uapi::EVIOCGBIT_0 & EVIOC_VAR_LEN_MASK;
866pub(crate) const EVIOCGBIT_EV_KEY_BASE: u32 = uapi::EVIOCGBIT_EV_KEY & EVIOC_VAR_LEN_MASK;
867pub(crate) const EVIOCGBIT_EV_ABS_BASE: u32 = uapi::EVIOCGBIT_EV_ABS & EVIOC_VAR_LEN_MASK;
868pub(crate) const EVIOCGBIT_EV_REL_BASE: u32 = uapi::EVIOCGBIT_EV_REL & EVIOC_VAR_LEN_MASK;
869pub(crate) const EVIOCGBIT_EV_SW_BASE: u32 = uapi::EVIOCGBIT_EV_SW & EVIOC_VAR_LEN_MASK;
870pub(crate) const EVIOCGBIT_EV_LED_BASE: u32 = uapi::EVIOCGBIT_EV_LED & EVIOC_VAR_LEN_MASK;
871pub(crate) const EVIOCGBIT_EV_FF_BASE: u32 = uapi::EVIOCGBIT_EV_FF & EVIOC_VAR_LEN_MASK;
872pub(crate) const EVIOCGBIT_EV_MSC_BASE: u32 = uapi::EVIOCGBIT_EV_MSC & EVIOC_VAR_LEN_MASK;
873pub(crate) const EVIOCGBIT_EV_SND_BASE: u32 = uapi::EVIOCGBIT_EV_SND & EVIOC_VAR_LEN_MASK;
874
875pub(crate) const fn eviocgabs(axis: u32) -> u32 {
876    uapi::EVIOCGABS_X + axis
877}
878pub(crate) const EVIOCGABS_MT_PRESSURE: u32 = eviocgabs(uapi::ABS_MT_PRESSURE);
879pub(crate) const EVIOCGABS_MT_TOUCH_MAJOR: u32 = eviocgabs(uapi::ABS_MT_TOUCH_MAJOR);
880pub(crate) const EVIOCGABS_MT_TOUCH_MINOR: u32 = eviocgabs(uapi::ABS_MT_TOUCH_MINOR);
881
882impl FileOps for InputFile {
883    fileops_impl_nonseekable!();
884    fileops_impl_noop_sync!();
885
886    fn open(&self, file: &FileObject, _current_task: &CurrentTask) -> Result<(), Errno> {
887        if let Some(inspect) = &self.inspect_status {
888            inspect.set_open_timestamp(zx::MonotonicInstant::get().into_nanos());
889            if (file.flags() & OpenFlags::NONBLOCK) != OpenFlags::NONBLOCK {
890                inspect.set_opened_without_nonblock();
891            }
892        }
893        Ok(())
894    }
895
896    fn close(
897        self: Box<Self>,
898        _file: &starnix_core::vfs::FileObjectState,
899        _current_task: &CurrentTask,
900    ) {
901        if let Some(inspect) = &self.inspect_status {
902            inspect.set_closed();
903            inspect.set_closed_timestamp(zx::MonotonicInstant::get().into_nanos());
904        }
905    }
906
907    fn ioctl(
908        &self,
909        _file: &FileObject,
910        current_task: &CurrentTask,
911        request: u32,
912        arg: SyscallArg,
913    ) -> Result<SyscallResult, Errno> {
914        let user_addr = UserAddress::from(arg);
915        match request {
916            uapi::EVIOCGVERSION => {
917                current_task.write_object(UserRef::new(user_addr), &self.driver_version)?;
918                Ok(SUCCESS)
919            }
920            uapi::EVIOCGID => {
921                let input_id = self.info.lock().input_id;
922                current_task.write_object(UserRef::new(user_addr), &input_id)?;
923                Ok(SUCCESS)
924            }
925            uapi::EVIOCGABS_MT_SLOT => {
926                current_task
927                    .write_object(UserRef::new(user_addr), &self.config.mt_slot_axis_info)?;
928                Ok(SUCCESS)
929            }
930            uapi::EVIOCGABS_MT_TRACKING_ID => {
931                current_task
932                    .write_object(UserRef::new(user_addr), &self.config.mt_tracking_id_axis_info)?;
933                Ok(SUCCESS)
934            }
935            uapi::EVIOCGABS_X | uapi::EVIOCGABS_MT_POSITION_X => {
936                current_task.write_object(UserRef::new(user_addr), &self.config.x_axis_info)?;
937                Ok(SUCCESS)
938            }
939            uapi::EVIOCGABS_Y | uapi::EVIOCGABS_MT_POSITION_Y => {
940                current_task.write_object(UserRef::new(user_addr), &self.config.y_axis_info)?;
941                Ok(SUCCESS)
942            }
943            EVIOCGABS_MT_PRESSURE => {
944                current_task
945                    .write_object(UserRef::new(user_addr), &self.config.mt_pressure_axis_info)?;
946                Ok(SUCCESS)
947            }
948            EVIOCGABS_MT_TOUCH_MAJOR => {
949                current_task
950                    .write_object(UserRef::new(user_addr), &self.config.mt_touch_major_axis_info)?;
951                Ok(SUCCESS)
952            }
953            EVIOCGABS_MT_TOUCH_MINOR => {
954                current_task
955                    .write_object(UserRef::new(user_addr), &self.config.mt_touch_minor_axis_info)?;
956                Ok(SUCCESS)
957            }
958            uapi::EVIOCGRAB => {
959                // Xorg and libevdev use EVIOCGRAB to obtain exclusive access to the device.
960                track_stub!(TODO("https://fxbug.dev/322873200"), "EVIOCGRAB");
961                Ok(SUCCESS)
962            }
963
964            request_with_params => {
965                // The lowest 14 bits of the top 16 bits are the unsigned buffer length in bytes.
966                let buffer_bytes_count =
967                    ((request_with_params >> uapi::_IOC_SIZESHIFT) & uapi::_IOC_SIZEMASK) as usize;
968
969                let dir = (request_with_params >> uapi::_IOC_DIRSHIFT) & uapi::_IOC_DIRMASK;
970                let ioc_type = (request_with_params >> uapi::_IOC_TYPESHIFT) & uapi::_IOC_TYPEMASK;
971
972                // Validate that this is an evdev read ioctl ('E').
973                if dir != uapi::_IOC_READ || ioc_type != (b'E' as u32) {
974                    track_stub!(
975                        TODO("https://fxbug.dev/322873200"),
976                        "input ioctl invalid type or dir",
977                        request_with_params
978                    );
979                    return error!(EINVAL);
980                }
981
982                // Helper to copy a bitset slice to user memory.
983                // Note: Linux evdev's bits_to_user returns the number of bytes copied, whereas
984                // existing Starnix ioctl handlers and tests expect 0 (SUCCESS).
985                let write_bits = |bits: &[u8]| -> Result<SyscallResult, Errno> {
986                    if buffer_bytes_count == 0 {
987                        return Ok(SUCCESS);
988                    }
989                    // Zero out the entire user buffer in case the user reads too much.
990                    if current_task.zero(user_addr, buffer_bytes_count)? != buffer_bytes_count {
991                        return error!(EFAULT);
992                    }
993                    let to_copy = std::cmp::min(bits.len(), buffer_bytes_count);
994                    current_task.write_memory(user_addr, &bits[..to_copy])?;
995                    Ok(SUCCESS)
996                };
997
998                // Helper to copy a NUL-terminated string to user memory and return bytes written
999                // including the trailing NUL.
1000                let write_string = |bytes: &[u8]| -> Result<SyscallResult, Errno> {
1001                    if buffer_bytes_count == 0 {
1002                        return Ok(SUCCESS);
1003                    }
1004                    // Zero out the entire user buffer in case the user reads too much.
1005                    if current_task.zero(user_addr, buffer_bytes_count)? != buffer_bytes_count {
1006                        return error!(EFAULT);
1007                    }
1008                    let to_copy = std::cmp::min(bytes.len(), buffer_bytes_count.saturating_sub(1));
1009                    current_task.write_memory(user_addr, &bytes[..to_copy])?;
1010                    // String queries (EVIOCGNAME, EVIOCGPHYS) return the number of bytes written,
1011                    // including the trailing NUL.
1012                    Ok((to_copy + 1).into())
1013                };
1014
1015                match request_with_params & EVIOC_VAR_LEN_MASK {
1016                    EVIOCGBIT_0_BASE => write_bits(&self.config.supported_event_types.bytes),
1017                    EVIOCGBIT_EV_KEY_BASE => write_bits(&self.config.supported_keys.bytes),
1018                    EVIOCGBIT_EV_ABS_BASE => {
1019                        write_bits(&self.config.supported_position_attributes.bytes)
1020                    }
1021                    EVIOCGBIT_EV_REL_BASE => {
1022                        write_bits(&self.config.supported_motion_attributes.bytes)
1023                    }
1024                    EVIOCGBIT_EV_SW_BASE => write_bits(&self.config.supported_switches.bytes),
1025                    EVIOCGBIT_EV_LED_BASE => write_bits(&self.config.supported_leds.bytes),
1026                    EVIOCGBIT_EV_FF_BASE => write_bits(&self.config.supported_haptics.bytes),
1027                    EVIOCGBIT_EV_MSC_BASE => write_bits(&self.config.supported_misc_features.bytes),
1028                    EVIOCGBIT_EV_SND_BASE => write_bits(&[]),
1029                    EVIOCGPROP_BASE => write_bits(&self.config.properties.bytes),
1030                    EVIOCGNAME_BASE => {
1031                        let name = self.info.lock().name.clone();
1032                        write_string(name.as_bytes())
1033                    }
1034                    EVIOCGPHYS_BASE => {
1035                        let phys = format!("starnix/{}", self.info.lock().name);
1036                        write_string(phys.as_bytes())
1037                    }
1038                    EVIOCGUNIQ_BASE => {
1039                        // Starnix synthetic devices do not have a unique identifier (serial/MAC).
1040                        // Linux returns -ENOENT when no uniq string is set.
1041                        error!(ENOENT)
1042                    }
1043                    EVIOCGKEY_BASE | EVIOCGLED_BASE | EVIOCGSND_BASE | EVIOCGSW_BASE => {
1044                        // Current-state queries: which keys are held down, which LEDs are
1045                        // lit, which sounds are playing, and which switches are toggled.
1046                        // These are distinct from the EVIOCGBIT capability queries above,
1047                        // which report what the device *can* do rather than what it is
1048                        // doing right now.
1049                        //
1050                        // Starnix does not track any of this per-device state, so report
1051                        // all-zeros: nothing held, lit, playing, or toggled. That is an
1052                        // honest answer for these devices rather than a fabrication, but
1053                        // it is still an approximation, hence the stub.
1054                        //
1055                        // These must not fall through to EINVAL. `xf86-input-evdev` issues
1056                        // all four unconditionally during PreInit and treats any failure as
1057                        // fatal, so returning an error here prevents X11 from bringing up
1058                        // the keyboard, mouse, and touchscreen entirely.
1059                        track_stub!(
1060                            TODO("https://fxbug.dev/322873200"),
1061                            "evdev current-state query",
1062                            request_with_params
1063                        );
1064                        write_bits(&[])
1065                    }
1066                    _ => {
1067                        track_stub!(
1068                            TODO("https://fxbug.dev/322873200"),
1069                            "input ioctl",
1070                            request_with_params
1071                        );
1072                        error!(EINVAL)
1073                    }
1074                }
1075            }
1076        }
1077    }
1078
1079    fn read(
1080        &self,
1081        _file: &FileObject,
1082        current_task: &CurrentTask,
1083        offset: usize,
1084        data: &mut dyn OutputBuffer,
1085    ) -> Result<usize, Errno> {
1086        fuchsia_trace::duration!("input", "InputFile::read");
1087        debug_assert!(offset == 0);
1088        let input_event_size = InputEventPtr::size_of_object_for(current_task);
1089
1090        // The limit of the buffer is determined by taking the available bytes
1091        // and using integer division on the size of uapi::input_event in bytes.
1092        let limit = data.available() / input_event_size;
1093        let events = self.read_events(limit);
1094        if events.is_empty() {
1095            // Returns `EAGAIN` for file is opened with or without `O_NONBLOCK`.
1096            log_info!("read() returning EAGAIN");
1097            return error!(EAGAIN);
1098        }
1099
1100        let last_event_timeval = events.last().expect("events is nonempty").event.time;
1101        let last_event_time_ns = duration_from_timeval::<zx::MonotonicTimeline>(last_event_timeval)
1102            .unwrap()
1103            .into_nanos();
1104        self.inspect_status
1105            .clone()
1106            .map(|status| status.count_read_events(events.len() as u64, last_event_time_ns));
1107
1108        for event in &events {
1109            if let Some(trace_id) = event.trace_id {
1110                fuchsia_trace::duration!("input", "linux_event_read");
1111                fuchsia_trace::flow_end!("input", "linux_event", trace_id);
1112            }
1113        }
1114
1115        if current_task.is_arch32() {
1116            let events: Result<Vec<uapi::arch32::input_event>, _> =
1117                events.iter().map(|e| uapi::arch32::input_event::try_from(e.event)).collect();
1118            let events = events.map_err(|_| errno!(EINVAL))?;
1119            data.write_all(events.as_bytes())
1120        } else {
1121            let events: Vec<uapi::input_event> = events.iter().map(|e| e.event).collect();
1122            data.write_all(events.as_bytes())
1123        }
1124    }
1125
1126    fn write(
1127        &self,
1128        _file: &FileObject,
1129        _current_task: &CurrentTask,
1130        offset: usize,
1131        _data: &mut dyn InputBuffer,
1132    ) -> Result<usize, Errno> {
1133        debug_assert!(offset == 0);
1134        track_stub!(TODO("https://fxbug.dev/322874385"), "write() on input device");
1135        error!(EOPNOTSUPP)
1136    }
1137
1138    fn wait_async(
1139        &self,
1140        _file: &FileObject,
1141        _current_task: &CurrentTask,
1142        waiter: &Waiter,
1143        events: FdEvents,
1144        handler: EventHandler,
1145    ) -> Option<WaitCanceler> {
1146        Some(self.waiters.wait_async_fd_events(waiter, events, handler))
1147    }
1148
1149    fn query_events(
1150        &self,
1151        _file: &FileObject,
1152        _current_task: &CurrentTask,
1153    ) -> Result<FdEvents, Errno> {
1154        Ok(if self.events.is_empty() { FdEvents::empty() } else { FdEvents::POLLIN })
1155    }
1156}
1157
1158pub struct ArcInputFile(pub Arc<InputFile>);
1159
1160impl FileOps for ArcInputFile {
1161    fileops_impl_nonseekable!();
1162    fileops_impl_noop_sync!();
1163
1164    fn open(&self, file: &FileObject, current_task: &CurrentTask) -> Result<(), Errno> {
1165        self.0.as_ref().open(file, current_task)
1166    }
1167
1168    fn close(
1169        self: Box<Self>,
1170        _file: &starnix_core::vfs::FileObjectState,
1171        _current_task: &CurrentTask,
1172    ) {
1173        let arc_file = *self;
1174        if let Some(inspect) = &arc_file.0.inspect_status {
1175            inspect.set_closed();
1176            inspect.set_closed_timestamp(zx::MonotonicInstant::get().into_nanos());
1177        }
1178    }
1179
1180    fn ioctl(
1181        &self,
1182        file: &FileObject,
1183        current_task: &CurrentTask,
1184        request: u32,
1185        arg: SyscallArg,
1186    ) -> Result<SyscallResult, Errno> {
1187        self.0.as_ref().ioctl(file, current_task, request, arg)
1188    }
1189
1190    fn read(
1191        &self,
1192        file: &FileObject,
1193        current_task: &CurrentTask,
1194        offset: usize,
1195        data: &mut dyn OutputBuffer,
1196    ) -> Result<usize, Errno> {
1197        self.0.as_ref().read(file, current_task, offset, data)
1198    }
1199
1200    fn write(
1201        &self,
1202        file: &FileObject,
1203        current_task: &CurrentTask,
1204        offset: usize,
1205        data: &mut dyn InputBuffer,
1206    ) -> Result<usize, Errno> {
1207        self.0.as_ref().write(file, current_task, offset, data)
1208    }
1209
1210    fn wait_async(
1211        &self,
1212        file: &FileObject,
1213        current_task: &CurrentTask,
1214        waiter: &Waiter,
1215        events: FdEvents,
1216        handler: EventHandler,
1217    ) -> Option<WaitCanceler> {
1218        self.0.as_ref().wait_async(file, current_task, waiter, events, handler)
1219    }
1220
1221    fn query_events(
1222        &self,
1223        file: &FileObject,
1224        current_task: &CurrentTask,
1225    ) -> Result<FdEvents, Errno> {
1226        self.0.as_ref().query_events(file, current_task)
1227    }
1228}
1229
1230#[derive(Clone, Copy, Debug, PartialEq, Eq)]
1231pub struct BitSet<const NUM_BYTES: usize> {
1232    bytes: [u8; NUM_BYTES],
1233}
1234
1235impl<const NUM_BYTES: usize> BitSet<{ NUM_BYTES }> {
1236    pub const fn new() -> Self {
1237        Self { bytes: [0; NUM_BYTES] }
1238    }
1239
1240    pub const fn list<const N: usize>(bits: [u32; N]) -> Self {
1241        let mut bitset = Self::new();
1242        let mut i = 0;
1243        while i < bits.len() {
1244            bitset.set(bits[i]);
1245            i += 1;
1246        }
1247        bitset
1248    }
1249
1250    pub const fn set(&mut self, bitnum: u32) {
1251        let bitnum = bitnum as usize;
1252        let byte = bitnum / 8;
1253        let bit = bitnum % 8;
1254        self.bytes[byte] |= 1 << bit;
1255    }
1256
1257    pub fn get(&self, bitnum: u32) -> bool {
1258        let bitnum = bitnum as usize;
1259        let byte = bitnum / 8;
1260        let bit = bitnum % 8;
1261        (self.bytes[byte] & (1 << bit)) != 0
1262    }
1263}
1264
1265impl<const NUM_BYTES: usize> std::ops::BitOrAssign for BitSet<{ NUM_BYTES }> {
1266    fn bitor_assign(&mut self, rhs: Self) {
1267        for i in 0..NUM_BYTES {
1268            self.bytes[i] |= rhs.bytes[i];
1269        }
1270    }
1271}
1272
1273#[cfg(test)]
1274mod tests {
1275    use super::*;
1276    use crate::input_device::InputDeviceInfo;
1277    use std::sync::atomic::Ordering;
1278
1279    #[test]
1280    fn test_keyboard_input_file_attributes() {
1281        let inspector = fuchsia_inspect::Inspector::default();
1282        let node = inspector.root();
1283        let keyboard_file = InputFile::new_keyboard(
1284            InputDeviceInfo::new(
1285                uapi::input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
1286                "keyboard_test".to_string(),
1287            ),
1288            node,
1289        );
1290
1291        // Event types: EV_KEY
1292        assert!(keyboard_file.config.supported_event_types.get(uapi::EV_KEY));
1293        assert!(!keyboard_file.config.supported_event_types.get(uapi::EV_REL));
1294        assert!(!keyboard_file.config.supported_event_types.get(uapi::EV_ABS));
1295
1296        // Mapped Linux keys from KEY_MAP
1297        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_A));
1298        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_Z));
1299        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_1));
1300        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_ENTER));
1301        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_SPACE));
1302        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_ESC));
1303        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_TAB));
1304        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_BACKSPACE));
1305        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_LEFTSHIFT));
1306        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_LEFTCTRL));
1307        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_LEFTALT));
1308        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_POWER));
1309        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_VOLUMEUP));
1310        assert!(keyboard_file.config.supported_keys.get(uapi::KEY_VOLUMEDOWN));
1311        assert!(keyboard_file.config.supported_keys.get(uapi::BTN_MISC));
1312
1313        // Properties: INPUT_PROP_DIRECT
1314        assert!(keyboard_file.config.properties.get(uapi::INPUT_PROP_DIRECT));
1315        assert!(!keyboard_file.config.properties.get(uapi::INPUT_PROP_POINTER));
1316    }
1317
1318    #[test]
1319    fn test_read_events_no_notify_when_buffer_full() {
1320        let inspector = fuchsia_inspect::Inspector::default();
1321        let node = inspector.root();
1322        let input_file = InputFile::new_touch(
1323            InputDeviceInfo::new(
1324                uapi::input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
1325                "touch_test".to_string(),
1326            ),
1327            100,
1328            100,
1329            node,
1330        );
1331
1332        // Add some events.
1333        let event1 = uapi::input_event {
1334            type_: uapi::EV_KEY as u16,
1335            code: 1,
1336            value: 1,
1337            ..Default::default()
1338        };
1339        let event2 = uapi::input_event {
1340            type_: uapi::EV_KEY as u16,
1341            code: 2,
1342            value: 1,
1343            ..Default::default()
1344        };
1345        input_file.add_events(vec![event1, event2]);
1346
1347        // Verify that adding events triggered a notification.
1348        let notify_count =
1349            input_file.inspect_status.as_ref().unwrap().fd_notify_count.load(Ordering::Relaxed);
1350        assert_eq!(notify_count, 1);
1351
1352        // Read with a limit of 1.
1353        let events = input_file.read_events(1);
1354        assert_eq!(events.len(), 1);
1355
1356        // Verify that no additional notification was sent despite more events remaining.
1357        let notify_count =
1358            input_file.inspect_status.as_ref().unwrap().fd_notify_count.load(Ordering::Relaxed);
1359        assert_eq!(notify_count, 1);
1360    }
1361
1362    #[test]
1363    fn test_bitset_set_and_get() {
1364        let mut bitset = BitSet::<4>::new();
1365        assert!(!bitset.get(0));
1366        assert!(!bitset.get(15));
1367        assert!(!bitset.get(31));
1368
1369        bitset.set(0);
1370        bitset.set(15);
1371        bitset.set(31);
1372
1373        assert!(bitset.get(0));
1374        assert!(bitset.get(15));
1375        assert!(bitset.get(31));
1376        assert!(!bitset.get(1));
1377        assert!(!bitset.get(14));
1378        assert!(!bitset.get(30));
1379    }
1380
1381    #[test]
1382    fn test_mouse_capabilities() {
1383        let inspector = fuchsia_inspect::Inspector::default();
1384        let node = inspector.root();
1385        let mouse_file = InputFile::new_mouse(
1386            InputDeviceInfo::new(
1387                uapi::input_id { bustype: 0, vendor: 0, product: 0, version: 0 },
1388                "mouse_test".to_string(),
1389            ),
1390            node,
1391        );
1392
1393        // EV_KEY and EV_REL supported, EV_ABS not supported.
1394        assert!(mouse_file.config.supported_event_types.get(uapi::EV_KEY));
1395        assert!(mouse_file.config.supported_event_types.get(uapi::EV_REL));
1396        assert!(!mouse_file.config.supported_event_types.get(uapi::EV_ABS));
1397
1398        // Buttons supported: BTN_LEFT (BTN_MOUSE), BTN_RIGHT, BTN_MIDDLE, BTN_SIDE, BTN_EXTRA.
1399        assert!(mouse_file.config.supported_keys.get(BTN_LEFT));
1400        assert!(mouse_file.config.supported_keys.get(BTN_RIGHT));
1401        assert!(mouse_file.config.supported_keys.get(BTN_MIDDLE));
1402        assert!(mouse_file.config.supported_keys.get(BTN_SIDE));
1403        assert!(mouse_file.config.supported_keys.get(BTN_EXTRA));
1404        assert!(!mouse_file.config.supported_keys.get(BTN_TOUCH));
1405
1406        // Relative motion: REL_X, REL_Y, REL_WHEEL, REL_HWHEEL.
1407        assert!(mouse_file.config.supported_motion_attributes.get(REL_X));
1408        assert!(mouse_file.config.supported_motion_attributes.get(REL_Y));
1409        assert!(mouse_file.config.supported_motion_attributes.get(REL_WHEEL));
1410        assert!(mouse_file.config.supported_motion_attributes.get(REL_HWHEEL));
1411
1412        // Properties: INPUT_PROP_POINTER supported, INPUT_PROP_DIRECT not supported.
1413        assert!(mouse_file.config.properties.get(INPUT_PROP_POINTER));
1414        assert!(!mouse_file.config.properties.get(INPUT_PROP_DIRECT));
1415    }
1416
1417    #[::fuchsia::test]
1418    fn test_parse_axis_info() {
1419        let axis_mm = fidl_fuchsia_input_report::Axis {
1420            range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1421            unit: fidl_fuchsia_input_report::Unit {
1422                type_: fidl_fuchsia_input_report::UnitType::Meters,
1423                exponent: -3,
1424            },
1425        };
1426        let absinfo = parse_axis_info(&axis_mm);
1427        assert_eq!(absinfo.minimum, 0);
1428        assert_eq!(absinfo.maximum, 1080);
1429        assert_eq!(absinfo.resolution, 1);
1430
1431        let axis_um = fidl_fuchsia_input_report::Axis {
1432            range: fidl_fuchsia_input_report::Range { min: 0, max: 2400 },
1433            unit: fidl_fuchsia_input_report::Unit {
1434                type_: fidl_fuchsia_input_report::UnitType::Meters,
1435                exponent: -6,
1436            },
1437        };
1438        let absinfo_um = parse_axis_info(&axis_um);
1439        assert_eq!(absinfo_um.minimum, 0);
1440        assert_eq!(absinfo_um.maximum, 2400);
1441        assert_eq!(absinfo_um.resolution, 1000);
1442
1443        let axis_none = fidl_fuchsia_input_report::Axis {
1444            range: fidl_fuchsia_input_report::Range { min: -100, max: 100 },
1445            unit: fidl_fuchsia_input_report::Unit {
1446                type_: fidl_fuchsia_input_report::UnitType::None,
1447                exponent: 0,
1448            },
1449        };
1450        let absinfo_none = parse_axis_info(&axis_none);
1451        assert_eq!(absinfo_none.minimum, -100);
1452        assert_eq!(absinfo_none.maximum, 100);
1453        assert_eq!(absinfo_none.resolution, 0);
1454
1455        let axis_clamp = fidl_fuchsia_input_report::Axis {
1456            range: fidl_fuchsia_input_report::Range { min: i64::MIN, max: i64::MAX },
1457            unit: fidl_fuchsia_input_report::Unit {
1458                type_: fidl_fuchsia_input_report::UnitType::None,
1459                exponent: 0,
1460            },
1461        };
1462        let absinfo_clamp = parse_axis_info(&axis_clamp);
1463        assert_eq!(absinfo_clamp.minimum, i32::MIN);
1464        assert_eq!(absinfo_clamp.maximum, i32::MAX);
1465
1466        let axis_extreme_max = fidl_fuchsia_input_report::Axis {
1467            range: fidl_fuchsia_input_report::Range { min: 0, max: 100 },
1468            unit: fidl_fuchsia_input_report::Unit {
1469                type_: fidl_fuchsia_input_report::UnitType::Meters,
1470                exponent: i32::MAX,
1471            },
1472        };
1473        let absinfo_extreme_max = parse_axis_info(&axis_extreme_max);
1474        assert_eq!(absinfo_extreme_max.resolution, 0);
1475
1476        let axis_extreme_min = fidl_fuchsia_input_report::Axis {
1477            range: fidl_fuchsia_input_report::Range { min: 0, max: 100 },
1478            unit: fidl_fuchsia_input_report::Unit {
1479                type_: fidl_fuchsia_input_report::UnitType::Meters,
1480                exponent: i32::MIN,
1481            },
1482        };
1483        let absinfo_extreme_min = parse_axis_info(&axis_extreme_min);
1484        assert_eq!(absinfo_extreme_min.resolution, 0);
1485    }
1486
1487    #[::fuchsia::test]
1488    fn test_input_file_from_touch_descriptor() {
1489        let inspector = fuchsia_inspect::Inspector::default();
1490        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1491            touch: Some(fidl_fuchsia_input_report::TouchDescriptor {
1492                input: Some(fidl_fuchsia_input_report::TouchInputDescriptor {
1493                    contacts: Some(vec![fidl_fuchsia_input_report::ContactInputDescriptor {
1494                        position_x: Some(fidl_fuchsia_input_report::Axis {
1495                            range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1496                            unit: fidl_fuchsia_input_report::Unit {
1497                                type_: fidl_fuchsia_input_report::UnitType::Meters,
1498                                exponent: -3,
1499                            },
1500                        }),
1501                        position_y: Some(fidl_fuchsia_input_report::Axis {
1502                            range: fidl_fuchsia_input_report::Range { min: 0, max: 2400 },
1503                            unit: fidl_fuchsia_input_report::Unit {
1504                                type_: fidl_fuchsia_input_report::UnitType::Meters,
1505                                exponent: -3,
1506                            },
1507                        }),
1508                        pressure: Some(fidl_fuchsia_input_report::Axis {
1509                            range: fidl_fuchsia_input_report::Range { min: 0, max: 255 },
1510                            unit: fidl_fuchsia_input_report::Unit {
1511                                type_: fidl_fuchsia_input_report::UnitType::None,
1512                                exponent: 0,
1513                            },
1514                        }),
1515                        contact_width: Some(fidl_fuchsia_input_report::Axis {
1516                            range: fidl_fuchsia_input_report::Range { min: 0, max: 100 },
1517                            unit: fidl_fuchsia_input_report::Unit {
1518                                type_: fidl_fuchsia_input_report::UnitType::None,
1519                                exponent: 0,
1520                            },
1521                        }),
1522                        contact_height: Some(fidl_fuchsia_input_report::Axis {
1523                            range: fidl_fuchsia_input_report::Range { min: 0, max: 100 },
1524                            unit: fidl_fuchsia_input_report::Unit {
1525                                type_: fidl_fuchsia_input_report::UnitType::None,
1526                                exponent: 0,
1527                            },
1528                        }),
1529                        ..Default::default()
1530                    }]),
1531                    max_contacts: Some(5),
1532                    touch_type: Some(fidl_fuchsia_input_report::TouchType::Touchscreen),
1533                    buttons: Some(vec![fidl_fuchsia_input_report::TouchButton::Palm]),
1534                    ..Default::default()
1535                }),
1536                ..Default::default()
1537            }),
1538            ..Default::default()
1539        };
1540
1541        let info = InputDeviceInfo::new(
1542            uapi::input_id { bustype: 3, vendor: 0x18d1, product: 0x5678, version: 1 },
1543            "test_touch".to_string(),
1544        );
1545        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1546
1547        assert!(file.config.supported_event_types.get(uapi::EV_ABS));
1548        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
1549        assert!(file.config.properties.get(uapi::INPUT_PROP_DIRECT));
1550        assert!(!file.config.properties.get(uapi::INPUT_PROP_POINTER));
1551
1552        assert_eq!(file.config.x_axis_info.minimum, 0);
1553        assert_eq!(file.config.x_axis_info.maximum, 1080);
1554        assert_eq!(file.config.x_axis_info.resolution, 1);
1555
1556        assert_eq!(file.config.y_axis_info.minimum, 0);
1557        assert_eq!(file.config.y_axis_info.maximum, 2400);
1558        assert_eq!(file.config.y_axis_info.resolution, 1);
1559
1560        assert_eq!(file.config.mt_slot_axis_info.minimum, 0);
1561        assert_eq!(file.config.mt_slot_axis_info.maximum, 4);
1562        assert_eq!(file.config.mt_tracking_id_axis_info.maximum, i32::MAX);
1563
1564        assert_eq!(file.config.mt_pressure_axis_info.maximum, 255);
1565        assert_eq!(file.config.mt_touch_major_axis_info.maximum, 100);
1566        assert_eq!(file.config.mt_touch_minor_axis_info.maximum, 100);
1567
1568        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_SLOT));
1569        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_TRACKING_ID));
1570        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_POSITION_X));
1571        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_POSITION_Y));
1572        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_PRESSURE));
1573        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_TOUCH_MAJOR));
1574        assert!(file.config.supported_position_attributes.get(uapi::ABS_MT_TOUCH_MINOR));
1575        assert!(!file.config.supported_position_attributes.get(uapi::ABS_X));
1576        assert!(!file.config.supported_position_attributes.get(uapi::ABS_Y));
1577
1578        assert!(file.config.supported_keys.get(uapi::BTN_TOUCH));
1579        assert!(file.config.supported_keys.get(uapi::KEY_SLEEP));
1580        assert!(!file.config.supported_keys.get(uapi::KEY_UP));
1581    }
1582
1583    #[::fuchsia::test]
1584    fn test_input_file_from_touch_descriptor_no_buttons() {
1585        let inspector = fuchsia_inspect::Inspector::default();
1586        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1587            touch: Some(fidl_fuchsia_input_report::TouchDescriptor {
1588                input: Some(fidl_fuchsia_input_report::TouchInputDescriptor {
1589                    contacts: Some(vec![fidl_fuchsia_input_report::ContactInputDescriptor {
1590                        position_x: Some(fidl_fuchsia_input_report::Axis {
1591                            range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1592                            unit: fidl_fuchsia_input_report::Unit {
1593                                type_: fidl_fuchsia_input_report::UnitType::None,
1594                                exponent: 0,
1595                            },
1596                        }),
1597                        position_y: Some(fidl_fuchsia_input_report::Axis {
1598                            range: fidl_fuchsia_input_report::Range { min: 0, max: 2400 },
1599                            unit: fidl_fuchsia_input_report::Unit {
1600                                type_: fidl_fuchsia_input_report::UnitType::None,
1601                                exponent: 0,
1602                            },
1603                        }),
1604                        ..Default::default()
1605                    }]),
1606                    buttons: None,
1607                    ..Default::default()
1608                }),
1609                ..Default::default()
1610            }),
1611            ..Default::default()
1612        };
1613
1614        let info = InputDeviceInfo::new(
1615            uapi::input_id { bustype: 3, vendor: 0x18d1, product: 0x5678, version: 1 },
1616            "test_touch_no_buttons".to_string(),
1617        );
1618        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1619
1620        assert!(file.config.supported_keys.get(uapi::BTN_TOUCH));
1621        assert!(!file.config.supported_keys.get(uapi::KEY_SLEEP));
1622        assert!(!file.config.supported_keys.get(uapi::KEY_UP));
1623    }
1624
1625    #[::fuchsia::test]
1626    fn test_input_file_from_touchpad_descriptor() {
1627        let inspector = fuchsia_inspect::Inspector::default();
1628        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1629            touch: Some(fidl_fuchsia_input_report::TouchDescriptor {
1630                input: Some(fidl_fuchsia_input_report::TouchInputDescriptor {
1631                    contacts: Some(vec![fidl_fuchsia_input_report::ContactInputDescriptor {
1632                        position_x: Some(fidl_fuchsia_input_report::Axis {
1633                            range: fidl_fuchsia_input_report::Range { min: 0, max: 1920 },
1634                            unit: fidl_fuchsia_input_report::Unit {
1635                                type_: fidl_fuchsia_input_report::UnitType::Meters,
1636                                exponent: -3,
1637                            },
1638                        }),
1639                        position_y: Some(fidl_fuchsia_input_report::Axis {
1640                            range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1641                            unit: fidl_fuchsia_input_report::Unit {
1642                                type_: fidl_fuchsia_input_report::UnitType::Meters,
1643                                exponent: -3,
1644                            },
1645                        }),
1646                        ..Default::default()
1647                    }]),
1648                    max_contacts: Some(2),
1649                    touch_type: Some(fidl_fuchsia_input_report::TouchType::Touchpad),
1650                    ..Default::default()
1651                }),
1652                ..Default::default()
1653            }),
1654            ..Default::default()
1655        };
1656
1657        let info = InputDeviceInfo::new(
1658            uapi::input_id { bustype: 3, vendor: 0x18d1, product: 0x1234, version: 1 },
1659            "test_touchpad".to_string(),
1660        );
1661        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1662
1663        assert!(file.config.supported_event_types.get(uapi::EV_ABS));
1664        assert!(file.config.properties.get(uapi::INPUT_PROP_POINTER));
1665        assert!(!file.config.properties.get(uapi::INPUT_PROP_DIRECT));
1666        assert!(file.config.supported_keys.get(uapi::BTN_TOOL_FINGER));
1667        assert_eq!(file.config.mt_slot_axis_info.maximum, 1);
1668    }
1669
1670    #[::fuchsia::test]
1671    fn test_input_file_from_keyboard_descriptor() {
1672        let inspector = fuchsia_inspect::Inspector::default();
1673        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1674            keyboard: Some(fidl_fuchsia_input_report::KeyboardDescriptor {
1675                input: Some(fidl_fuchsia_input_report::KeyboardInputDescriptor {
1676                    keys3: Some(vec![
1677                        fidl_fuchsia_input::Key::A,
1678                        fidl_fuchsia_input::Key::B,
1679                        fidl_fuchsia_input::Key::Enter,
1680                    ]),
1681                    ..Default::default()
1682                }),
1683                output: Some(fidl_fuchsia_input_report::KeyboardOutputDescriptor {
1684                    leds: Some(vec![fidl_fuchsia_input_report::LedType::CapsLock]),
1685                    ..Default::default()
1686                }),
1687                ..Default::default()
1688            }),
1689            ..Default::default()
1690        };
1691
1692        let info = InputDeviceInfo::new(
1693            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1694            "test_keyboard".to_string(),
1695        );
1696        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1697
1698        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
1699        assert!(file.config.supported_event_types.get(uapi::EV_LED));
1700        assert!(!file.config.properties.get(uapi::INPUT_PROP_DIRECT));
1701
1702        assert!(file.config.supported_keys.get(uapi::KEY_A));
1703        assert!(file.config.supported_keys.get(uapi::KEY_B));
1704        assert!(file.config.supported_keys.get(uapi::KEY_ENTER));
1705        assert!(file.config.supported_leds.get(uapi::LED_CAPSL));
1706    }
1707
1708    #[::fuchsia::test]
1709    fn test_input_file_from_keyboard_descriptor_fallback_keys() {
1710        let inspector = fuchsia_inspect::Inspector::default();
1711        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1712            keyboard: Some(fidl_fuchsia_input_report::KeyboardDescriptor {
1713                input: Some(fidl_fuchsia_input_report::KeyboardInputDescriptor {
1714                    keys3: None,
1715                    ..Default::default()
1716                }),
1717                ..Default::default()
1718            }),
1719            ..Default::default()
1720        };
1721
1722        let info = InputDeviceInfo::new(
1723            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1724            "test_keyboard".to_string(),
1725        );
1726        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1727
1728        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
1729        assert!(!file.config.properties.get(uapi::INPUT_PROP_DIRECT));
1730
1731        // When !has_keys, standard keycodes from KEY_MAP plus media keys should be set.
1732        assert!(file.config.supported_keys.get(uapi::KEY_A));
1733        assert!(file.config.supported_keys.get(uapi::KEY_Z));
1734        assert!(file.config.supported_keys.get(uapi::KEY_ENTER));
1735        assert!(file.config.supported_keys.get(uapi::KEY_POWER));
1736        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEUP));
1737        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEDOWN));
1738    }
1739
1740    #[::fuchsia::test]
1741    fn test_input_file_from_keyboard_descriptor_unknown_led_does_not_set_ev_led() {
1742        let inspector = fuchsia_inspect::Inspector::default();
1743        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1744            keyboard: Some(fidl_fuchsia_input_report::KeyboardDescriptor {
1745                input: Some(fidl_fuchsia_input_report::KeyboardInputDescriptor {
1746                    keys3: Some(vec![fidl_fuchsia_input::Key::A]),
1747                    ..Default::default()
1748                }),
1749                output: Some(fidl_fuchsia_input_report::KeyboardOutputDescriptor {
1750                    leds: Some(vec![
1751                        fidl_fuchsia_input_report::LedType::from_primitive_allow_unknown(999),
1752                    ]),
1753                    ..Default::default()
1754                }),
1755                ..Default::default()
1756            }),
1757            ..Default::default()
1758        };
1759
1760        let info = InputDeviceInfo::new(
1761            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1762            "test_keyboard_unknown_led".to_string(),
1763        );
1764        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1765
1766        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
1767        assert!(!file.config.supported_event_types.get(uapi::EV_LED));
1768        assert_eq!(file.config.supported_leds, BitSet::new());
1769    }
1770
1771    #[::fuchsia::test]
1772    fn test_input_file_from_mouse_descriptor() {
1773        let inspector = fuchsia_inspect::Inspector::default();
1774        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1775            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1776                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1777                    movement_x: Some(fidl_fuchsia_input_report::Axis {
1778                        range: fidl_fuchsia_input_report::Range { min: -127, max: 127 },
1779                        unit: fidl_fuchsia_input_report::Unit {
1780                            type_: fidl_fuchsia_input_report::UnitType::None,
1781                            exponent: 0,
1782                        },
1783                    }),
1784                    movement_y: Some(fidl_fuchsia_input_report::Axis {
1785                        range: fidl_fuchsia_input_report::Range { min: -127, max: 127 },
1786                        unit: fidl_fuchsia_input_report::Unit {
1787                            type_: fidl_fuchsia_input_report::UnitType::None,
1788                            exponent: 0,
1789                        },
1790                    }),
1791                    scroll_v: Some(fidl_fuchsia_input_report::Axis {
1792                        range: fidl_fuchsia_input_report::Range { min: -1, max: 1 },
1793                        unit: fidl_fuchsia_input_report::Unit {
1794                            type_: fidl_fuchsia_input_report::UnitType::None,
1795                            exponent: 0,
1796                        },
1797                    }),
1798                    ..Default::default()
1799                }),
1800                ..Default::default()
1801            }),
1802            ..Default::default()
1803        };
1804
1805        let info = InputDeviceInfo::new(
1806            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1807            "test_mouse".to_string(),
1808        );
1809        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1810
1811        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
1812        assert!(file.config.supported_event_types.get(uapi::EV_REL));
1813        assert!(file.config.properties.get(uapi::INPUT_PROP_POINTER));
1814
1815        assert!(file.config.supported_keys.get(uapi::BTN_LEFT));
1816        assert!(file.config.supported_keys.get(uapi::BTN_RIGHT));
1817        assert!(file.config.supported_motion_attributes.get(uapi::REL_X));
1818        assert!(file.config.supported_motion_attributes.get(uapi::REL_Y));
1819        assert!(file.config.supported_motion_attributes.get(uapi::REL_WHEEL));
1820    }
1821
1822    #[::fuchsia::test]
1823    fn test_input_file_from_mouse_descriptor_scroll_only() {
1824        let inspector = fuchsia_inspect::Inspector::default();
1825        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1826            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1827                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1828                    scroll_v: Some(fidl_fuchsia_input_report::Axis {
1829                        range: fidl_fuchsia_input_report::Range { min: -1, max: 1 },
1830                        unit: fidl_fuchsia_input_report::Unit {
1831                            type_: fidl_fuchsia_input_report::UnitType::None,
1832                            exponent: 0,
1833                        },
1834                    }),
1835                    ..Default::default()
1836                }),
1837                ..Default::default()
1838            }),
1839            ..Default::default()
1840        };
1841
1842        let info = InputDeviceInfo::new(
1843            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1844            "test_mouse_scroll".to_string(),
1845        );
1846        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1847
1848        assert!(file.config.supported_event_types.get(uapi::EV_REL));
1849        assert!(file.config.supported_motion_attributes.get(uapi::REL_X));
1850        assert!(file.config.supported_motion_attributes.get(uapi::REL_Y));
1851        assert!(file.config.supported_motion_attributes.get(uapi::REL_WHEEL));
1852    }
1853
1854    #[::fuchsia::test]
1855    fn test_input_file_from_mouse_descriptor_with_buttons() {
1856        let inspector = fuchsia_inspect::Inspector::default();
1857        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1858            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1859                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1860                    buttons: Some(vec![1, 2]),
1861                    movement_x: Some(fidl_fuchsia_input_report::Axis {
1862                        range: fidl_fuchsia_input_report::Range { min: -10, max: 10 },
1863                        unit: fidl_fuchsia_input_report::Unit {
1864                            type_: fidl_fuchsia_input_report::UnitType::None,
1865                            exponent: 0,
1866                        },
1867                    }),
1868                    ..Default::default()
1869                }),
1870                ..Default::default()
1871            }),
1872            ..Default::default()
1873        };
1874
1875        let info = InputDeviceInfo::new(
1876            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1877            "test_mouse_buttons".to_string(),
1878        );
1879        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1880
1881        assert!(file.config.supported_keys.get(uapi::BTN_LEFT));
1882        assert!(file.config.supported_keys.get(uapi::BTN_RIGHT));
1883        assert!(!file.config.supported_keys.get(uapi::BTN_MIDDLE));
1884    }
1885
1886    #[::fuchsia::test]
1887    fn test_input_file_from_mouse_descriptor_fallback_rel() {
1888        let inspector = fuchsia_inspect::Inspector::default();
1889        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1890            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1891                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1892                    movement_x: None,
1893                    movement_y: None,
1894                    scroll_v: None,
1895                    scroll_h: None,
1896                    ..Default::default()
1897                }),
1898                ..Default::default()
1899            }),
1900            ..Default::default()
1901        };
1902
1903        let info = InputDeviceInfo::new(
1904            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1905            "test_mouse".to_string(),
1906        );
1907        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1908
1909        // When !has_rel on a standalone mouse, it falls back to standard relative axes.
1910        assert!(file.config.supported_event_types.get(uapi::EV_REL));
1911        assert!(file.config.supported_motion_attributes.get(uapi::REL_X));
1912        assert!(file.config.supported_motion_attributes.get(uapi::REL_Y));
1913        assert!(file.config.supported_motion_attributes.get(uapi::REL_WHEEL));
1914        assert!(file.config.supported_motion_attributes.get(uapi::REL_HWHEEL));
1915    }
1916
1917    #[::fuchsia::test]
1918    fn test_input_file_from_mouse_descriptor_absolute_coordinates() {
1919        let inspector = fuchsia_inspect::Inspector::default();
1920        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1921            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1922                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1923                    position_x: Some(fidl_fuchsia_input_report::Axis {
1924                        range: fidl_fuchsia_input_report::Range { min: 0, max: 1920 },
1925                        unit: fidl_fuchsia_input_report::Unit {
1926                            type_: fidl_fuchsia_input_report::UnitType::None,
1927                            exponent: 0,
1928                        },
1929                    }),
1930                    position_y: Some(fidl_fuchsia_input_report::Axis {
1931                        range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1932                        unit: fidl_fuchsia_input_report::Unit {
1933                            type_: fidl_fuchsia_input_report::UnitType::None,
1934                            exponent: 0,
1935                        },
1936                    }),
1937                    ..Default::default()
1938                }),
1939                ..Default::default()
1940            }),
1941            ..Default::default()
1942        };
1943
1944        let info = InputDeviceInfo::new(
1945            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
1946            "test_abs_mouse".to_string(),
1947        );
1948        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
1949
1950        // Mice never report EV_ABS or BTN_TOUCH; even when position axes are
1951        // present in the descriptor, mice fall back to relative motion (EV_REL).
1952        assert!(!file.config.supported_event_types.get(uapi::EV_ABS));
1953        assert!(file.config.supported_event_types.get(uapi::EV_REL));
1954        assert!(!file.config.supported_position_attributes.get(uapi::ABS_X));
1955        assert!(!file.config.supported_position_attributes.get(uapi::ABS_Y));
1956        assert!(!file.config.supported_keys.get(uapi::BTN_TOUCH));
1957        assert!(file.config.supported_motion_attributes.get(uapi::REL_X));
1958        assert!(file.config.supported_motion_attributes.get(uapi::REL_Y));
1959    }
1960
1961    #[::fuchsia::test]
1962    fn test_input_file_from_composite_touch_and_mouse() {
1963        let inspector = fuchsia_inspect::Inspector::default();
1964        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
1965            touch: Some(fidl_fuchsia_input_report::TouchDescriptor {
1966                input: Some(fidl_fuchsia_input_report::TouchInputDescriptor {
1967                    contacts: Some(vec![fidl_fuchsia_input_report::ContactInputDescriptor {
1968                        position_x: Some(fidl_fuchsia_input_report::Axis {
1969                            range: fidl_fuchsia_input_report::Range { min: 0, max: 1080 },
1970                            unit: fidl_fuchsia_input_report::Unit {
1971                                type_: fidl_fuchsia_input_report::UnitType::None,
1972                                exponent: 0,
1973                            },
1974                        }),
1975                        position_y: Some(fidl_fuchsia_input_report::Axis {
1976                            range: fidl_fuchsia_input_report::Range { min: 0, max: 2400 },
1977                            unit: fidl_fuchsia_input_report::Unit {
1978                                type_: fidl_fuchsia_input_report::UnitType::None,
1979                                exponent: 0,
1980                            },
1981                        }),
1982                        ..Default::default()
1983                    }]),
1984                    ..Default::default()
1985                }),
1986                ..Default::default()
1987            }),
1988            mouse: Some(fidl_fuchsia_input_report::MouseDescriptor {
1989                input: Some(fidl_fuchsia_input_report::MouseInputDescriptor {
1990                    position_x: Some(fidl_fuchsia_input_report::Axis {
1991                        range: fidl_fuchsia_input_report::Range { min: 0, max: 500 },
1992                        unit: fidl_fuchsia_input_report::Unit {
1993                            type_: fidl_fuchsia_input_report::UnitType::None,
1994                            exponent: 0,
1995                        },
1996                    }),
1997                    movement_x: Some(fidl_fuchsia_input_report::Axis {
1998                        range: fidl_fuchsia_input_report::Range { min: -10, max: 10 },
1999                        unit: fidl_fuchsia_input_report::Unit {
2000                            type_: fidl_fuchsia_input_report::UnitType::None,
2001                            exponent: 0,
2002                        },
2003                    }),
2004                    ..Default::default()
2005                }),
2006                ..Default::default()
2007            }),
2008            ..Default::default()
2009        };
2010
2011        let info = InputDeviceInfo::new(
2012            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
2013            "composite".to_string(),
2014        );
2015        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
2016
2017        // Touch sets INPUT_PROP_DIRECT; mouse must NOT overwrite or add INPUT_PROP_POINTER.
2018        assert!(file.config.properties.get(uapi::INPUT_PROP_DIRECT));
2019        assert!(!file.config.properties.get(uapi::INPUT_PROP_POINTER));
2020
2021        // Touch axes (0..1080) must NOT be overwritten by mouse position_x (0..500).
2022        assert_eq!(file.config.x_axis_info.maximum, 1080);
2023        assert!(file.config.supported_event_types.get(uapi::EV_REL));
2024        assert!(file.config.supported_event_types.get(uapi::EV_ABS));
2025    }
2026
2027    #[::fuchsia::test]
2028    fn test_input_file_from_composite_touch_keyboard_descriptor() {
2029        let inspector = fuchsia_inspect::Inspector::default();
2030        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
2031            touch: Some(fidl_fuchsia_input_report::TouchDescriptor {
2032                input: Some(fidl_fuchsia_input_report::TouchInputDescriptor {
2033                    touch_type: Some(fidl_fuchsia_input_report::TouchType::Touchpad),
2034                    ..Default::default()
2035                }),
2036                ..Default::default()
2037            }),
2038            keyboard: Some(fidl_fuchsia_input_report::KeyboardDescriptor {
2039                input: Some(fidl_fuchsia_input_report::KeyboardInputDescriptor {
2040                    keys3: None,
2041                    ..Default::default()
2042                }),
2043                ..Default::default()
2044            }),
2045            ..Default::default()
2046        };
2047
2048        let info = InputDeviceInfo::new(
2049            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
2050            "composite_touch_keyboard".to_string(),
2051        );
2052        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
2053
2054        // Touch keys (BTN_TOUCH, BTN_TOOL_FINGER) must not be overwritten by keyboard fallback keys.
2055        assert!(file.config.supported_keys.get(uapi::BTN_TOUCH));
2056        assert!(file.config.supported_keys.get(uapi::BTN_TOOL_FINGER));
2057
2058        // Keyboard fallback keys must also be present.
2059        assert!(file.config.supported_keys.get(uapi::KEY_A));
2060        assert!(file.config.supported_keys.get(uapi::KEY_Z));
2061        assert!(file.config.supported_keys.get(uapi::KEY_ENTER));
2062    }
2063
2064    #[::fuchsia::test]
2065    fn test_input_file_from_consumer_control_descriptor() {
2066        let inspector = fuchsia_inspect::Inspector::default();
2067        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
2068            consumer_control: Some(fidl_fuchsia_input_report::ConsumerControlDescriptor {
2069                input: Some(fidl_fuchsia_input_report::ConsumerControlInputDescriptor {
2070                    buttons: Some(vec![
2071                        fidl_fuchsia_input_report::ConsumerControlButton::VolumeUp,
2072                        fidl_fuchsia_input_report::ConsumerControlButton::VolumeDown,
2073                        fidl_fuchsia_input_report::ConsumerControlButton::Power,
2074                        fidl_fuchsia_input_report::ConsumerControlButton::Pause,
2075                        fidl_fuchsia_input_report::ConsumerControlButton::MicMute,
2076                    ]),
2077                    ..Default::default()
2078                }),
2079                ..Default::default()
2080            }),
2081            ..Default::default()
2082        };
2083
2084        let info = InputDeviceInfo::new(
2085            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
2086            "test_buttons".to_string(),
2087        );
2088        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
2089
2090        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
2091        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEUP));
2092        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEDOWN));
2093        assert!(file.config.supported_keys.get(uapi::KEY_POWER));
2094        assert!(file.config.supported_keys.get(uapi::KEY_PAUSE));
2095        assert!(file.config.supported_keys.get(uapi::KEY_MICMUTE));
2096    }
2097
2098    #[::fuchsia::test]
2099    fn test_input_file_from_consumer_control_descriptor_fallback() {
2100        let inspector = fuchsia_inspect::Inspector::default();
2101        let descriptor = fidl_fuchsia_input_report::DeviceDescriptor {
2102            consumer_control: Some(fidl_fuchsia_input_report::ConsumerControlDescriptor {
2103                input: Some(fidl_fuchsia_input_report::ConsumerControlInputDescriptor {
2104                    buttons: None,
2105                    ..Default::default()
2106                }),
2107                ..Default::default()
2108            }),
2109            ..Default::default()
2110        };
2111
2112        let info = InputDeviceInfo::new(
2113            uapi::input_id { bustype: 3, vendor: 1, product: 2, version: 3 },
2114            "test_buttons_fallback".to_string(),
2115        );
2116        let file = InputFile::from_device_descriptor(info, &descriptor, inspector.root());
2117
2118        assert!(file.config.supported_event_types.get(uapi::EV_KEY));
2119        assert!(file.config.supported_keys.get(uapi::KEY_POWER));
2120        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEUP));
2121        assert!(file.config.supported_keys.get(uapi::KEY_VOLUMEDOWN));
2122        assert!(!file.config.supported_keys.get(uapi::KEY_PAUSE));
2123    }
2124}