blob: 42f8e0586623b9acbbcc04965e9ae841c5f203df [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use {
crate::input_device,
crate::input_handler::InputHandler,
crate::mouse,
crate::utils::Position,
async_trait::async_trait,
fidl_fuchsia_ui_input as fidl_ui_input, fidl_fuchsia_ui_scenic as fidl_ui_scenic,
fuchsia_scenic as scenic,
futures::{channel::mpsc::Sender, SinkExt},
std::collections::HashSet,
};
/// A [`MouseHandler`] tracks the mouse position and sends updates to clients.
pub struct MouseHandler {
/// The current position.
current_position: Position,
/// The maximum position sent to clients, used to bound relative movements
/// and scale absolute positions from device coordinates.
max_position: Position,
/// A [`Sender`] used to communicate the current position.
position_sender: Option<Sender<Position>>,
/// The Scenic session to send pointer events to.
scenic_session: scenic::SessionPtr,
/// The Scenic compositor id to tag pointer events with.
scenic_compositor_id: u32,
}
#[async_trait]
impl InputHandler for MouseHandler {
async fn handle_input_event(
&mut self,
input_event: input_device::InputEvent,
) -> Vec<input_device::InputEvent> {
match input_event {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Mouse(mouse_event),
device_descriptor: input_device::InputDeviceDescriptor::Mouse(mouse_descriptor),
event_time,
} => {
self.update_cursor_position(&mouse_event, &mouse_descriptor).await;
self.send_events_to_scenic(
mouse_event.phase,
&mouse_event.buttons,
&mouse_descriptor,
event_time,
)
.await;
vec![]
}
_ => vec![input_event],
}
}
}
impl MouseHandler {
/// Creates a new [`MouseHandler`] that sends pointer events to Scenic and tracks cursor
/// position.
///
/// # Parameters
/// - `max_position`: The maximum position, used to bound events sent to clients.
/// - `position_sender`: A [`Sender`] used to communicate the current position.
/// - `scenic_session`: The Scenic session to send pointer events to.
/// - `scenic_compositor_id`: The Scenic compositor id to tag pointer events with.
pub fn new(
max_position: Position,
position_sender: Option<Sender<Position>>,
scenic_session: scenic::SessionPtr,
scenic_compositor_id: u32,
) -> MouseHandler {
MouseHandler {
max_position,
position_sender,
scenic_session,
scenic_compositor_id,
current_position: Position { x: 0.0, y: 0.0 },
}
}
/// Updates the current cursor position according to the received mouse event.
///
/// The updated position is sent to a client via `self.position_sender`.
///
/// If there is no movement, the location is not sent to clients.
///
/// # Parameters
/// - `mouse_event`: The mouse event to use to update the cursor location.
/// - `device_descriptor`: The descriptor for the input device generating the input reports.
async fn update_cursor_position(
&mut self,
mouse_event: &mouse::MouseEvent,
device_descriptor: &mouse::MouseDeviceDescriptor,
) {
self.current_position = match mouse_event.location {
mouse::MouseLocation::Relative(offset) if offset != Position::zero() => {
self.current_position + offset
}
mouse::MouseLocation::Absolute(position) if position != self.current_position => {
self.scale_absolute_position(&position, &device_descriptor)
}
_ => return,
};
Position::clamp(&mut self.current_position, Position::zero(), self.max_position);
if let Some(position_sender) = &mut self.position_sender {
let _ = position_sender.send(self.current_position).await;
}
}
/// Sends a pointer event with the given phase and buttons to Scenic.
///
/// # Parameters
/// - `phase`: The phase of the buttons associated with the mouse event.
/// - `buttons`: The buttons associated with the event.
/// - `device_descriptor`: The descriptor for the input device generating the input reports.
/// - `event_time`: The time in nanoseconds when the event was first recorded.
async fn send_events_to_scenic(
&self,
phase: fidl_ui_input::PointerEventPhase,
buttons: &HashSet<mouse::MouseButton>,
device_descriptor: &mouse::MouseDeviceDescriptor,
event_time: input_device::EventTime,
) {
let buttons = mouse::get_u32_from_buttons(buttons);
let pointer_event = fidl_ui_input::PointerEvent {
event_time,
device_id: device_descriptor.device_id,
pointer_id: 0,
type_: fidl_ui_input::PointerEventType::Mouse,
phase,
x: self.current_position.x,
y: self.current_position.y,
radius_major: 0.0,
radius_minor: 0.0,
buttons,
};
let pointer_command = fidl_ui_input::SendPointerInputCmd {
compositor_id: self.scenic_compositor_id,
pointer_event,
};
let send_pointer_command = fidl_ui_input::Command::SendPointerInput(pointer_command);
let mut session = self.scenic_session.lock();
session.enqueue(fidl_ui_scenic::Command::Input(send_pointer_command));
session.flush();
}
/// Returns an absolute cursor position scaled from device coordinates to the handler's
/// max position.
///
/// Returns the original position if the device descriptor does not specify ranges
/// for absolute coordinates.
///
/// # Parameters
/// - `position`: Absolute cursor position in device coordinates.
/// - `device_descriptor`: The descriptor for the input device generating the input reports.
fn scale_absolute_position(
&self,
position: &Position,
device_descriptor: &mouse::MouseDeviceDescriptor,
) -> Position {
match (device_descriptor.absolute_x_range, device_descriptor.absolute_y_range) {
(Some(x_range), Some(y_range)) => {
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))
}
_ => *position,
}
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::testing_utilities::create_mouse_event, crate::utils::Position,
fidl_fuchsia_input_report as fidl_input_report, fidl_fuchsia_ui_scenic as fidl_ui_scenic,
fuchsia_async as fasync, fuchsia_zircon as zx, futures::StreamExt,
};
const SCENIC_COMPOSITOR_ID: u32 = 1;
const SCENIC_DISPLAY_WIDTH: f32 = 100.0;
const SCENIC_DISPLAY_HEIGHT: f32 = 150.0;
fn mouse_device_descriptor(device_id: u32) -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Mouse(mouse::MouseDeviceDescriptor {
device_id,
absolute_x_range: None,
absolute_y_range: None,
})
}
/// Creates a PointerEvent with the given parameters.
///
/// # Parameters
/// - `position`: The location of the event.
/// - `phase`: The phase of the event.
/// - `device_id`: The id of the device where this event originated.
/// - `event_time: The time of the event.
fn create_pointer_event(
position: Position,
phase: fidl_ui_input::PointerEventPhase,
device_id: u32,
event_time: input_device::EventTime,
) -> fidl_ui_input::PointerEvent {
fidl_ui_input::PointerEvent {
event_time,
device_id,
pointer_id: 0,
type_: fidl_ui_input::PointerEventType::Mouse,
phase,
x: position.x,
y: position.y,
radius_major: 0.0,
radius_minor: 0.0,
buttons: 0,
}
}
/// Validates the event `command` against `expected_event`.
///
/// # Parameters
/// - `command`: The command received by the Scenic session.
/// - `expected_event`: The expected event.
fn verify_pointer_event(
command: fidl_ui_scenic::Command,
expected_event: fidl_ui_input::PointerEvent,
) {
match command {
fidl_ui_scenic::Command::Input(fidl_ui_input::Command::SendPointerInput(
fidl_ui_input::SendPointerInputCmd {
compositor_id: _,
pointer_event:
fidl_ui_input::PointerEvent {
event_time,
device_id,
pointer_id,
type_,
phase,
x,
y,
radius_major: _,
radius_minor: _,
buttons,
},
},
)) => {
assert_eq!(event_time, expected_event.event_time);
assert_eq!(type_, fidl_ui_input::PointerEventType::Mouse);
assert_eq!(device_id, expected_event.device_id);
assert_eq!(pointer_id, expected_event.pointer_id);
assert_eq!(phase, expected_event.phase);
assert_eq!(x, expected_event.x);
assert_eq!(y, expected_event.y);
assert_eq!(buttons, expected_event.buttons);
}
_ => {
assert!(false);
}
}
}
// Tests that a mouse move event both sends an update to scenic and sends the current cursor
// location via the cursor location sender.
#[fasync::run_singlethreaded(test)]
async fn move_event() {
const DEVICE_ID: u32 = 1;
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let (sender, mut receiver) = futures::channel::mpsc::channel(1);
let mut mouse_handler = MouseHandler::new(
Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT },
Some(sender),
scenic_session.clone(),
SCENIC_COMPOSITOR_ID,
);
let cursor_relative_position = Position { x: 50.0, y: 75.0 };
let cursor_location = mouse::MouseLocation::Relative(cursor_relative_position);
let descriptor = mouse_device_descriptor(DEVICE_ID);
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![create_mouse_event(
cursor_location,
fidl_ui_input::PointerEventPhase::Move,
HashSet::<mouse::MouseButton>::new(),
event_time,
&descriptor,
)];
let expected_commands = vec![create_pointer_event(
cursor_relative_position,
fidl_ui_input::PointerEventPhase::Move,
DEVICE_ID,
event_time,
)];
assert_input_event_sequence_generates_scenic_events!(
input_handler: mouse_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_pointer_event,
);
let expected_cursor_location = cursor_relative_position;
match receiver.next().await {
Some(cursor_location) => assert_eq!(cursor_location, expected_cursor_location),
_ => assert!(false),
};
}
// Tests that a mouse move event that moves outside the width and height of the display
// is bounded to the display.
#[fasync::run_singlethreaded(test)]
async fn move_outside_max_bounds_event() {
const DEVICE_ID: u32 = 1;
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let (sender, mut receiver) = futures::channel::mpsc::channel(1);
let mut mouse_handler = MouseHandler::new(
Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT },
Some(sender),
scenic_session.clone(),
SCENIC_COMPOSITOR_ID,
);
let start = Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT };
let cursor_location = mouse::MouseLocation::Relative(Position {
x: SCENIC_DISPLAY_WIDTH + 2.0,
y: SCENIC_DISPLAY_HEIGHT + 2.0,
});
let descriptor = mouse_device_descriptor(DEVICE_ID);
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![create_mouse_event(
cursor_location,
fidl_ui_input::PointerEventPhase::Move,
HashSet::<mouse::MouseButton>::new(),
event_time,
&descriptor,
)];
let expected_commands = vec![create_pointer_event(
start,
fidl_ui_input::PointerEventPhase::Move,
DEVICE_ID,
event_time,
)];
assert_input_event_sequence_generates_scenic_events!(
input_handler: mouse_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_pointer_event,
);
let expected_cursor_location =
Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT };
match receiver.next().await {
Some(cursor_location) => assert_eq!(cursor_location, expected_cursor_location),
_ => assert!(false),
};
}
// Tests that a mouse move event that moves outside the width and height of the display
// is bounded to the display.
#[fasync::run_singlethreaded(test)]
async fn move_outside_min_bounds_event() {
const DEVICE_ID: u32 = 1;
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let (sender, mut receiver) = futures::channel::mpsc::channel(1);
let mut mouse_handler = MouseHandler::new(
Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT },
Some(sender),
scenic_session.clone(),
SCENIC_COMPOSITOR_ID,
);
let cursor_location = mouse::MouseLocation::Relative(Position { x: -20.0, y: -15.0 });
let descriptor = mouse_device_descriptor(DEVICE_ID);
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![create_mouse_event(
cursor_location,
fidl_ui_input::PointerEventPhase::Move,
HashSet::<mouse::MouseButton>::new(),
event_time,
&descriptor,
)];
let expected_commands = vec![create_pointer_event(
Position { x: 0.0, y: 0.0 },
fidl_ui_input::PointerEventPhase::Move,
DEVICE_ID,
event_time,
)];
assert_input_event_sequence_generates_scenic_events!(
input_handler: mouse_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_pointer_event,
);
let expected_cursor_location = Position { x: 0.0, y: 0.0 };
match receiver.next().await {
Some(cursor_location) => assert_eq!(cursor_location, expected_cursor_location),
_ => assert!(false),
};
}
// Tests that an absolute mouse move event scales the location from device coordinates to
// between {0, 0} and the handler's maximum position.
#[fasync::run_until_stalled(test)]
async fn move_absolute_event() {
const DEVICE_ID: u32 = 1;
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let (sender, mut receiver) = futures::channel::mpsc::channel(1);
let mut mouse_handler = MouseHandler::new(
Position { x: SCENIC_DISPLAY_WIDTH, y: SCENIC_DISPLAY_HEIGHT },
Some(sender),
scenic_session.clone(),
SCENIC_COMPOSITOR_ID,
);
// 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 = SCENIC_DISPLAY_WIDTH, h = SCENIC_DISPLAY_HEIGHT
let cursor_location = mouse::MouseLocation::Absolute(Position { x: -25.0, y: 25.0 });
let descriptor = input_device::InputDeviceDescriptor::Mouse(mouse::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 }),
});
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![create_mouse_event(
cursor_location,
fidl_ui_input::PointerEventPhase::Move,
HashSet::<mouse::MouseButton>::new(),
event_time,
&descriptor,
)];
let expected_position =
Position { x: SCENIC_DISPLAY_WIDTH * 0.25, y: SCENIC_DISPLAY_HEIGHT * 0.75 };
let expected_commands = vec![create_pointer_event(
expected_position,
fidl_ui_input::PointerEventPhase::Move,
DEVICE_ID,
event_time,
)];
assert_input_event_sequence_generates_scenic_events!(
input_handler: mouse_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_pointer_event,
);
let expected_cursor_location = expected_position;
match receiver.next().await {
Some(cursor_location) => assert_eq!(cursor_location, expected_cursor_location),
_ => assert!(false),
};
}
}