blob: e8f916cbeaa2dd8b901630ff52a06971c82c7781 [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::UnhandledInputHandler,
anyhow::Error,
async_trait::async_trait,
fidl_fuchsia_input::Key,
fidl_fuchsia_ui_input3::{KeyEvent, KeyEventType, LockState, Modifiers},
fidl_fuchsia_ui_shortcut as ui_shortcut, fuchsia_zircon as zx,
std::rc::Rc,
};
pub struct ShortcutHandler {
/// The proxy to the Shortcut manager service.
manager: ui_shortcut::ManagerProxy,
}
#[async_trait(?Send)]
impl UnhandledInputHandler for ShortcutHandler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
unhandled_input_event: input_device::UnhandledInputEvent,
) -> Vec<input_device::InputEvent> {
match unhandled_input_event {
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(ref keyboard_device_event),
device_descriptor: input_device::InputDeviceDescriptor::Keyboard(_),
event_time,
trace_id: _,
} => {
let key_event = create_key_event(
&keyboard_device_event.get_key(),
keyboard_device_event.get_event_type().clone(),
keyboard_device_event.get_modifiers().clone(),
keyboard_device_event.get_lock_state().clone(),
event_time,
);
// If either pressed_keys or released_keys triggered a shortcut, consume the event
let handled = handle_key_event(key_event, &self.manager).await;
vec![input_device::InputEvent::from(unhandled_input_event).into_handled_if(handled)]
}
_ => vec![input_device::InputEvent::from(unhandled_input_event)],
}
}
}
impl ShortcutHandler {
/// Creates a new [`ShortcutHandler`] and connects Keyboard and Shortcut services.
pub fn new(shortcut_manager_proxy: ui_shortcut::ManagerProxy) -> Result<Rc<Self>, Error> {
let handler = ShortcutHandler { manager: shortcut_manager_proxy };
Ok(Rc::new(handler))
}
}
/// 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 with the KeyEvent.
/// `lock_state`: The modifiers associated with the KeyEvent.
/// `event_time`: The time in nanoseconds when the event was first recorded.
fn create_key_event(
key: &Key,
event_type: KeyEventType,
modifiers: Option<Modifiers>,
lock_state: Option<LockState>,
event_time: zx::Time,
) -> KeyEvent {
KeyEvent {
timestamp: Some(event_time.into_nanos()),
type_: Some(event_type),
key: Some(*key),
modifiers,
lock_state,
..KeyEvent::EMPTY
}
}
/// Returns true if the Shortcut Manager handles the `key_event`.
///
/// # Parameters
/// `key_event`: The KeyEvent to handle by the Shortcut Manager.
/// `shortcut_manager`: The Shortcut Manager
async fn handle_key_event(
key_event: KeyEvent,
shortcut_manager: &ui_shortcut::ManagerProxy,
) -> bool {
match shortcut_manager.handle_key3_event(key_event).await {
Ok(true) => true,
Ok(false) | Err(_) => false,
}
}
#[cfg(test)]
mod tests {
use {
super::*, crate::keyboard_binding, crate::testing_utilities,
assert_matches::assert_matches, fidl_fuchsia_ui_input3 as fidl_ui_input3,
fuchsia_async as fasync, fuchsia_zircon as zx, futures::StreamExt,
pretty_assertions::assert_eq,
};
/// Creates an [`ShortcutHandler`] for tests.
fn create_shortcut_handler(
key_event_consumed_response: bool,
_key2_event_consumed_response: bool,
) -> Rc<ShortcutHandler> {
let (shortcut_manager_proxy, mut shortcut_manager_request_stream) =
fidl::endpoints::create_proxy_and_stream::<ui_shortcut::ManagerMarker>()
.expect("Failed to create ShortcutManagerProxy and stream");
fuchsia_async::Task::local(async move {
loop {
match shortcut_manager_request_stream.next().await {
Some(Ok(ui_shortcut::ManagerRequest::HandleKey3Event {
event: _,
responder,
..
})) => {
responder
.send(key_event_consumed_response)
.expect("error responding to HandleKeyEvent");
}
_ => assert!(false),
}
}
})
.detach();
ShortcutHandler::new(shortcut_manager_proxy).expect("Failed to create ShortcutHandler.")
}
fn create_unhandled_keyboard_event(
key3: fidl_fuchsia_input::Key,
event_type: fidl_fuchsia_ui_input3::KeyEventType,
modifiers: Option<fidl_ui_input3::Modifiers>,
event_time: zx::Time,
) -> input_device::UnhandledInputEvent {
let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor { keys: vec![key3] },
);
let keyboard_event =
keyboard_binding::KeyboardEvent::new(key3, event_type).into_with_modifiers(modifiers);
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(keyboard_event),
device_descriptor,
event_time,
trace_id: None,
}
}
/// Sends a pressed key event to the ShortcutHandler.
async fn press_key(
pressed_key3: fidl_fuchsia_input::Key,
modifiers: Option<fidl_ui_input3::Modifiers>,
event_time: zx::Time,
shortcut_handler: Rc<ShortcutHandler>,
) -> Vec<input_device::InputEvent> {
let input_event = create_unhandled_keyboard_event(
pressed_key3,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
modifiers,
event_time,
);
shortcut_handler.handle_unhandled_input_event(input_event).await
}
/// Sends a release key event to the ShortcutHandler.
async fn release_key(
released_key3: fidl_fuchsia_input::Key,
modifiers: Option<fidl_ui_input3::Modifiers>,
event_time: zx::Time,
shortcut_handler: Rc<ShortcutHandler>,
) -> Vec<input_device::InputEvent> {
let input_event = create_unhandled_keyboard_event(
released_key3,
fidl_fuchsia_ui_input3::KeyEventType::Released,
modifiers,
event_time,
);
shortcut_handler.handle_unhandled_input_event(input_event).await
}
/// Tests that a press key event is not consumed if it is not a shortcut.
#[fasync::run_singlethreaded(test)]
async fn press_key_no_shortcut() {
let shortcut_handler = create_shortcut_handler(false, false);
let modifiers = None;
let key3 = fidl_fuchsia_input::Key::A;
let event_time = zx::Time::get_monotonic();
let handle_result = press_key(key3, modifiers, event_time, shortcut_handler).await;
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::No, .. }]
);
let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor { keys: vec![key3] },
);
let input_event = testing_utilities::create_keyboard_event(
key3,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
modifiers,
event_time,
&device_descriptor,
/* keymap= */ None,
);
assert_eq!(handle_result[0], input_event);
}
/// Tests that a press key shortcut is consumed.
#[fasync::run_singlethreaded(test)]
async fn press_key_activates_shortcut() {
let event_time = zx::Time::get_monotonic();
let shortcut_handler = create_shortcut_handler(true, false);
let handle_result = press_key(
fidl_fuchsia_input::Key::CapsLock,
Some(fidl_ui_input3::Modifiers::CAPS_LOCK),
event_time,
shortcut_handler,
)
.await;
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
/// Tests that a release key event is not consumed if it is not a shortcut.
#[fasync::run_singlethreaded(test)]
async fn release_key_no_shortcut() {
let shortcut_handler = create_shortcut_handler(false, false);
let key3 = fidl_fuchsia_input::Key::A;
let modifiers = None;
let event_time = zx::Time::get_monotonic();
let handle_result = release_key(key3, modifiers, event_time, shortcut_handler).await;
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::No, .. }]
);
let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor { keys: vec![key3] },
);
let input_event = testing_utilities::create_keyboard_event(
key3,
fidl_fuchsia_ui_input3::KeyEventType::Released,
modifiers,
event_time,
&device_descriptor,
/* keymap= */ None,
);
assert_eq!(handle_result[0], input_event);
}
/// Tests that a release key event triggers a registered shortcut.
#[fasync::run_singlethreaded(test)]
async fn release_key_triggers_shortcut() {
let shortcut_handler = create_shortcut_handler(true, false);
let event_time = zx::Time::get_monotonic();
let handle_result = release_key(
fidl_fuchsia_input::Key::CapsLock,
Some(fidl_ui_input3::Modifiers::CAPS_LOCK),
event_time,
shortcut_handler,
)
.await;
assert_matches!(
handle_result.as_slice(),
[input_device::InputEvent { handled: input_device::Handled::Yes, .. }]
);
}
}