| // Copyright 2023 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| use crate::uinput::vfs::{CloseFreeSafe, NamespaceNode}; |
| use crate::{InputEventsRelayHandle, InputFile, OpenedFiles}; |
| use bit_vec::BitVec; |
| use fidl_fuchsia_ui_test_input::{ |
| self as futinput, CoordinateUnit, DisplayDimensions, KeyboardSimulateKeyEventRequest, |
| RegistryRegisterKeyboardAndGetDeviceInfoRequest, |
| RegistryRegisterTouchScreenAndGetDeviceInfoRequest, |
| }; |
| use starnix_core::device::kobject::{Device, DeviceMetadata}; |
| use starnix_core::device::{DeviceMode, DeviceOps}; |
| use starnix_core::fileops_impl_seekless; |
| use starnix_core::mm::MemoryAccessorExt; |
| use starnix_core::task::CurrentTask; |
| use starnix_core::vfs::{ |
| self, FileObject, FileOps, FsString, default_ioctl, fileops_impl_noop_sync, |
| }; |
| use starnix_logging::log_warn; |
| use starnix_modules_input_event_conversion::key_linux_to_fuchsia::LinuxKeyboardEventParser; |
| use starnix_modules_input_event_conversion::touch_linux_to_fuchsia::LinuxTouchEventParser; |
| use starnix_sync::{FileOpsCore, LockEqualOrBefore, Locked, Mutex, Unlocked}; |
| use starnix_syscalls::{SUCCESS, SyscallArg, SyscallResult}; |
| use starnix_uapi::device_type::INPUT_MAJOR; |
| use starnix_uapi::errors::Errno; |
| use starnix_uapi::open_flags::OpenFlags; |
| use starnix_uapi::user_address::{MultiArchUserRef, UserRef}; |
| use starnix_uapi::{device_type, errno, error, uapi}; |
| use std::sync::Arc; |
| use std::sync::atomic::{AtomicI32, Ordering}; |
| |
| // Return the current uinput API version 5, it also told caller this uinput |
| // supports UI_DEV_SETUP. |
| const UINPUT_VERSION: u32 = 5; |
| |
| type InputEventPtr = MultiArchUserRef<uapi::input_event, uapi::arch32::input_event>; |
| |
| #[derive(Clone)] |
| enum DeviceType { |
| Keyboard, |
| Touchscreen(i32, i32), |
| } |
| |
| pub fn register_uinput_device( |
| locked: &mut Locked<Unlocked>, |
| system_task: &CurrentTask, |
| input_event_relay_handle: Arc<InputEventsRelayHandle>, |
| ) -> Result<(), Errno> { |
| let kernel = system_task.kernel(); |
| let registry = &kernel.device_registry; |
| let misc_class = registry.objects.misc_class(); |
| let device = UinputDevice::new(input_event_relay_handle); |
| registry.register_device( |
| locked, |
| system_task, |
| "uinput".into(), |
| DeviceMetadata::new("uinput".into(), device_type::DeviceType::UINPUT, DeviceMode::Char), |
| misc_class, |
| device, |
| )?; |
| Ok(()) |
| } |
| |
| fn add_and_register_input_device<L>( |
| locked: &mut Locked<L>, |
| system_task: &CurrentTask, |
| dev_ops: impl DeviceOps, |
| device_id: u32, |
| ) -> Result<Device, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let kernel = system_task.kernel(); |
| let registry = &kernel.device_registry; |
| |
| let input_class = registry.objects.input_class(); |
| |
| registry.register_device( |
| locked, |
| system_task, |
| FsString::from(format!("event{}", device_id)).as_ref(), |
| DeviceMetadata::new( |
| format!("input/event{}", device_id).into(), |
| starnix_uapi::device_type::DeviceType::new(INPUT_MAJOR, device_id), |
| DeviceMode::Char, |
| ), |
| input_class, |
| dev_ops, |
| ) |
| } |
| |
| #[derive(Clone)] |
| struct UinputDevice { |
| input_event_relay: Arc<InputEventsRelayHandle>, |
| } |
| |
| impl UinputDevice { |
| pub fn new(input_event_relay: Arc<InputEventsRelayHandle>) -> Self { |
| Self { input_event_relay } |
| } |
| } |
| |
| impl DeviceOps for UinputDevice { |
| fn open( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _current_task: &CurrentTask, |
| _id: device_type::DeviceType, |
| _node: &NamespaceNode, |
| _flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| Ok(Box::new(UinputDeviceFile::new(self.input_event_relay.clone()))) |
| } |
| } |
| |
| enum CreatedDevice { |
| None, |
| Keyboard(futinput::KeyboardSynchronousProxy, LinuxKeyboardEventParser), |
| // LinuxTouchEventParser need to boxed to avoid warning: large-enum-variant. |
| Touchscreen(futinput::TouchScreenSynchronousProxy, Box<LinuxTouchEventParser>), |
| } |
| |
| struct Range { |
| min: i32, |
| max: i32, |
| } |
| struct UinputDeviceMutableState { |
| enabled_evbits: BitVec, |
| input_id: Option<uapi::input_id>, |
| created_device: CreatedDevice, |
| k_device: Option<Device>, |
| device_id: Option<u32>, |
| x_range: Option<Range>, |
| y_range: Option<Range>, |
| } |
| |
| impl UinputDeviceMutableState { |
| fn get_id_and_device_type(&self) -> Option<(uapi::input_id, DeviceType)> { |
| let input_id = match self.input_id { |
| Some(input_id) => input_id, |
| None => return None, |
| }; |
| // Currently only support Keyboard and Touchscreen, if evbits contains |
| // EV_ABS, consider it is Touchscreen. This need to be revisit when we |
| // want to support more device types. |
| let device_type = match self.enabled_evbits.clone().get(uapi::EV_ABS as usize) { |
| Some(true) => { |
| // TODO(b/304595635): Check if screen size is required. |
| let mut touchscreen_width = 1000; |
| let mut touchscreen_height = 1000; |
| |
| if self.x_range.is_some() && self.y_range.is_some() { |
| let x_range = self.x_range.as_ref().unwrap(); |
| let y_range = self.y_range.as_ref().unwrap(); |
| touchscreen_width = x_range.max - x_range.min; |
| touchscreen_height = y_range.max - y_range.min; |
| } |
| DeviceType::Touchscreen(touchscreen_width, touchscreen_height) |
| } |
| Some(false) | None => DeviceType::Keyboard, |
| }; |
| |
| Some((input_id, device_type)) |
| } |
| } |
| |
| struct UinputDeviceFile { |
| input_event_relay: Arc<InputEventsRelayHandle>, |
| inner: Mutex<UinputDeviceMutableState>, |
| } |
| |
| impl UinputDeviceFile { |
| pub fn new(input_event_relay: Arc<InputEventsRelayHandle>) -> Self { |
| Self { |
| input_event_relay, |
| inner: Mutex::new(UinputDeviceMutableState { |
| enabled_evbits: BitVec::from_elem(uapi::EV_CNT as usize, false), |
| input_id: None, |
| created_device: CreatedDevice::None, |
| k_device: None, |
| device_id: None, |
| x_range: None, |
| y_range: None, |
| }), |
| } |
| } |
| |
| /// UI_SET_EVBIT caller pass a u32 as the event type "EV_*" to set this |
| /// uinput device may handle events with the given event type. |
| fn ui_set_evbit(&self, arg: SyscallArg) -> Result<SyscallResult, Errno> { |
| let evbit: u32 = arg.into(); |
| match evbit { |
| uapi::EV_KEY | uapi::EV_ABS => { |
| self.inner.lock().enabled_evbits.set(evbit as usize, true); |
| Ok(SUCCESS) |
| } |
| _ => { |
| log_warn!("UI_SET_EVBIT with unsupported evbit {}", evbit); |
| error!(EPERM) |
| } |
| } |
| } |
| |
| /// UI_ABS_SETUP caller pass in event codes and min and max value for |
| /// the event code. |
| fn ui_abs_setup( |
| &self, |
| current_task: &CurrentTask, |
| abs_setup: UserRef<starnix_uapi::uinput_abs_setup>, |
| ) -> Result<SyscallResult, Errno> { |
| let setup: starnix_uapi::uinput_abs_setup = current_task.read_object(abs_setup)?; |
| let code: u32 = setup.code.into(); |
| match code { |
| uapi::ABS_MT_POSITION_X => { |
| self.inner.lock().x_range = |
| Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum }); |
| } |
| uapi::ABS_MT_POSITION_Y => { |
| self.inner.lock().y_range = |
| Some(Range { min: setup.absinfo.minimum, max: setup.absinfo.maximum }); |
| } |
| _ => { |
| log_warn!("UI_ABS_SETUP ignore event code {}", setup.code); |
| } |
| } |
| Ok(SUCCESS) |
| } |
| |
| /// UI_GET_VERSION caller pass a address for u32 to `arg` to receive the |
| /// uinput version. ioctl returns SUCCESS(0) for success calls, and |
| /// EFAULT(14) for given address is null. |
| fn ui_get_version( |
| &self, |
| current_task: &CurrentTask, |
| user_version: UserRef<u32>, |
| ) -> Result<SyscallResult, Errno> { |
| let response: u32 = UINPUT_VERSION; |
| current_task.write_object(user_version, &response)?; |
| Ok(SUCCESS) |
| } |
| |
| /// UI_DEV_SETUP set the name of device and input_id (bustype, vendor id, |
| /// product id, version) to the uinput device. |
| fn ui_dev_setup( |
| &self, |
| current_task: &CurrentTask, |
| user_uinput_setup: UserRef<uapi::uinput_setup>, |
| ) -> Result<SyscallResult, Errno> { |
| let uinput_setup = current_task.read_object(user_uinput_setup)?; |
| self.inner.lock().input_id = Some(uinput_setup.id); |
| Ok(SUCCESS) |
| } |
| |
| fn ui_dev_create<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ) -> Result<SyscallResult, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| // Only eng and userdebug builds include the `fuchsia.ui.test.input` service. |
| let registry = match fuchsia_component::client::connect_to_protocol_sync::< |
| futinput::RegistryMarker, |
| >() { |
| Ok(proxy) => Some(proxy), |
| Err(_) => { |
| log_warn!("Could not connect to fuchsia.ui.test.input/Registry"); |
| None |
| } |
| }; |
| self.ui_dev_create_inner(locked, current_task, registry) |
| } |
| |
| /// UI_DEV_CREATE calls create the uinput device with given information |
| /// from previous ioctl() calls. |
| fn ui_dev_create_inner<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| // Takes `registry` arg so we can manually inject a mock registry in unit tests. |
| registry: Option<futinput::RegistrySynchronousProxy>, |
| ) -> Result<SyscallResult, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| match registry { |
| Some(proxy) => { |
| let mut inner = self.inner.lock(); |
| let (input_id, device_type) = match inner.get_id_and_device_type() { |
| Some((id, dev)) => (id, dev), |
| None => return error!(EINVAL), |
| }; |
| |
| let open_files: OpenedFiles = Default::default(); |
| |
| let registered_device_id = match device_type { |
| DeviceType::Keyboard => { |
| let (key_client, key_server) = |
| fidl::endpoints::create_sync_proxy::<futinput::KeyboardMarker>(); |
| inner.created_device = |
| CreatedDevice::Keyboard(key_client, LinuxKeyboardEventParser::create()); |
| |
| // Register a keyboard |
| let register_res = proxy.register_keyboard_and_get_device_info( |
| RegistryRegisterKeyboardAndGetDeviceInfoRequest { |
| device: Some(key_server), |
| ..Default::default() |
| }, |
| zx::MonotonicInstant::INFINITE, |
| ); |
| |
| match register_res { |
| Ok(resp) => match resp.device_id { |
| Some(device_id) => { |
| inner.device_id = Some(device_id); |
| self.input_event_relay.add_keyboard_device( |
| device_id, |
| open_files.clone(), |
| None, |
| ); |
| device_id |
| } |
| None => { |
| log_warn!( |
| "register_keyboard_and_get_device_info response does not include a device_id" |
| ); |
| return error!(EPERM); |
| } |
| }, |
| Err(e) => { |
| log_warn!( |
| "Uinput could not register Keyboard device to Registry: {:?}", |
| e |
| ); |
| return error!(EPERM); |
| } |
| } |
| } |
| DeviceType::Touchscreen(_width, _height) => { |
| let (touch_client, touch_server) = |
| fidl::endpoints::create_sync_proxy::<futinput::TouchScreenMarker>(); |
| inner.created_device = CreatedDevice::Touchscreen( |
| touch_client, |
| Box::new(LinuxTouchEventParser::create()), |
| ); |
| |
| let mut request = RegistryRegisterTouchScreenAndGetDeviceInfoRequest { |
| device: Some(touch_server), |
| coordinate_unit: Some(CoordinateUnit::PhysicalPixels), |
| ..Default::default() |
| }; |
| |
| if inner.x_range.is_some() && inner.y_range.is_some() { |
| request.coordinate_unit = Some(CoordinateUnit::RegisteredDimensions); |
| let x_range = inner.x_range.as_ref().unwrap(); |
| let y_range = inner.y_range.as_ref().unwrap(); |
| request.display_dimensions = Some(DisplayDimensions { |
| min_x: x_range.min.into(), |
| max_x: x_range.max.into(), |
| min_y: y_range.min.into(), |
| max_y: y_range.max.into(), |
| }); |
| } |
| |
| // Register a touchscreen |
| let register_res = proxy.register_touch_screen_and_get_device_info( |
| request, |
| zx::Instant::INFINITE, |
| ); |
| |
| match register_res { |
| Ok(resp) => match resp.device_id { |
| Some(device_id) => { |
| inner.device_id = Some(device_id); |
| self.input_event_relay.add_touch_device( |
| device_id, |
| open_files.clone(), |
| None, |
| ); |
| device_id |
| } |
| None => { |
| log_warn!( |
| "register_touch_screen_and_get_device_info response does not include a device_id" |
| ); |
| return error!(EPERM); |
| } |
| }, |
| Err(e) => { |
| log_warn!( |
| "Uinput could not register Keyboard device to Registry: {:?}", |
| e |
| ); |
| return error!(EPERM); |
| } |
| } |
| } |
| }; |
| |
| let device = add_and_register_input_device( |
| locked, |
| current_task, |
| VirtualDevice { input_id, device_type, open_files }, |
| registered_device_id, |
| )?; |
| inner.k_device = Some(device); |
| |
| new_device(); |
| |
| Ok(SUCCESS) |
| } |
| None => { |
| log_warn!("No Registry available for Uinput."); |
| error!(EPERM) |
| } |
| } |
| } |
| |
| fn ui_dev_destroy<L>( |
| &self, |
| locked: &mut Locked<L>, |
| current_task: &CurrentTask, |
| ) -> Result<SyscallResult, Errno> |
| where |
| L: LockEqualOrBefore<FileOpsCore>, |
| { |
| let mut inner = self.inner.lock(); |
| match inner.device_id { |
| Some(device_id) => { |
| self.input_event_relay.remove_device(device_id); |
| } |
| None => { |
| // This is possible if caller does not call create device but calls destroy. |
| // No cleanup is needed for input event relay in this case. |
| } |
| } |
| |
| match inner.k_device.clone() { |
| Some(device) => { |
| let kernel = current_task.kernel(); |
| kernel.device_registry.remove_device(locked, current_task, device); |
| } |
| None => { |
| log_warn!("UI_DEV_DESTROY kHandle not found"); |
| return error!(EPERM); |
| } |
| } |
| inner.k_device = None; |
| inner.created_device = CreatedDevice::None; |
| |
| destroy_device(); |
| |
| Ok(SUCCESS) |
| } |
| } |
| |
| // TODO(b/312467059): Remove once ESC -> Power workaround can be remove. |
| static COUNT_OF_UINPUT_DEVICE: AtomicI32 = AtomicI32::new(0); |
| |
| fn new_device() { |
| let _ = COUNT_OF_UINPUT_DEVICE.fetch_add(1, Ordering::SeqCst); |
| } |
| |
| fn destroy_device() { |
| let _ = COUNT_OF_UINPUT_DEVICE.fetch_sub(1, Ordering::SeqCst); |
| } |
| |
| pub fn uinput_running() -> bool { |
| COUNT_OF_UINPUT_DEVICE.load(Ordering::SeqCst) > 0 |
| } |
| |
| /// `UinputDeviceFile` doesn't implement the `close` method. |
| impl CloseFreeSafe for UinputDeviceFile {} |
| impl FileOps for UinputDeviceFile { |
| fileops_impl_seekless!(); |
| fileops_impl_noop_sync!(); |
| |
| fn ioctl( |
| &self, |
| locked: &mut Locked<Unlocked>, |
| file: &FileObject, |
| current_task: &CurrentTask, |
| request: u32, |
| arg: SyscallArg, |
| ) -> Result<SyscallResult, Errno> { |
| match request { |
| uapi::UI_GET_VERSION => self.ui_get_version(current_task, arg.into()), |
| uapi::UI_SET_EVBIT => self.ui_set_evbit(arg), |
| uapi::UI_ABS_SETUP => self.ui_abs_setup(current_task, arg.into()), |
| // `fuchsia.ui.test.input.Registry` does not use some uinput ioctl |
| // request, just ignore the request and return SUCCESS, even args |
| // is invalid. |
| uapi::UI_SET_KEYBIT |
| | uapi::UI_SET_ABSBIT |
| | uapi::UI_SET_PHYS |
| | uapi::UI_SET_PROPBIT => Ok(SUCCESS), |
| uapi::UI_DEV_SETUP => self.ui_dev_setup(current_task, arg.into()), |
| uapi::UI_DEV_CREATE => self.ui_dev_create(locked, current_task), |
| uapi::UI_DEV_DESTROY => self.ui_dev_destroy(locked, current_task), |
| // default_ioctl() handles file system related requests and reject |
| // others. |
| _ => { |
| log_warn!("receive unknown ioctl request: {:?}", request); |
| default_ioctl(file, locked, current_task, request, arg) |
| } |
| } |
| } |
| |
| fn write( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &vfs::FileObject, |
| current_task: &starnix_core::task::CurrentTask, |
| _offset: usize, |
| data: &mut dyn vfs::buffers::InputBuffer, |
| ) -> Result<usize, Errno> { |
| let content = data.read_all()?; |
| let event = |
| InputEventPtr::read_from_prefix(current_task, &content).map_err(|_| errno!(EINVAL))?; |
| let mut inner = self.inner.lock(); |
| |
| match &mut inner.created_device { |
| CreatedDevice::Keyboard(proxy, parser) => { |
| let input_report = parser.handle(event); |
| match input_report { |
| Ok(Some(report)) => { |
| if let Some(keyboard_report) = report.keyboard { |
| let res = proxy.simulate_key_event( |
| &KeyboardSimulateKeyEventRequest { |
| report: Some(keyboard_report), |
| ..Default::default() |
| }, |
| zx::MonotonicInstant::INFINITE, |
| ); |
| if res.is_err() { |
| return error!(EIO); |
| } |
| } |
| } |
| Ok(None) => (), |
| Err(e) => return Err(e), |
| } |
| } |
| CreatedDevice::Touchscreen(proxy, parser) => { |
| let input_report = parser.handle(event); |
| match input_report { |
| Ok(Some(report)) => { |
| if let Some(touch_report) = report.touch { |
| let res = proxy.simulate_touch_event( |
| &touch_report, |
| zx::MonotonicInstant::INFINITE, |
| ); |
| if res.is_err() { |
| return error!(EIO); |
| } |
| } |
| } |
| Ok(None) => (), |
| Err(e) => return Err(e), |
| } |
| } |
| CreatedDevice::None => return error!(EINVAL), |
| } |
| |
| Ok(content.len()) |
| } |
| |
| fn read( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _file: &vfs::FileObject, |
| _current_task: &starnix_core::task::CurrentTask, |
| _offset: usize, |
| _data: &mut dyn vfs::buffers::OutputBuffer, |
| ) -> Result<usize, Errno> { |
| log_warn!("uinput FD does not support read()."); |
| error!(EINVAL) |
| } |
| } |
| |
| #[derive(Clone)] |
| pub struct VirtualDevice { |
| input_id: uapi::input_id, |
| device_type: DeviceType, |
| open_files: OpenedFiles, |
| } |
| |
| impl DeviceOps for VirtualDevice { |
| fn open( |
| &self, |
| _locked: &mut Locked<FileOpsCore>, |
| _current_task: &CurrentTask, |
| _id: device_type::DeviceType, |
| _node: &NamespaceNode, |
| _flags: OpenFlags, |
| ) -> Result<Box<dyn FileOps>, Errno> { |
| let input_file = match &self.device_type { |
| DeviceType::Keyboard => Arc::new(InputFile::new_keyboard(self.input_id, None)), |
| DeviceType::Touchscreen(width, height) => { |
| Arc::new(InputFile::new_touch(self.input_id, width.clone(), height.clone(), None)) |
| } |
| }; |
| |
| self.open_files.lock().push(Arc::downgrade(&input_file)); |
| |
| Ok(Box::new(input_file)) |
| } |
| } |
| |
| #[cfg(test)] |
| mod test { |
| use super::*; |
| use crate::{EventProxyMode, start_input_relays_for_test}; |
| use starnix_core::task::Kernel; |
| #[allow(deprecated, reason = "pre-existing usage")] |
| use starnix_core::testing::{AutoReleasableTask, create_kernel_task_and_unlocked}; |
| use starnix_core::vfs::FileHandle; |
| use std::sync::Arc; |
| use test_case::test_case; |
| |
| async fn new_kernel_objects() -> ( |
| Arc<UinputDeviceFile>, |
| Arc<Kernel>, |
| AutoReleasableTask, |
| FileHandle, |
| &'static mut Locked<Unlocked>, |
| ) { |
| #[allow(deprecated, reason = "pre-existing usage")] |
| let (kernel, current_task, locked) = create_kernel_task_and_unlocked(); |
| let (input_relay_handle, _, _, _, _, _, _, _, _, _, _, _) = |
| start_input_relays_for_test(locked, ¤t_task, EventProxyMode::None).await; |
| let dev = Arc::new(UinputDeviceFile::new(input_relay_handle)); |
| |
| let root_namespace_node = current_task |
| .lookup_path_from_root(locked, ".".into()) |
| .expect("failed to get namespace node for root"); |
| |
| let file_object = FileObject::new( |
| locked, |
| ¤t_task, |
| Box::new(dev.clone()), |
| // The input node doesn't really live at the root of the filesystem. |
| // But the test doesn't need to be 100% representative of production. |
| root_namespace_node, |
| OpenFlags::empty(), |
| ) |
| .expect("FileObject::new failed"); |
| (dev, kernel, current_task, file_object, locked) |
| } |
| |
| #[test_case(uapi::EV_KEY, vec![uapi::EV_KEY as usize] => Ok(SUCCESS))] |
| #[test_case(uapi::EV_ABS, vec![uapi::EV_ABS as usize] => Ok(SUCCESS))] |
| #[test_case(uapi::EV_REL, vec![] => error!(EPERM))] |
| #[::fuchsia::test] |
| async fn ui_set_evbit(bit: u32, expected_evbits: Vec<usize>) -> Result<SyscallResult, Errno> { |
| let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await; |
| let locked = locked.cast_locked::<Unlocked>(); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_EVBIT, |
| SyscallArg::from(bit as u64), |
| ); |
| for expected_evbit in expected_evbits { |
| assert!(dev.inner.lock().enabled_evbits.get(expected_evbit).unwrap()); |
| } |
| r |
| } |
| |
| #[::fuchsia::test] |
| async fn ui_set_evbit_call_multi() { |
| let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await; |
| let locked = locked.cast_locked::<Unlocked>(); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_EVBIT, |
| SyscallArg::from(uapi::EV_KEY as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_EVBIT, |
| SyscallArg::from(uapi::EV_ABS as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_KEY as usize).unwrap()); |
| assert!(dev.inner.lock().enabled_evbits.get(uapi::EV_ABS as usize).unwrap()); |
| } |
| |
| #[::fuchsia::test] |
| async fn ui_set_keybit() { |
| let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await; |
| let locked = locked.cast_locked::<Unlocked>(); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_KEYBIT, |
| SyscallArg::from(uapi::BTN_TOUCH as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| |
| // also test call multi times. |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_KEYBIT, |
| SyscallArg::from(uapi::KEY_SPACE as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| } |
| |
| #[::fuchsia::test] |
| async fn ui_set_absbit() { |
| let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await; |
| let locked = locked.cast_locked::<Unlocked>(); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_ABSBIT, |
| SyscallArg::from(uapi::ABS_MT_SLOT as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| |
| // also test call multi times. |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_ABSBIT, |
| SyscallArg::from(uapi::ABS_MT_TOUCH_MAJOR as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| } |
| |
| #[::fuchsia::test] |
| async fn ui_set_propbit() { |
| let (dev, _kernel, current_task, file_object, locked) = new_kernel_objects().await; |
| let locked = locked.cast_locked::<Unlocked>(); |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_PROPBIT, |
| SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| |
| // also test call multi times. |
| let r = dev.ioctl( |
| locked, |
| &file_object, |
| ¤t_task, |
| uapi::UI_SET_PROPBIT, |
| SyscallArg::from(uapi::INPUT_PROP_DIRECT as u64), |
| ); |
| assert_eq!(r, Ok(SUCCESS)); |
| } |
| } |