| // Copyright 2022 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 async_utils::event::Event as AsyncEvent; |
| use fidl_fuchsia_input::Key; |
| use fidl_fuchsia_input_injection::InputDeviceRegistryMarker; |
| use fidl_fuchsia_input_report::{ |
| ConsumerControlInputReport, ContactInputReport, DeviceInformation, InputReport, |
| KeyboardInputReport, MouseInputReport, TouchInputReport, |
| }; |
| use fidl_fuchsia_ui_input::KeyboardReport; |
| use fidl_fuchsia_ui_test_input::{ |
| CoordinateUnit, KeyboardRequest, KeyboardRequestStream, MediaButtonsDeviceRequest, |
| MediaButtonsDeviceRequestStream, MouseRequest, MouseRequestStream, RegistryRequest, |
| RegistryRequestStream, TouchScreenRequest, TouchScreenRequestStream, |
| }; |
| use fuchsia_component::client::connect_to_protocol; |
| use futures::{StreamExt, TryStreamExt}; |
| use keymaps::inverse_keymap::{InverseKeymap, Shift}; |
| use keymaps::usages::{hid_usage_to_input3_key, Usages}; |
| use std::time::Duration; |
| use tracing::{error, info, warn}; |
| use { |
| fidl_fuchsia_math as math, fidl_fuchsia_ui_display_singleton as display_info, |
| fuchsia_async as fasync, |
| }; |
| |
| mod input_device; |
| mod input_device_registry; |
| mod input_reports_reader; |
| |
| /// Use this to place required DeviceInformation into DeviceDescriptor. |
| fn new_fake_device_info() -> DeviceInformation { |
| DeviceInformation { |
| product_id: Some(42), |
| vendor_id: Some(43), |
| version: Some(u32::MAX), |
| polling_rate: Some(1000), |
| ..Default::default() |
| } |
| } |
| |
| /// Converts the `input` string into a key sequence under the `InverseKeymap` derived from `keymap`. |
| /// |
| /// This is intended for end-to-end and input testing only; for production use cases and general |
| /// testing, IME injection should be used instead. |
| /// |
| /// A translation from `input` to a sequence of keystrokes is not guaranteed to exist. If a |
| /// translation does not exist, `None` is returned. |
| /// |
| /// The sequence does not contain pauses except between repeated keys or to clear a shift state, |
| /// though the sequence does terminate with an empty report (no keys pressed). A shift key |
| /// transition is sent in advance of each series of keys that needs it. |
| /// |
| /// Note that there is currently no way to distinguish between particular key releases. As such, |
| /// only one key release report is generated even in combinations, e.g. Shift + A. |
| /// |
| /// # Example |
| /// |
| /// ``` |
| /// let key_sequence = derive_key_sequence(&keymaps::US_QWERTY, "A").unwrap(); |
| /// |
| /// // [shift, A, clear] |
| /// assert_eq!(key_sequence.len(), 3); |
| /// ``` |
| /// |
| /// TODO(https://fxbug.dev/42059899): Simplify the logic in this test. |
| fn derive_key_sequence(keymap: &keymaps::Keymap<'_>, input: &str) -> Option<Vec<KeyboardReport>> { |
| let inverse_keymap = InverseKeymap::new(keymap); |
| let mut reports = vec![]; |
| let mut shift_pressed = false; |
| let mut last_usage = None; |
| |
| for ch in input.chars() { |
| let key_stroke = inverse_keymap.get(&ch)?; |
| |
| match key_stroke.shift { |
| Shift::Yes if !shift_pressed => { |
| shift_pressed = true; |
| last_usage = Some(0); |
| } |
| Shift::No if shift_pressed => { |
| shift_pressed = false; |
| last_usage = Some(0); |
| } |
| _ => { |
| if last_usage == Some(key_stroke.usage) { |
| last_usage = Some(0); |
| } |
| } |
| } |
| |
| if let Some(0) = last_usage { |
| reports.push(KeyboardReport { |
| pressed_keys: if shift_pressed { |
| vec![Usages::HidUsageKeyLeftShift as u32] |
| } else { |
| vec![] |
| }, |
| }); |
| } |
| |
| last_usage = Some(key_stroke.usage); |
| |
| reports.push(KeyboardReport { |
| pressed_keys: if shift_pressed { |
| vec![key_stroke.usage, Usages::HidUsageKeyLeftShift as u32] |
| } else { |
| vec![key_stroke.usage] |
| }, |
| }); |
| } |
| |
| reports.push(KeyboardReport { pressed_keys: vec![] }); |
| |
| Some(reports) |
| } |
| |
| fn convert_keyboard_report_to_keys(report: &KeyboardReport) -> Vec<Key> { |
| report |
| .pressed_keys |
| .iter() |
| .map(|&usage| { |
| hid_usage_to_input3_key(usage.try_into().expect("failed to convert usage to u16")) |
| .unwrap_or_else(|| panic!("no Key for usage {:?}", usage)) |
| }) |
| .collect() |
| } |
| |
| /// Serves `fuchsia.ui.test.input.Registry`. |
| pub async fn handle_registry_request_stream(request_stream: RegistryRequestStream) { |
| request_stream |
| .try_for_each_concurrent(None, |request| async { |
| let input_device_registry = connect_to_protocol::<InputDeviceRegistryMarker>() |
| .expect("connect to input_device_registry"); |
| let got_input_reports_reader = AsyncEvent::new(); |
| |
| let mut registry = input_device_registry::InputDeviceRegistry::new( |
| input_device_registry, |
| got_input_reports_reader.clone(), |
| ); |
| let mut task_group = fasync::TaskGroup::new(); |
| |
| match request { |
| RegistryRequest::RegisterTouchScreen { payload, responder, .. } => { |
| info!("register touchscreen"); |
| let device = payload |
| .device |
| .expect("no touchscreen device provided in registration request"); |
| let (min_x, max_x, min_y, max_y) = match payload.coordinate_unit { |
| Some(CoordinateUnit::PhysicalPixels) => { |
| let display_info_proxy = |
| connect_to_protocol::<display_info::InfoMarker>() |
| .expect("failed to connect to display info service"); |
| let display_dimensions = display_info_proxy |
| .get_metrics() |
| .await |
| .expect("failed to get display metrics") |
| .extent_in_px |
| .expect("display metrics missing extent in px"); |
| ( |
| 0, |
| display_dimensions.width as i64, |
| 0, |
| display_dimensions.height as i64, |
| ) |
| } |
| _ => (-1000, 1000, -1000, 1000), |
| }; |
| |
| task_group.spawn(async move { |
| let touchscreen_device = registry |
| .add_touchscreen_device(min_x, max_x, min_y, max_y) |
| .expect("failed to create fake touchscreen device"); |
| |
| handle_touchscreen_request_stream( |
| touchscreen_device, |
| device |
| .into_stream() |
| .expect("failed to convert touchscreen device to stream"), |
| ) |
| .await; |
| }); |
| |
| info!("wait for input-pipeline setup input-reader"); |
| let _ = got_input_reports_reader.wait().await; |
| info!("input-pipeline setup input-reader"); |
| |
| responder.send().expect("Failed to respond to RegisterTouchScreen request"); |
| } |
| RegistryRequest::RegisterMediaButtonsDevice { payload, responder, .. } => { |
| info!("register media buttons device"); |
| |
| if let Some(device) = payload.device { |
| task_group.spawn(async move { |
| let media_buttons_device = registry |
| .add_media_buttons_device() |
| .expect("failed to create fake media buttons device"); |
| handle_media_buttons_device_request_stream( |
| media_buttons_device, |
| device |
| .into_stream() |
| .expect("failed to convert media buttons device to stream"), |
| ) |
| .await; |
| }); |
| } else { |
| error!("no media buttons device provided in registration request"); |
| } |
| |
| info!("wait for input-pipeline setup input-reader"); |
| let _ = got_input_reports_reader.wait().await; |
| info!("input-pipeline setup input-reader"); |
| |
| responder |
| .send() |
| .expect("Failed to respond to RegisterMediaButtonsDevice request"); |
| } |
| RegistryRequest::RegisterKeyboard { payload, responder, .. } => { |
| info!("register keyboard device"); |
| |
| if let Some(device) = payload.device { |
| task_group.spawn(async move { |
| let keyboard_device = registry |
| .add_keyboard_device() |
| .expect("failed to create fake keyboard device"); |
| handle_keyboard_request_stream( |
| keyboard_device, |
| device |
| .into_stream() |
| .expect("failed to convert keyboard device to stream"), |
| ) |
| .await; |
| }); |
| } else { |
| error!("no keyboard device provided in registration request"); |
| } |
| |
| info!("wait for input-pipeline setup input-reader"); |
| let _ = got_input_reports_reader.wait().await; |
| info!("input-pipeline setup input-reader"); |
| |
| responder.send().expect("Failed to respond to RegisterKeyboard request"); |
| } |
| RegistryRequest::RegisterMouse { payload, responder } => { |
| info!("register mouse device"); |
| |
| if let Some(device) = payload.device { |
| task_group.spawn(async move { |
| let mouse_device = registry |
| .add_mouse_device() |
| .expect("failed to create fake mouse device"); |
| |
| handle_mouse_request_stream( |
| mouse_device, |
| device |
| .into_stream() |
| .expect("failed to convert mouse device to stream"), |
| ) |
| .await; |
| }); |
| } else { |
| error!("no mouse device provided in registration request"); |
| } |
| |
| info!("wait for input-pipeline setup input-reader"); |
| let _ = got_input_reports_reader.wait().await; |
| info!("input-pipeline setup input-reader"); |
| |
| responder.send().expect("Failed to respond to RegisterMouse request"); |
| } |
| } |
| |
| task_group.join().await; |
| Ok(()) |
| }) |
| .await |
| .expect("failed to serve test realm factory request stream"); |
| } |
| |
| fn input_report_for_touch_contacts(contacts: Vec<(u32, math::Vec_)>) -> InputReport { |
| let contact_input_reports = contacts |
| .into_iter() |
| .map(|(contact_id, location)| ContactInputReport { |
| contact_id: Some(contact_id), |
| position_x: Some(location.x as i64), |
| position_y: Some(location.y as i64), |
| contact_width: Some(0), |
| contact_height: Some(0), |
| ..Default::default() |
| }) |
| .collect(); |
| |
| let touch_input_report = TouchInputReport { |
| contacts: Some(contact_input_reports), |
| pressed_buttons: Some(vec![]), |
| ..Default::default() |
| }; |
| |
| InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| touch: Some(touch_input_report), |
| ..Default::default() |
| } |
| } |
| |
| /// Serves `fuchsia.ui.test.input.TouchScreen`. |
| async fn handle_touchscreen_request_stream( |
| touchscreen_device: input_device::InputDevice, |
| mut request_stream: TouchScreenRequestStream, |
| ) { |
| while let Some(request) = request_stream.next().await { |
| match request { |
| Ok(TouchScreenRequest::SimulateTap { payload, responder }) => { |
| if let Some(tap_location) = payload.tap_location { |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![(1, tap_location)])) |
| .expect("Failed to send tap input report"); |
| |
| // Send a report with an empty set of touch contacts, so that input |
| // pipeline generates a pointer event with phase == UP. |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![])) |
| .expect("failed to send empty input report"); |
| |
| responder.send().expect("Failed to send SimulateTap response"); |
| } else { |
| warn!("SimulateTap request missing tap location"); |
| } |
| } |
| Ok(TouchScreenRequest::SimulateSwipe { payload, responder }) => { |
| // Compute the x- and y- displacements between successive touch events. |
| let start_location = payload.start_location.expect("missing start location"); |
| let end_location = payload.end_location.expect("missing end location"); |
| let move_event_count = payload.move_event_count.expect("missing move event count"); |
| assert_ne!(move_event_count, 0); |
| |
| let start_x_f = start_location.x as f64; |
| let start_y_f = start_location.y as f64; |
| let end_x_f = end_location.x as f64; |
| let end_y_f = end_location.y as f64; |
| let move_event_count_f = move_event_count as f64; |
| let step_size_x = (end_x_f - start_x_f) / move_event_count_f; |
| let step_size_y = (end_y_f - start_y_f) / move_event_count_f; |
| |
| // Generate an event at `start_location`, followed by `move_event_count - 1` |
| // evenly-spaced events, followed by an event at `end_location`. |
| for i in 0..move_event_count + 1 { |
| let i_f = i as f64; |
| let event_x = start_x_f + (i_f * step_size_x); |
| let event_y = start_y_f + (i_f * step_size_y); |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![( |
| 1, |
| math::Vec_ { x: event_x as i32, y: event_y as i32 }, |
| )])) |
| .expect("Failed to send tap input report"); |
| } |
| |
| // Send a report with an empty set of touch contacts, so that input |
| // pipeline generates a pointer event with phase == UP. |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![])) |
| .expect("failed to send empty input report"); |
| |
| responder.send().expect("Failed to send SimulateSwipe response"); |
| } |
| Ok(TouchScreenRequest::SimulateMultiTap { payload, responder }) => { |
| let tap_locations = payload.tap_locations.expect("missing tap locations"); |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts( |
| tap_locations |
| .into_iter() |
| .enumerate() |
| .map(|(i, it)| (i as u32, it)) |
| .collect(), |
| )) |
| .expect("Failed to send tap input report"); |
| |
| // Send a report with an empty set of touch contacts, so that input |
| // pipeline generates a pointer event with phase == UP. |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![])) |
| .expect("failed to send empty input report"); |
| responder.send().expect("Failed to send SimulateMultiTap response"); |
| } |
| Ok(TouchScreenRequest::SimulateMultiFingerGesture { payload, responder }) => { |
| // Compute the x- and y- displacements between successive touch events. |
| let start_locations = payload.start_locations.expect("missing start locations"); |
| let end_locations = payload.end_locations.expect("missing end locations"); |
| let move_event_count = payload.move_event_count.expect("missing move event count"); |
| let finger_count = payload.finger_count.expect("missing finger count") as usize; |
| |
| let move_event_count_f = move_event_count as f32; |
| |
| let mut steps: Vec<math::VecF> = vec![]; |
| |
| for finger in 0..finger_count { |
| let start_x = start_locations[finger].x as f32; |
| let start_y = start_locations[finger].y as f32; |
| let end_x = end_locations[finger].x as f32; |
| let end_y = end_locations[finger].y as f32; |
| let step_x = (end_x - start_x) / move_event_count_f; |
| let step_y = (end_y - start_y) / move_event_count_f; |
| steps.push(math::VecF { x: step_x, y: step_y }); |
| } |
| |
| // Generate an event at `start_location`, followed by `move_event_count - 1` |
| // evenly-spaced events, followed by an event at `end_location`. |
| for i in 0..move_event_count { |
| let i_f = i as f32; |
| |
| let mut contacts: Vec<(u32, math::Vec_)> = vec![]; |
| |
| for finger in 0..finger_count { |
| let start_x = start_locations[finger].x as f32; |
| let start_y = start_locations[finger].y as f32; |
| let event_x = (start_x + i_f * steps[finger].x) as i32; |
| let event_y = (start_y + i_f * steps[finger].y) as i32; |
| contacts.push((finger as u32, math::Vec_ { x: event_x, y: event_y })); |
| } |
| |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(contacts)) |
| .expect("Failed to send tap input report"); |
| } |
| |
| // Send a report with an empty set of touch contacts, so that input |
| // pipeline generates a pointer event with phase == UP. |
| touchscreen_device |
| .send_input_report(input_report_for_touch_contacts(vec![])) |
| .expect("failed to send empty input report"); |
| |
| responder.send().expect("Failed to send SimulateMultiFingerGesture response"); |
| } |
| Ok(TouchScreenRequest::SimulateTouchEvent { report, responder }) => { |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| touch: Some(report), |
| ..Default::default() |
| }; |
| touchscreen_device |
| .send_input_report(input_report) |
| .expect("failed to send empty input report"); |
| responder.send().expect("Failed to send SimulateTouchEvent response"); |
| } |
| Err(e) => { |
| error!("Error on touchscreen channel: {}", e); |
| return; |
| } |
| } |
| } |
| } |
| |
| /// Serves `fuchsia.ui.test.input.MediaButtonsDevice`. |
| async fn handle_media_buttons_device_request_stream( |
| media_buttons_device: input_device::InputDevice, |
| mut request_stream: MediaButtonsDeviceRequestStream, |
| ) { |
| while let Some(request) = request_stream.next().await { |
| match request { |
| Ok(MediaButtonsDeviceRequest::SimulateButtonPress { payload, responder }) => { |
| if let Some(button) = payload.button { |
| let media_buttons_input_report = ConsumerControlInputReport { |
| pressed_buttons: Some(vec![button]), |
| ..Default::default() |
| }; |
| |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| consumer_control: Some(media_buttons_input_report), |
| ..Default::default() |
| }; |
| |
| media_buttons_device |
| .send_input_report(input_report) |
| .expect("Failed to send button press input report"); |
| |
| // Send a report with an empty set of pressed buttons, |
| // so that input pipeline generates a media buttons |
| // event with the target button being released. |
| let empty_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| consumer_control: Some(ConsumerControlInputReport { |
| pressed_buttons: Some(vec![]), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }; |
| |
| media_buttons_device |
| .send_input_report(empty_report) |
| .expect("Failed to send button release input report"); |
| |
| responder.send().expect("Failed to send SimulateButtonPress response"); |
| } else { |
| warn!("SimulateButtonPress request missing button"); |
| } |
| } |
| Ok(MediaButtonsDeviceRequest::SendButtonsState { payload, responder }) => { |
| let buttons = match payload.buttons { |
| Some(buttons) => buttons, |
| None => vec![], |
| }; |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| consumer_control: Some(ConsumerControlInputReport { |
| pressed_buttons: Some(buttons), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }; |
| |
| media_buttons_device |
| .send_input_report(input_report) |
| .expect("Failed to send button press input report"); |
| |
| responder.send().expect("Failed to send SimulateButtonsPress response"); |
| } |
| Err(e) => { |
| error!("Error on media buttons device channel: {}", e); |
| return; |
| } |
| } |
| } |
| } |
| |
| /// Serves `fuchsia.ui.test.input.Keyboard`. |
| async fn handle_keyboard_request_stream( |
| keyboard_device: input_device::InputDevice, |
| mut request_stream: KeyboardRequestStream, |
| ) { |
| while let Some(request) = request_stream.next().await { |
| match request { |
| Ok(KeyboardRequest::SimulateUsAsciiTextEntry { payload, responder }) => { |
| if let Some(text) = payload.text { |
| let key_sequence = derive_key_sequence(&keymaps::US_QWERTY, &text) |
| .expect("Failed to derive key sequence"); |
| |
| let mut key_iter = key_sequence.into_iter().peekable(); |
| while let Some(keyboard_report) = key_iter.next() { |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| keyboard: Some(KeyboardInputReport { |
| pressed_keys3: Some(convert_keyboard_report_to_keys( |
| &keyboard_report, |
| )), |
| ..Default::default() |
| }), |
| ..Default::default() |
| }; |
| |
| keyboard_device |
| .send_input_report(input_report) |
| .expect("Failed to send key event report"); |
| |
| if key_iter.peek().is_some() { |
| fuchsia_async::Timer::new(Duration::from_millis(100)).await; |
| } |
| } |
| |
| responder.send().expect("Failed to send SimulateTextEntry response"); |
| } else { |
| warn!("SimulateTextEntry request missing text"); |
| } |
| } |
| Ok(KeyboardRequest::SimulateKeyEvent { payload, responder }) => { |
| let keyboard_report = payload.report.expect("no report"); |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| keyboard: Some(keyboard_report), |
| ..Default::default() |
| }; |
| |
| keyboard_device |
| .send_input_report(input_report) |
| .expect("Failed to send key event report"); |
| |
| responder.send().expect("Failed to send SimulateKeyEvent response"); |
| } |
| Err(e) => { |
| error!("Error on keyboard device channel: {}", e); |
| return; |
| } |
| } |
| } |
| } |
| |
| /// Serves `fuchsia.ui.test.input.Mouse`. |
| async fn handle_mouse_request_stream( |
| mouse_device: input_device::InputDevice, |
| mut request_stream: MouseRequestStream, |
| ) { |
| while let Some(request) = request_stream.next().await { |
| match request { |
| Ok(MouseRequest::SimulateMouseEvent { payload, responder }) => { |
| let mut mouse_input_report = MouseInputReport { |
| movement_x: payload.movement_x, |
| movement_y: payload.movement_y, |
| scroll_v: payload.scroll_v_detent, |
| scroll_h: payload.scroll_h_detent, |
| ..Default::default() |
| }; |
| if let Some(pressed_buttons) = payload.pressed_buttons { |
| mouse_input_report.pressed_buttons = Some( |
| pressed_buttons |
| .into_iter() |
| .map(|b| { |
| b.into_primitive() |
| .try_into() |
| .expect("failed to convert MouseButton to u8") |
| }) |
| .collect(), |
| ); |
| } |
| |
| let input_report = InputReport { |
| event_time: Some(fasync::Time::now().into_nanos()), |
| mouse: Some(mouse_input_report), |
| ..Default::default() |
| }; |
| |
| mouse_device |
| .send_input_report(input_report) |
| .expect("Failed to send key event report"); |
| |
| responder.send().expect("Failed to send SimulateMouseEvent response"); |
| } |
| Err(e) => { |
| error!("Error on keyboard device channel: {}", e); |
| return; |
| } |
| } |
| } |
| } |
| |
| #[cfg(test)] |
| mod tests { |
| // Most of the functions in this file need to bind to FIDL services in |
| // this component's environment to do their work, but a component can't |
| // modify its own environment. Hence, we can't validate those functions. |
| // |
| // However, we can (and do) validate derive_key_sequence(). |
| |
| use super::{derive_key_sequence, KeyboardReport, Usages}; |
| use pretty_assertions::assert_eq; |
| |
| // TODO(https://fxbug.dev/42059899): Remove this macro. |
| macro_rules! reports { |
| ( $( [ $( $usages:expr ),* ] ),* $( , )? ) => { |
| Some(vec![ |
| $( |
| KeyboardReport { |
| pressed_keys: vec![$($usages as u32),*] |
| } |
| ),* |
| ]) |
| } |
| } |
| |
| #[test] |
| fn lowercase() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "lowercase"), |
| reports![ |
| [Usages::HidUsageKeyL], |
| [Usages::HidUsageKeyO], |
| [Usages::HidUsageKeyW], |
| [Usages::HidUsageKeyE], |
| [Usages::HidUsageKeyR], |
| [Usages::HidUsageKeyC], |
| [Usages::HidUsageKeyA], |
| [Usages::HidUsageKeyS], |
| [Usages::HidUsageKeyE], |
| [], |
| ] |
| ); |
| } |
| |
| #[test] |
| fn numerics() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "0123456789"), |
| reports![ |
| [Usages::HidUsageKey0], |
| [Usages::HidUsageKey1], |
| [Usages::HidUsageKey2], |
| [Usages::HidUsageKey3], |
| [Usages::HidUsageKey4], |
| [Usages::HidUsageKey5], |
| [Usages::HidUsageKey6], |
| [Usages::HidUsageKey7], |
| [Usages::HidUsageKey8], |
| [Usages::HidUsageKey9], |
| [], |
| ] |
| ); |
| } |
| |
| #[test] |
| fn internet_text_entry() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "http://127.0.0.1:8080"), |
| reports![ |
| [Usages::HidUsageKeyH], |
| [Usages::HidUsageKeyT], |
| [], |
| [Usages::HidUsageKeyT], |
| [Usages::HidUsageKeyP], |
| // ':' |
| // Shift is actuated first on its own, then together with |
| // the key. |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeySemicolon, Usages::HidUsageKeyLeftShift], |
| [], |
| [Usages::HidUsageKeySlash], |
| [], |
| [Usages::HidUsageKeySlash], |
| [Usages::HidUsageKey1], |
| [Usages::HidUsageKey2], |
| [Usages::HidUsageKey7], |
| [Usages::HidUsageKeyDot], |
| [Usages::HidUsageKey0], |
| [Usages::HidUsageKeyDot], |
| [Usages::HidUsageKey0], |
| [Usages::HidUsageKeyDot], |
| [Usages::HidUsageKey1], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeySemicolon, Usages::HidUsageKeyLeftShift], |
| [], |
| [Usages::HidUsageKey8], |
| [Usages::HidUsageKey0], |
| [Usages::HidUsageKey8], |
| [Usages::HidUsageKey0], |
| [], |
| ] |
| ); |
| } |
| |
| #[test] |
| fn sentence() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "Hello, world!"), |
| reports![ |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyH, Usages::HidUsageKeyLeftShift], |
| [], |
| [Usages::HidUsageKeyE], |
| [Usages::HidUsageKeyL], |
| [], |
| [Usages::HidUsageKeyL], |
| [Usages::HidUsageKeyO], |
| [Usages::HidUsageKeyComma], |
| [Usages::HidUsageKeySpace], |
| [Usages::HidUsageKeyW], |
| [Usages::HidUsageKeyO], |
| [Usages::HidUsageKeyR], |
| [Usages::HidUsageKeyL], |
| [Usages::HidUsageKeyD], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKey1, Usages::HidUsageKeyLeftShift], |
| [], |
| ] |
| ); |
| } |
| |
| #[test] |
| fn hold_shift() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "ALL'S WELL!"), |
| reports![ |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyA, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift], |
| [], |
| [Usages::HidUsageKeyApostrophe], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyS, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeySpace, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyW, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyE, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyL, Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKey1, Usages::HidUsageKeyLeftShift], |
| [], |
| ] |
| ); |
| } |
| |
| #[test] |
| fn tab_and_newline() { |
| assert_eq!( |
| derive_key_sequence(&keymaps::US_QWERTY, "\tHello\n"), |
| reports![ |
| [Usages::HidUsageKeyTab], |
| [Usages::HidUsageKeyLeftShift], |
| [Usages::HidUsageKeyH, Usages::HidUsageKeyLeftShift], |
| [], |
| [Usages::HidUsageKeyE], |
| [Usages::HidUsageKeyL], |
| [], |
| [Usages::HidUsageKeyL], |
| [Usages::HidUsageKeyO], |
| [Usages::HidUsageKeyEnter], |
| [], |
| ] |
| ); |
| } |
| } |