#![warn(clippy::await_holding_refcell_ref)]
use crate::input_handler::{InputHandler, InputHandlerStatus};
use crate::utils::{CursorMessage, Position, Size};
use crate::{input_device, metrics, mouse_binding};
use anyhow::{anyhow, Context, Error, Result};
use async_trait::async_trait;
use async_utils::hanging_get::client::HangingGetStream;
use fidl::endpoints::create_proxy;
use fidl_fuchsia_input_report::Range;
use fuchsia_component::client::connect_to_protocol;
use fuchsia_inspect::health::Reporter;
use futures::channel::mpsc::Sender;
use futures::stream::StreamExt;
use futures::SinkExt;
use metrics_registry::*;
use std::cell::{Ref, RefCell, RefMut};
use std::collections::HashMap;
use std::rc::Rc;
use {
fidl_fuchsia_input_interaction_observation as interaction_observation,
fidl_fuchsia_ui_pointerinjector as pointerinjector,
fidl_fuchsia_ui_pointerinjector_configuration as pointerinjector_config,
};
const MOUSE_DISTANCE_IN_MM_TO_DISPLAY_LOGICAL_PIXEL: f32 = 10.0;
pub struct MouseInjectorHandler {
mutable_state: RefCell<MutableState>,
context_view_ref: fidl_fuchsia_ui_views::ViewRef,
target_view_ref: fidl_fuchsia_ui_views::ViewRef,
max_position: Position,
injector_registry_proxy: pointerinjector::RegistryProxy,
configuration_proxy: pointerinjector_config::SetupProxy,
pub inspect_status: InputHandlerStatus,
metrics_logger: metrics::MetricsLogger,
aggregator_proxy: interaction_observation::AggregatorProxy,
}
struct MutableState {
viewport: Option<pointerinjector::Viewport>,
injectors: HashMap<u32, pointerinjector::DeviceProxy>,
current_position: Position,
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()));
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));
}
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));
}
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));
}
if let Err(e) = self.report_mouse_activity(event_time).await {
tracing::error!("report_mouse_activity failed: {}", e);
}
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 {
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>()?;
let aggregator_proxy = connect_to_protocol::<interaction_observation::AggregatorMarker>()?;
Self::new_handler(
aggregator_proxy,
configuration_proxy,
injector_registry_proxy,
display_size,
cursor_message_sender,
input_handlers_node,
metrics_logger,
)
.await
}
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 aggregator_proxy = connect_to_protocol::<interaction_observation::AggregatorMarker>()?;
let injector_registry_proxy = connect_to_protocol::<pointerinjector::RegistryMarker>()?;
Self::new_handler(
aggregator_proxy,
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()
}
async fn new_handler(
aggregator_proxy: interaction_observation::AggregatorProxy,
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> {
let (context_view_ref, target_view_ref) = configuration_proxy.get_view_refs().await?;
let inspect_status = InputHandlerStatus::new(
input_handlers_node,
"mouse_injector_handler",
false,
);
let handler = Rc::new(Self {
mutable_state: RefCell::new(MutableState {
viewport: None,
injectors: HashMap::new(),
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,
aggregator_proxy,
});
Ok(handler)
}
async fn ensure_injector_registered(
self: &Rc<Self>,
mouse_event: &mouse_binding::MouseEvent,
mouse_descriptor: &mouse_binding::MouseDeviceDescriptor,
event_time: zx::MonotonicInstant,
) -> Result<(), anyhow::Error> {
if self.inner().injectors.contains_key(&mouse_descriptor.device_id) {
return Ok(());
}
let (device_proxy, device_server) = create_proxy::<pointerinjector::DeviceMarker>();
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()
};
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);
self.inner_mut().injectors.insert(mouse_descriptor.device_id, device_proxy.clone());
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(())
}
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(())
}
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))
}
async fn send_event_to_scenic(
&self,
mouse_event: &mouse_binding::MouseEvent,
mouse_descriptor: &mouse_binding::MouseDeviceDescriptor,
event_time: zx::MonotonicInstant,
) -> 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
))
}
}
fn create_pointer_sample_event(
&self,
mouse_event: &mouse_binding::MouseEvent,
event_time: zx::MonotonicInstant,
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()
}
}
async fn report_mouse_activity(
&self,
event_time: zx::MonotonicInstant,
) -> Result<(), fidl::Error> {
self.aggregator_proxy.report_discrete_activity(event_time.into_nanos()).await
}
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)) => {
self.inner_mut().viewport = Some(new_viewport.clone());
let injectors = self.inner().injectors.values().cloned().collect::<Vec<_>>();
for injector in injectors {
let events = &[pointerinjector::Event {
timestamp: Some(fuchsia_async::MonotonicInstant::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;
}
}
}
}
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::*;
use 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,
};
use assert_matches::assert_matches;
use futures::channel::mpsc;
use pretty_assertions::assert_eq;
use std::collections::HashSet;
use std::ops::Add;
use test_case::test_case;
use {
fidl_fuchsia_input_report as fidl_input_report,
fidl_fuchsia_ui_pointerinjector as pointerinjector, fuchsia_async as fasync,
};
const DISPLAY_WIDTH_IN_PHYSICAL_PX: f32 = 100.0;
const DISPLAY_HEIGHT_IN_PHYSICAL_PX: f32 = 100.0;
const COUNTS_PER_MM: u32 = 12;
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,
});
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);
}
_ => {}
};
}
}
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();
let _ = injector_sender.send(injector_stream);
responder.send().expect("failed to respond");
}
_ => {}
};
} else {
panic!("RegistryRequestStream failed.");
}
}
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();
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;
}
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."),
}
}
}
async fn handle_aggregator_request_stream(
mut stream: interaction_observation::AggregatorRequestStream,
expected_times: Vec<i64>,
) {
for expected_time in expected_times {
if let Some(request) = stream.next().await {
match request {
Ok(interaction_observation::AggregatorRequest::ReportDiscreteActivity {
event_time,
responder,
}) => {
assert_eq!(event_time, expected_time);
responder.send().expect("failed to respond");
}
other => panic!("expected aggregator report request, but got {:?}", other),
};
} else {
panic!("AggregatorRequestStream failed.");
}
}
}
fn create_viewport(min: f32, max: f32) -> pointerinjector::Viewport {
pointerinjector::Viewport {
extents: Some([[min, min], [max, max]]),
viewport_to_context_transform: None,
..Default::default()
}
}
#[fuchsia::test]
fn receives_viewport_updates() {
let mut exec = fasync::TestExecutor::new();
let (aggregator_proxy, _) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, _) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let (sender, _) = futures::channel::mpsc::channel::<CursorMessage>(0);
let inspector = fuchsia_inspect::Inspector::default();
let test_node = inspector.root().create_child("test_node");
let mouse_handler_fut = MouseInjectorHandler::new_handler(
aggregator_proxy,
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");
let (injector_device_proxy, mut injector_device_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::DeviceMarker>();
mouse_handler.inner_mut().injectors.insert(1, injector_device_proxy);
{
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());
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());
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),
}
});
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
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),
};
assert!(exec.run_until_stalled(&mut watch_viewport_fut).is_pending());
}
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),
}
});
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,
})
}
#[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],
) {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let input_event = create_mouse_event(
move_location,
None, None, None, mouse_binding::MousePhase::Move,
HashSet::new(),
HashSet::new(),
event_time,
&DESCRIPTOR,
);
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, None, None, None, event_time,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![],
expected_position,
Some(expected_relative_motion),
None, None, None, event_time,
),
];
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);
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time.into_nanos()],
);
let (handle_result, _, _, _) =
futures::join!(handle_event_fut, registry_fut, device_fut, aggregator_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."),
}
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn move_absolute_event() {
const DEVICE_ID: u32 = 1;
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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: -25.0, y: 25.0 });
let event_time = zx::MonotonicInstant::get();
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, None, None, mouse_binding::MousePhase::Move,
HashSet::new(),
HashSet::new(),
event_time,
&descriptor,
);
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, None, None, None, event_time,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![],
expected_position,
None, None, None, None, event_time,
),
];
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);
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time.into_nanos()],
);
let (handle_result, _, _, _) =
futures::join!(handle_event_fut, registry_fut, device_fut, aggregator_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."),
}
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
#[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>,
) {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let input_event = create_mouse_event(
cursor_location,
None, None, None, phase,
HashSet::from_iter(affected_buttons.clone()),
HashSet::from_iter(pressed_buttons.clone()),
event_time,
&DESCRIPTOR,
);
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, None, None, None, event_time,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
pressed_buttons.clone(),
expected_position,
None, None, None, None, event_time,
),
];
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);
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time.into_nanos()],
);
let (handle_result, _, _, _) =
futures::join!(handle_event_fut, registry_fut, device_fut, aggregator_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."),
}
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
#[fuchsia::test(allow_stalls = false)]
async fn down_up_event() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
let event1 = create_mouse_event(
cursor_location,
None, None, None, 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, None, None, mouse_binding::MousePhase::Up,
HashSet::from_iter(vec![1]),
HashSet::new(),
event_time2,
&DESCRIPTOR,
);
let expected_position = Position { x: 0.0, y: 0.0 };
let (injector_stream_sender, injector_stream_receiver) =
mpsc::unbounded::<Vec<pointerinjector::Event>>();
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 aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time1.into_nanos(), event_time2.into_nanos()],
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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, None, None, None, event_time1,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![1],
expected_position,
None, None, None, None, event_time1,
)
])
);
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, None, None, None, event_time2,
)])
);
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."),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn down_down_up_up_event() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
let event_time4 = event_time3.add(zx::MonotonicDuration::from_micros(1));
let event1 = create_mouse_event(
cursor_location,
None, None, None, 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, None, None, 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, None, None, 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, None, None, mouse_binding::MousePhase::Up,
HashSet::from_iter(vec![2]),
HashSet::new(),
event_time4,
&DESCRIPTOR,
);
let expected_position = Position { x: 0.0, y: 0.0 };
let (injector_stream_sender, injector_stream_receiver) =
mpsc::unbounded::<Vec<pointerinjector::Event>>();
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 aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![
event_time1.into_nanos(),
event_time2.into_nanos(),
event_time3.into_nanos(),
event_time4.into_nanos(),
],
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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, None, None, None, event_time1,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![1],
expected_position,
None, None, None, None, event_time1,
)
])
);
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);
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]),
}
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, None, None, None, event_time3,
)])
);
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, None, None, None, event_time4,
)])
);
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."),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn down_move_up_event() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
let event_time3 = event_time2.add(zx::MonotonicDuration::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, None, None, 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, None, None, 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, None, None, mouse_binding::MousePhase::Up,
HashSet::from_iter(vec![1]),
HashSet::from_iter(vec![]),
event_time3,
&DESCRIPTOR,
);
let (injector_stream_sender, injector_stream_receiver) =
mpsc::unbounded::<Vec<pointerinjector::Event>>();
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 aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time1.into_nanos(), event_time2.into_nanos(), event_time3.into_nanos()],
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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, None, None, None, event_time1,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![1],
zero_position,
None, None, None, None, event_time1,
)
])
);
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."),
}
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, None, None, event_time2,
)])
);
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."),
}
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, None, None, None, event_time3,
)])
);
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."),
}
}
#[fuchsia::test(allow_stalls = false)]
async fn handler_ignores_handled_events() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
let config_request_stream_fut =
handle_configuration_request_stream(&mut configuration_request_stream);
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(
aggregator_proxy,
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::MonotonicInstant::get();
let input_events = vec![create_mouse_event_with_handled(
cursor_location,
None, None, None, 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,
aggregator_request_stream,
)
.await;
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), None, Some(mouse_binding::PrecisionScroll::No), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, Some(1), None, Some(false), zx::MonotonicInstant::ZERO,
); "v tick scroll"
)]
#[test_case(
create_mouse_event(
zero_relative_location(),
None, wheel_delta_ticks(1, None), Some(mouse_binding::PrecisionScroll::No), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, None, Some(1), Some(false), zx::MonotonicInstant::ZERO,
); "h tick scroll"
)]
#[test_case(
create_mouse_event(
zero_relative_location(),
wheel_delta_ticks(1, Some(120.0)), None, Some(mouse_binding::PrecisionScroll::No), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event_with_wheel_physical_pixel(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, Some(1), None, Some(120.0), None, Some(false), zx::MonotonicInstant::ZERO,
); "v tick scroll with physical pixel"
)]
#[test_case(
create_mouse_event(
zero_relative_location(),
None, wheel_delta_ticks(1, Some(120.0)), Some(mouse_binding::PrecisionScroll::No), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event_with_wheel_physical_pixel(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, None, Some(1), None, Some(120.0), Some(false), zx::MonotonicInstant::ZERO,
); "h tick scroll with physical pixel"
)]
#[test_case(
create_mouse_event(
zero_relative_location(),
wheel_delta_mm(1.0, Some(120.0)), None, Some(mouse_binding::PrecisionScroll::Yes), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event_with_wheel_physical_pixel(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, None, None, Some(120.0), None, Some(true), zx::MonotonicInstant::ZERO,
); "v mm scroll with physical pixel"
)]
#[test_case(
create_mouse_event(
zero_relative_location(),
None, wheel_delta_mm(1.0, Some(120.0)), Some(mouse_binding::PrecisionScroll::Yes), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
zx::MonotonicInstant::ZERO,
&DESCRIPTOR,
),
create_mouse_pointer_sample_event_with_wheel_physical_pixel(
pointerinjector::EventPhase::Change,
vec![],
Position { x: 50.0, y: 50.0 },
None, None, None, None, Some(120.0), Some(true), zx::MonotonicInstant::ZERO,
); "h mm scroll with physical pixel"
)]
#[fuchsia::test(allow_stalls = false)]
async fn scroll(event: input_device::InputEvent, want_event: pointerinjector::Event) {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
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 test_node = inspector.root().create_child("test_node");
let mouse_handler_fut = MouseInjectorHandler::new_handler(
aggregator_proxy,
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 (injector_stream_sender, injector_stream_receiver) =
mpsc::unbounded::<Vec<pointerinjector::Event>>();
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::MonotonicInstant::get();
let event = input_device::InputEvent { event_time, ..event };
let want_event =
pointerinjector::Event { timestamp: Some(event_time.into_nanos()), ..want_event };
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event.event_time.into_nanos()],
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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);
}
#[fuchsia::test(allow_stalls = false)]
async fn down_scroll_up_scroll() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
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 test_node = inspector.root().create_child("test_node");
let mouse_handler_fut = MouseInjectorHandler::new_handler(
aggregator_proxy,
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 (injector_stream_sender, injector_stream_receiver) =
mpsc::unbounded::<Vec<pointerinjector::Event>>();
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::MonotonicInstant::get();
let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
let event_time4 = event_time3.add(zx::MonotonicDuration::from_micros(1));
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![
event_time1.into_nanos(),
event_time2.into_nanos(),
event_time3.into_nanos(),
event_time4.into_nanos(),
],
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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, None, None, 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), None, Some(mouse_binding::PrecisionScroll::No), 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, 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), None, Some(mouse_binding::PrecisionScroll::No), mouse_binding::MousePhase::Wheel,
HashSet::new(),
HashSet::new(),
event_time4,
&DESCRIPTOR,
);
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, None, None, None, event_time1,
),
create_mouse_pointer_sample_event(
pointerinjector::EventPhase::Change,
vec![1],
expected_position,
None, None, None, None, event_time1,
),
])
);
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, Some(1), None, Some(false), event_time2,
)])
);
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, None, None, None, event_time3,
)])
);
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, Some(1), None, Some(false), 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>();
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",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
#[fuchsia::test(allow_stalls = false)]
async fn mouse_injector_handler_inspect_counts_events() {
let (aggregator_proxy, aggregator_request_stream) =
fidl::endpoints::create_proxy_and_stream::<interaction_observation::AggregatorMarker>();
let (configuration_proxy, mut configuration_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector_config::SetupMarker>();
let (injector_registry_proxy, injector_registry_request_stream) =
fidl::endpoints::create_proxy_and_stream::<pointerinjector::RegistryMarker>();
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_handler(
aggregator_proxy,
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::MonotonicInstant::get();
let event_time2 = event_time1.add(zx::MonotonicDuration::from_micros(1));
let event_time3 = event_time2.add(zx::MonotonicDuration::from_micros(1));
let aggregator_fut = handle_aggregator_request_stream(
aggregator_request_stream,
vec![event_time1.into_nanos(), event_time2.into_nanos(), event_time3.into_nanos()],
);
let input_events = vec![
create_mouse_event(
cursor_location,
None, None, None, mouse_binding::MousePhase::Down,
HashSet::from_iter(vec![1]),
HashSet::from_iter(vec![1]),
event_time1,
&DESCRIPTOR,
),
create_mouse_event(
cursor_location,
None, None, None, mouse_binding::MousePhase::Up,
HashSet::from_iter(vec![1]),
HashSet::new(),
event_time2,
&DESCRIPTOR,
),
create_mouse_event_with_handled(
cursor_location,
None, None, None, mouse_binding::MousePhase::Down,
HashSet::from_iter(vec![1]),
HashSet::from_iter(vec![1]),
event_time3,
&DESCRIPTOR,
input_device::Handled::Yes,
),
];
let (injector_stream_sender, _) = mpsc::unbounded::<Vec<pointerinjector::Event>>();
let registry_fut = handle_registry_request_stream2(
injector_registry_request_stream,
injector_stream_sender,
);
let _registry_task = fasync::Task::local(registry_fut);
let _aggregator_task = fasync::Task::local(aggregator_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",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
}