blob: c66d5563cfaca634e4731662287d6b855cf69548 [file]
// 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::{synthesizer, usages::Usages},
anyhow::Error,
async_trait::async_trait,
fidl::endpoints,
fidl_fuchsia_ui_input::{
self, Axis, AxisScale, DeviceDescriptor, InputDeviceMarker, InputDeviceProxy,
InputDeviceRegistryMarker, InputReport, KeyboardDescriptor, KeyboardReport,
MediaButtonsDescriptor, MediaButtonsReport, Range, Touch, TouchscreenDescriptor,
TouchscreenReport,
},
fuchsia_component as app,
};
// Provides a handle to an `impl synthesizer::InputDeviceRegistry`, which works with input
// pipelines that support the (legacy) `fuchsia.ui.input.InputDeviceRegistry` protocol.
pub struct InputDeviceRegistry {
svc_dir_path: Option<String>,
}
// Wraps `DeviceDescriptor` FIDL table fields for descriptors into a single Rust type,
// allowing us to pass any of them to `self.register_device()`.
#[derive(Debug)]
enum UniformDeviceDescriptor {
Keyboard(KeyboardDescriptor),
MediaButtons(MediaButtonsDescriptor),
Touchscreen(TouchscreenDescriptor),
}
impl synthesizer::InputDeviceRegistry for self::InputDeviceRegistry {
fn add_touchscreen_device(
&mut self,
width: u32,
height: u32,
) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
self.add_device(UniformDeviceDescriptor::Touchscreen(TouchscreenDescriptor {
x: Axis {
range: Range { min: 0, max: width as i32 },
resolution: 1,
scale: AxisScale::Linear,
},
y: Axis {
range: Range { min: 0, max: height as i32 },
resolution: 1,
scale: AxisScale::Linear,
},
max_finger_id: 255,
}))
}
fn add_keyboard_device(&mut self) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
self.add_device(UniformDeviceDescriptor::Keyboard(KeyboardDescriptor {
keys: (Usages::HidUsageKeyA as u32..Usages::HidUsageKeyRightGui as u32).collect(),
}))
}
fn add_media_buttons_device(&mut self) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
self.add_device(UniformDeviceDescriptor::MediaButtons(MediaButtonsDescriptor {
buttons: fidl_fuchsia_ui_input::MIC_MUTE
| fidl_fuchsia_ui_input::VOLUME_DOWN
| fidl_fuchsia_ui_input::VOLUME_UP,
}))
}
}
impl InputDeviceRegistry {
// Create a new injector. Use default path to the service directory
// containing the InputDeviceRegistry protocol.
pub fn new() -> Self {
Self { svc_dir_path: None }
}
// Create a new injector. Use |svc_dir_path| as the custom path to
// the service directory containing the InputDeviceRegistry protocol.
pub fn new_with_path(svc_dir_path: String) -> Self {
Self { svc_dir_path: Some(svc_dir_path) }
}
fn add_device(
&self,
descriptor: UniformDeviceDescriptor,
) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
let registry = if let Some(path) = &self.svc_dir_path {
app::client::connect_to_service_at::<InputDeviceRegistryMarker>(path.as_str())?
} else {
app::client::connect_to_service::<InputDeviceRegistryMarker>()?
};
let mut device = DeviceDescriptor {
device_info: None,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
};
match descriptor {
UniformDeviceDescriptor::Keyboard(descriptor) => {
device.keyboard = Some(Box::new(descriptor))
}
UniformDeviceDescriptor::Touchscreen(descriptor) => {
device.touchscreen = Some(Box::new(descriptor))
}
UniformDeviceDescriptor::MediaButtons(descriptor) => {
device.media_buttons = Some(Box::new(descriptor))
}
};
let (input_device_client, input_device_server) =
endpoints::create_endpoints::<InputDeviceMarker>()?;
registry.register_device(&mut device, input_device_server)?;
Ok(Box::new(InputDevice::new(input_device_client.into_proxy()?)))
}
}
// Provides a handle to an `impl synthesizer::InputDevice`, which works with input
// pipelines that support the (legacy) `fuchsia.ui.input.InputDeviceRegistry` protocol.
struct InputDevice {
fidl_proxy: InputDeviceProxy,
}
#[async_trait(?Send)]
impl synthesizer::InputDevice for self::InputDevice {
fn media_buttons(
&mut self,
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
camera_disable: bool,
time: u64,
) -> Result<(), Error> {
self.fidl_proxy
.dispatch_report(&mut self::media_buttons(
volume_up,
volume_down,
mic_mute,
reset,
pause,
camera_disable,
time,
))
.map_err(Into::into)
}
fn key_press(&mut self, keyboard: KeyboardReport, time: u64) -> Result<(), Error> {
self.fidl_proxy.dispatch_report(&mut self::key_press(keyboard, time)).map_err(Into::into)
}
fn key_press_usage(&mut self, usage: Option<u32>, time: u64) -> Result<(), Error> {
self.fidl_proxy.dispatch_report(&mut self::key_press_usage(usage, time)).map_err(Into::into)
}
fn tap(&mut self, pos: Option<(u32, u32)>, time: u64) -> Result<(), Error> {
self.fidl_proxy.dispatch_report(&mut self::tap(pos, time)).map_err(Into::into)
}
fn multi_finger_tap(&mut self, fingers: Option<Vec<Touch>>, time: u64) -> Result<(), Error> {
self.fidl_proxy
.dispatch_report(&mut self::multi_finger_tap(fingers, time))
.map_err(Into::into)
}
async fn serve_reports(self: Box<Self>) -> Result<(), Error> {
Ok(())
}
}
impl InputDevice {
fn new(fidl_proxy: InputDeviceProxy) -> Self {
Self { fidl_proxy }
}
}
fn media_buttons(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
camera_disable: bool,
time: u64,
) -> InputReport {
InputReport {
event_time: time,
keyboard: None,
media_buttons: Some(Box::new(MediaButtonsReport {
volume_up,
volume_down,
mic_mute,
reset,
pause,
camera_disable,
})),
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0,
}
}
fn key_press(keyboard: KeyboardReport, time: u64) -> InputReport {
InputReport {
event_time: time,
keyboard: Some(Box::new(keyboard)),
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0,
}
}
fn key_press_usage(usage: Option<u32>, time: u64) -> InputReport {
key_press(
KeyboardReport {
pressed_keys: match usage {
Some(usage) => vec![usage],
None => vec![],
},
},
time,
)
}
fn tap(pos: Option<(u32, u32)>, time: u64) -> InputReport {
match pos {
Some((x, y)) => multi_finger_tap(
Some(vec![Touch { finger_id: 1, x: x as i32, y: y as i32, width: 0, height: 0 }]),
time,
),
None => multi_finger_tap(None, time),
}
}
fn multi_finger_tap(fingers: Option<Vec<Touch>>, time: u64) -> InputReport {
InputReport {
event_time: time,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(Box::new(TouchscreenReport {
touches: match fingers {
Some(fingers) => fingers,
None => vec![],
},
})),
sensor: None,
trace_id: 0,
}
}
#[cfg(test)]
mod tests {
use {
super::*,
fidl::endpoints,
fidl_fuchsia_ui_input::InputDeviceRequest,
fuchsia_async as fasync,
futures::{pin_mut, StreamExt},
matches::{assert_matches, matches},
proptest::prelude::*,
std::task::Poll,
synthesizer::InputDevice as _,
};
proptest! {
#[test]
fn media_buttons_populates_report_correctly(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
camera_disable: bool,
event_time: u64
) {
let test_fut = async {
let (fidl_proxy, request_stream) =
match endpoints::create_proxy_and_stream::<InputDeviceMarker>() {
Ok(r) => r,
Err(e) => return Err(anyhow::Error::from(e)) as Result<(), Error>,
};
let mut input_device = InputDevice { fidl_proxy };
input_device
.media_buttons(volume_up, volume_down, mic_mute, reset, pause, camera_disable, event_time)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
let expected_report = InputReport {
event_time,
keyboard: None,
media_buttons: Some(Box::new(MediaButtonsReport {
volume_up,
volume_down,
mic_mute,
reset,
pause,
camera_disable,
})),
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0
};
assert!(
matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport { report, .. })]
if *report == expected_report
), "got {:#?} but expected [Ok(DispatchReport {{\nreport: {:#?},\n ..}})]",
reports,
expected_report
);
Ok(())
};
pin_mut!(test_fut);
assert_matches!(
fasync::Executor::new()
.expect("internal error: failed to create executor")
.run_until_stalled(&mut test_fut),
Poll::Ready(_)
);
}
}
#[fasync::run_until_stalled(test)]
async fn key_press_populates_report_correctly() -> Result<(), Error> {
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.key_press(KeyboardReport { pressed_keys: vec![1, 2, 3] }, 200)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 200,
keyboard: Some(report),
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0
},
..
})] if **report == KeyboardReport { pressed_keys: vec![1, 2, 3] }
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_usage_populates_report_correctly_when_a_key_is_pressed() -> Result<(), Error>
{
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.key_press_usage(Some(1), 300)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 300,
keyboard: Some(report),
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0
},
..
})] if **report == KeyboardReport { pressed_keys: vec![1] }
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn key_press_usage_populates_report_correctly_when_no_key_is_pressed() -> Result<(), Error>
{
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.key_press_usage(None, 400)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 400,
keyboard: Some(report),
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0
},
..
})] if ** report == KeyboardReport { pressed_keys: vec![] }
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn tap_populates_report_correctly_when_finger_is_present() -> Result<(), Error> {
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.tap(Some((10, 20)), 500)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 500,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(report),
sensor: None,
trace_id: 0
},
..
})] if **report == TouchscreenReport {
touches: vec![Touch { finger_id: 1, x: 10, y: 20, width: 0, height: 0 },]
}
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn tap_populates_report_correctly_when_finger_is_absent() -> Result<(), Error> {
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.tap(None, 600)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 600,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(report),
sensor: None,
trace_id: 0
},
..
})] if **report == TouchscreenReport { touches: vec![] }
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_populates_report_correctly_when_fingers_are_present(
) -> Result<(), Error> {
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.multi_finger_tap(
Some(vec![
Touch { finger_id: 1, x: 99, y: 100, width: 10, height: 20 },
Touch { finger_id: 2, x: 199, y: 201, width: 30, height: 40 }
]),
700,
)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 700,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(report),
sensor: None,
trace_id: 0
},
..
})] if **report == TouchscreenReport {
touches: vec![
Touch { finger_id: 1, x: 99, y: 100, width: 10, height: 20 },
Touch { finger_id: 2, x: 199, y: 201, width: 30, height: 40 }
]
}
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn multi_finger_tap_populates_report_correctly_when_no_fingers_are_present(
) -> Result<(), Error> {
let (fidl_proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = InputDevice { fidl_proxy };
input_device.multi_finger_tap(None, 800)?;
std::mem::drop(input_device); // Close channel to terminate stream.
let reports = request_stream.collect::<Vec<_>>().await;
assert_matches!(
reports.as_slice(),
[Ok(InputDeviceRequest::DispatchReport {
report:
InputReport {
event_time: 800,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(report),
sensor: None,
trace_id: 0
},
..
})] if **report == TouchscreenReport { touches: vec![] }
);
Ok(())
}
#[test]
fn serve_reports_resolves_immediately() -> Result<(), Error> {
let mut executor =
fasync::Executor::new().expect("internal error: failed to create executor");
let (fidl_proxy, _request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceMarker>()?;
let mut input_device = Box::new(InputDevice { fidl_proxy });
input_device.multi_finger_tap(None, 900)?; // Sends `InputReport`.
assert_matches!(
executor.run_until_stalled(&mut input_device.serve_reports()),
Poll::Ready(Ok(()))
);
Ok(())
}
}