blob: b710c2d3ca35eb8eb57892d5ca80610b48ebf9a6 [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 std::{
thread,
time::{Duration, SystemTime},
};
use anyhow::Error;
use fidl::endpoints::{self, ServerEnd};
use fidl_fuchsia_ui_input::{
self, Axis, AxisScale, DeviceDescriptor, InputDeviceMarker, InputDeviceProxy,
InputDeviceRegistryMarker, InputReport, KeyboardDescriptor, KeyboardReport,
MediaButtonsDescriptor, MediaButtonsReport, Range, Touch, TouchscreenDescriptor,
TouchscreenReport,
};
use fuchsia_component as app;
pub mod inverse_keymap;
pub mod keymaps;
pub mod usages;
use crate::{inverse_keymap::InverseKeymap, usages::Usages};
trait ServerConsumer {
fn consume(
&self,
device: &mut DeviceDescriptor,
server: ServerEnd<InputDeviceMarker>,
) -> Result<(), Error>;
}
struct RegistryServerConsumer;
impl ServerConsumer for RegistryServerConsumer {
fn consume(
&self,
device: &mut DeviceDescriptor,
server: ServerEnd<InputDeviceMarker>,
) -> Result<(), Error> {
let registry = app::client::connect_to_service::<InputDeviceRegistryMarker>()?;
registry.register_device(device, server)?;
Ok(())
}
}
macro_rules! register_device {
( $consumer:expr , $field:ident : $value:expr ) => {{
let mut device = DeviceDescriptor {
device_info: None,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
};
device.$field = Some(Box::new($value));
let (input_device_client, input_device_server) =
endpoints::create_endpoints::<InputDeviceMarker>()?;
$consumer.consume(&mut device, input_device_server)?;
Ok(input_device_client.into_proxy()?)
}};
}
fn register_touchsreen(
consumer: impl ServerConsumer,
width: u32,
height: u32,
) -> Result<InputDeviceProxy, Error> {
register_device! {
consumer,
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 register_keyboard(consumer: impl ServerConsumer) -> Result<InputDeviceProxy, Error> {
register_device! {
consumer,
keyboard: KeyboardDescriptor {
keys: (Usages::HidUsageKeyA as u32..Usages::HidUsageKeyRightGui as u32).collect(),
}
}
}
fn register_media_buttons(consumer: impl ServerConsumer) -> Result<InputDeviceProxy, Error> {
register_device! {
consumer,
media_buttons: MediaButtonsDescriptor {
buttons: fidl_fuchsia_ui_input::MIC_MUTE
| fidl_fuchsia_ui_input::VOLUME_DOWN
| fidl_fuchsia_ui_input::VOLUME_UP,
}
}
}
fn nanos_from_epoch() -> Result<u64, Error> {
SystemTime::now()
.duration_since(SystemTime::UNIX_EPOCH)
.map(|duration| duration.as_nanos() as u64)
.map_err(Into::into)
}
fn repeat_with_delay(
times: usize,
delay: Duration,
f1: impl Fn(usize) -> Result<(), Error>,
f2: impl Fn(usize) -> Result<(), Error>,
) -> Result<(), Error> {
for i in 0..times {
f1(i)?;
thread::sleep(delay);
f2(i)?;
}
Ok(())
}
fn media_buttons(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
time: u64,
) -> InputReport {
InputReport {
event_time: time,
keyboard: None,
media_buttons: Some(Box::new(MediaButtonsReport {
volume_up,
volume_down,
mic_mute,
reset,
pause,
})),
mouse: None,
stylus: None,
touchscreen: None,
sensor: None,
trace_id: 0,
}
}
fn media_button_event(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
consumer: impl ServerConsumer,
) -> Result<(), Error> {
let input_device = register_media_buttons(consumer)?;
input_device
.dispatch_report(&mut media_buttons(
volume_up,
volume_down,
mic_mute,
reset,
pause,
nanos_from_epoch()?,
))
.map_err(Into::into)
}
/// Simulates a media button event.
pub async fn media_button_event_command(
volume_up: bool,
volume_down: bool,
mic_mute: bool,
reset: bool,
pause: bool,
) -> Result<(), Error> {
media_button_event(volume_up, volume_down, mic_mute, reset, pause, RegistryServerConsumer)
}
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 keyboard_event(
usage: u32,
duration: Duration,
consumer: impl ServerConsumer,
) -> Result<(), Error> {
let input_device = register_keyboard(consumer)?;
repeat_with_delay(
1,
duration,
|_| {
// Key pressed.
input_device
.dispatch_report(&mut key_press_usage(Some(usage), nanos_from_epoch()?))
.map_err(Into::into)
},
|_| {
// Key released.
input_device
.dispatch_report(&mut key_press_usage(None, nanos_from_epoch()?))
.map_err(Into::into)
},
)
}
/// Simulates a key press of specified `usage`.
///
/// `duration` is the time spent between key-press and key-release events.
pub async fn keyboard_event_command(usage: u32, duration: Duration) -> Result<(), Error> {
keyboard_event(usage, duration, RegistryServerConsumer)
}
fn text(input: String, duration: Duration, consumer: impl ServerConsumer) -> Result<(), Error> {
let input_device = register_keyboard(consumer)?;
let key_sequence = InverseKeymap::new(keymaps::QWERTY_MAP)
.derive_key_sequence(&input)
.ok_or_else(|| anyhow::format_err!("Cannot translate text to key sequence"))?;
let stroke_duration = duration / (key_sequence.len() - 1) as u32;
let mut key_iter = key_sequence.into_iter().peekable();
while let Some(keyboard) = key_iter.next() {
let result: Result<(), Error> = input_device
.dispatch_report(&mut key_press(keyboard, nanos_from_epoch()?))
.map_err(Into::into);
result?;
if key_iter.peek().is_some() {
thread::sleep(stroke_duration);
}
}
Ok(())
}
/// Simulates `input` being typed on a [qwerty] keyboard by making use of [`InverseKeymap`].
///
/// `duration` is divided equally between all keyboard events.
///
/// [qwerty]: keymaps/constant.QWERTY_MAP.html
pub async fn text_command(input: String, duration: Duration) -> Result<(), Error> {
text(input, duration, RegistryServerConsumer)
}
fn tap(pos: Option<(u32, u32)>, time: u64) -> InputReport {
InputReport {
event_time: time,
keyboard: None,
media_buttons: None,
mouse: None,
stylus: None,
touchscreen: Some(Box::new(TouchscreenReport {
touches: match pos {
Some((x, y)) => {
vec![Touch { finger_id: 1, x: x as i32, y: y as i32, width: 0, height: 0 }]
}
None => vec![],
},
})),
sensor: None,
trace_id: 0,
}
}
fn tap_event(
x: u32,
y: u32,
width: u32,
height: u32,
tap_event_count: usize,
duration: Duration,
consumer: impl ServerConsumer,
) -> Result<(), Error> {
let input_device = register_touchsreen(consumer, width, height)?;
let tap_duration = duration / tap_event_count as u32;
repeat_with_delay(
tap_event_count,
tap_duration,
|_| {
// Touch down.
input_device
.dispatch_report(&mut tap(Some((x, y)), nanos_from_epoch()?))
.map_err(Into::into)
},
|_| {
// Touch up.
input_device.dispatch_report(&mut tap(None, nanos_from_epoch()?)).map_err(Into::into)
},
)
}
/// Simulates `tap_event_count` taps at coordinates `(x, y)` for a touchscreen of explicit
/// `width` and `height`.
///
/// `duration` is divided equally between touch-down and touch-up event pairs, while the
/// transition between these pairs is immediate.
pub async fn tap_event_command(
x: u32,
y: u32,
width: u32,
height: u32,
tap_event_count: usize,
duration: Duration,
) -> Result<(), Error> {
tap_event(x, y, width, height, tap_event_count, duration, RegistryServerConsumer)
}
fn swipe(
x0: u32,
y0: u32,
x1: u32,
y1: u32,
width: u32,
height: u32,
move_event_count: usize,
duration: Duration,
consumer: impl ServerConsumer,
) -> Result<(), Error> {
let input_device = register_touchsreen(consumer, width, height)?;
let mut delta_x = x1 as f64 - x0 as f64;
let mut delta_y = y1 as f64 - y0 as f64;
let swipe_event_delay = if move_event_count > 1 {
// We have move_event_count + 2 events:
// DOWN
// MOVE x move_event_count
// UP
// so we need (move_event_count + 1) delays.
delta_x /= move_event_count as f64;
delta_y /= move_event_count as f64;
duration / (move_event_count + 1) as u32
} else {
duration
};
repeat_with_delay(
move_event_count + 2,
swipe_event_delay,
|i| {
let time = nanos_from_epoch()?;
let mut report = match i {
// DOWN
0 => tap(Some((x0, y0)), time),
// MOVE
i if i <= move_event_count => tap(
Some((
(x0 as f64 + (i as f64 * delta_x).round()) as u32,
(y0 as f64 + (i as f64 * delta_y).round()) as u32,
)),
time,
),
// UP
_ => tap(None, time),
};
input_device.dispatch_report(&mut report).map_err(Into::into)
},
|_| Ok(()),
)
}
/// Simulates swipe from coordinates `(x0, y0)` to `(x1, y1)` for a touchscreen of explicit
/// `width` and `height`, with `move_event_count` touch-move events in between.
///
/// `duration` is the time spent between the touch-down and first touch-move events when
/// `move_event_count > 0` or between the touch-down the touch-up events otherwise.
pub async fn swipe_command(
x0: u32,
y0: u32,
x1: u32,
y1: u32,
width: u32,
height: u32,
move_event_count: usize,
duration: Duration,
) -> Result<(), Error> {
swipe(x0, y0, x1, y1, width, height, move_event_count, duration, RegistryServerConsumer)
}
#[cfg(test)]
mod tests {
use super::*;
use fidl_fuchsia_ui_input::InputDeviceRequest;
use fuchsia_async as fasync;
use futures::TryStreamExt;
macro_rules! assert_reports_eq {
(
$report:ident ,
$event:expr ,
[ $( { $name:ident : $value:expr } ),* $( , )? ]
$( , )?
) => {{
let mut executor = fasync::Executor::new().expect("failed to create executor");
struct $report;
impl ServerConsumer for $report {
fn consume(
&self,
_: &mut DeviceDescriptor,
server: ServerEnd<InputDeviceMarker>,
) -> Result<(), Error> {
fasync::spawn(async move {
let mut stream = server.into_stream().expect("failed to convert to stream");
$(
let option = stream.try_next().await
.expect("failed to await on the next element");
assert!(option.is_some(), "stream should not be empty");
if let Some(request) = option {
let InputDeviceRequest::DispatchReport { report, .. } = request;
assert_eq!(
report.$name,
Some(Box::new($value)),
);
}
)*
assert!(
stream.try_next().await
.expect("failed to await on the next element")
.is_none(),
"stream should be empty"
);
});
Ok(())
}
}
executor.run(async { $event }, 2).expect("failed to run input event");
}};
}
#[test]
fn media_event_report() {
assert_reports_eq!(TestConsumer,
media_button_event(true, false, true, false, true, TestConsumer),
[
{
media_buttons: MediaButtonsReport {
volume_up: true,
volume_down: false,
mic_mute: true,
reset: false,
pause: true,
}
},
]
);
}
#[test]
fn keyboard_event_report() {
assert_reports_eq!(TestConsumer,
keyboard_event(40, Duration::from_millis(0), TestConsumer),
[
{
keyboard: KeyboardReport {
pressed_keys: vec![40]
}
},
{
keyboard: KeyboardReport {
pressed_keys: vec![]
}
},
]
);
}
#[test]
fn text_event_report() {
assert_reports_eq!(TestConsumer,
text("A".to_string(), Duration::from_millis(0), TestConsumer),
[
{
keyboard: KeyboardReport {
pressed_keys: vec![225]
}
},
{
keyboard: KeyboardReport {
pressed_keys: vec![4, 225]
}
},
{
keyboard: KeyboardReport {
pressed_keys: vec![]
}
},
]
);
}
#[test]
fn tap_event_report() {
assert_reports_eq!(TestConsumer,
tap_event(10, 10, 1000, 1000, 1, Duration::from_millis(0), TestConsumer),
[
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 10,
y: 10,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![],
}
},
]
);
}
#[test]
fn swipe_event_report() {
assert_reports_eq!(TestConsumer,
swipe(10, 10, 100, 100, 1000, 1000, 2, Duration::from_millis(0), TestConsumer),
[
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 10,
y: 10,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 55,
y: 55,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 100,
y: 100,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![],
}
},
]
);
}
#[test]
fn swipe_event_report_inverted() {
assert_reports_eq!(TestConsumer,
swipe(100, 100, 10, 10, 1000, 1000, 2, Duration::from_millis(0), TestConsumer),
[
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 100,
y: 100,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 55,
y: 55,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![Touch {
finger_id: 1,
x: 10,
y: 10,
width: 0,
height: 0,
}],
}
},
{
touchscreen: TouchscreenReport {
touches: vec![],
}
},
]
);
}
}