blob: 34736d5aba730ef1d8dc6b014adc7112e3d7c628 [file] [log] [blame]
// 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_device, crate::input_handler::InputHandler, anyhow::Error,
async_trait::async_trait, fidl_fuchsia_input as fidl_input,
fidl_fuchsia_ui_input as fidl_ui_input, fidl_fuchsia_ui_input3 as fidl_ui_input3,
fuchsia_component::client::connect_to_service, fuchsia_scenic as scenic,
fuchsia_syslog::fx_log_err, input_synthesis::keymaps::QWERTY_MAP,
input_synthesis::usages::input3_key_to_hid_usage, std::convert::TryInto,
};
/// [`ImeHandler`] is responsible for dispatching key events to the IME service, thus making sure
/// that key events are delivered to application runtimes (e.g., web, Flutter).
///
/// Key events are also sent to Scenic, as some application runtimes rely on Scenic to deliver
/// key events.
pub struct ImeHandler {
/// The proxy to the IME service.
ime_proxy: fidl_ui_input::ImeServiceProxy,
/// The Scenic session to send keyboard events to.
scenic_session: scenic::SessionPtr,
/// The id of the compositor used for the scene's layer stack. This is used when sending
/// keyboard events to the `scenic_session`.
scenic_compositor_id: u32,
}
#[async_trait]
impl InputHandler for ImeHandler {
async fn handle_input_event(
&mut self,
input_event: input_device::InputEvent,
) -> Vec<input_device::InputEvent> {
match input_event {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(keyboard_device_event),
device_descriptor:
input_device::InputDeviceDescriptor::Keyboard(_keyboard_device_descriptor),
event_time,
} => {
let pressed_key_events: Vec<fidl_ui_input3::KeyEvent> = keyboard_device_event
.get_keys3(fidl_ui_input3::KeyEventType::Pressed)
.into_iter()
.map(|key| {
create_key_event(
&key,
fidl_ui_input3::KeyEventType::Pressed,
keyboard_device_event.modifiers3,
event_time,
)
})
.collect();
let pressed_keyboard_events =
self.dispatch_keys(pressed_key_events, event_time).await;
let released_key_events: Vec<fidl_ui_input3::KeyEvent> = keyboard_device_event
.get_keys3(fidl_ui_input3::KeyEventType::Released)
.into_iter()
.map(|key| {
create_key_event(
&key,
fidl_ui_input3::KeyEventType::Released,
keyboard_device_event.modifiers3,
event_time,
)
})
.collect();
let released_keyboard_events =
self.dispatch_keys(released_key_events, event_time).await;
let keyboard_events =
pressed_keyboard_events.into_iter().chain(released_keyboard_events).collect();
// Dispatch KeyboardEvents to Scenic.
send_events_to_scenic(
keyboard_events,
self.scenic_session.clone(),
self.scenic_compositor_id,
);
// Consume the input event.
vec![]
}
no_match => vec![no_match],
}
}
}
#[allow(dead_code)]
impl ImeHandler {
/// Creates a new [`ImeHandler`] and connects to IME and Keyboard services.
///
/// # Parameters
/// `scenic_session`: The Scenic session to send keyboard events to.
/// `scenic_compositor_id`: The id of the compositor used for the scene's layer stack.
pub async fn new(
scenic_session: scenic::SessionPtr,
scenic_compositor_id: u32,
) -> Result<Self, Error> {
let ime = connect_to_service::<fidl_ui_input::ImeServiceMarker>()?;
Self::new_handler(scenic_session, scenic_compositor_id, ime).await
}
/// Creates a new [`ImeHandler`].
///
/// # Parameters
/// `scenic_session`: The Scenic session to send keyboard events to.
/// `scenic_compositor_id`: The id of the compositor used for the scene's layer stack.
/// `ime_proxy`: A proxy to the IME service.
async fn new_handler(
scenic_session: scenic::SessionPtr,
scenic_compositor_id: u32,
ime_proxy: fidl_ui_input::ImeServiceProxy,
) -> Result<Self, Error> {
let handler = ImeHandler { ime_proxy, scenic_session, scenic_compositor_id };
Ok(handler)
}
/// Dispatches key events to IME and returns KeyboardEvents for unhandled events.
///
/// # Parameters
/// `key_events`: The key events to dispatch.
/// `event_time`: The time in nanoseconds when the events were first recorded.
///
/// # Returns
/// A vector of KeyboardEvents to be sent to the Scenic session.
/// This will be removed once Scenic no longer handles keyboard events.
async fn dispatch_keys(
&mut self,
key_events: Vec<fidl_ui_input3::KeyEvent>,
event_time: input_device::EventTime,
) -> Vec<fidl_ui_input::KeyboardEvent> {
let mut events: Vec<fidl_ui_input::KeyboardEvent> = vec![];
for key_event in key_events {
let keyboard_event = create_keyboard_event(&key_event, event_time);
match self.ime_proxy.dispatch_key3(key_event).await {
Ok(was_handled) => {
if !was_handled {
events.push(keyboard_event);
}
}
Err(err) => fx_log_err!("Failed to dispatch key to IME: {:?}", err),
};
}
events
}
}
/// Returns a KeyEvent with the given parameters.
///
/// # Parameters
/// `key`: The key associated with the KeyEvent.
/// `event_type`: The type of key, either pressed or released.
/// `modifiers`: The modifiers associated the KeyEvent.
/// `event_time`: The time in nanoseconds when the event was first recorded.
fn create_key_event(
key: &fidl_input::Key,
event_type: fidl_ui_input3::KeyEventType,
modifiers: Option<fidl_ui_input3::Modifiers>,
event_time: input_device::EventTime,
) -> fidl_ui_input3::KeyEvent {
fidl_ui_input3::KeyEvent {
timestamp: Some(event_time.try_into().unwrap_or_default()),
type_: Some(event_type),
key: Some(*key),
modifiers,
..fidl_ui_input3::KeyEvent::EMPTY
}
}
/// Returns a Keyboard with the given parameters.
///
/// # Parameters
/// `event`: The KeyEvent to create the KeyboardEvent from.
/// `event_time`: The time in nanoseconds when the event was first recorded.
fn create_keyboard_event(
event: &fidl_ui_input3::KeyEvent,
event_time: input_device::EventTime,
) -> fidl_ui_input::KeyboardEvent {
let phase = match event.type_ {
Some(fidl_ui_input3::KeyEventType::Released) => fidl_ui_input::KeyboardEventPhase::Released,
_ => fidl_ui_input::KeyboardEventPhase::Pressed,
};
let modifiers: u32 = event
.modifiers
.map(|modifiers| modifiers.bits())
.unwrap_or_default()
.try_into()
.unwrap_or_default();
let hid_usage = event.key.map(input3_key_to_hid_usage).unwrap_or_default();
let code_point = hid_usage_to_codepoint(hid_usage).unwrap_or(0);
fidl_ui_input::KeyboardEvent {
event_time,
device_id: 1,
phase,
hid_usage,
code_point,
modifiers,
}
}
/// Returns a Unicode codepoint that corresponds with the USB HID code.
///
/// This assumes a standard "qwerty" keymap. Note that this only returns
/// lowercase characters.
///
/// # Parameters
/// `hid_usage`: The USB HID code for a physical key.
fn hid_usage_to_codepoint(hid_usage: u32) -> Option<u32> {
let hid_usage_size = hid_usage as usize;
if hid_usage_size < QWERTY_MAP.len() {
QWERTY_MAP[hid_usage_size].map(|entry| entry.0 as u32)
} else {
None
}
}
/// Sends `keyboard_events` to the Scenic session.
///
/// This will be removed once Scenic no longer handles keyboard events.
///
/// # Parameters
/// `keyboard_events`: The events to send to the Scenic session.
/// `scenic_session`: The Scenic session.
/// `scenic_compositor_id`: The id of the compositor used for the scene's layer stack.
fn send_events_to_scenic(
keyboard_events: Vec<fidl_ui_input::KeyboardEvent>,
scenic_session: scenic::SessionPtr,
scenic_compositor_id: u32,
) {
for keyboard_event in keyboard_events {
let command =
fidl_ui_input::Command::SendKeyboardInput(fidl_ui_input::SendKeyboardInputCmd {
compositor_id: scenic_compositor_id,
keyboard_event,
});
scenic_session.lock().enqueue(fidl_fuchsia_ui_scenic::Command::Input(command));
scenic_session.lock().flush()
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::keyboard, crate::testing_utilities, fidl_fuchsia_input as fidl_input,
fidl_fuchsia_ui_input2 as fidl_ui_input2, fidl_fuchsia_ui_input3 as fidl_ui_input3,
fidl_fuchsia_ui_scenic as fidl_ui_scenic, fuchsia_async as fasync, fuchsia_zircon as zx,
futures::StreamExt,
};
const COMPOSITOR_ID: u32 = 1;
/// Creates an [`ImeHandler`] for tests.
///
/// This routes key events from the IME service to a key listener.
///
/// # Parameters
/// - `scenic_session`: The Scenic session to send keyboard events to.
async fn create_ime_handler(scenic_session: scenic::SessionPtr) -> ImeHandler {
let (ime_proxy, mut ime_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_input::ImeServiceMarker>()
.expect("Failed to create ImeProxy and stream.");
fuchsia_async::Task::spawn(async move {
loop {
match ime_request_stream.next().await {
Some(Ok(fidl_ui_input::ImeServiceRequest::DispatchKey3 {
event: _,
responder,
..
})) => {
// Respond that the event was not handled so that all events are
// dispatched to Scenic.
responder.send(false).expect("error responding to DispatchKey");
}
_ => assert!(false),
}
}
})
.detach();
ImeHandler::new_handler(scenic_session, COMPOSITOR_ID, ime_proxy)
.await
.expect("Failed to create ImeHandler.")
}
/// Validates the first event in `cmds` against `expected_event`.
///
/// # Parameters
/// - `commands`: The commands received by the Scenic session.
/// - `expected_event`: The expected event.
fn verify_keyboard_event(
command: fidl_ui_scenic::Command,
expected_event: fidl_ui_input::KeyboardEvent,
) {
match command {
fidl_ui_scenic::Command::Input(fidl_ui_input::Command::SendKeyboardInput(
fidl_ui_input::SendKeyboardInputCmd {
compositor_id: _,
keyboard_event:
fidl_ui_input::KeyboardEvent {
event_time,
device_id,
phase,
hid_usage,
code_point,
modifiers,
},
},
)) => {
assert_eq!(event_time, expected_event.event_time);
assert_eq!(device_id, expected_event.device_id);
assert_eq!(phase, expected_event.phase);
assert_eq!(hid_usage, expected_event.hid_usage);
assert_eq!(code_point, expected_event.code_point);
assert_eq!(modifiers, expected_event.modifiers);
}
_ => {
assert!(false);
}
}
}
/// Tests that a pressed key event is dispatched.
#[fasync::run_singlethreaded(test)]
async fn pressed_key() {
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let mut ime_handler = create_ime_handler(scenic_session.clone()).await;
let device_descriptor =
input_device::InputDeviceDescriptor::Keyboard(keyboard::KeyboardDeviceDescriptor {
keys2: vec![fidl_ui_input2::Key::A],
keys3: vec![fidl_input::Key::A],
});
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![testing_utilities::create_keyboard_event(
vec![fidl_ui_input2::Key::A],
vec![fidl_input::Key::A],
vec![],
vec![],
None,
None,
event_time,
&device_descriptor,
)];
let expected_commands = vec![fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Pressed,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::A),
code_point: 'a' as u32,
modifiers: 0,
}];
assert_input_event_sequence_generates_scenic_events!(
input_handler: ime_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_keyboard_event,
);
}
/// Tests that a released key event is dispatched.
#[fasync::run_singlethreaded(test)]
async fn released_key() {
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let mut ime_handler = create_ime_handler(scenic_session.clone()).await;
let device_descriptor =
input_device::InputDeviceDescriptor::Keyboard(keyboard::KeyboardDeviceDescriptor {
keys2: vec![fidl_ui_input2::Key::A],
keys3: vec![fidl_input::Key::A],
});
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events = vec![testing_utilities::create_keyboard_event(
vec![],
vec![],
vec![fidl_ui_input2::Key::A],
vec![fidl_input::Key::A],
None,
None,
event_time,
&device_descriptor,
)];
let expected_commands = vec![fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Released,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::A),
code_point: 'a' as u32,
modifiers: 0,
}];
assert_input_event_sequence_generates_scenic_events!(
input_handler: ime_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_keyboard_event,
);
}
/// Tests that both pressed and released keys are dispatched appropriately.
#[fasync::run_singlethreaded(test)]
async fn pressed_and_released_key() {
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let mut ime_handler = create_ime_handler(scenic_session.clone()).await;
let device_descriptor =
input_device::InputDeviceDescriptor::Keyboard(keyboard::KeyboardDeviceDescriptor {
keys2: vec![fidl_ui_input2::Key::A, fidl_ui_input2::Key::B],
keys3: vec![fidl_input::Key::A, fidl_input::Key::B],
});
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events: Vec<input_device::InputEvent> = vec![
testing_utilities::create_keyboard_event(
vec![fidl_ui_input2::Key::A],
vec![fidl_input::Key::A],
vec![],
vec![],
None,
None,
event_time,
&device_descriptor,
),
testing_utilities::create_keyboard_event(
vec![fidl_ui_input2::Key::B],
vec![fidl_input::Key::B],
vec![fidl_ui_input2::Key::A],
vec![fidl_input::Key::A],
None,
None,
event_time,
&device_descriptor,
),
];
let expected_commands = vec![
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Pressed,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::A),
code_point: 'a' as u32,
modifiers: 0,
},
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Pressed,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::B),
code_point: 'b' as u32,
modifiers: 0,
},
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Released,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::A),
code_point: 'a' as u32,
modifiers: 0,
},
];
assert_input_event_sequence_generates_scenic_events!(
input_handler: ime_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_keyboard_event,
);
}
/// Tests that modifier keys are dispatched appropriately.
#[fasync::run_singlethreaded(test)]
async fn repeated_modifier_key() {
let (session_proxy, mut session_request_stream) =
fidl::endpoints::create_proxy_and_stream::<fidl_ui_scenic::SessionMarker>()
.expect("Failed to create ScenicProxy and stream.");
let scenic_session: scenic::SessionPtr = scenic::Session::new(session_proxy);
let mut ime_handler = create_ime_handler(scenic_session.clone()).await;
let device_descriptor =
input_device::InputDeviceDescriptor::Keyboard(keyboard::KeyboardDeviceDescriptor {
keys2: vec![fidl_ui_input2::Key::A, fidl_ui_input2::Key::CapsLock],
keys3: vec![fidl_input::Key::A, fidl_input::Key::CapsLock],
});
let event_time = zx::Time::get_monotonic().into_nanos() as input_device::EventTime;
let input_events: Vec<input_device::InputEvent> = vec![
testing_utilities::create_keyboard_event(
vec![fidl_ui_input2::Key::CapsLock],
vec![fidl_input::Key::CapsLock],
vec![],
vec![],
Some(fidl_ui_input2::Modifiers::CapsLock),
Some(fidl_ui_input3::Modifiers::CapsLock),
event_time,
&device_descriptor,
),
testing_utilities::create_keyboard_event(
vec![fidl_ui_input2::Key::A],
vec![fidl_input::Key::A],
vec![],
vec![],
Some(fidl_ui_input2::Modifiers::CapsLock),
Some(fidl_ui_input3::Modifiers::CapsLock),
event_time,
&device_descriptor,
),
testing_utilities::create_keyboard_event(
vec![],
vec![],
vec![fidl_ui_input2::Key::CapsLock],
vec![fidl_input::Key::CapsLock],
None,
None,
event_time,
&device_descriptor,
),
];
let expected_commands = vec![
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Pressed,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::CapsLock),
code_point: 0,
modifiers: 1, // fidl_ui_input3::Modifiers::CapsLock
},
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Pressed,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::A),
code_point: 'a' as u32,
modifiers: 1, // fidl_ui_input3::Modifiers::CapsLock
},
fidl_ui_input::KeyboardEvent {
event_time: event_time,
device_id: 1,
phase: fidl_ui_input::KeyboardEventPhase::Released,
hid_usage: input3_key_to_hid_usage(fidl_input::Key::CapsLock),
code_point: 0,
modifiers: 0,
},
];
assert_input_event_sequence_generates_scenic_events!(
input_handler: ime_handler,
input_events: input_events,
expected_commands: expected_commands,
scenic_session_request_stream: session_request_stream,
assert_command: verify_keyboard_event,
);
}
}