blob: 26e435db05e564aff9e60f44b72983e55373c52b [file] [log] [blame]
// Copyright 2019 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::{self, InputDeviceBinding},
anyhow::{format_err, Error},
async_trait::async_trait,
fidl_fuchsia_input_report as fidl_input_report,
fidl_fuchsia_input_report::{InputDeviceProxy, InputReport},
fuchsia_async as fasync,
fuchsia_syslog::fx_log_info,
futures::channel::mpsc::{Receiver, Sender},
futures::StreamExt,
std::collections::HashSet,
std::iter::FromIterator,
};
pub type MouseButton = u8;
/// A [`MouseEvent`] represents a pointer event with a specified phase, and the buttons
/// involved in said phase. The supported phases for mice include Up, Down, and Move.
///
/// # Example
/// The following MouseEvent represents a movement of 40 units in the x axis and 20 units
/// in the y axis while hold the primary button down.
///
/// ```
/// let mouse_device_event = input_device::InputDeviceEvent::Mouse({
/// MouseEvent {
/// movement_x: 40,
/// movement_y: 20,
/// phase: fidl_fuchsia_ui_input::PointerEventPhase::Move,
/// buttons: 1,
/// }});
/// ```
#[derive(Clone, Debug, PartialEq)]
pub struct MouseEvent {
/// The movement in the x axis.
pub movement_x: i64,
/// The movement in the y axis.
pub movement_y: i64,
/// The phase of the [`buttons`] associated with this input event.
pub phase: fidl_fuchsia_ui_input::PointerEventPhase,
/// The buttons relevant to this event.
pub buttons: HashSet<MouseButton>,
}
/// A [`MouseBinding`] represents a connection to a mouse input device.
///
/// The [`MouseBinding`] parses and exposes mouse descriptor properties (e.g., the range of
/// possible x values) for the device it is associated with. It also parses [`InputReport`]s
/// from the device, and sends them to clients via [`MouseBinding::input_event_stream()`].
///
/// # Example
/// ```
/// let mut mouse_device: MouseBinding = input_device::InputDeviceBinding::new().await?;
///
/// while let Some(report) = mouse_device.input_event_stream().next().await {}
/// ```
pub struct MouseBinding {
/// The channel to stream InputEvents to.
event_sender: Sender<input_device::InputEvent>,
/// The receiving end of the input event channel. Clients use this indirectly via
/// [`input_event_stream()`].
event_receiver: Receiver<input_device::InputEvent>,
/// Holds information about this device. Currently empty because no information is needed for
/// the supported use cases.
device_descriptor: MouseDeviceDescriptor,
}
#[derive(Copy, Clone, Debug, PartialEq)]
pub struct MouseDeviceDescriptor {
/// The id of the connected mouse input device.
pub device_id: u32,
}
#[async_trait]
impl input_device::InputDeviceBinding for MouseBinding {
fn input_event_sender(&self) -> Sender<input_device::InputEvent> {
self.event_sender.clone()
}
fn input_event_stream(&mut self) -> &mut Receiver<input_device::InputEvent> {
return &mut self.event_receiver;
}
fn get_device_descriptor(&self) -> input_device::InputDeviceDescriptor {
input_device::InputDeviceDescriptor::Mouse(self.device_descriptor.clone())
}
fn process_reports(
report: InputReport,
previous_report: Option<InputReport>,
device_descriptor: &input_device::InputDeviceDescriptor,
input_event_sender: &mut Sender<input_device::InputEvent>,
) -> Option<InputReport> {
// Fail early if the new InputReport isn't a MouseInputReport
let mouse_report: &fidl_input_report::MouseInputReport = match &report.mouse {
Some(mouse) => mouse,
None => {
fx_log_info!("Not processing non-mouse InputReport.");
return previous_report;
}
};
let previous_buttons: HashSet<MouseButton> =
buttons_from_optional_report(&previous_report.as_ref());
let current_buttons: HashSet<MouseButton> = buttons_from_report(&report);
// Down events are sent for the buttons which did not exist in the previous report.
send_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
current_buttons.difference(&previous_buttons).cloned().collect(),
device_descriptor,
input_event_sender,
);
// Move events are sent for both previous and current buttons.
send_mouse_event(
mouse_report.movement_x.unwrap_or_default(),
mouse_report.movement_y.unwrap_or_default(),
fidl_fuchsia_ui_input::PointerEventPhase::Move,
current_buttons.union(&previous_buttons).cloned().collect(),
device_descriptor,
input_event_sender,
);
// Up events are sent for previous buttons that are no longer present in the current buttons.
send_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Up,
previous_buttons.difference(&current_buttons).cloned().collect(),
device_descriptor,
input_event_sender,
);
Some(report)
}
async fn any_input_device() -> Result<InputDeviceProxy, Error> {
let mut devices = Self::all_devices().await?;
devices.pop().ok_or(format_err!("Couldn't find a default mouse."))
}
async fn all_devices() -> Result<Vec<InputDeviceProxy>, Error> {
input_device::all_devices(input_device::InputDeviceType::Mouse).await
}
async fn bind_device(device: &InputDeviceProxy) -> Result<Self, Error> {
let device_descriptor: fidl_input_report::DeviceDescriptor =
device.get_descriptor().await?;
match device_descriptor.mouse {
Some(_) => {
let (event_sender, event_receiver) =
futures::channel::mpsc::channel(input_device::INPUT_EVENT_BUFFER_SIZE);
let device_id = 0;
let device_descriptor: MouseDeviceDescriptor = MouseDeviceDescriptor { device_id };
Ok(MouseBinding { event_sender, event_receiver, device_descriptor })
}
descriptor => Err(format_err!("Mouse Descriptor failed to parse: \n {:?}", descriptor)),
}
}
}
/// Sends an InputEvent over `sender`.
///
/// When no buttons are present, only [`fidl_fuchsia_ui_input::PointerEventPhase::Move`] events will
/// be sent.
///
/// # Parameters
/// - `movement_x`: The movement in the x axis.
/// - `movement_y`: The movement in the y axis.
/// - `phase`: The phase of the [`buttons`] associated with the input event.
/// - `buttons`: The buttons relevant to the event.
/// - `sender`: The stream to send the MouseEvent to.
fn send_mouse_event(
movement_x: i64,
movement_y: i64,
phase: fidl_fuchsia_ui_input::PointerEventPhase,
buttons: HashSet<MouseButton>,
device_descriptor: &input_device::InputDeviceDescriptor,
sender: &mut Sender<input_device::InputEvent>,
) {
// Only send move events when there are no buttons pressed.
if phase != fidl_fuchsia_ui_input::PointerEventPhase::Move && buttons.is_empty() {
return;
}
// Don't send move events when there is no movement.
if phase == fidl_fuchsia_ui_input::PointerEventPhase::Move && movement_x == 0 && movement_y == 0
{
return;
}
match sender.try_send(input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Mouse(MouseEvent {
movement_x,
movement_y,
phase,
buttons,
}),
device_descriptor: device_descriptor.clone(),
}) {
Err(e) => fx_log_info!("Failed to send MouseEvent with error: {:?}", e),
_ => {}
}
}
/// Returns a u32 representation of `vector`, where each u8 of `vector` is an id of a button and
/// indicates the position of a bit to set.
///
/// This supports vectors with numbers from 1 - fidl_input_report::MOUSE_MAX_NUM_BUTTONS.
///
/// # Parameters
/// - `vector`: The vector containing the position of bits to be set.
///
/// # Example
/// ```
/// let bits = get_u32_from_vector(&vec![1, 3, 5]);
/// assert_eq!(bits, 21 /* ...00010101 */)
/// ```
pub fn get_u32_from_buttons(buttons: &HashSet<MouseButton>) -> u32 {
let mut bits: u32 = 0;
for button in buttons {
if *button > 0 && *button <= fidl_input_report::MOUSE_MAX_NUM_BUTTONS as u8 {
bits = ((1 as u32) << *button - 1) | bits;
}
}
bits
}
/// Returns the set of pressed buttons present in the given input report.
///
/// # Parameters
/// - `report`: The input report to parse the mouse buttons from.
fn buttons_from_report(input_report: &fidl_input_report::InputReport) -> HashSet<MouseButton> {
buttons_from_optional_report(&Some(input_report))
}
/// Returns the set of pressed buttons present in the given input report.
///
/// # Parameters
/// - `report`: The input report to parse the mouse buttons from.
fn buttons_from_optional_report(
input_report: &Option<&fidl_input_report::InputReport>,
) -> HashSet<MouseButton> {
input_report
.as_ref()
.and_then(|unwrapped_report| unwrapped_report.mouse.as_ref())
.and_then(|mouse_report| match &mouse_report.pressed_buttons {
Some(buttons) => Some(HashSet::from_iter(buttons.iter().cloned())),
None => None,
})
.unwrap_or_default()
}
/// Returns a vector of [`MouseBindings`] for all currently connected mice.
///
/// # Errors
/// If there was an error binding to any mouse.
pub async fn all_mouse_bindings() -> Result<Vec<MouseBinding>, Error> {
let device_proxies = input_device::all_devices(input_device::InputDeviceType::Mouse).await?;
let mut device_bindings: Vec<MouseBinding> = vec![];
for device_proxy in device_proxies {
let device_binding: MouseBinding =
input_device::InputDeviceBinding::new(device_proxy).await?;
device_bindings.push(device_binding);
}
Ok(device_bindings)
}
/// Returns a stream of InputEvents from all mouse devices.
///
/// # Errors
/// If there was an error binding to any mouse.
pub async fn all_mouse_events() -> Result<Receiver<input_device::InputEvent>, Error> {
let bindings = all_mouse_bindings().await?;
let (event_sender, event_receiver) =
futures::channel::mpsc::channel(input_device::INPUT_EVENT_BUFFER_SIZE);
for mut mouse in bindings {
let mut sender = event_sender.clone();
fasync::spawn(async move {
while let Some(input_event) = mouse.input_event_stream().next().await {
let _ = sender.try_send(input_event);
}
});
}
Ok(event_receiver)
}
#[cfg(test)]
mod tests {
use {super::*, crate::testing_utilities};
// Tests that the right u32 representation is returned from a vector of digits.
#[test]
fn get_u32_from_buttons_test() {
let bits = get_u32_from_buttons(&HashSet::from_iter(vec![1, 3, 5].into_iter()));
assert_eq!(bits, 21 /* 0...00010101 */)
}
// Tests that the right u32 representation is returned from a vector of digits that includes 0.
#[test]
fn get_u32_with_0_in_vector() {
let bits = get_u32_from_buttons(&HashSet::from_iter(vec![0, 1, 3].into_iter()));
assert_eq!(bits, 5 /* 0...00000101 */)
}
// Tests that the right u32 representation is returned from an empty vector.
#[test]
fn get_u32_with_empty_vector() {
let bits = get_u32_from_buttons(&HashSet::new());
assert_eq!(bits, 0 /* 0...00000000 */)
}
// Tests that the right u32 representation is returned from a vector containing std::u8::MAX.
#[test]
fn get_u32_with_u8_max_in_vector() {
let bits = get_u32_from_buttons(&HashSet::from_iter(vec![1, 3, std::u8::MAX].into_iter()));
assert_eq!(bits, 5 /* 0...00000101 */)
}
// Tests that the right u32 representation is returned from a vector containing the largest
// button id possible.
#[test]
fn get_u32_with_max_mouse_buttons() {
let bits = get_u32_from_buttons(&HashSet::from_iter(
vec![1, 3, fidl_input_report::MOUSE_MAX_NUM_BUTTONS as MouseButton].into_iter(),
));
assert_eq!(bits, 2147483653 /* 10...00000101 */)
}
/// Tests that a report containing no buttons but with movement generates a move event.
#[fasync::run_singlethreaded(test)]
async fn movement_without_button() {
const MOVEMENT_X: i64 = 10;
const MOVEMENT_Y: i64 = 16;
let first_report =
testing_utilities::create_mouse_input_report(MOVEMENT_X, MOVEMENT_Y, vec![]);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report];
let expected_events = vec![testing_utilities::create_mouse_event(
MOVEMENT_X,
MOVEMENT_Y,
fidl_fuchsia_ui_input::PointerEventPhase::Move,
HashSet::new(),
&descriptor,
)];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
/// Tests that a report containing a new mouse button generates a down event.
#[fasync::run_singlethreaded(test)]
async fn down_without_movement() {
let mouse_button: MouseButton = 3;
let first_report = testing_utilities::create_mouse_input_report(0, 0, vec![mouse_button]);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report];
let expected_events = vec![testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
HashSet::from_iter(vec![mouse_button].into_iter()),
&descriptor,
)];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
/// Tests that a report containing a new mouse button with movement generates a down event and a
/// move event.
#[fasync::run_singlethreaded(test)]
async fn down_with_movement() {
const MOVEMENT_X: i64 = 10;
const MOVEMENT_Y: i64 = 16;
let mouse_button: MouseButton = 3;
let first_report = testing_utilities::create_mouse_input_report(
MOVEMENT_X,
MOVEMENT_Y,
vec![mouse_button],
);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report];
let expected_events = vec![
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
HashSet::from_iter(vec![mouse_button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
MOVEMENT_X,
MOVEMENT_Y,
fidl_fuchsia_ui_input::PointerEventPhase::Move,
HashSet::from_iter(vec![mouse_button].into_iter()),
&descriptor,
),
];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
/// Tests that a press and release of a mouse button without movement generates a down and up event.
#[fasync::run_singlethreaded(test)]
async fn down_up() {
let button = 1;
let first_report = testing_utilities::create_mouse_input_report(0, 0, vec![button]);
let second_report = testing_utilities::create_mouse_input_report(0, 0, vec![]);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report, second_report];
let expected_events = vec![
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Up,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
/// Tests that a press and release of a mouse button with movement generates down, move, and up events.
#[fasync::run_singlethreaded(test)]
async fn down_up_with_movement() {
const MOVEMENT_X: i64 = 10;
const MOVEMENT_Y: i64 = 16;
let button = 1;
let first_report = testing_utilities::create_mouse_input_report(0, 0, vec![button]);
let second_report =
testing_utilities::create_mouse_input_report(MOVEMENT_X, MOVEMENT_Y, vec![]);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report, second_report];
let expected_events = vec![
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
MOVEMENT_X,
MOVEMENT_Y,
fidl_fuchsia_ui_input::PointerEventPhase::Move,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Up,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
/// Tests that a press, move, and release of a button generates down, move, and up events.
/// This specifically tests the separate input report containing the movement, instead of sending
/// the movement as part of the down or up events.
#[fasync::run_singlethreaded(test)]
async fn down_move_up() {
const MOVEMENT_X: i64 = 10;
const MOVEMENT_Y: i64 = 16;
let button = 1;
let first_report = testing_utilities::create_mouse_input_report(0, 0, vec![button]);
let second_report =
testing_utilities::create_mouse_input_report(MOVEMENT_X, MOVEMENT_Y, vec![button]);
let third_report = testing_utilities::create_mouse_input_report(0, 0, vec![]);
let descriptor =
input_device::InputDeviceDescriptor::Mouse(MouseDeviceDescriptor { device_id: 1 });
let input_reports = vec![first_report, second_report, third_report];
let expected_events = vec![
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Down,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
MOVEMENT_X,
MOVEMENT_Y,
fidl_fuchsia_ui_input::PointerEventPhase::Move,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
testing_utilities::create_mouse_event(
0,
0,
fidl_fuchsia_ui_input::PointerEventPhase::Up,
HashSet::from_iter(vec![button].into_iter()),
&descriptor,
),
];
assert_input_report_sequence_generates_events!(
input_reports: input_reports,
expected_events: expected_events,
device_descriptor: descriptor,
device_type: MouseBinding,
);
}
}