blob: 05830a760eab51b29f391581872d22ae45217bd4 [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.
#![warn(missing_docs)]
use {
crate::{
modern_backend::input_reports_reader::InputReportsReader, synthesizer,
usages::hid_usage_to_input3_key,
},
anyhow::{format_err, Context as _, Error},
async_trait::async_trait,
fidl::endpoints::ServerEnd,
fidl::Error as FidlError,
fidl_fuchsia_input::Key,
fidl_fuchsia_input_report::{
ConsumerControlButton, ConsumerControlInputReport, ContactInputReport, DeviceDescriptor,
FeatureReport, InputDeviceRequest, InputDeviceRequestStream, InputReport,
InputReportsReaderMarker, KeyboardInputReport, MouseInputReport, TouchInputReport,
TOUCH_MAX_CONTACTS,
},
fidl_fuchsia_ui_input::{KeyboardReport, Touch},
fuchsia_async as fasync,
futures::{future, pin_mut, StreamExt, TryFutureExt},
std::convert::TryFrom as _,
};
/// Implements the `synthesizer::InputDevice` trait, and the server side of the
/// `fuchsia.input.report.InputDevice` FIDL protocol. Used by
/// `modern_backend::InputDeviceRegistry`.
///
/// # Notes
/// * Some of the methods of `fuchsia.input.report.InputDevice` are not relevant to
/// input injection, so this implemnentation does not support them:
/// * `SendOutputReport` provides a way to change keyboard LED state.
/// If these FIDL methods are invoked, `InputDevice::flush()` will resolve to Err.
/// * This implementation does not support multiple calls to `GetInputReportsReader`,
/// since:
/// * The ideal semantics for multiple calls are not obvious, and
/// * Each `InputDevice` has a single FIDL client (an input pipeline implementation),
/// and the current input pipeline implementation is happy to use a single
/// `InputReportsReader` for the lifetime of the `InputDevice`.
pub(super) struct InputDevice {
/// FIFO queue of reports to be consumed by calls to
/// `fuchsia.input.report.InputReportsReader.ReadInputReports()`.
/// Populated by calls to `synthesizer::InputDevice` trait methods.
report_sender: futures::channel::mpsc::UnboundedSender<InputReport>,
// `Task` to keep serving the `fuchsia.input.report.InputDevice` protocol.
input_device_task: fasync::Task<Result<(), Error>>,
}
impl std::convert::From<synthesizer::MediaButton> for ConsumerControlButton {
fn from(synthesizer_button: synthesizer::MediaButton) -> Self {
match synthesizer_button {
synthesizer::MediaButton::VolumeUp => Self::VolumeUp,
synthesizer::MediaButton::VolumeDown => Self::VolumeDown,
synthesizer::MediaButton::MicMute => Self::MicMute,
synthesizer::MediaButton::FactoryReset => Self::FactoryReset,
synthesizer::MediaButton::Pause => Self::Pause,
synthesizer::MediaButton::CameraDisable => Self::CameraDisable,
}
}
}
#[async_trait(?Send)]
impl synthesizer::InputDevice for self::InputDevice {
fn media_buttons(
&mut self,
pressed_buttons: Vec<synthesizer::MediaButton>,
time: u64,
) -> Result<(), Error> {
self.report_sender
.unbounded_send(InputReport {
event_time: Some(i64::try_from(time).context("converting time to i64")?),
consumer_control: Some(ConsumerControlInputReport {
pressed_buttons: Some(pressed_buttons.into_iter().map(Into::into).collect()),
..Default::default()
}),
..Default::default()
})
.context("sending media button InputReport")
}
// TODO(https://fxbug.dev/42142533): remove dependency on HID usage codes.
fn key_press(&mut self, report: KeyboardReport, time: u64) -> Result<(), Error> {
self.key_press_internal(report, time, Self::convert_keyboard_report_to_keys)
}
fn key_press_raw(&mut self, report: KeyboardReport, time: u64) -> Result<(), Error> {
self.key_press_internal(report, time, Self::convert_keyboard_report_to_keys_no_transform)
}
// TODO(https://fxbug.dev/42142533): remove reference to HID usage codes.
fn key_press_usage(&mut self, usage: Option<u32>, time: u64) -> Result<(), Error> {
self.key_press(KeyboardReport { pressed_keys: usage.into_iter().collect() }, time)
}
fn tap(&mut self, pos: Option<(u32, u32)>, time: u64) -> Result<(), Error> {
let fingers = pos.and_then(|(x, y)| {
Some(vec![Touch { finger_id: 1, x: x as i32, y: y as i32, width: 0, height: 0 }])
});
self.multi_finger_tap(fingers, time)
}
fn multi_finger_tap(&mut self, fingers: Option<Vec<Touch>>, time: u64) -> Result<(), Error> {
let num_fingers = match &fingers {
Some(fingers_vec) => fingers_vec.len(),
None => 0,
};
if num_fingers > usize::try_from(TOUCH_MAX_CONTACTS).context("usize is at least 32 bits")? {
return Err(format_err!(
"Got {} fingers, but max is {}",
num_fingers,
TOUCH_MAX_CONTACTS
));
}
self.multi_finger_tap_internal(
TouchInputReport {
contacts: Some(fingers.map_or_else(Vec::new, |fingers_vec| {
fingers_vec
.into_iter()
.map(|finger| ContactInputReport {
contact_id: Some(finger.finger_id),
position_x: Some(i64::from(finger.x)),
position_y: Some(i64::from(finger.y)),
contact_width: Some(i64::from(finger.width)),
contact_height: Some(i64::from(finger.height)),
..Default::default()
})
.collect()
})),
pressed_buttons: Some(vec![]),
..Default::default()
},
time,
)
}
fn mouse(&mut self, report: MouseInputReport, time: u64) -> Result<(), Error> {
self.report_sender
.unbounded_send(InputReport {
event_time: Some(i64::try_from(time).context("converting time to i64")?),
mouse: Some(report),
..Default::default()
})
.context("error sending mouse InputReport")
}
async fn flush(self: Box<Self>) -> Result<(), Error> {
let Self { input_device_task, report_sender } = *self;
std::mem::drop(report_sender); // Drop `report_sender` to close channel.
input_device_task.await
}
}
impl InputDevice {
/// Creates a new `InputDevice` that will create a task to:
/// a) process requests from `request_stream`, and
/// b) respond to `GetDescriptor` calls with the descriptor generated by `descriptor_generator()`
pub(super) fn new(
request_stream: InputDeviceRequestStream,
descriptor: DeviceDescriptor,
) -> Self {
let (report_sender, report_receiver) = futures::channel::mpsc::unbounded::<InputReport>();
// Create a `Task` to keep serving the `fuchsia.input.report.InputDevice` protocol.
let input_device_task =
fasync::Task::local(Self::serve_reports(request_stream, descriptor, report_receiver));
Self { report_sender, input_device_task }
}
/// Returns a `Future` which resolves when all `InputReport`s for this device
/// have been sent to a `fuchsia.input.InputReportsReader` client, or when
/// an error occurs.
///
/// # Resolves to
/// * `Ok(())` if all reports were written successfully
/// * `Err` otherwise. For example:
/// * The `fuchsia.input.InputDevice` client sent an invalid request.
/// * A FIDL error occurred while trying to read a FIDL request.
/// * A FIDL error occurred while trying to write a FIDL response.
///
/// # Corner cases
/// Resolves to `Err` if the `fuchsia.input.InputDevice` client did not call
/// `GetInputReportsReader()`, even if no `InputReport`s were queued.
///
/// # Note
/// When the `Future` resolves, `InputReports` may still be sitting unread in the
/// channel to the `fuchsia.input.InputReportsReader` client. (The client will
/// typically be an input pipeline implementation.)
async fn serve_reports(
request_stream: InputDeviceRequestStream,
descriptor: DeviceDescriptor,
report_receiver: futures::channel::mpsc::UnboundedReceiver<InputReport>,
) -> Result<(), Error> {
// Process `fuchsia.input.report.InputDevice` requests, waiting for the `InputDevice`
// client to provide a `ServerEnd<InputReportsReader>` by calling `GetInputReportsReader()`.
let mut input_reports_reader_server_end_stream = request_stream
.filter_map(|r| future::ready(Self::handle_device_request(r, &descriptor)));
let input_reports_reader_fut = {
let reader_server_end = input_reports_reader_server_end_stream
.next()
.await
.ok_or(format_err!("stream ended without a call to GetInputReportsReader"))?
.context("handling InputDeviceRequest")?;
InputReportsReader {
request_stream: reader_server_end
.into_stream()
.context("converting ServerEnd<InputReportsReader>")?,
report_receiver,
}
.into_future()
};
pin_mut!(input_reports_reader_fut);
// Create a `Future` to keep serving the `fuchsia.input.report.InputDevice` protocol.
// This time, receiving a `ServerEnd<InputReportsReaderMarker>` will be an `Err`.
let input_device_server_fut = async {
match input_reports_reader_server_end_stream.next().await {
Some(Ok(_server_end)) => {
// There are no obvious "best" semantics for how to handle multiple
// `GetInputReportsReader` calls, and there is no current need to
// do so. Instead of taking a guess at what the client might want
// in such a case, just return `Err`.
Err(format_err!(
"InputDevice does not support multiple GetInputReportsReader calls"
))
}
Some(Err(e)) => Err(e.context("handling InputDeviceRequest")),
None => Ok(()),
}
};
pin_mut!(input_device_server_fut);
// Now, process both `fuchsia.input.report.InputDevice` requests, and
// `fuchsia.input.report.InputReportsReader` requests. And keep processing
// `InputReportsReader` requests even if the `InputDevice` connection
// is severed.
future::select(
input_device_server_fut.and_then(|_: ()| future::pending()),
input_reports_reader_fut,
)
.await
.factor_first()
.0
}
/// Converts a [KeyboardReport] into a sequence of key presses, using the supplied
/// key-to-HID usage transformation function.
fn key_press_internal(
&mut self,
report: KeyboardReport,
time: u64,
transform: fn(r: &KeyboardReport) -> Result<Vec<Key>, Error>,
) -> Result<(), Error> {
self.report_sender
.unbounded_send(InputReport {
event_time: Some(i64::try_from(time).context("converting time to i64")?),
keyboard: Some(KeyboardInputReport {
pressed_keys3: Some(transform(&report)?),
..Default::default()
}),
..Default::default()
})
.context("sending key press InputReport")
}
fn multi_finger_tap_internal(
&mut self,
touch: TouchInputReport,
time: u64,
) -> Result<(), Error> {
self.report_sender
.unbounded_send(InputReport {
event_time: Some(i64::try_from(time).context("converting time to i64")?),
touch: Some(touch),
..Default::default()
})
.context("sending touch InputReport")
}
/// Processes a single request from an `InputDeviceRequestStream`
///
/// # Returns
/// * Some(Ok(ServerEnd<InputReportsReaderMarker>)) if the request yielded an
/// `InputReportsReader`. `InputDevice` should route its `InputReports` to the yielded
/// `InputReportsReader`.
/// * Some(Err) if the request yielded an `Error`
/// * None if the request was fully processed by `handle_device_request()`
fn handle_device_request(
request: Result<InputDeviceRequest, FidlError>,
descriptor: &DeviceDescriptor,
) -> Option<Result<ServerEnd<InputReportsReaderMarker>, Error>> {
match request {
Ok(InputDeviceRequest::GetInputReportsReader { reader: reader_server_end, .. }) => {
Some(Ok(reader_server_end))
}
Ok(InputDeviceRequest::GetDescriptor { responder }) => {
match responder.send(&descriptor) {
Ok(()) => None,
Err(e) => {
Some(Err(anyhow::Error::from(e).context("sending GetDescriptor response")))
}
}
}
Ok(InputDeviceRequest::GetFeatureReport { responder }) => {
match responder.send(Ok(&FeatureReport::default())) {
Ok(()) => None,
Err(e) => Some(Err(
anyhow::Error::from(e).context("sending GetFeatureReport response")
)),
}
}
Err(e) => {
// Fail fast.
//
// Panic here, since we don't have a good way to report an error from a
// background task. InputDevice::flush() exists, but this is unlikely
// to be called in tests, and it may get called way too late, after
// an error in this background task already caused some other error.
panic!("InputDevice got an error while reading request: {:?}", &e);
}
_ => {
// See the previous branch.
panic!(
"InputDevice::handle_device_request does not support this request: {:?}",
&request
);
}
}
}
fn convert_keyboard_report_to_keys(report: &KeyboardReport) -> Result<Vec<Key>, Error> {
report
.pressed_keys
.iter()
.map(|&usage| {
hid_usage_to_input3_key(usage as u16)
.ok_or_else(|| format_err!("no Key for usage {:?}", usage))
})
.collect()
}
/// Same as convert_keyboard_report_to_keys, but no additional calls to HID usage mapping.
///
/// The keyboard report in `convert_keyboard_report_to_keys` assumes USB HID usage page 7.
/// This set of keys is narrower than what Fuchsia supports so that function needs to map
/// back into Fuchsia USB HID encoding (see [fidl_fuchsia_input::Key]).
///
/// This function, in turn, uses the full range of [fidl_fuchsia_input::Key], so does not
/// need this conversion.
fn convert_keyboard_report_to_keys_no_transform(
report: &KeyboardReport,
) -> Result<Vec<Key>, Error> {
report
.pressed_keys
.iter()
.map(|&usage| {
Key::from_primitive(usage)
.ok_or(anyhow::anyhow!("could not convert to input::Key: {}", &usage))
})
.collect()
}
}
#[cfg(test)]
mod tests {
use {
super::{synthesizer::InputDevice as _, *},
fidl::endpoints,
fidl_fuchsia_input_report::{
DeviceDescriptor, InputDeviceMarker, KeyboardDescriptor, KeyboardInputDescriptor,
},
fuchsia_async as fasync,
};
const DEFAULT_REPORT_TIMESTAMP: u64 = 0;
mod responds_to_get_feature_report_request {
use super::*;
#[fasync::run_until_stalled(test)]
async fn single_request_before_call_to_get_feature_report() -> Result<(), Error> {
let (proxy, request_stream) = endpoints::create_proxy_and_stream::<InputDeviceMarker>()
.context("creating InputDevice proxy and stream")?;
let input_device_server_fut =
Box::new(InputDevice::new(request_stream, DeviceDescriptor::default())).flush();
let get_feature_report_fut = proxy.get_feature_report();
std::mem::drop(proxy); // Drop `proxy` to terminate `request_stream`.
let (_, get_feature_report_result) =
future::join(input_device_server_fut, get_feature_report_fut).await;
assert_eq!(
get_feature_report_result.context("fidl error")?,
Ok(FeatureReport::default())
);
Ok(())
}
}
mod responds_to_get_descriptor_request {
use {
super::{
utils::{make_input_device_proxy_and_struct, make_keyboard_descriptor},
*,
},
assert_matches::assert_matches,
futures::task::Poll,
};
#[fasync::run_until_stalled(test)]
async fn single_request_before_call_to_get_input_reports_reader() -> Result<(), Error> {
let (proxy, request_stream) = endpoints::create_proxy_and_stream::<InputDeviceMarker>()
.context("creating InputDevice proxy and stream")?;
let input_device_server_fut =
Box::new(InputDevice::new(request_stream, make_keyboard_descriptor(vec![Key::A])))
.flush();
let get_descriptor_fut = proxy.get_descriptor();
std::mem::drop(proxy); // Drop `proxy` to terminate `request_stream`.
let (_, get_descriptor_result) =
future::join(input_device_server_fut, get_descriptor_fut).await;
assert_eq!(
get_descriptor_result.context("fidl error")?,
make_keyboard_descriptor(vec![Key::A])
);
Ok(())
}
#[test]
fn multiple_requests_before_call_to_get_input_reports_reader() -> Result<(), Error> {
let mut executor = fasync::TestExecutor::new();
let (proxy, request_stream) = endpoints::create_proxy_and_stream::<InputDeviceMarker>()
.context("creating InputDevice proxy and stream")?;
let mut input_device_server_fut =
Box::new(InputDevice::new(request_stream, make_keyboard_descriptor(vec![Key::A])))
.flush();
let mut get_descriptor_fut = proxy.get_descriptor();
assert_matches!(
executor.run_until_stalled(&mut input_device_server_fut),
Poll::Pending
);
std::mem::drop(executor.run_until_stalled(&mut get_descriptor_fut));
let mut get_descriptor_fut = proxy.get_descriptor();
let _ = executor.run_until_stalled(&mut input_device_server_fut);
assert_matches!(
executor.run_until_stalled(&mut get_descriptor_fut),
Poll::Ready(Ok(_))
);
Ok(())
}
#[test]
fn after_call_to_get_input_reports_reader_with_report_pending() -> Result<(), Error> {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.context("internal error queuing input event")?;
let input_device_server_fut = input_device.flush();
pin_mut!(input_device_server_fut);
let (_input_reports_reader_proxy, input_reports_reader_server_end) =
endpoints::create_proxy::<InputReportsReaderMarker>()
.context("internal error creating InputReportsReader proxy and server end")?;
input_device_proxy
.get_input_reports_reader(input_reports_reader_server_end)
.context("sending get_input_reports_reader request")?;
assert_matches!(
executor.run_until_stalled(&mut input_device_server_fut),
Poll::Pending
);
let mut get_descriptor_fut = input_device_proxy.get_descriptor();
assert_matches!(
executor.run_until_stalled(&mut input_device_server_fut),
Poll::Pending
);
assert_matches!(executor.run_until_stalled(&mut get_descriptor_fut), Poll::Ready(_));
Ok(())
}
}
mod report_contents {
use {
super::{
utils::{get_input_reports, make_input_device_proxy_and_struct},
*,
},
crate::usages::Usages,
assert_matches::assert_matches,
std::convert::TryInto as _,
};
#[fasync::run_until_stalled(test)]
async fn media_buttons_generates_empty_consumer_controls_input_report() -> Result<(), Error>
{
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.media_buttons(vec![], DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
consumer_control: Some(ConsumerControlInputReport {
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn media_buttons_generates_full_consumer_controls_input_report() -> Result<(), Error>
{
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.media_buttons(
vec![
synthesizer::MediaButton::VolumeUp,
synthesizer::MediaButton::VolumeDown,
synthesizer::MediaButton::MicMute,
synthesizer::MediaButton::FactoryReset,
synthesizer::MediaButton::Pause,
synthesizer::MediaButton::CameraDisable,
],
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
consumer_control: Some(ConsumerControlInputReport {
pressed_buttons: Some(vec![
ConsumerControlButton::VolumeUp,
ConsumerControlButton::VolumeDown,
ConsumerControlButton::MicMute,
ConsumerControlButton::FactoryReset,
ConsumerControlButton::Pause,
ConsumerControlButton::CameraDisable,
]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn media_buttons_generates_partial_consumer_controls_input_report(
) -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.media_buttons(
vec![
synthesizer::MediaButton::VolumeUp,
synthesizer::MediaButton::MicMute,
synthesizer::MediaButton::Pause,
],
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
consumer_control: Some(ConsumerControlInputReport {
pressed_buttons: Some(vec![
ConsumerControlButton::VolumeUp,
ConsumerControlButton::MicMute,
ConsumerControlButton::Pause,
]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_generates_expected_keyboard_input_report() -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.key_press(
KeyboardReport {
pressed_keys: vec![Usages::HidUsageKeyA as u32, Usages::HidUsageKeyB as u32],
},
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
keyboard: Some(KeyboardInputReport {
pressed_keys3: Some(vec![Key::A, Key::B]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_usage_generates_expected_keyboard_input_report_for_some(
) -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device
.key_press_usage(Some(Usages::HidUsageKeyA as u32), DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
keyboard: Some(KeyboardInputReport {
pressed_keys3: Some(vec![Key::A]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_usage_generates_expected_keyboard_input_report_for_none(
) -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.key_press_usage(None, DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
keyboard: Some(KeyboardInputReport {
pressed_keys3: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_returns_error_if_usage_cannot_be_mapped_to_key() {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
assert_matches!(
input_device.key_press(
KeyboardReport { pressed_keys: vec![0xffff_ffff] },
DEFAULT_REPORT_TIMESTAMP
),
Err(_)
);
}
#[fasync::run_until_stalled(test)]
async fn key_press_usage_returns_error_if_usage_cannot_be_mapped_to_key() {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
assert_matches!(
input_device.key_press_usage(Some(0xffff_ffff), DEFAULT_REPORT_TIMESTAMP),
Err(_)
);
}
#[fasync::run_until_stalled(test)]
async fn key_events_generates_expected_keyboard_response() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.key_press_raw(
KeyboardReport {
pressed_keys: vec![Key::A.into_primitive(), Key::B.into_primitive()],
},
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
keyboard: Some(KeyboardInputReport {
pressed_keys3: Some(vec![Key::A, Key::B]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn tap_generates_expected_report_for_some() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.tap(Some((10, 20)), DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![ContactInputReport {
contact_id: Some(1),
position_x: Some(10),
position_y: Some(20),
pressure: None,
contact_width: Some(0),
contact_height: Some(0),
..Default::default()
}]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn tap_generates_expected_report_for_none() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.tap(None, DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_generates_report_for_single_finger() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.multi_finger_tap(
Some(vec![Touch { finger_id: 5, x: 10, y: 20, width: 100, height: 200 }]),
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![ContactInputReport {
contact_id: Some(5),
position_x: Some(10),
position_y: Some(20),
pressure: None,
contact_width: Some(100),
contact_height: Some(200),
..Default::default()
}]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_generates_expected_report_for_two_fingers() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.multi_finger_tap(
Some(vec![
Touch { finger_id: 5, x: 10, y: 20, width: 100, height: 200 },
Touch { finger_id: 0, x: 30, y: 40, width: 300, height: 400 },
]),
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![
ContactInputReport {
contact_id: Some(5),
position_x: Some(10),
position_y: Some(20),
pressure: None,
contact_width: Some(100),
contact_height: Some(200),
..Default::default()
},
ContactInputReport {
contact_id: Some(0),
position_x: Some(30),
position_y: Some(40),
pressure: None,
contact_width: Some(300),
contact_height: Some(400),
..Default::default()
}
]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_generates_expected_report_for_zero_fingers() -> Result<(), Error>
{
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.multi_finger_tap(Some(vec![]), DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_generates_expected_report_for_none() -> Result<(), Error> {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.multi_finger_tap(None, DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, _input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
touch: Some(TouchInputReport {
contacts: Some(vec![]),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_returns_error_when_num_fingers_is_to_large() {
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
assert_matches!(
input_device.multi_finger_tap(
Some(
(0..=TOUCH_MAX_CONTACTS)
.map(|i| Touch {
finger_id: i,
x: i as i32,
y: i as i32,
width: i,
height: i
})
.collect(),
),
DEFAULT_REPORT_TIMESTAMP,
),
Err(_)
);
}
#[fasync::run_until_stalled(test)]
async fn mouse_generates_empty_mouse_input_report() -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.mouse(MouseInputReport::default(), DEFAULT_REPORT_TIMESTAMP)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
mouse: Some(MouseInputReport::default()),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn mouse_generates_full_mouse_input_report() -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.mouse(
MouseInputReport {
movement_x: Some(10),
movement_y: Some(15),
pressed_buttons: Some(vec![1, 2, 3]),
scroll_v: Some(1),
scroll_h: Some(-1),
..Default::default()
},
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
mouse: Some(MouseInputReport {
movement_x: Some(10),
movement_y: Some(15),
pressed_buttons: Some(vec![1, 2, 3]),
scroll_v: Some(1),
scroll_h: Some(-1),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn mouse_generates_partial_mouse_input_report() -> Result<(), Error> {
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device.mouse(
MouseInputReport {
movement_x: Some(10),
movement_y: Some(15),
pressed_buttons: Some(vec![]),
..Default::default()
},
DEFAULT_REPORT_TIMESTAMP,
)?;
let input_reports = get_input_reports(input_device, input_device_proxy).await;
assert_eq!(
input_reports.as_slice(),
[InputReport {
event_time: Some(
DEFAULT_REPORT_TIMESTAMP.try_into().expect("converting to i64")
),
mouse: Some(MouseInputReport {
movement_x: Some(10),
movement_y: Some(15),
pressed_buttons: Some(vec![]),
..Default::default()
}),
..Default::default()
}]
);
Ok(())
}
}
mod future_resolution {
use {
super::{
utils::{make_input_device_proxy_and_struct, make_input_reports_reader_proxy},
*,
},
futures::task::Poll,
};
mod yields_ok_after_all_reports_are_sent_to_input_reports_reader {
use {super::*, assert_matches::assert_matches};
#[test]
fn if_device_request_channel_was_closed() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let _input_reports_fut = input_reports_reader_proxy.read_input_reports();
let mut input_device_fut = input_device.flush();
std::mem::drop(input_device_proxy); // Close device request channel.
assert_matches!(
executor.run_until_stalled(&mut input_device_fut),
Poll::Ready(Ok(()))
);
}
#[test]
fn even_if_device_request_channel_is_open() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let _input_reports_fut = input_reports_reader_proxy.read_input_reports();
let mut input_device_fut = input_device.flush();
assert_matches!(
executor.run_until_stalled(&mut input_device_fut),
Poll::Ready(Ok(()))
);
}
#[test]
fn even_if_reports_was_empty_and_device_request_channel_is_open() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, input_device) = make_input_device_proxy_and_struct();
let input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
let _input_reports_fut = input_reports_reader_proxy.read_input_reports();
let mut input_device_fut = input_device.flush();
assert_matches!(
executor.run_until_stalled(&mut input_device_fut),
Poll::Ready(Ok(()))
);
}
}
mod yields_err_if_peer_closed_device_channel_without_calling_get_input_reports_reader {
use super::*;
use assert_matches::assert_matches;
#[test]
fn if_reports_were_available() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let mut input_device_fut = input_device.flush();
std::mem::drop(input_device_proxy);
assert_matches!(
executor.run_until_stalled(&mut input_device_fut),
Poll::Ready(Err(_))
)
}
#[test]
fn even_if_no_reports_were_available() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, input_device) = make_input_device_proxy_and_struct();
let mut input_device_fut = input_device.flush();
std::mem::drop(input_device_proxy);
assert_matches!(
executor.run_until_stalled(&mut input_device_fut),
Poll::Ready(Err(_))
)
}
}
mod is_pending_if_peer_has_device_channel_open_and_has_not_called_get_input_reports_reader {
use super::*;
use assert_matches::assert_matches;
#[test]
fn if_reports_were_available() {
let mut executor = fasync::TestExecutor::new();
let (_input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let mut input_device_fut = input_device.flush();
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
#[test]
fn even_if_no_reports_were_available() {
let mut executor = fasync::TestExecutor::new();
let (_input_device_proxy, input_device) = make_input_device_proxy_and_struct();
let mut input_device_fut = input_device.flush();
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
#[test]
fn even_if_get_device_descriptor_has_been_called() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, input_device) = make_input_device_proxy_and_struct();
let mut input_device_fut = input_device.flush();
let _get_descriptor_fut = input_device_proxy.get_descriptor();
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
}
mod is_pending_if_peer_has_not_read_any_reports_when_a_report_is_available {
use super::*;
use assert_matches::assert_matches;
#[test]
fn if_device_request_channel_is_open() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let _input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let mut input_device_fut = input_device.flush();
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
#[test]
fn even_if_device_channel_is_closed() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let _input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
input_device
.key_press(KeyboardReport { pressed_keys: vec![] }, DEFAULT_REPORT_TIMESTAMP)
.expect("queuing input report");
let mut input_device_fut = input_device.flush();
std::mem::drop(input_device_proxy); // Terminate `InputDeviceRequestStream`.
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
}
mod is_pending_if_peer_did_not_read_all_reports {
use {
super::*, assert_matches::assert_matches,
fidl_fuchsia_input_report::MAX_DEVICE_REPORT_COUNT,
};
#[test]
fn if_device_request_channel_is_open() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
(0..=MAX_DEVICE_REPORT_COUNT).for_each(|_| {
input_device
.key_press(
KeyboardReport { pressed_keys: vec![] },
DEFAULT_REPORT_TIMESTAMP,
)
.expect("queuing input report");
});
// One query isn't enough to consume all of the reports queued above.
let _input_reports_fut = input_reports_reader_proxy.read_input_reports();
let mut input_device_fut = input_device.flush();
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
#[test]
fn even_if_device_request_channel_is_closed() {
let mut executor = fasync::TestExecutor::new();
let (input_device_proxy, mut input_device) = make_input_device_proxy_and_struct();
let input_reports_reader_proxy =
make_input_reports_reader_proxy(&input_device_proxy);
(0..=MAX_DEVICE_REPORT_COUNT).for_each(|_| {
input_device
.key_press(
KeyboardReport { pressed_keys: vec![] },
DEFAULT_REPORT_TIMESTAMP,
)
.expect("queuing input report");
});
// One query isn't enough to consume all of the reports queued above.
let _input_reports_fut = input_reports_reader_proxy.read_input_reports();
let mut input_device_fut = input_device.flush();
std::mem::drop(input_device_proxy); // Terminate `InputDeviceRequestStream`.
assert_matches!(executor.run_until_stalled(&mut input_device_fut), Poll::Pending)
}
}
}
// Because `input_synthesis` is a library, unsupported use cases should yield `Error`s,
// rather than panic!()-ing.
mod unsupported_use_cases {
use {
super::{utils::make_input_device_proxy_and_struct, *},
assert_matches::assert_matches,
};
#[fasync::run_until_stalled(test)]
async fn multiple_get_input_reports_reader_requests_yield_error() -> Result<(), Error> {
let (input_device_proxy, input_device) = make_input_device_proxy_and_struct();
let (_input_reports_reader_proxy, input_reports_reader_server_end) =
endpoints::create_proxy::<InputReportsReaderMarker>()
.context("creating InputReportsReader proxy and server end")?;
input_device_proxy
.get_input_reports_reader(input_reports_reader_server_end)
.expect("sending first get_input_reports_reader request");
let (_input_reports_reader_proxy, input_reports_reader_server_end) =
endpoints::create_proxy::<InputReportsReaderMarker>()
.context("internal error creating InputReportsReader proxy and server end")?;
input_device_proxy
.get_input_reports_reader(input_reports_reader_server_end)
.expect("sending second get_input_reports_reader request");
let input_device_fut = input_device.flush();
assert_matches!(input_device_fut.await, Err(_));
Ok(())
}
}
mod utils {
use {
super::*,
fidl_fuchsia_input_report::{InputDeviceProxy, InputReportsReaderProxy},
fuchsia_zircon as zx,
};
/// Creates a `DeviceDescriptor` for a keyboard which has the keys enumerated
/// in `keys`.
pub(super) fn make_keyboard_descriptor(keys: Vec<Key>) -> DeviceDescriptor {
DeviceDescriptor {
keyboard: Some(KeyboardDescriptor {
input: Some(KeyboardInputDescriptor {
keys3: Some(keys),
..Default::default()
}),
..Default::default()
}),
..Default::default()
}
}
/// Creates an `InputDeviceProxy`, for sending `fuchsia.input.report.InputDevice`
/// requests, and an `InputDevice` struct that will receive the FIDL requests
/// from the `InputDeviceProxy`.
///
/// # Returns
/// A tuple of the proxy and struct. The struct is `Box`-ed so that the caller
/// can easily invoke `flush()`.
pub(super) fn make_input_device_proxy_and_struct() -> (InputDeviceProxy, Box<InputDevice>) {
let (input_device_proxy, input_device_request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()
.expect("creating InputDevice proxy and stream");
let input_device = Box::new(InputDevice::new(
input_device_request_stream,
DeviceDescriptor::default(),
));
(input_device_proxy, input_device)
}
/// Creates an `InputReportsReaderProxy`, for sending
/// `fuchsia.input.report.InputReportsReader` reqests, and registers that
/// `InputReportsReader` with the `InputDevice` bound to `InputDeviceProxy`.
///
/// # Returns
/// The newly created `InputReportsReaderProxy`.
pub(super) fn make_input_reports_reader_proxy(
input_device_proxy: &InputDeviceProxy,
) -> InputReportsReaderProxy {
let (input_reports_reader_proxy, input_reports_reader_server_end) =
endpoints::create_proxy::<InputReportsReaderMarker>()
.expect("internal error creating InputReportsReader proxy and server end");
input_device_proxy
.get_input_reports_reader(input_reports_reader_server_end)
.expect("sending get_input_reports_reader request");
input_reports_reader_proxy
}
/// Serves `fuchsia.input.report.InputDevice` and `fuchsia.input.report.InputReportsReader`
/// protocols using `input_device`, and reads `InputReport`s with one call to
/// `input_device_proxy.read_input_reports()`. Then drops the connections to
/// `fuchsia.input.report.InputDevice` and `fuchsia.input.report.InputReportsReader`.
///
/// # Returns
/// The reports provided by the `InputDevice`.
pub(super) async fn get_input_reports(
input_device: Box<InputDevice>,
input_device_proxy: InputDeviceProxy,
) -> Vec<InputReport> {
let input_reports_reader_proxy = make_input_reports_reader_proxy(&input_device_proxy);
let input_device_server_fut = input_device.flush();
let input_reports_fut = input_reports_reader_proxy.read_input_reports();
std::mem::drop(input_reports_reader_proxy); // Close channel to `input_reports_reader_server_end`
std::mem::drop(input_device_proxy); // Terminate `input_device_request_stream`.
future::join(input_device_server_fut, input_reports_fut)
.await
.1
.expect("fidl error")
.map_err(zx::Status::from_raw)
.expect("service error")
}
}
}