| // 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::types::{ |
| ActionResult, KeyPressRequest, MultiFingerSwipeRequest, MultiFingerTapRequest, SwipeRequest, |
| TapRequest, TextRequest, |
| }; |
| use anyhow::{Context, Error}; |
| use fuchsia_syslog::macros::fx_log_info; |
| use serde_json::{from_value, Value}; |
| use std::{convert::TryFrom, time::Duration}; |
| |
| const DEFAULT_DIMENSION: u32 = 1000; |
| const DEFAULT_DURATION: Duration = Duration::from_millis(300); |
| const DEFAULT_KEY_EVENT_DURATION: Duration = Duration::from_millis(0); |
| const DEFAULT_KEY_PRESS_DURATION: Duration = Duration::from_millis(0); |
| const DEFAULT_TAP_EVENT_COUNT: usize = 1; |
| |
| macro_rules! validate_fingers { |
| ( $fingers:expr, $field:ident $comparator:tt $limit:expr ) => { |
| match $fingers.iter().enumerate().find(|(_, finger)| !(finger.$field $comparator $limit)) { |
| None => Ok(()), |
| Some((finger_num, finger)) => Err(anyhow!( |
| "finger {}: expected {} {} {}, but {} is not {} {}", |
| finger_num, |
| stringify!($field), |
| stringify!($comparator), |
| stringify!($limit), |
| finger.$field, |
| stringify!($comparator), |
| $limit |
| )), |
| } |
| }; |
| } |
| |
| /// Perform Input fidl operations. |
| /// |
| /// Note this object is shared among all threads created by server. |
| /// |
| #[derive(Debug)] |
| pub struct InputFacade {} |
| |
| impl InputFacade { |
| pub fn new() -> InputFacade { |
| InputFacade {} |
| } |
| |
| /// Tap at coordinates (x, y) for a touchscreen with default or custom |
| /// width, height, duration, and tap event counts |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed to TapRequest |
| /// * must include: |
| /// * `x`: X axis coordinate |
| /// * `y`: Y axis coordinate |
| /// * optionally includes any of: |
| /// * `width`: Horizontal resolution of the touch panel, defaults to 1000 |
| /// * `height`: Vertical resolution of the touch panel, defaults to 1000 |
| /// * `tap_event_count`: Number of tap events to send (`duration` is divided over the tap |
| /// events), defaults to 1 |
| /// * `duration`: Duration of the event(s) in milliseconds, defaults to 300 |
| pub async fn tap(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing Tap in Input Facade."); |
| let req: TapRequest = from_value(args)?; |
| let width = req.width.unwrap_or(DEFAULT_DIMENSION); |
| let height = req.height.unwrap_or(DEFAULT_DIMENSION); |
| let tap_event_count = req.tap_event_count.unwrap_or(DEFAULT_TAP_EVENT_COUNT); |
| let duration = req.duration.map_or(DEFAULT_DURATION, Duration::from_millis); |
| |
| input_synthesis::tap_event_command(req.x, req.y, width, height, tap_event_count, duration) |
| .await?; |
| Ok(ActionResult::Success) |
| } |
| |
| /// Multi-Finger Taps for a touchscreen with default or custom |
| /// width, height, duration, and tap event counts. |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed by MultiFingerTapRequest |
| /// * must include: |
| /// * `fingers`: List of FIDL struct `Touch` defined at |
| /// sdk/fidl/fuchsia.ui.input/input_reports.fidl. |
| /// * optionally includes any of: |
| /// * `width`: Horizontal resolution of the touch panel, defaults to 1000 |
| /// * `height`: Vertical resolution of the touch panel, defaults to 1000 |
| /// * `tap_event_count`: Number of multi-finger tap events to send |
| /// (`duration` is divided over the events), defaults to 1 |
| /// * `duration`: Duration of the event(s) in milliseconds, defaults to 0 |
| /// |
| /// Example: |
| /// To send a 2-finger triple tap over 3s. |
| /// multi_finger_tap(MultiFingerTap { |
| /// tap_event_count: 3, |
| /// duration: 3000, |
| /// fingers: [ |
| /// Touch { finger_id: 1, x: 0, y: 0, width: 0, height: 0 }, |
| /// Touch { finger_id: 2, x: 20, y: 20, width: 0, height: 0 }, |
| /// ] |
| /// }); |
| /// |
| pub async fn multi_finger_tap(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing MultiFingerTap in Input Facade."); |
| let req: MultiFingerTapRequest = from_value(args)?; |
| let width = req.width.unwrap_or(DEFAULT_DIMENSION); |
| let height = req.height.unwrap_or(DEFAULT_DIMENSION); |
| let tap_event_count = req.tap_event_count.unwrap_or(DEFAULT_TAP_EVENT_COUNT); |
| let duration = req.duration.map_or(DEFAULT_DURATION, Duration::from_millis); |
| |
| input_synthesis::multi_finger_tap_event_command( |
| req.fingers, |
| width, |
| height, |
| tap_event_count, |
| duration, |
| ) |
| .await?; |
| Ok(ActionResult::Success) |
| } |
| |
| /// Swipe from coordinates (x0, y0) to (x1, y1) for a touchscreen with default |
| /// or custom width, height, duration, and tap event counts |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed to SwipeRequest |
| /// * must include: |
| /// * `x0`: X axis start coordinate |
| /// * `y0`: Y axis start coordinate |
| /// * `x1`: X axis end coordinate |
| /// * `y1`: Y axis end coordinate |
| /// * optionally includes any of: |
| /// * `width`: Horizontal resolution of the touch panel, defaults to 1000 |
| /// * `height`: Vertical resolution of the touch panel, defaults to 1000 |
| /// * `tap_event_count`: Number of move events to send in between the down and up events of |
| /// the swipe, defaults to `duration / 17` (to emulate a 60 HZ sensor) |
| /// * `duration`: Duration of the event(s) in milliseconds, default to 300 |
| pub async fn swipe(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing Swipe in Input Facade."); |
| let req: SwipeRequest = from_value(args)?; |
| let width = req.width.unwrap_or(DEFAULT_DIMENSION); |
| let height = req.height.unwrap_or(DEFAULT_DIMENSION); |
| let duration = req.duration.map_or(DEFAULT_DURATION, Duration::from_millis); |
| let tap_event_count = req.tap_event_count.unwrap_or_else(|| { |
| // 17 msec per move event, to emulate a ~60Hz sensor. |
| duration.as_millis() as usize / 17 |
| }); |
| |
| input_synthesis::swipe_command( |
| req.x0, |
| req.y0, |
| req.x1, |
| req.y1, |
| height, |
| width, |
| tap_event_count, |
| duration, |
| ) |
| .await?; |
| Ok(ActionResult::Success) |
| } |
| |
| /// Swipes multiple fingers from start positions to end positions for a touchscreen. |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed to `MultiFingerSwipeRequest` |
| /// * must include: |
| /// * `fingers`: List of `FingerSwipe`s. |
| /// * All `x0` and `x1` values must be in the range (0, width), regardless of |
| /// whether the width is defaulted or explicitly specified. |
| /// * All `y0` and `y1` values must be in the range (0, height), regardless of |
| /// whether the height is defaulted or explicitly specified. |
| /// * optionally includes any of: |
| /// * `width`: Horizontal resolution of the touch panel, defaults to 1000 |
| /// * `height`: Vertical resolution of the touch panel, defaults to 1000 |
| /// * `move_event_count`: Number of move events to send in between the down and up events of |
| /// the swipe. |
| /// * Defaults to `duration / 17` (to emulate a 60 HZ sensor). |
| /// * If 0, only the down and up events will be sent. |
| /// * `duration`: Duration of the event(s) in milliseconds |
| /// * Defaults to 300 milliseconds. |
| /// * Must be large enough to allow for at least one nanosecond per move event. |
| /// |
| /// # Returns |
| /// * `Ok(ActionResult::Success)` if the arguments were successfully parsed and events |
| /// successfully injected. |
| /// * `Err(Error)` otherwise. |
| /// |
| /// # Example |
| /// To send a two-finger swipe, with four events over two seconds: |
| /// |
| /// ``` |
| /// multi_finger_swipe(MultiFingerSwipeRequest { |
| /// fingers: [ |
| /// FingerSwipe { x0: 0, y0: 0, x1: 100, y1: 0 }, |
| /// FingerSwipe { x0: 0, y0: 100, x1: 100, y1: 100 }, |
| /// ], |
| /// move_event_count: 4 |
| /// duration: 2000, |
| /// }); |
| /// ``` |
| pub async fn multi_finger_swipe(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing MultiFingerSwipe in Input Facade."); |
| let req: MultiFingerSwipeRequest = from_value(args)?; |
| let width = req.width.unwrap_or(DEFAULT_DIMENSION); |
| let height = req.height.unwrap_or(DEFAULT_DIMENSION); |
| let duration = req.duration.map_or(DEFAULT_DURATION, Duration::from_millis); |
| let move_event_count = req.move_event_count.unwrap_or_else(|| { |
| // 17 msec per move event, to emulate a ~60Hz sensor. |
| duration.as_millis() as usize / 17 |
| }); |
| ensure!( |
| duration.as_nanos() |
| >= u128::try_from(move_event_count) |
| .context("internal error while validating `duration`")?, |
| "`duration` of {} nsec is too short for `move_event_count` of {}; \ |
| all events would have same timestamp", |
| duration.as_nanos(), |
| move_event_count |
| ); |
| validate_fingers!(req.fingers, x0 <= width)?; |
| validate_fingers!(req.fingers, x1 <= width)?; |
| validate_fingers!(req.fingers, y0 <= height)?; |
| validate_fingers!(req.fingers, y1 <= height)?; |
| |
| let start_fingers = |
| req.fingers.iter().map(|finger| (finger.x0, finger.y0)).collect::<Vec<_>>(); |
| let end_fingers = |
| req.fingers.iter().map(|finger| (finger.x1, finger.y1)).collect::<Vec<_>>(); |
| |
| input_synthesis::multi_finger_swipe_command( |
| start_fingers, |
| end_fingers, |
| width, |
| height, |
| move_event_count, |
| duration, |
| ) |
| .await?; |
| Ok(ActionResult::Success) |
| } |
| |
| /// Enters `text`, as if typed on a keyboard, with `key_event_duration` between key events. |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed to `TextRequest` |
| /// * must include: |
| /// * `text`: the characters to be input. |
| /// * Must be non-empty. |
| /// * All characters within `text` must be representable using the current |
| /// keyboard layout and locale. (At present, it is assumed that the current |
| /// layout and locale are `US-QWERTY` and `en-US`, respectively.) |
| /// * If these constraints are violated, returns an `Err`. |
| /// * optionally includes: |
| /// * `key_event_duration`: Duration of each event in milliseconds |
| /// * Serves as a lower bound on the time between key events (actual time may be |
| /// higher due to system load). |
| /// * Defaults to 0 milliseconds (each event is sent as quickly as possible). |
| /// * The number of events is `>= 2 * text.len()`: |
| /// * To account for both key-down and key-up events for every character. |
| /// * To account for modifier keys (e.g. capital letters require pressing the |
| /// shift key). |
| /// |
| /// # Returns |
| /// * `Ok(ActionResult::Success)` if the arguments were successfully parsed and events |
| /// successfully injected. |
| /// * `Err(Error)` otherwise. |
| /// |
| /// # Example |
| /// To send "hello world", with 1 millisecond between each key event: |
| /// |
| /// ``` |
| /// text(TextRequest { |
| /// text: "hello world", |
| /// key_event_duration: 1, |
| /// }); |
| /// ``` |
| pub async fn text(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing Text in Input Facade."); |
| let req: TextRequest = from_value(args)?; |
| let text = match req.text.len() { |
| 0 => Err(format_err!("`text` must be non-empty")), |
| _ => Ok(req.text), |
| }?; |
| let key_event_duration = |
| req.key_event_duration.map_or(DEFAULT_KEY_EVENT_DURATION, Duration::from_millis); |
| |
| input_synthesis::text_command(text, key_event_duration).await?; |
| Ok(ActionResult::Success) |
| } |
| |
| /// Simulates a single key down + up sequence, for the given `hid_usage_id`. |
| /// |
| /// # Arguments |
| /// * `value`: will be parsed to `KeyPressRequest` |
| /// * must include |
| /// * `hid_usage_id`: desired HID Usage ID, per [HID Usages and Descriptions]. |
| /// * The Usage ID will be interpreted in the context of "Usage Page" 0x07, which |
| /// is the "Keyboard/Keypad" page. |
| /// * Because Usage IDs are defined by an external standard, it is impractical |
| /// to validate this parameter. As such, any value can be injected successfully. |
| /// However, the interpretation of unrecognized values is subject to the choices |
| /// of the system under test. |
| /// * optionally includes: |
| /// * `key_press_duration`: time between the down event and the up event, in milliseconds |
| /// * Serves as a lower bound on the time between the down event and the up event |
| /// (actual time may be higher due to system load). |
| /// * Defaults to 0 milliseconds (the up event is sent immediately after the down event) |
| /// |
| /// # Returns |
| /// * `Ok(ActionResult::Success)` if the arguments were successfully parsed and events |
| /// successfully injected. |
| /// * `Err(Error)` otherwise. |
| /// |
| /// # Future directions |
| /// Per fxbug.dev/63532, this method will be replaced with a method that deals in |
| /// `fuchsia.input.Key`s, instead of HID Usage IDs. |
| /// |
| /// # Example |
| /// To simulate a press of the `ENTER` key, with 1 millisecond between the down and |
| /// up events: |
| /// |
| /// ``` |
| /// key_press(KeyPressRequest { |
| /// hid_usage_id: 40, |
| /// key_press_duration: 1, |
| /// }); |
| /// ``` |
| /// |
| /// [HID Usages and Descriptions]: https://www.usb.org/sites/default/files/documents/hut1_12v2.pdf |
| pub async fn key_press(&self, args: Value) -> Result<ActionResult, Error> { |
| fx_log_info!("Executing KeyboardEvent in Input Facade."); |
| let req: KeyPressRequest = from_value(args)?; |
| let hid_usage_id = req.hid_usage_id; |
| let key_press_duration = |
| req.key_press_duration.map_or(DEFAULT_KEY_PRESS_DURATION, Duration::from_millis); |
| |
| input_synthesis::keyboard_event_command(hid_usage_id.into(), key_press_duration).await?; |
| Ok(ActionResult::Success) |
| } |
| } |