| // Copyright 2021 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. |
| |
| #![warn(clippy::await_holding_refcell_ref)] |
| |
| use { |
| crate::input_handler::{InputHandler, InputHandlerStatus}, |
| crate::utils::{CursorMessage, Position, Size}, |
| crate::{input_device, metrics, mouse_binding}, |
| anyhow::{anyhow, Context, Error, Result}, |
| async_trait::async_trait, |
| async_utils::hanging_get::client::HangingGetStream, |
| fidl::endpoints::create_proxy, |
| fidl_fuchsia_input_report::Range, |
| fidl_fuchsia_ui_pointerinjector as pointerinjector, |
| fidl_fuchsia_ui_pointerinjector_configuration as pointerinjector_config, |
| fuchsia_component::client::connect_to_protocol, |
| fuchsia_inspect::health::Reporter, |
| fuchsia_zircon as zx, |
| futures::{channel::mpsc::Sender, stream::StreamExt, SinkExt}, |
| metrics_registry::*, |
| std::{ |
| cell::{Ref, RefCell, RefMut}, |
| collections::HashMap, |
| rc::Rc, |
| }, |
| }; |
| |
| /// Each mm of physical movement by the mouse translates to the cursor moving |
| /// on the display by 10 logical pixels. |
| /// Because pointer_display_scale_handler scaled for device pixel ratio, here |
| /// only need to apply mm * logical pixel scale factor to get physical pixel. |
| /// TODO(https://fxbug.dev/42066909): need to revisit this |
| /// 1. allow users to adjust how fast the mouse move. |
| /// 2. allow different value per monitor model. |
| const MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL: f32 = 10.0; |
| |
| /// A [`MouseInjectorHandler`] parses mouse events and forwards them to Scenic through the |
| /// fidl_fuchsia_pointerinjector protocols. |
| pub struct MouseInjectorHandler { |
| /// The mutable fields of this handler. |
| mutable_state: RefCell<MutableState>, |
| |
| /// The scope and coordinate system of injection. |
| /// See [`fidl_fuchsia_pointerinjector::Context`] for more details. |
| context_view_ref: fidl_fuchsia_ui_views::ViewRef, |
| |
| /// The region where dispatch is attempted for injected events. |
| /// See [`fidl_fuchsia_pointerinjector::Target`] for more details. |
| target_view_ref: fidl_fuchsia_ui_views::ViewRef, |
| |
| /// The maximum position sent to clients, used to bound relative movements |
| /// and scale absolute positions from device coordinates. |
| max_position: Position, |
| |
| /// The FIDL proxy to register new injectors. |
| injector_registry_proxy: pointerinjector::RegistryProxy, |
| |
| /// The FIDL proxy used to get configuration details for pointer injection. |
| configuration_proxy: pointerinjector_config::SetupProxy, |
| |
| /// The inventory of this handler's Inspect status. |
| pub inspect_status: InputHandlerStatus, |
| |
| metrics_logger: metrics::MetricsLogger, |
| } |
| |
| struct MutableState { |
| /// A rectangular region that directs injected events into a target. |
| /// See fidl_fuchsia_pointerinjector::Viewport for more details. |
| viewport: Option<pointerinjector::Viewport>, |
| |
| /// The injectors registered with Scenic, indexed by their device ids. |
| injectors: HashMap<u32, pointerinjector::DeviceProxy>, |
| |
| /// The current position. |
| current_position: Position, |
| |
| /// A [`Sender`] used to communicate the current cursor state. |
| cursor_message_sender: Sender<CursorMessage>, |
| } |
| |
| #[async_trait(?Send)] |
| impl InputHandler for MouseInjectorHandler { |
| async fn handle_input_event( |
| self: Rc<Self>, |
| mut input_event: input_device::InputEvent, |
| ) -> Vec<input_device::InputEvent> { |
| match input_event { |
| input_device::InputEvent { |
| device_event: input_device::InputDeviceEvent::Mouse(ref mouse_event), |
| device_descriptor: |
| input_device::InputDeviceDescriptor::Mouse(ref mouse_device_descriptor), |
| event_time, |
| handled: input_device::Handled::No, |
| trace_id: _, |
| } => { |
| self.inspect_status |
| .count_received_event(input_device::InputEvent::from(input_event.clone())); |
| // TODO(https://fxbug.dev/42171756): Investigate latency introduced by waiting for update_cursor_renderer |
| if let Err(e) = |
| self.update_cursor_renderer(mouse_event, &mouse_device_descriptor).await |
| { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorUpdateCursorRendererFailed, |
| std::format!("update_cursor_renderer failed: {}", e)); |
| } |
| |
| // Create a new injector if this is the first time seeing device_id. |
| if let Err(e) = self |
| .ensure_injector_registered(&mouse_event, &mouse_device_descriptor, event_time) |
| .await |
| { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorEnsureInjectorRegisteredFailed, |
| std::format!("ensure_injector_registered failed: {}", e)); |
| } |
| |
| // Handle the event. |
| if let Err(e) = self |
| .send_event_to_scenic(&mouse_event, &mouse_device_descriptor, event_time) |
| .await |
| { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorSendEventToScenicFailed, |
| std::format!("send_event_to_scenic failed: {}", e)); |
| } |
| |
| // Consume the input event. |
| input_event.handled = input_device::Handled::Yes; |
| self.inspect_status.count_handled_event(); |
| } |
| _ => {} |
| } |
| vec![input_event] |
| } |
| |
| fn set_handler_healthy(self: std::rc::Rc<Self>) { |
| self.inspect_status.health_node.borrow_mut().set_ok(); |
| } |
| |
| fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) { |
| self.inspect_status.health_node.borrow_mut().set_unhealthy(msg); |
| } |
| } |
| |
| impl MouseInjectorHandler { |
| /// Creates a new mouse handler that holds mouse pointer injectors. |
| /// The caller is expected to spawn a task to continually watch for updates to the viewport. |
| /// Example: |
| /// let handler = MouseInjectorHandler::new(display_size).await?; |
| /// fasync::Task::local(handler.clone().watch_viewport()).detach(); |
| /// |
| /// # Parameters |
| /// - `display_size`: The size of the associated display. |
| /// - `cursor_message_sender`: A [`Sender`] used to communicate the current cursor state. |
| /// |
| /// # Errors |
| /// If unable to connect to pointerinjector protocols. |
| pub async fn new( |
| display_size: Size, |
| cursor_message_sender: Sender<CursorMessage>, |
| input_handlers_node: &fuchsia_inspect::Node, |
| metrics_logger: metrics::MetricsLogger, |
| ) -> Result<Rc<Self>, Error> { |
| let configuration_proxy = connect_to_protocol::<pointerinjector_config::SetupMarker>()?; |
| let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?; |
| |
| Self::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| display_size, |
| cursor_message_sender, |
| input_handlers_node, |
| metrics_logger, |
| ) |
| .await |
| } |
| |
| /// Creates a new mouse handler that holds mouse pointer injectors. |
| /// The caller is expected to spawn a task to continually watch for updates to the viewport. |
| /// Example: |
| /// let handler = MouseInjectorHandler::new_with_config_proxy(config_proxy, display_size).await?; |
| /// fasync::Task::local(handler.clone().watch_viewport()).detach(); |
| /// |
| /// # Parameters |
| /// - `configuration_proxy`: A proxy used to get configuration details for pointer |
| /// injection. |
| /// - `display_size`: The size of the associated display. |
| /// - `cursor_message_sender`: A [`Sender`] used to communicate the current cursor state. |
| /// |
| /// # Errors |
| /// If unable to get injection view refs from `configuration_proxy`. |
| /// If unable to connect to pointerinjector Registry protocol. |
| pub async fn new_with_config_proxy( |
| configuration_proxy: pointerinjector_config::SetupProxy, |
| display_size: Size, |
| cursor_message_sender: Sender<CursorMessage>, |
| input_handlers_node: &fuchsia_inspect::Node, |
| metrics_logger: metrics::MetricsLogger, |
| ) -> Result<Rc<Self>, Error> { |
| let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?; |
| Self::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| display_size, |
| cursor_message_sender, |
| input_handlers_node, |
| metrics_logger, |
| ) |
| .await |
| } |
| |
| fn inner(&self) -> Ref<'_, MutableState> { |
| self.mutable_state.borrow() |
| } |
| |
| fn inner_mut(&self) -> RefMut<'_, MutableState> { |
| self.mutable_state.borrow_mut() |
| } |
| |
| /// Creates a new mouse handler that holds mouse pointer injectors. |
| /// The caller is expected to spawn a task to continually watch for updates to the viewport. |
| /// Example: |
| /// let handler = MouseInjectorHandler::new_handler(None, None, display_size).await?; |
| /// fasync::Task::local(handler.clone().watch_viewport()).detach(); |
| /// |
| /// # Parameters |
| /// - `configuration_proxy`: A proxy used to get configuration details for pointer |
| /// injection. |
| /// - `injector_registry_proxy`: A proxy used to register new pointer injectors. |
| /// - `display_size`: The size of the associated display. |
| /// - `cursor_message_sender`: A [`Sender`] used to communicate the current cursor state. |
| /// |
| /// # Errors |
| /// If unable to get injection view refs from `configuration_proxy`. |
| async fn new_handler( |
| configuration_proxy: pointerinjector_config::SetupProxy, |
| injector_registry_proxy: pointerinjector::RegistryProxy, |
| display_size: Size, |
| cursor_message_sender: Sender<CursorMessage>, |
| input_handlers_node: &fuchsia_inspect::Node, |
| metrics_logger: metrics::MetricsLogger, |
| ) -> Result<Rc<Self>, Error> { |
| // Get the context and target views to inject into. |
| let (context_view_ref, target_view_ref) = configuration_proxy.get_view_refs().await?; |
| let inspect_status = InputHandlerStatus::new( |
| input_handlers_node, |
| "mouse_injector_handler", |
| /* generates_events */ false, |
| ); |
| let handler = Rc::new(Self { |
| mutable_state: RefCell::new(MutableState { |
| viewport: None, |
| injectors: HashMap::new(), |
| // Initially centered. |
| current_position: Position { |
| x: display_size.width / 2.0, |
| y: display_size.height / 2.0, |
| }, |
| cursor_message_sender, |
| }), |
| context_view_ref, |
| target_view_ref, |
| max_position: Position { x: display_size.width, y: display_size.height }, |
| injector_registry_proxy, |
| configuration_proxy, |
| inspect_status, |
| metrics_logger, |
| }); |
| |
| Ok(handler) |
| } |
| |
| /// Adds a new pointer injector and tracks it in `self.injectors` if one doesn't exist at |
| /// `mouse_descriptor.device_id`. |
| /// |
| /// # Parameters |
| /// - `mouse_event`: The mouse event to send to Scenic. |
| /// - `mouse_descriptor`: The descriptor for the device that sent the mouse event. |
| /// - `event_time`: The time in nanoseconds when the event was first recorded. |
| async fn ensure_injector_registered( |
| self: &Rc<Self>, |
| mouse_event: &mouse_binding::MouseEvent, |
| mouse_descriptor: &mouse_binding::MouseDeviceDescriptor, |
| event_time: zx::Time, |
| ) -> Result<(), anyhow::Error> { |
| if self.inner().injectors.contains_key(&mouse_descriptor.device_id) { |
| return Ok(()); |
| } |
| |
| // Create a new injector. |
| let (device_proxy, device_server) = create_proxy::<pointerinjector::DeviceMarker>() |
| .context("Failed to create DeviceProxy.")?; |
| let context = fuchsia_scenic::duplicate_view_ref(&self.context_view_ref) |
| .context("Failed to duplicate context view ref.")?; |
| let target = fuchsia_scenic::duplicate_view_ref(&self.target_view_ref) |
| .context("Failed to duplicate target view ref.")?; |
| |
| let viewport = self.inner().viewport.clone(); |
| let config = pointerinjector::Config { |
| device_id: Some(mouse_descriptor.device_id), |
| device_type: Some(pointerinjector::DeviceType::Mouse), |
| context: Some(pointerinjector::Context::View(context)), |
| target: Some(pointerinjector::Target::View(target)), |
| viewport, |
| dispatch_policy: Some(pointerinjector::DispatchPolicy::MouseHoverAndLatchInTarget), |
| scroll_v_range: mouse_descriptor.wheel_v_range.clone(), |
| scroll_h_range: mouse_descriptor.wheel_h_range.clone(), |
| buttons: mouse_descriptor.buttons.clone(), |
| ..Default::default() |
| }; |
| |
| // Register the new injector. |
| self.injector_registry_proxy |
| .register(config, device_server) |
| .await |
| .context("Failed to register injector.")?; |
| tracing::info!("Registered injector with device id {:?}", mouse_descriptor.device_id); |
| |
| // Keep track of the injector. |
| self.inner_mut().injectors.insert(mouse_descriptor.device_id, device_proxy.clone()); |
| |
| // Inject ADD event the first time a MouseDevice is seen. |
| let events_to_send = &[self.create_pointer_sample_event( |
| mouse_event, |
| event_time, |
| pointerinjector::EventPhase::Add, |
| self.inner().current_position, |
| None, |
| )]; |
| device_proxy.inject(events_to_send).await.context("Failed to ADD new MouseDevice.")?; |
| |
| Ok(()) |
| } |
| |
| /// Updates the current cursor position according to the received mouse event. |
| /// |
| /// The updated cursor state is sent via `self.inner.cursor_message_sender` to a client |
| /// that renders the cursor on-screen. |
| /// |
| /// If there is no movement, the location is not sent. |
| /// |
| /// # Parameters |
| /// - `mouse_event`: The mouse event to use to update the cursor location. |
| /// - `mouse_descriptor`: The descriptor for the input device generating the input reports. |
| async fn update_cursor_renderer( |
| &self, |
| mouse_event: &mouse_binding::MouseEvent, |
| mouse_descriptor: &mouse_binding::MouseDeviceDescriptor, |
| ) -> Result<(), anyhow::Error> { |
| let mut new_position = match (mouse_event.location, mouse_descriptor) { |
| ( |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters, |
| }), |
| _, |
| ) => { |
| self.inner().current_position |
| + self.relative_movement_mm_to_phyical_pixel(millimeters) |
| } |
| ( |
| mouse_binding::MouseLocation::Absolute(position), |
| mouse_binding::MouseDeviceDescriptor { |
| absolute_x_range: Some(x_range), |
| absolute_y_range: Some(y_range), |
| .. |
| }, |
| ) => self.scale_absolute_position(&position, &x_range, &y_range), |
| (mouse_binding::MouseLocation::Absolute(_), _) => { |
| return Err(anyhow!( |
| "Received an Absolute mouse location without absolute device ranges." |
| )) |
| } |
| }; |
| Position::clamp(&mut new_position, Position::zero(), self.max_position); |
| self.inner_mut().current_position = new_position; |
| |
| let mut cursor_message_sender = self.inner().cursor_message_sender.clone(); |
| cursor_message_sender |
| .send(CursorMessage::SetPosition(new_position)) |
| .await |
| .context("Failed to send current mouse position to cursor renderer")?; |
| |
| Ok(()) |
| } |
| |
| /// Returns an absolute cursor position scaled from device coordinates to the handler's |
| /// max position. |
| /// |
| /// # Parameters |
| /// - `position`: Absolute cursor position in device coordinates. |
| /// - `x_range`: The range of possible x values of absolute mouse positions. |
| /// - `y_range`: The range of possible y values of absolute mouse positions. |
| fn scale_absolute_position( |
| &self, |
| position: &Position, |
| x_range: &Range, |
| y_range: &Range, |
| ) -> Position { |
| let range_min = Position { x: x_range.min as f32, y: y_range.min as f32 }; |
| let range_max = Position { x: x_range.max as f32, y: y_range.max as f32 }; |
| self.max_position * ((*position - range_min) / (range_max - range_min)) |
| } |
| |
| /// Sends the given event to Scenic. |
| /// |
| /// # Parameters |
| /// - `mouse_event`: The mouse event to send to Scenic. |
| /// - `mouse_descriptor`: The descriptor for the device that sent the mouse event. |
| /// - `event_time`: The time in nanoseconds when the event was first recorded. |
| async fn send_event_to_scenic( |
| &self, |
| mouse_event: &mouse_binding::MouseEvent, |
| mouse_descriptor: &mouse_binding::MouseDeviceDescriptor, |
| event_time: zx::Time, |
| ) -> Result<(), anyhow::Error> { |
| let injector = self.inner().injectors.get(&mouse_descriptor.device_id).cloned(); |
| if let Some(injector) = injector { |
| let relative_motion = match mouse_event.location { |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: offset_mm, |
| }) if mouse_event.phase == mouse_binding::MousePhase::Move => { |
| let offset = self.relative_movement_mm_to_phyical_pixel(offset_mm); |
| Some([offset.x, offset.y]) |
| } |
| _ => None, |
| }; |
| let events_to_send = &[self.create_pointer_sample_event( |
| mouse_event, |
| event_time, |
| pointerinjector::EventPhase::Change, |
| self.inner().current_position, |
| relative_motion, |
| )]; |
| let _ = injector.inject(events_to_send).await; |
| |
| Ok(()) |
| } else { |
| Err(anyhow::format_err!( |
| "No injector found for mouse device {}.", |
| mouse_descriptor.device_id |
| )) |
| } |
| } |
| |
| /// Creates a [`fidl_fuchsia_ui_pointerinjector::Event`] representing the given MouseEvent. |
| /// |
| /// # Parameters |
| /// - `mouse_event`: The mouse event to send to Scenic. |
| /// - `event_time`: The time in nanoseconds when the event was first recorded. |
| /// - `phase`: The EventPhase to send to Scenic. |
| /// - `current_position`: The current cursor position. |
| /// - `relative_motion`: The relative motion to send to Scenic. |
| fn create_pointer_sample_event( |
| &self, |
| mouse_event: &mouse_binding::MouseEvent, |
| event_time: zx::Time, |
| phase: pointerinjector::EventPhase, |
| current_position: Position, |
| relative_motion: Option<[f32; 2]>, |
| ) -> pointerinjector::Event { |
| let pointer_sample = pointerinjector::PointerSample { |
| pointer_id: Some(0), |
| phase: Some(phase), |
| position_in_viewport: Some([current_position.x, current_position.y]), |
| scroll_v: match mouse_event.wheel_delta_v { |
| Some(mouse_binding::WheelDelta { |
| raw_data: mouse_binding::RawWheelDelta::Ticks(tick), |
| .. |
| }) => Some(tick), |
| _ => None, |
| }, |
| scroll_h: match mouse_event.wheel_delta_h { |
| Some(mouse_binding::WheelDelta { |
| raw_data: mouse_binding::RawWheelDelta::Ticks(tick), |
| .. |
| }) => Some(tick), |
| _ => None, |
| }, |
| scroll_v_physical_pixel: match mouse_event.wheel_delta_v { |
| Some(mouse_binding::WheelDelta { physical_pixel: Some(pixel), .. }) => { |
| Some(pixel.into()) |
| } |
| _ => None, |
| }, |
| scroll_h_physical_pixel: match mouse_event.wheel_delta_h { |
| Some(mouse_binding::WheelDelta { physical_pixel: Some(pixel), .. }) => { |
| Some(pixel.into()) |
| } |
| _ => None, |
| }, |
| is_precision_scroll: match mouse_event.phase { |
| mouse_binding::MousePhase::Wheel => match mouse_event.is_precision_scroll { |
| Some(mouse_binding::PrecisionScroll::Yes) => Some(true), |
| Some(mouse_binding::PrecisionScroll::No) => Some(false), |
| None => { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorMissingIsPrecisionScroll, |
| "mouse wheel event does not have value in is_precision_scroll."); |
| None |
| } |
| }, |
| _ => None, |
| }, |
| pressed_buttons: Some(Vec::from_iter(mouse_event.pressed_buttons.iter().cloned())), |
| relative_motion, |
| ..Default::default() |
| }; |
| pointerinjector::Event { |
| timestamp: Some(event_time.into_nanos()), |
| data: Some(pointerinjector::Data::PointerSample(pointer_sample)), |
| trace_flow_id: None, |
| ..Default::default() |
| } |
| } |
| |
| /// Watches for viewport updates from the scene manager. |
| pub async fn watch_viewport(self: Rc<Self>) { |
| let configuration_proxy = self.configuration_proxy.clone(); |
| let mut viewport_stream = HangingGetStream::new( |
| configuration_proxy, |
| pointerinjector_config::SetupProxy::watch_viewport, |
| ); |
| loop { |
| match viewport_stream.next().await { |
| Some(Ok(new_viewport)) => { |
| // Update the viewport tracked by this handler. |
| self.inner_mut().viewport = Some(new_viewport.clone()); |
| |
| // Update Scenic with the latest viewport. |
| let injectors = self.inner().injectors.values().cloned().collect::<Vec<_>>(); |
| for injector in injectors { |
| let events = &[pointerinjector::Event { |
| timestamp: Some(fuchsia_async::Time::now().into_nanos()), |
| data: Some(pointerinjector::Data::Viewport(new_viewport.clone())), |
| trace_flow_id: Some(fuchsia_trace::Id::new().into()), |
| ..Default::default() |
| }]; |
| injector.inject(events).await.expect("Failed to inject updated viewport."); |
| } |
| } |
| Some(Err(e)) => { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorErrorWhileReadingViewportUpdate, |
| std::format!("Error while reading viewport update: {}", e)); |
| return; |
| } |
| None => { |
| self.metrics_logger.log_error( |
| InputPipelineErrorMetricDimensionEvent::MouseInjectorViewportUpdateStreamTerminatedUnexpectedly, |
| "Viewport update stream terminated unexpectedly"); |
| return; |
| } |
| } |
| } |
| } |
| |
| /// Converts a relative movement given in millimeters to movement in phyical pixel. |
| /// Because pointer_display_scale_handler scaled for device pixel ratio, this method |
| /// only need to apply phyical distance to logical pixel scale factor. |
| fn relative_movement_mm_to_phyical_pixel(&self, movement_mm: Position) -> Position { |
| Position { |
| x: movement_mm.x * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| y: movement_mm.y * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| use { |
| super::*, |
| crate::testing_utilities::{ |
| assert_handler_ignores_input_event_sequence, create_mouse_event, |
| create_mouse_event_with_handled, create_mouse_pointer_sample_event, |
| create_mouse_pointer_sample_event_with_wheel_physical_pixel, |
| }, |
| assert_matches::assert_matches, |
| fidl_fuchsia_input_report as fidl_input_report, |
| fidl_fuchsia_ui_pointerinjector as pointerinjector, fuchsia_async as fasync, |
| fuchsia_zircon as zx, |
| futures::channel::mpsc, |
| pretty_assertions::assert_eq, |
| std::collections::HashSet, |
| std::ops::Add, |
| test_case::test_case, |
| }; |
| |
| const DISPLAY_WIDTH_IN_PHYSICAL_PX: f32 = 100.0; |
| const DISPLAY_HEIGHT_IN_PHYSICAL_PX: f32 = 100.0; |
| const COUNTS_PER_MM: u32 = 12; |
| |
| /// Returns an |input_device::InputDeviceDescriptor::MouseDescriptor|. |
| const DESCRIPTOR: input_device::InputDeviceDescriptor = |
| input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor { |
| device_id: 1, |
| absolute_x_range: Some(fidl_input_report::Range { min: 0, max: 100 }), |
| absolute_y_range: Some(fidl_input_report::Range { min: 0, max: 100 }), |
| wheel_v_range: Some(fidl_input_report::Axis { |
| range: fidl_input_report::Range { min: -1, max: 1 }, |
| unit: fidl_input_report::Unit { |
| type_: fidl_input_report::UnitType::Other, |
| exponent: 0, |
| }, |
| }), |
| wheel_h_range: Some(fidl_input_report::Axis { |
| range: fidl_input_report::Range { min: -1, max: 1 }, |
| unit: fidl_input_report::Unit { |
| type_: fidl_input_report::UnitType::Other, |
| exponent: 0, |
| }, |
| }), |
| buttons: None, |
| counts_per_mm: COUNTS_PER_MM, |
| }); |
| |
| /// Handles |fidl_fuchsia_pointerinjector_configuration::SetupRequest::GetViewRefs|. |
| async fn handle_configuration_request_stream( |
| stream: &mut pointerinjector_config::SetupRequestStream, |
| ) { |
| if let Some(Ok(request)) = stream.next().await { |
| match request { |
| pointerinjector_config::SetupRequest::GetViewRefs { responder, .. } => { |
| let context = fuchsia_scenic::ViewRefPair::new() |
| .expect("Failed to create viewrefpair.") |
| .view_ref; |
| let target = fuchsia_scenic::ViewRefPair::new() |
| .expect("Failed to create viewrefpair.") |
| .view_ref; |
| let _ = responder.send(context, target); |
| } |
| _ => {} |
| }; |
| } |
| } |
| |
| /// Handles |fidl_fuchsia_pointerinjector::RegistryRequest|s by forwarding the registered device |
| /// over `injector_sender` to be handled by handle_device_request_stream(). |
| async fn handle_registry_request_stream( |
| mut stream: pointerinjector::RegistryRequestStream, |
| injector_sender: futures::channel::oneshot::Sender<pointerinjector::DeviceRequestStream>, |
| ) { |
| if let Some(request) = stream.next().await { |
| match request { |
| Ok(pointerinjector::RegistryRequest::Register { |
| config: _, |
| injector, |
| responder, |
| .. |
| }) => { |
| let injector_stream = |
| injector.into_stream().expect("Failed to get stream from server end."); |
| let _ = injector_sender.send(injector_stream); |
| responder.send().expect("failed to respond"); |
| } |
| _ => {} |
| }; |
| } else { |
| panic!("RegistryRequestStream failed."); |
| } |
| } |
| |
| // Handles |fidl_fuchsia_pointerinjector::RegistryRequest|s |
| async fn handle_registry_request_stream2( |
| mut stream: pointerinjector::RegistryRequestStream, |
| injector_sender: mpsc::UnboundedSender<Vec<pointerinjector::Event>>, |
| ) { |
| let (injector, responder) = match stream.next().await { |
| Some(Ok(pointerinjector::RegistryRequest::Register { |
| config: _, |
| injector, |
| responder, |
| .. |
| })) => (injector, responder), |
| other => panic!("expected register request, but got {:?}", other), |
| }; |
| let injector_stream: pointerinjector::DeviceRequestStream = |
| injector.into_stream().expect("Failed to get stream from server end."); |
| responder.send().expect("failed to respond"); |
| injector_stream |
| .for_each(|request| { |
| futures::future::ready({ |
| match request { |
| Ok(pointerinjector::DeviceRequest::Inject { |
| events, |
| responder: device_injector_responder, |
| }) => { |
| let _ = injector_sender.unbounded_send(events); |
| device_injector_responder.send().expect("failed to respond") |
| } |
| Err(e) => panic!("FIDL error {}", e), |
| } |
| }) |
| }) |
| .await; |
| } |
| |
| /// Handles |fidl_fuchsia_pointerinjector::DeviceRequest|s by asserting the injector stream |
| /// received on `injector_stream_receiver` gets `expected_events`. |
| async fn handle_device_request_stream( |
| injector_stream_receiver: futures::channel::oneshot::Receiver< |
| pointerinjector::DeviceRequestStream, |
| >, |
| expected_events: Vec<pointerinjector::Event>, |
| ) { |
| let mut injector_stream = |
| injector_stream_receiver.await.expect("Failed to get DeviceRequestStream."); |
| for expected_event in expected_events { |
| match injector_stream.next().await { |
| Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => { |
| assert_eq!(events, vec![expected_event]); |
| responder.send().expect("failed to respond"); |
| } |
| Some(Err(e)) => panic!("FIDL error {}", e), |
| None => panic!("Expected another event."), |
| } |
| } |
| } |
| |
| // Creates a |pointerinjector::Viewport|. |
| fn create_viewport(min: f32, max: f32) -> pointerinjector::Viewport { |
| pointerinjector::Viewport { |
| extents: Some([[min, min], [max, max]]), |
| viewport_to_context_transform: None, |
| ..Default::default() |
| } |
| } |
| |
| // Tests that MouseInjectorHandler::receives_viewport_updates() tracks viewport updates |
| // and notifies injectors about said updates. |
| #[fuchsia::test] |
| fn receives_viewport_updates() { |
| let mut exec = fasync::TestExecutor::new(); |
| |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, _) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(0); |
| |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| |
| // Create mouse handler. |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| let (mouse_handler_res, _) = exec.run_singlethreaded(futures::future::join( |
| mouse_handler_fut, |
| config_request_stream_fut, |
| )); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| // Add an injector. |
| let (injector_device_proxy, mut injector_device_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| mouse_handler.inner_mut().injectors.insert(1, injector_device_proxy); |
| |
| // This nested block is used to bound the lifetime of `watch_viewport_fut`. |
| { |
| // Request a viewport update. |
| let watch_viewport_fut = mouse_handler.clone().watch_viewport(); |
| futures::pin_mut!(watch_viewport_fut); |
| assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending()); |
| |
| // Send a viewport update. |
| match exec.run_singlethreaded(&mut configuration_request_stream.next()) { |
| Some(Ok(pointerinjector_config::SetupRequest::WatchViewport { |
| responder, .. |
| })) => { |
| responder.send(&create_viewport(0.0, 100.0)).expect("Failed to send viewport."); |
| } |
| other => panic!("Received unexpected value: {:?}", other), |
| }; |
| assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending()); |
| |
| // Check that the injector received an updated viewport |
| exec.run_singlethreaded(async { |
| match injector_device_request_stream.next().await { |
| Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => { |
| assert_eq!(events.len(), 1); |
| assert!(events[0].data.is_some()); |
| assert_eq!( |
| events[0].data, |
| Some(pointerinjector::Data::Viewport(create_viewport(0.0, 100.0))) |
| ); |
| responder.send().expect("injector stream failed to respond."); |
| } |
| other => panic!("Received unexpected value: {:?}", other), |
| } |
| }); |
| |
| // Request viewport update. |
| assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending()); |
| |
| // Send viewport update. |
| match exec.run_singlethreaded(&mut configuration_request_stream.next()) { |
| Some(Ok(pointerinjector_config::SetupRequest::WatchViewport { |
| responder, .. |
| })) => { |
| responder |
| .send(&create_viewport(100.0, 200.0)) |
| .expect("Failed to send viewport."); |
| } |
| other => panic!("Received unexpected value: {:?}", other), |
| }; |
| |
| // Process viewport update. |
| assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending()); |
| } |
| |
| // Check that the injector received an updated viewport |
| exec.run_singlethreaded(async { |
| match injector_device_request_stream.next().await { |
| Some(Ok(pointerinjector::DeviceRequest::Inject { events, responder })) => { |
| assert_eq!(events.len(), 1); |
| assert!(events[0].data.is_some()); |
| assert_eq!( |
| events[0].data, |
| Some(pointerinjector::Data::Viewport(create_viewport(100.0, 200.0))) |
| ); |
| responder.send().expect("injector stream failed to respond."); |
| } |
| other => panic!("Received unexpected value: {:?}", other), |
| } |
| }); |
| |
| // Check the viewport on the handler is accurate. |
| let expected_viewport = create_viewport(100.0, 200.0); |
| assert_eq!(mouse_handler.inner().viewport, Some(expected_viewport)); |
| } |
| |
| fn wheel_delta_ticks( |
| ticks: i64, |
| physical_pixel: Option<f32>, |
| ) -> Option<mouse_binding::WheelDelta> { |
| Some(mouse_binding::WheelDelta { |
| raw_data: mouse_binding::RawWheelDelta::Ticks(ticks), |
| physical_pixel, |
| }) |
| } |
| |
| fn wheel_delta_mm(mm: f32, physical_pixel: Option<f32>) -> Option<mouse_binding::WheelDelta> { |
| Some(mouse_binding::WheelDelta { |
| raw_data: mouse_binding::RawWheelDelta::Millimeters(mm), |
| physical_pixel, |
| }) |
| } |
| |
| // Tests that a mouse move event both sends an update to scenic and sends the current cursor |
| // location via the cursor location sender. |
| #[test_case( |
| mouse_binding::MouseLocation::Relative( |
| mouse_binding::RelativeLocation { |
| millimeters: Position { x: 1.0, y: 2.0 } |
| }), |
| Position { |
| x: DISPLAY_WIDTH_IN_PHYSICAL_PX / 2.0 |
| + 1.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| y: DISPLAY_HEIGHT_IN_PHYSICAL_PX / 2.0 |
| + 2.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| }, |
| [ |
| 1.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| 2.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| ]; "Valid move event." |
| )] |
| #[test_case( |
| mouse_binding::MouseLocation::Relative( |
| mouse_binding::RelativeLocation { |
| millimeters: Position { |
| x: DISPLAY_WIDTH_IN_PHYSICAL_PX / MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL + 2.0, |
| y: DISPLAY_HEIGHT_IN_PHYSICAL_PX / MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL + 1.0, |
| }}), |
| Position { |
| x: DISPLAY_WIDTH_IN_PHYSICAL_PX, |
| y: DISPLAY_HEIGHT_IN_PHYSICAL_PX, |
| }, |
| [ |
| DISPLAY_WIDTH_IN_PHYSICAL_PX + 2.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| DISPLAY_HEIGHT_IN_PHYSICAL_PX + 1.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| ]; "Move event exceeds max bounds." |
| )] |
| #[test_case( |
| mouse_binding::MouseLocation::Relative( |
| mouse_binding::RelativeLocation { |
| millimeters: Position { |
| x: -(DISPLAY_WIDTH_IN_PHYSICAL_PX / MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL + 2.0), |
| y: -(DISPLAY_HEIGHT_IN_PHYSICAL_PX / MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL + 1.0), |
| }}), |
| Position { x: 0.0, y: 0.0 }, |
| [ |
| -(DISPLAY_WIDTH_IN_PHYSICAL_PX + 2.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL), |
| -(DISPLAY_HEIGHT_IN_PHYSICAL_PX + 1.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL), |
| ]; "Move event exceeds min bounds." |
| )] |
| #[fuchsia::test(allow_stalls = false)] |
| async fn move_event( |
| move_location: mouse_binding::MouseLocation, |
| expected_position: Position, |
| expected_relative_motion: [f32; 2], |
| ) { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let event_time = zx::Time::get_monotonic(); |
| let input_event = create_mouse_event( |
| move_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Move, |
| HashSet::new(), |
| HashSet::new(), |
| event_time, |
| &DESCRIPTOR, |
| ); |
| |
| // Handle event. |
| let handle_event_fut = mouse_handler.handle_input_event(input_event); |
| let expected_events = vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| Some(expected_relative_motion), |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| ]; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| futures::channel::oneshot::channel::<pointerinjector::DeviceRequestStream>(); |
| let registry_fut = handle_registry_request_stream( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| let device_fut = handle_device_request_stream(injector_stream_receiver, expected_events); |
| |
| // Await all futures concurrently. If this completes, then the mouse event was handled and |
| // matches `expected_events`. |
| let (handle_result, _, _) = futures::join!(handle_event_fut, registry_fut, device_fut); |
| |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| pretty_assertions::assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| |
| // No unhandled events. |
| assert_matches!( |
| handle_result.as_slice(), |
| [input_device::InputEvent { handled: input_device::Handled::Yes, .. }] |
| ); |
| } |
| |
| // Tests that an absolute mouse move event scales the location from device coordinates to |
| // between {0, 0} and the handler's maximum position. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn move_absolute_event() { |
| const DEVICE_ID: u32 = 1; |
| |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| // The location is rescaled from the device coordinate system defined |
| // by `absolute_x_range` and `absolute_y_range`, to the display coordinate |
| // system defined by `max_position`. |
| // |
| // -50 y 0 +------------------ w |
| // | | . |
| // | | . |
| // | | . |
| // -50 x -----o----- 50 -> | . . . . . . . . . |
| // | | . |
| // * { x: -25, y: 25 } | * { x: w * 0.25, y: h * 0.75 } |
| // | | . |
| // 50 h | . |
| // |
| // Where w = DISPLAY_WIDTH, h = DISPLAY_HEIGHT |
| let cursor_location = |
| mouse_binding::MouseLocation::Absolute(Position { x: -25.0, y: 25.0 }); |
| let event_time = zx::Time::get_monotonic(); |
| let descriptor = |
| input_device::InputDeviceDescriptor::Mouse(mouse_binding::MouseDeviceDescriptor { |
| device_id: DEVICE_ID, |
| absolute_x_range: Some(fidl_input_report::Range { min: -50, max: 50 }), |
| absolute_y_range: Some(fidl_input_report::Range { min: -50, max: 50 }), |
| wheel_v_range: None, |
| wheel_h_range: None, |
| buttons: None, |
| counts_per_mm: COUNTS_PER_MM, |
| }); |
| let input_event = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Move, |
| HashSet::new(), |
| HashSet::new(), |
| event_time, |
| &descriptor, |
| ); |
| |
| // Handle event. |
| let handle_event_fut = mouse_handler.handle_input_event(input_event); |
| let expected_position = Position { |
| x: DISPLAY_WIDTH_IN_PHYSICAL_PX * 0.25, |
| y: DISPLAY_WIDTH_IN_PHYSICAL_PX * 0.75, |
| }; |
| let expected_events = vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| ]; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| futures::channel::oneshot::channel::<pointerinjector::DeviceRequestStream>(); |
| let registry_fut = handle_registry_request_stream( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| let device_fut = handle_device_request_stream(injector_stream_receiver, expected_events); |
| |
| // Await all futures concurrently. If this completes, then the mouse event was handled and |
| // matches `expected_events`. |
| let (handle_result, _, _) = futures::join!(handle_event_fut, registry_fut, device_fut); |
| |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| |
| // No unhandled events. |
| assert_matches!( |
| handle_result.as_slice(), |
| [input_device::InputEvent { handled: input_device::Handled::Yes, .. }] |
| ); |
| } |
| |
| // Tests that mouse down and up events inject button press state. |
| #[test_case( |
| mouse_binding::MousePhase::Down, |
| vec![1], vec![1]; "Down event injects button press state." |
| )] |
| #[test_case( |
| mouse_binding::MousePhase::Up, |
| vec![1], vec![]; "Up event injects button press state." |
| )] |
| #[fuchsia::test(allow_stalls = false)] |
| async fn button_state_event( |
| phase: mouse_binding::MousePhase, |
| affected_buttons: Vec<mouse_binding::MouseButton>, |
| pressed_buttons: Vec<mouse_binding::MouseButton>, |
| ) { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let cursor_location = mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }); |
| let event_time = zx::Time::get_monotonic(); |
| |
| let input_event = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| phase, |
| HashSet::from_iter(affected_buttons.clone()), |
| HashSet::from_iter(pressed_buttons.clone()), |
| event_time, |
| &DESCRIPTOR, |
| ); |
| |
| // Handle event. |
| let handle_event_fut = mouse_handler.handle_input_event(input_event); |
| let expected_position = Position { x: 0.0, y: 0.0 }; |
| let expected_events = vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| pressed_buttons.clone(), |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| pressed_buttons.clone(), |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time, |
| ), |
| ]; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| futures::channel::oneshot::channel::<pointerinjector::DeviceRequestStream>(); |
| let registry_fut = handle_registry_request_stream( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| let device_fut = handle_device_request_stream(injector_stream_receiver, expected_events); |
| |
| // Await all futures concurrently. If this completes, then the mouse event was handled and |
| // matches `expected_events`. |
| let (handle_result, _, _) = futures::join!(handle_event_fut, registry_fut, device_fut); |
| |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| pretty_assertions::assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| |
| // No unhandled events. |
| assert_matches!( |
| handle_result.as_slice(), |
| [input_device::InputEvent { handled: input_device::Handled::Yes, .. }] |
| ); |
| } |
| |
| // Tests that mouse down followed by mouse up events inject button press state. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn down_up_event() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| // Note: The size of the CursorMessage channel's buffer is 2 to allow for one cursor |
| // update for every input event being sent. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(2); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let cursor_location = mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }); |
| let event_time1 = zx::Time::get_monotonic(); |
| let event_time2 = event_time1.add(fuchsia_zircon::Duration::from_micros(1)); |
| |
| let event1 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time1, |
| &DESCRIPTOR, |
| ); |
| |
| let event2 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![1]), |
| HashSet::new(), |
| event_time2, |
| &DESCRIPTOR, |
| ); |
| |
| let expected_position = Position { x: 0.0, y: 0.0 }; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| // Up to 2 events per handle_input_event() call. |
| let mut injector_stream_receiver = injector_stream_receiver.ready_chunks(2); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| |
| mouse_handler.clone().handle_input_event(event1).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ) |
| ]) |
| ); |
| |
| // Send another input event. |
| mouse_handler.clone().handle_input_event(event2).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time2, |
| )]) |
| ); |
| |
| // Wait until validation is complete. |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| } |
| |
| /// Tests that two staggered button presses followed by stagged releases generate four mouse |
| /// events with distinct `affected_button` and `pressed_button`. |
| /// Specifically, we test and expect the following in order: |
| /// | Action | MousePhase | Injected Phase | `pressed_buttons` | |
| /// | ---------------- | ---------- | -------------- | ----------------- | |
| /// | Press button 1 | Down | Change | [1] | |
| /// | Press button 2 | Down | Change | [1, 2] | |
| /// | Release button 1 | Up | Change | [2] | |
| /// | Release button 2 | Up | Change | [] | |
| #[fuchsia::test(allow_stalls = false)] |
| async fn down_down_up_up_event() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| // Note: The size of the CursorMessage channel's buffer is 4 to allow for one cursor |
| // update for every input event being sent. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(4); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let cursor_location = mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }); |
| let event_time1 = zx::Time::get_monotonic(); |
| let event_time2 = event_time1.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time3 = event_time2.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time4 = event_time3.add(fuchsia_zircon::Duration::from_micros(1)); |
| |
| let event1 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time1, |
| &DESCRIPTOR, |
| ); |
| let event2 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![2]), |
| HashSet::from_iter(vec![1, 2]), |
| event_time2, |
| &DESCRIPTOR, |
| ); |
| let event3 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![2]), |
| event_time3, |
| &DESCRIPTOR, |
| ); |
| let event4 = create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![2]), |
| HashSet::new(), |
| event_time4, |
| &DESCRIPTOR, |
| ); |
| |
| let expected_position = Position { x: 0.0, y: 0.0 }; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| // Up to 2 events per handle_input_event() call. |
| let mut injector_stream_receiver = injector_stream_receiver.ready_chunks(2); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| mouse_handler.clone().handle_input_event(event1).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ) |
| ]) |
| ); |
| |
| // Send another down event. |
| mouse_handler.clone().handle_input_event(event2).await; |
| let pointer_sample_event2 = injector_stream_receiver |
| .next() |
| .await |
| .map(|events| events.concat()) |
| .expect("Failed to receive pointer sample event."); |
| let expected_event_time: i64 = event_time2.into_nanos(); |
| assert_eq!(pointer_sample_event2.len(), 1); |
| |
| // We must break this event result apart for assertions since the |
| // `pressed_buttons` can be given with elements in any order. |
| match &pointer_sample_event2[0] { |
| pointerinjector::Event { |
| timestamp: Some(actual_event_time), |
| data: |
| Some(pointerinjector::Data::PointerSample(pointerinjector::PointerSample { |
| pointer_id: Some(0), |
| phase: Some(pointerinjector::EventPhase::Change), |
| position_in_viewport: Some(actual_position), |
| scroll_v: None, |
| scroll_h: None, |
| pressed_buttons: Some(actual_buttons), |
| relative_motion: None, |
| .. |
| })), |
| .. |
| } => { |
| assert_eq!(actual_event_time, &expected_event_time); |
| assert_eq!(actual_position[0], expected_position.x); |
| assert_eq!(actual_position[1], expected_position.y); |
| assert_eq!( |
| HashSet::<mouse_binding::MouseButton>::from_iter(actual_buttons.clone()), |
| HashSet::from_iter(vec![1, 2]) |
| ); |
| } |
| _ => panic!("Unexpected pointer sample event: {:?}", pointer_sample_event2[0]), |
| } |
| |
| // Send another up event. |
| mouse_handler.clone().handle_input_event(event3).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![2], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time3, |
| )]) |
| ); |
| |
| // Send another up event. |
| mouse_handler.clone().handle_input_event(event4).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time4, |
| )]) |
| ); |
| |
| // Wait until validation is complete. |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| } |
| |
| /// Tests that button press, mouse move, and button release inject changes accordingly. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn down_move_up_event() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| // Note: The size of the CursorMessage channel's buffer is 3 to allow for one cursor |
| // update for every input event being sent. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(3); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let event_time1 = zx::Time::get_monotonic(); |
| let event_time2 = event_time1.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time3 = event_time2.add(fuchsia_zircon::Duration::from_micros(1)); |
| let zero_position = Position { x: 0.0, y: 0.0 }; |
| let expected_position = Position { |
| x: 10.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| y: 5.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| }; |
| let expected_relative_motion = [ |
| 10.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| 5.0 * MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL, |
| ]; |
| let event1 = create_mouse_event( |
| mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }), |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time1, |
| &DESCRIPTOR, |
| ); |
| let event2 = create_mouse_event( |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: Position { x: 10.0, y: 5.0 }, |
| }), |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Move, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time2, |
| &DESCRIPTOR, |
| ); |
| let event3 = create_mouse_event( |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: Position { x: 0.0, y: 0.0 }, |
| }), |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![]), |
| event_time3, |
| &DESCRIPTOR, |
| ); |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| // Up to 2 events per handle_input_event() call. |
| let mut injector_stream_receiver = injector_stream_receiver.ready_chunks(2); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| mouse_handler.clone().handle_input_event(event1).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![1], |
| zero_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| zero_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ) |
| ]) |
| ); |
| |
| // Wait until cursor position validation is complete. |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, zero_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| |
| // Send a move event. |
| mouse_handler.clone().handle_input_event(event2).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| expected_position, |
| Some(expected_relative_motion), |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time2, |
| )]) |
| ); |
| |
| // Wait until cursor position validation is complete. |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| |
| // Send an up event. |
| mouse_handler.clone().handle_input_event(event3).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time3, |
| )]) |
| ); |
| |
| // Wait until cursor position validation is complete. |
| match receiver.next().await { |
| Some(CursorMessage::SetPosition(position)) => { |
| assert_eq!(position, expected_position); |
| } |
| Some(CursorMessage::SetVisibility(_)) => { |
| panic!("Received unexpected cursor visibility update.") |
| } |
| None => panic!("Did not receive cursor update."), |
| } |
| } |
| |
| // Tests that a mouse move event that has already been handled is not forwarded to scenic. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn handler_ignores_handled_events() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, mut receiver) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let cursor_relative_position = Position { x: 50.0, y: 75.0 }; |
| let cursor_location = |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: Position { |
| x: cursor_relative_position.x / COUNTS_PER_MM as f32, |
| y: cursor_relative_position.y / COUNTS_PER_MM as f32, |
| }, |
| }); |
| let event_time = zx::Time::get_monotonic(); |
| let input_events = vec![create_mouse_event_with_handled( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Move, |
| HashSet::new(), |
| HashSet::new(), |
| event_time, |
| &DESCRIPTOR, |
| input_device::Handled::Yes, |
| )]; |
| |
| assert_handler_ignores_input_event_sequence( |
| mouse_handler, |
| input_events, |
| injector_registry_request_stream, |
| ) |
| .await; |
| |
| // The cursor location stream should not receive any position. |
| assert!(receiver.next().await.is_none()); |
| } |
| |
| fn zero_relative_location() -> mouse_binding::MouseLocation { |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: Position { x: 0.0, y: 0.0 }, |
| }) |
| } |
| |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| wheel_delta_ticks(1, None), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::No), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| Some(1), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(false), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "v tick scroll" |
| )] |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| None, /*wheel_delta_v*/ |
| wheel_delta_ticks(1, None), /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::No), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| Some(1), /*wheel_delta_h*/ |
| Some(false), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "h tick scroll" |
| )] |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| wheel_delta_ticks(1, Some(120.0)), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::No), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event_with_wheel_physical_pixel( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| Some(1), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(120.0), /*wheel_delta_v_physical_pixel*/ |
| None, /*wheel_delta_h_physical_pixel*/ |
| Some(false), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "v tick scroll with physical pixel" |
| )] |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| None, /*wheel_delta_v*/ |
| wheel_delta_ticks(1, Some(120.0)), /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::No), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event_with_wheel_physical_pixel( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| Some(1), /*wheel_delta_h*/ |
| None, /*wheel_delta_v_physical_pixel*/ |
| Some(120.0), /*wheel_delta_h_physical_pixel*/ |
| Some(false), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "h tick scroll with physical pixel" |
| )] |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| wheel_delta_mm(1.0, Some(120.0)), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::Yes), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event_with_wheel_physical_pixel( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(120.0), /*wheel_delta_v_physical_pixel*/ |
| None, /*wheel_delta_h_physical_pixel*/ |
| Some(true), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "v mm scroll with physical pixel" |
| )] |
| #[test_case( |
| create_mouse_event( |
| zero_relative_location(), |
| None, /*wheel_delta_v*/ |
| wheel_delta_mm(1.0, Some(120.0)), /*wheel_delta_h*/ |
| Some(mouse_binding::PrecisionScroll::Yes), /*is_precision_scroll*/ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| zx::Time::ZERO, |
| &DESCRIPTOR, |
| ), |
| create_mouse_pointer_sample_event_with_wheel_physical_pixel( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| Position { x: 50.0, y: 50.0 }, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*wheel_delta_v_physical_pixel*/ |
| Some(120.0), /*wheel_delta_h_physical_pixel*/ |
| Some(true), /*is_precision_scroll*/ |
| zx::Time::ZERO, |
| ); "h mm scroll with physical pixel" |
| )] |
| /// Test simple scroll in vertical and horizontal. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn scroll(event: input_device::InputEvent, want_event: pointerinjector::Event) { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| // Up to 2 events per handle_input_event() call. |
| let mut injector_stream_receiver = injector_stream_receiver.ready_chunks(2); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| let event_time = zx::Time::get_monotonic(); |
| |
| let event = input_device::InputEvent { event_time, ..event }; |
| |
| let want_event = |
| pointerinjector::Event { timestamp: Some(event_time.into_nanos()), ..want_event }; |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| |
| mouse_handler.clone().handle_input_event(event).await; |
| let got_events = |
| injector_stream_receiver.next().await.map(|events| events.concat()).unwrap(); |
| pretty_assertions::assert_eq!(got_events.len(), 2); |
| assert_matches!( |
| got_events[0], |
| pointerinjector::Event { |
| data: Some(pointerinjector::Data::PointerSample(pointerinjector::PointerSample { |
| phase: Some(pointerinjector::EventPhase::Add), |
| .. |
| })), |
| .. |
| } |
| ); |
| |
| pretty_assertions::assert_eq!(got_events[1], want_event); |
| } |
| |
| /// Test button down -> scroll -> button up -> continue scroll. |
| #[fuchsia::test(allow_stalls = false)] |
| async fn down_scroll_up_scroll() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| |
| // Create MouseInjectorHandler. |
| let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let test_node = inspector.root().create_child("test_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &test_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, injector_stream_receiver) = |
| mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| // Up to 2 events per handle_input_event() call. |
| let mut injector_stream_receiver = injector_stream_receiver.ready_chunks(2); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| let event_time1 = zx::Time::get_monotonic(); |
| let event_time2 = event_time1.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time3 = event_time2.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time4 = event_time3.add(fuchsia_zircon::Duration::from_micros(1)); |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| |
| let zero_location = |
| mouse_binding::MouseLocation::Relative(mouse_binding::RelativeLocation { |
| millimeters: Position { x: 0.0, y: 0.0 }, |
| }); |
| let expected_position = Position { x: 50.0, y: 50.0 }; |
| |
| let down_event = create_mouse_event( |
| zero_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time1, |
| &DESCRIPTOR, |
| ); |
| |
| let wheel_event = create_mouse_event( |
| zero_location, |
| wheel_delta_ticks(1, None), /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| Some(mouse_binding::PrecisionScroll::No), /* is_precision_scroll */ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time2, |
| &DESCRIPTOR, |
| ); |
| |
| let up_event = create_mouse_event( |
| zero_location, |
| None, |
| None, |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![1]), |
| HashSet::new(), |
| event_time3, |
| &DESCRIPTOR, |
| ); |
| |
| let continue_wheel_event = create_mouse_event( |
| zero_location, |
| wheel_delta_ticks(1, None), /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| Some(mouse_binding::PrecisionScroll::No), /* is_precision_scroll */ |
| mouse_binding::MousePhase::Wheel, |
| HashSet::new(), |
| HashSet::new(), |
| event_time4, |
| &DESCRIPTOR, |
| ); |
| |
| // Handle button down event. |
| mouse_handler.clone().handle_input_event(down_event).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![ |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Add, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ), |
| create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time1, |
| ), |
| ]) |
| ); |
| |
| // Handle wheel event with button pressing. |
| mouse_handler.clone().handle_input_event(wheel_event).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![1], |
| expected_position, |
| None, /*relative_motion*/ |
| Some(1), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(false), /*is_precision_scroll*/ |
| event_time2, |
| )]) |
| ); |
| |
| // Handle button up event. |
| mouse_handler.clone().handle_input_event(up_event).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| None, /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| None, /*is_precision_scroll*/ |
| event_time3, |
| )]) |
| ); |
| |
| // Handle wheel event after button released. |
| mouse_handler.clone().handle_input_event(continue_wheel_event).await; |
| assert_eq!( |
| injector_stream_receiver.next().await.map(|events| events.concat()), |
| Some(vec![create_mouse_pointer_sample_event( |
| pointerinjector::EventPhase::Change, |
| vec![], |
| expected_position, |
| None, /*relative_motion*/ |
| Some(1), /*wheel_delta_v*/ |
| None, /*wheel_delta_h*/ |
| Some(false), /*is_precision_scroll*/ |
| event_time4, |
| )]) |
| ); |
| } |
| |
| #[fuchsia::test(allow_stalls = false)] |
| async fn mouse_injector_handler_initialized_with_inspect_node() { |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let fake_handlers_node = inspector.root().create_child("input_handlers_node"); |
| let mouse_handler_fut = MouseInjectorHandler::new_with_config_proxy( |
| configuration_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &fake_handlers_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let _handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| diagnostics_assertions::assert_data_tree!(inspector, root: { |
| input_handlers_node: { |
| mouse_injector_handler: { |
| events_received_count: 0u64, |
| events_handled_count: 0u64, |
| last_received_timestamp_ns: 0u64, |
| "fuchsia.inspect.Health": { |
| status: "STARTING_UP", |
| // Timestamp value is unpredictable and not relevant in this context, |
| // so we only assert that the property is present. |
| start_timestamp_nanos: diagnostics_assertions::AnyProperty |
| }, |
| } |
| } |
| }); |
| } |
| |
| #[fuchsia::test(allow_stalls = false)] |
| async fn mouse_injector_handler_inspect_counts_events() { |
| // Set up fidl streams. |
| let (configuration_proxy, mut configuration_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>() |
| .expect("Failed to create pointerinjector Setup proxy and stream."); |
| let (injector_registry_proxy, injector_registry_request_stream) = |
| fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>() |
| .expect("Failed to create pointerinjector Registry proxy and stream."); |
| let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(1); |
| |
| let inspector = fuchsia_inspect::Inspector::default(); |
| let fake_handlers_node = inspector.root().create_child("input_handlers_node"); |
| |
| // Create mouse handler. |
| let mouse_handler_fut = MouseInjectorHandler::new_handler( |
| configuration_proxy, |
| injector_registry_proxy, |
| Size { width: DISPLAY_WIDTH_IN_PHYSICAL_PX, height: DISPLAY_HEIGHT_IN_PHYSICAL_PX }, |
| sender, |
| &fake_handlers_node, |
| metrics::MetricsLogger::default(), |
| ); |
| let config_request_stream_fut = |
| handle_configuration_request_stream(&mut configuration_request_stream); |
| let (mouse_handler_res, _) = futures::join!(mouse_handler_fut, config_request_stream_fut); |
| let mouse_handler = mouse_handler_res.expect("Failed to create mouse handler"); |
| |
| let cursor_location = mouse_binding::MouseLocation::Absolute(Position { x: 0.0, y: 0.0 }); |
| let event_time1 = zx::Time::get_monotonic(); |
| let event_time2 = event_time1.add(fuchsia_zircon::Duration::from_micros(1)); |
| let event_time3 = event_time2.add(fuchsia_zircon::Duration::from_micros(1)); |
| |
| let input_events = vec![ |
| create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time1, |
| &DESCRIPTOR, |
| ), |
| create_mouse_event( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Up, |
| HashSet::from_iter(vec![1]), |
| HashSet::new(), |
| event_time2, |
| &DESCRIPTOR, |
| ), |
| create_mouse_event_with_handled( |
| cursor_location, |
| None, /* wheel_delta_v */ |
| None, /* wheel_delta_h */ |
| None, /* is_precision_scroll */ |
| mouse_binding::MousePhase::Down, |
| HashSet::from_iter(vec![1]), |
| HashSet::from_iter(vec![1]), |
| event_time3, |
| &DESCRIPTOR, |
| input_device::Handled::Yes, |
| ), |
| ]; |
| |
| // Create a channel for the the registered device's handle to be forwarded to the |
| // DeviceRequestStream handler. This allows the registry_fut to complete and allows |
| // handle_input_event() to continue. |
| let (injector_stream_sender, _) = mpsc::unbounded::<Vec<pointerinjector::Event>>(); |
| let registry_fut = handle_registry_request_stream2( |
| injector_registry_request_stream, |
| injector_stream_sender, |
| ); |
| |
| // Run all futures until the handler future completes. |
| let _registry_task = fasync::Task::local(registry_fut); |
| |
| for input_event in input_events { |
| mouse_handler.clone().handle_input_event(input_event).await; |
| } |
| |
| let last_received_event_time: u64 = event_time2.into_nanos().try_into().unwrap(); |
| |
| diagnostics_assertions::assert_data_tree!(inspector, root: { |
| input_handlers_node: { |
| mouse_injector_handler: { |
| events_received_count: 2u64, |
| events_handled_count: 2u64, |
| last_received_timestamp_ns: last_received_event_time, |
| "fuchsia.inspect.Health": { |
| status: "STARTING_UP", |
| // Timestamp value is unpredictable and not relevant in this context, |
| // so we only assert that the property is present. |
| start_timestamp_nanos: diagnostics_assertions::AnyProperty |
| }, |
| } |
| } |
| }); |
| } |
| } |