use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
use crate::{input_device, keyboard_binding};
use async_trait::async_trait;
use fuchsia_inspect::health::Reporter;
use std::cell::RefCell;
use std::rc::Rc;
#[derive(Debug, Default)]
pub struct KeymapHandler {
modifier_state: RefCell<keymaps::ModifierState>,
lock_state: RefCell<keymaps::LockStateKeys>,
pub inspect_status: InputHandlerStatus,
}
#[async_trait(?Send)]
impl UnhandledInputHandler for KeymapHandler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
input_event: input_device::UnhandledInputEvent,
) -> Vec<input_device::InputEvent> {
match input_event.clone() {
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(event),
device_descriptor,
event_time,
trace_id: _,
} => {
self.inspect_status
.count_received_event(input_device::InputEvent::from(input_event));
vec![input_device::InputEvent::from(self.process_keyboard_event(
event,
device_descriptor,
event_time,
))]
}
_ => vec![input_device::InputEvent::from(input_event)],
}
}
fn set_handler_healthy(self: std::rc::Rc<Self>) {
self.inspect_status.health_node.borrow_mut().set_ok();
}
fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
}
}
impl KeymapHandler {
pub fn new(input_handlers_node: &fuchsia_inspect::Node) -> Rc<Self> {
let inspect_status = InputHandlerStatus::new(
input_handlers_node,
"keymap_handler",
false,
);
Rc::new(Self {
modifier_state: Default::default(),
lock_state: Default::default(),
inspect_status,
})
}
fn process_keyboard_event(
self: &Rc<Self>,
event: keyboard_binding::KeyboardEvent,
device_descriptor: input_device::InputDeviceDescriptor,
event_time: zx::MonotonicInstant,
) -> input_device::UnhandledInputEvent {
let (key, event_type) = (event.get_key(), event.get_event_type());
tracing::debug!(
concat!(
"Keymap::process_keyboard_event: key:{:?}, ",
"modifier_state:{:?}, lock_state: {:?}, event_type: {:?}"
),
key,
self.modifier_state.borrow(),
self.lock_state.borrow(),
event_type
);
self.modifier_state.borrow_mut().update(event_type, key);
self.lock_state.borrow_mut().update(event_type, key);
let key_meaning = keymaps::select_keymap(&event.get_keymap()).apply(
key,
&*self.modifier_state.borrow(),
&*self.lock_state.borrow(),
);
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(
event.into_with_key_meaning(key_meaning),
),
device_descriptor,
event_time,
trace_id: None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input_handler::InputHandler;
use crate::{consumer_controls_binding, testing_utilities};
use pretty_assertions::assert_eq;
use std::convert::TryFrom as _;
use {
fidl_fuchsia_input as finput, fidl_fuchsia_ui_input3 as finput3, fuchsia_async as fasync,
zx,
};
fn create_unhandled_keyboard_event(
key: finput::Key,
event_type: finput3::KeyEventType,
keymap: Option<String>,
) -> input_device::UnhandledInputEvent {
let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor {
keys: vec![finput::Key::A, finput::Key::B],
..Default::default()
},
);
let (_, event_time_u64) = testing_utilities::event_times();
input_device::UnhandledInputEvent::try_from(
testing_utilities::create_keyboard_event_with_time(
key,
event_type,
None,
event_time_u64,
&device_descriptor,
keymap,
),
)
.unwrap()
}
fn create_unhandled_consumer_controls_event(
pressed_buttons: Vec<fidl_fuchsia_input_report::ConsumerControlButton>,
event_time: zx::MonotonicInstant,
device_descriptor: &input_device::InputDeviceDescriptor,
) -> input_device::UnhandledInputEvent {
input_device::UnhandledInputEvent::try_from(
testing_utilities::create_consumer_controls_event(
pressed_buttons,
event_time,
device_descriptor,
),
)
.unwrap()
}
fn get_key_meaning(event: &input_device::InputEvent) -> Option<finput3::KeyMeaning> {
match event {
input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(event),
..
} => event.get_key_meaning(),
_ => None,
}
}
#[fasync::run_singlethreaded(test)]
async fn test_keymap_application() {
#[derive(Debug)]
struct TestCase {
events: Vec<input_device::UnhandledInputEvent>,
expected: Vec<Option<finput3::KeyMeaning>>,
}
let tests: Vec<TestCase> = vec![
TestCase {
events: vec![create_unhandled_keyboard_event(
finput::Key::A,
finput3::KeyEventType::Pressed,
Some("US_QWERTY".into()),
)],
expected: vec![
Some(finput3::KeyMeaning::Codepoint(97)), ],
},
TestCase {
events: vec![create_unhandled_consumer_controls_event(
vec![],
zx::MonotonicInstant::ZERO,
&input_device::InputDeviceDescriptor::ConsumerControls(
consumer_controls_binding::ConsumerControlsDeviceDescriptor {
buttons: vec![],
device_id: 0,
},
),
)],
expected: vec![None],
},
TestCase {
events: vec![
create_unhandled_keyboard_event(
finput::Key::LeftShift,
finput3::KeyEventType::Pressed,
Some("US_QWERTY".into()),
),
create_unhandled_keyboard_event(
finput::Key::A,
finput3::KeyEventType::Pressed,
Some("US_QWERTY".into()),
),
],
expected: vec![
Some(finput3::KeyMeaning::NonPrintableKey(finput3::NonPrintableKey::Shift)),
Some(finput3::KeyMeaning::Codepoint(65)), ],
},
TestCase {
events: vec![
create_unhandled_keyboard_event(
finput::Key::Tab,
finput3::KeyEventType::Pressed,
Some("US_QWERTY".into()),
),
create_unhandled_keyboard_event(
finput::Key::A,
finput3::KeyEventType::Pressed,
Some("US_QWERTY".into()),
),
],
expected: vec![
Some(finput3::KeyMeaning::NonPrintableKey(finput3::NonPrintableKey::Tab)),
Some(finput3::KeyMeaning::Codepoint(97)), ],
},
];
let inspector = fuchsia_inspect::Inspector::default();
let test_node = inspector.root().create_child("test_node");
for test in &tests {
let mut actual: Vec<Option<finput3::KeyMeaning>> = vec![];
let handler = KeymapHandler::new(&test_node);
for event in &test.events {
let mut result = handler
.clone()
.handle_unhandled_input_event(event.clone())
.await
.iter()
.map(get_key_meaning)
.collect();
actual.append(&mut result);
}
assert_eq!(test.expected, actual);
}
}
#[fuchsia::test]
fn keymap_handler_initialized_with_inspect_node() {
let inspector = fuchsia_inspect::Inspector::default();
let fake_handlers_node = inspector.root().create_child("input_handlers_node");
let _handler = KeymapHandler::new(&fake_handlers_node);
diagnostics_assertions::assert_data_tree!(inspector, root: {
input_handlers_node: {
keymap_handler: {
events_received_count: 0u64,
events_handled_count: 0u64,
last_received_timestamp_ns: 0u64,
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
#[fasync::run_singlethreaded(test)]
async fn keymap_handler_inspect_counts_events() {
let inspector = fuchsia_inspect::Inspector::default();
let fake_handlers_node = inspector.root().create_child("input_handlers_node");
let keymap_handler = KeymapHandler::new(&fake_handlers_node);
let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
keyboard_binding::KeyboardDeviceDescriptor {
keys: vec![finput::Key::A, finput::Key::B],
..Default::default()
},
);
let (_, event_time_u64) = testing_utilities::event_times();
let input_events = vec![
testing_utilities::create_keyboard_event_with_time(
finput::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
None,
event_time_u64,
&device_descriptor,
None,
),
testing_utilities::create_keyboard_event_with_handled(
finput::Key::B,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
None,
event_time_u64,
&device_descriptor,
None,
None,
input_device::Handled::Yes,
),
testing_utilities::create_keyboard_event_with_time(
finput::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Released,
None,
event_time_u64,
&device_descriptor,
None,
),
testing_utilities::create_fake_input_event(event_time_u64),
testing_utilities::create_keyboard_event_with_time(
finput::Key::B,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
None,
event_time_u64,
&device_descriptor,
None,
),
];
for input_event in input_events {
let _ = keymap_handler.clone().handle_input_event(input_event).await;
}
let last_event_timestamp: u64 = event_time_u64.into_nanos().try_into().unwrap();
diagnostics_assertions::assert_data_tree!(inspector, root: {
input_handlers_node: {
keymap_handler: {
events_received_count: 3u64,
events_handled_count: 0u64,
last_received_timestamp_ns: last_event_timestamp,
"fuchsia.inspect.Health": {
status: "STARTING_UP",
start_timestamp_nanos: diagnostics_assertions::AnyProperty
},
}
}
});
}
}