blob: e59080795ecb1f93a3f3fb4dd903f004f4945e96 [file] [log] [blame]
// Copyright 2021 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_trait::async_trait;
use {
anyhow::{format_err, Result},
fidl_fuchsia_input as input, fidl_fuchsia_ui_input as ui_input,
fidl_fuchsia_ui_input3 as ui_input3, fuchsia_zircon as zx,
futures::{
stream::{self, StreamExt, TryStreamExt},
FutureExt,
},
};
pub fn default_state() -> ui_input::TextInputState {
ui_input::TextInputState {
revision: 1,
text: "".to_string(),
selection: ui_input::TextSelection {
base: 0,
extent: 0,
affinity: ui_input::TextAffinity::Upstream,
},
composing: ui_input::TextRange { start: -1, end: -1 },
}
}
// Measure text in utf16 code units.
pub fn measure_utf16(s: &str) -> usize {
s.chars().map(|c| c.len_utf16()).sum::<usize>()
}
// Setup IME text edit state using contents and selection indices.
pub async fn setup_ime(
ime: &ui_input::InputMethodEditorProxy,
text: &str,
base: i64,
extent: i64,
) -> Result<()> {
let mut state = default_state();
state.text = text.to_string();
state.selection.base = base;
state.selection.extent = extent;
ime.set_state(&state).map_err(Into::into)
}
// Bind a new IME to the service.
pub fn bind_editor(
ime_service: &ui_input::ImeServiceProxy,
) -> Result<(ui_input::InputMethodEditorProxy, ui_input::InputMethodEditorClientRequestStream)> {
let (ime, ime_server_end) =
fidl::endpoints::create_proxy::<ui_input::InputMethodEditorMarker>().unwrap();
let (editor_client_end, editor_request_stream) =
fidl::endpoints::create_request_stream().unwrap();
ime_service.get_input_method_editor(
ui_input::KeyboardType::Text,
ui_input::InputMethodAction::Done,
&default_state(),
editor_client_end,
ime_server_end,
)?;
Ok((ime, editor_request_stream))
}
/// Provides a method for dispatching keys, in case the tests need to vary
/// them.
#[async_trait]
pub trait KeyDispatcher {
async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool>;
}
/// A [KeyDispatcher] that uses `fuchsia.ui.input.InputMethodEditor` to dispatch keypresses.
pub struct InputMethodEditorDispatcher<'a> {
pub ime: &'a ui_input::InputMethodEditorProxy,
}
#[async_trait]
impl<'a> KeyDispatcher for InputMethodEditorDispatcher<'a> {
async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
Ok(self.ime.dispatch_key3(&event).await?)
}
}
/// A [KeyDispatcher] that uses `fuchsia.ui.input3.KeyEventInjector` to dispatch keypresses.
pub struct KeyEventInjectorDispatcher<'a> {
pub key_event_injector: &'a ui_input3::KeyEventInjectorProxy,
}
#[async_trait]
impl<'a> KeyDispatcher for KeyEventInjectorDispatcher<'a> {
async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
Ok(self.key_event_injector.inject(&event).await? == ui_input3::KeyEventStatus::Handled)
}
}
/// A fixture that can use different services to send key events. Use [KeySimulator::new] to
/// create a new instance.
///
/// # Example
///
/// ```ignore
/// let ime_service = connect_to_protocol::<ui_input::ImeServiceMarker>()
/// .context("Failed to connect to IME Service")?;
/// let key_dispatcher = test_helpers::ImeServiceKeyDispatcher { ime_service: &ime_service };
/// let key_simulator = test_helpers::KeySimulator::new(&key_dispatcher);
/// // Thereafter...
/// key_simulator.dispatch(fidl_fuchsia_ui_input3::KeyEvent{...}).await?;
/// ```
pub struct KeySimulator<'a> {
dispatcher: &'a dyn KeyDispatcher,
}
impl<'a> KeySimulator<'a> {
pub fn new(dispatcher: &'a dyn KeyDispatcher) -> Self {
KeySimulator { dispatcher }
}
pub async fn dispatch(&self, event: ui_input3::KeyEvent) -> Result<bool> {
self.dispatcher.dispatch(event).await
}
// Simulate a key press and release of `key`.
async fn simulate_keypress(&self, key: input::Key) -> Result<()> {
self.dispatch(ui_input3::KeyEvent {
timestamp: Some(0),
type_: Some(ui_input3::KeyEventType::Pressed),
key: Some(key),
..Default::default()
})
.await?;
self.dispatch(ui_input3::KeyEvent {
timestamp: Some(0),
type_: Some(ui_input3::KeyEventType::Released),
key: Some(key),
..Default::default()
})
.await?;
Ok(())
}
// Simulate keypress with `held_keys` pressed down.
async fn simulate_ime_keypress_with_held_keys(
&self,
key: input::Key,
held_keys: Vec<input::Key>,
) {
let held_keys_down =
held_keys.iter().map(|k| (ui_input3::KeyEventType::Pressed, *k)).into_iter();
let held_keys_up =
held_keys.iter().map(|k| (ui_input3::KeyEventType::Released, *k)).into_iter();
let key_press_and_release =
vec![(ui_input3::KeyEventType::Pressed, key), (ui_input3::KeyEventType::Released, key)]
.into_iter();
let sequence = held_keys_down.chain(key_press_and_release).chain(held_keys_up);
stream::iter(sequence)
.for_each(|(type_, key)| {
self.dispatch(ui_input3::KeyEvent {
timestamp: Some(0),
type_: Some(type_),
key: Some(key),
..Default::default()
})
.map(|_| ())
})
.await;
}
}
// Simulate keypress by injecting an event into supplied key event injector.
pub async fn simulate_keypress(
key_event_injector: &ui_input3::KeyEventInjectorProxy,
key: input::Key,
) -> Result<()> {
let key_dispatcher = KeyEventInjectorDispatcher { key_event_injector: &key_event_injector };
let key_simulator = KeySimulator::new(&key_dispatcher);
key_simulator.simulate_keypress(key).await
}
// Simulate keypress by injecting into IME.
pub async fn simulate_ime_keypress(ime: &ui_input::InputMethodEditorProxy, key: input::Key) {
simulate_ime_keypress_with_held_keys(ime, key, Vec::new()).await
}
// Simulate keypress by injecting into IME, with `held_keys` pressed down.
pub async fn simulate_ime_keypress_with_held_keys(
ime: &ui_input::InputMethodEditorProxy,
key: input::Key,
held_keys: Vec<input::Key>,
) {
let key_dispatcher = InputMethodEditorDispatcher { ime: &ime };
let key_simulator = KeySimulator::new(&key_dispatcher);
key_simulator.simulate_ime_keypress_with_held_keys(key, held_keys).await
}
// Get next IME message, assuming it's a `InputMethodEditorClientRequest::DidUpdateState`.
pub async fn get_state_update(
editor_stream: &mut ui_input::InputMethodEditorClientRequestStream,
) -> Result<(ui_input::TextInputState, Option<ui_input::KeyboardEvent>)> {
editor_stream
.map(|request| match request {
Ok(ui_input::InputMethodEditorClientRequest::DidUpdateState {
state, event, ..
}) => {
let keyboard_event = event.map(|e| {
if let ui_input::InputEvent::Keyboard(keyboard_event) = *e {
keyboard_event
} else {
panic!("expected DidUpdateState to only send Keyboard events");
}
});
Ok((state, keyboard_event))
}
Ok(msg) => Err(format_err!("request should be DidUpdateState, got {:?}", msg)),
Err(err) => Err(Into::into(err)),
})
.try_next()
.await
.map(|maybe_msg| maybe_msg.ok_or(format_err!("ime should have sent message")))?
}
// Get next IME message, assuming it's a `InputMethodEditorClientRequest::OnAction`.
pub async fn get_action(
editor_stream: &mut ui_input::InputMethodEditorClientRequestStream,
) -> Result<ui_input::InputMethodAction> {
editor_stream
.map(|request| match request {
Ok(ui_input::InputMethodEditorClientRequest::OnAction { action, .. }) => Ok(action),
Ok(msg) => Err(format_err!("request should be OnAction, got {:?}", msg)),
Err(err) => Err(Into::into(err)),
})
.try_next()
.await
.map(|maybe_msg| maybe_msg.ok_or(format_err!("ime should have sent message")))?
}
/// Used to reduce verbosity of instantiating `KeyMeaning`s.
pub struct KeyMeaningWrapper(Option<ui_input3::KeyMeaning>);
impl From<ui_input3::KeyMeaning> for KeyMeaningWrapper {
fn from(src: ui_input3::KeyMeaning) -> Self {
KeyMeaningWrapper(src.into())
}
}
impl From<Option<ui_input3::KeyMeaning>> for KeyMeaningWrapper {
fn from(src: Option<ui_input3::KeyMeaning>) -> Self {
KeyMeaningWrapper(src)
}
}
impl From<KeyMeaningWrapper> for Option<ui_input3::KeyMeaning> {
fn from(src: KeyMeaningWrapper) -> Self {
src.0
}
}
impl From<char> for KeyMeaningWrapper {
fn from(src: char) -> Self {
Some(ui_input3::KeyMeaning::Codepoint(src as u32)).into()
}
}
impl From<ui_input3::NonPrintableKey> for KeyMeaningWrapper {
fn from(src: ui_input3::NonPrintableKey) -> Self {
Some(ui_input3::KeyMeaning::NonPrintableKey(src)).into()
}
}
/// Creates a `KeyEvent` with the given parameters.
pub fn create_key_event(
timestamp: zx::Time,
event_type: ui_input3::KeyEventType,
key: impl Into<Option<input::Key>>,
modifiers: impl Into<Option<ui_input3::Modifiers>>,
key_meaning: impl Into<KeyMeaningWrapper>,
) -> ui_input3::KeyEvent {
let key_meaning: KeyMeaningWrapper = key_meaning.into();
ui_input3::KeyEvent {
timestamp: Some(timestamp.into_nanos()),
type_: Some(event_type),
key: key.into(),
modifiers: modifiers.into(),
key_meaning: key_meaning.into(),
..Default::default()
}
}