input_pipeline/
keymap_handler.rs

1// Copyright 2021 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5//! Implements applying keymaps to hardware keyboard key events.
6//!
7//! See [KeymapHandler] for details.
8
9use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
10use crate::{input_device, keyboard_binding};
11use async_trait::async_trait;
12use fuchsia_inspect::health::Reporter;
13
14use std::cell::RefCell;
15use std::rc::Rc;
16
17/// `KeymapHandler` applies a keymap to a keyboard event, resolving each key press
18/// to a sequence of Unicode code points.  This allows basic keymap application,
19/// but does not lend itself to generalized text editing.
20///
21/// Create a new one with [KeymapHandler::new].
22#[derive(Debug, Default)]
23pub struct KeymapHandler {
24    /// Tracks the state of the modifier keys.
25    modifier_state: RefCell<keymaps::ModifierState>,
26
27    /// Tracks the lock state (NumLock, CapsLock).
28    lock_state: RefCell<keymaps::LockStateKeys>,
29
30    /// The inventory of this handler's Inspect status.
31    pub inspect_status: InputHandlerStatus,
32}
33
34/// This trait implementation allows the [KeymapHandler] to be hooked up into the input
35/// pipeline.
36#[async_trait(?Send)]
37impl UnhandledInputHandler for KeymapHandler {
38    async fn handle_unhandled_input_event(
39        self: Rc<Self>,
40        input_event: input_device::UnhandledInputEvent,
41    ) -> Vec<input_device::InputEvent> {
42        fuchsia_trace::duration!("input", "keymap_handler");
43        match input_event {
44            // Decorate a keyboard event with key meaning.
45            input_device::UnhandledInputEvent {
46                device_event: input_device::InputDeviceEvent::Keyboard(event),
47                device_descriptor,
48                event_time,
49                trace_id,
50            } => {
51                fuchsia_trace::duration!("input", "keymap_handler[processing]");
52                self.inspect_status.count_received_event(&event_time);
53                vec![input_device::InputEvent::from(self.process_keyboard_event(
54                    event,
55                    device_descriptor,
56                    event_time,
57                    trace_id,
58                ))]
59            }
60            // Pass other events unchanged.
61            _ => {
62                // TODO: b/478249522 - add cobalt logging
63                log::warn!("Unhandled input event: {:?}", input_event.get_event_type());
64                vec![input_device::InputEvent::from(input_event)]
65            }
66        }
67    }
68
69    fn set_handler_healthy(self: std::rc::Rc<Self>) {
70        self.inspect_status.health_node.borrow_mut().set_ok();
71    }
72
73    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
74        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
75    }
76
77    fn get_name(&self) -> &'static str {
78        "KeymapHandler"
79    }
80
81    fn interest(&self) -> Vec<input_device::InputEventType> {
82        vec![input_device::InputEventType::Keyboard]
83    }
84}
85
86impl KeymapHandler {
87    /// Creates a new instance of the keymap handler.
88    pub fn new(input_handlers_node: &fuchsia_inspect::Node) -> Rc<Self> {
89        let inspect_status = InputHandlerStatus::new(
90            input_handlers_node,
91            "keymap_handler",
92            /* generates_events */ false,
93        );
94        Rc::new(Self {
95            modifier_state: Default::default(),
96            lock_state: Default::default(),
97            inspect_status,
98        })
99    }
100
101    /// Attaches a key meaning to each passing keyboard event.
102    fn process_keyboard_event(
103        self: &Rc<Self>,
104        event: keyboard_binding::KeyboardEvent,
105        device_descriptor: input_device::InputDeviceDescriptor,
106        event_time: zx::MonotonicInstant,
107        trace_id: Option<fuchsia_trace::Id>,
108    ) -> input_device::UnhandledInputEvent {
109        if let Some(trace_id) = trace_id {
110            fuchsia_trace::flow_step!("input", "event_in_input_pipeline", trace_id.into());
111        }
112
113        let (key, event_type) = (event.get_key(), event.get_event_type());
114        log::debug!(
115            concat!(
116                "Keymap::process_keyboard_event: key:{:?}, ",
117                "modifier_state:{:?}, lock_state: {:?}, event_type: {:?}"
118            ),
119            key,
120            self.modifier_state.borrow(),
121            self.lock_state.borrow(),
122            event_type
123        );
124
125        self.modifier_state.borrow_mut().update(event_type, key);
126        self.lock_state.borrow_mut().update(event_type, key);
127        let key_meaning = keymaps::select_keymap(&event.get_keymap()).apply(
128            key,
129            &*self.modifier_state.borrow(),
130            &*self.lock_state.borrow(),
131        );
132        input_device::UnhandledInputEvent {
133            device_event: input_device::InputDeviceEvent::Keyboard(
134                event.into_with_key_meaning(key_meaning),
135            ),
136            device_descriptor,
137            event_time,
138            trace_id,
139        }
140    }
141}
142
143#[cfg(test)]
144mod tests {
145    use super::*;
146    use crate::input_handler::InputHandler;
147    use crate::{consumer_controls_binding, testing_utilities};
148    use pretty_assertions::assert_eq;
149    use std::convert::TryFrom as _;
150    use {
151        fidl_fuchsia_input as finput, fidl_fuchsia_ui_input3 as finput3, fuchsia_async as fasync,
152        zx,
153    };
154
155    // A mod-specific version of `testing_utilities::create_keyboard_event`.
156    fn create_unhandled_keyboard_event(
157        key: finput::Key,
158        event_type: finput3::KeyEventType,
159        keymap: Option<String>,
160    ) -> input_device::UnhandledInputEvent {
161        let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
162            keyboard_binding::KeyboardDeviceDescriptor {
163                keys: vec![finput::Key::A, finput::Key::B],
164                ..Default::default()
165            },
166        );
167        let (_, event_time_u64) = testing_utilities::event_times();
168        input_device::UnhandledInputEvent::try_from(
169            testing_utilities::create_keyboard_event_with_time(
170                key,
171                event_type,
172                /* modifiers= */ None,
173                event_time_u64,
174                &device_descriptor,
175                keymap,
176            ),
177        )
178        .unwrap()
179    }
180
181    // A mod-specific version of `testing_utilities::create_consumer_controls_event`.
182    fn create_unhandled_consumer_controls_event(
183        pressed_buttons: Vec<fidl_fuchsia_input_report::ConsumerControlButton>,
184        event_time: zx::MonotonicInstant,
185        device_descriptor: &input_device::InputDeviceDescriptor,
186    ) -> input_device::UnhandledInputEvent {
187        input_device::UnhandledInputEvent::try_from(
188            testing_utilities::create_consumer_controls_event(
189                pressed_buttons,
190                event_time,
191                device_descriptor,
192            ),
193        )
194        .unwrap()
195    }
196
197    fn get_key_meaning(event: &input_device::InputEvent) -> Option<finput3::KeyMeaning> {
198        match event {
199            input_device::InputEvent {
200                device_event: input_device::InputDeviceEvent::Keyboard(event),
201                ..
202            } => event.get_key_meaning(),
203            _ => None,
204        }
205    }
206
207    #[fasync::run_singlethreaded(test)]
208    async fn test_keymap_application() {
209        // Not using test_case crate because it does not compose very well with
210        // async test execution.
211        #[derive(Debug)]
212        struct TestCase {
213            events: Vec<input_device::UnhandledInputEvent>,
214            expected: Vec<Option<finput3::KeyMeaning>>,
215        }
216        let tests: Vec<TestCase> = vec![
217            TestCase {
218                events: vec![create_unhandled_keyboard_event(
219                    finput::Key::A,
220                    finput3::KeyEventType::Pressed,
221                    Some("US_QWERTY".into()),
222                )],
223                expected: vec![
224                    Some(finput3::KeyMeaning::Codepoint(97)), // a
225                ],
226            },
227            TestCase {
228                // A non-keyboard event.
229                events: vec![create_unhandled_consumer_controls_event(
230                    vec![],
231                    zx::MonotonicInstant::ZERO,
232                    &input_device::InputDeviceDescriptor::ConsumerControls(
233                        consumer_controls_binding::ConsumerControlsDeviceDescriptor {
234                            buttons: vec![],
235                            device_id: 0,
236                        },
237                    ),
238                )],
239                expected: vec![None],
240            },
241            TestCase {
242                events: vec![
243                    create_unhandled_keyboard_event(
244                        finput::Key::LeftShift,
245                        finput3::KeyEventType::Pressed,
246                        Some("US_QWERTY".into()),
247                    ),
248                    create_unhandled_keyboard_event(
249                        finput::Key::A,
250                        finput3::KeyEventType::Pressed,
251                        Some("US_QWERTY".into()),
252                    ),
253                ],
254                expected: vec![
255                    Some(finput3::KeyMeaning::NonPrintableKey(finput3::NonPrintableKey::Shift)),
256                    Some(finput3::KeyMeaning::Codepoint(65)), // A
257                ],
258            },
259            TestCase {
260                events: vec![
261                    create_unhandled_keyboard_event(
262                        finput::Key::Tab,
263                        finput3::KeyEventType::Pressed,
264                        Some("US_QWERTY".into()),
265                    ),
266                    create_unhandled_keyboard_event(
267                        finput::Key::A,
268                        finput3::KeyEventType::Pressed,
269                        Some("US_QWERTY".into()),
270                    ),
271                ],
272                expected: vec![
273                    Some(finput3::KeyMeaning::NonPrintableKey(finput3::NonPrintableKey::Tab)),
274                    Some(finput3::KeyMeaning::Codepoint(97)), // a
275                ],
276            },
277        ];
278        let inspector = fuchsia_inspect::Inspector::default();
279        let test_node = inspector.root().create_child("test_node");
280        for test in &tests {
281            let mut actual: Vec<Option<finput3::KeyMeaning>> = vec![];
282            let handler = KeymapHandler::new(&test_node);
283            for event in &test.events {
284                let mut result = handler
285                    .clone()
286                    .handle_unhandled_input_event(event.clone())
287                    .await
288                    .iter()
289                    .map(get_key_meaning)
290                    .collect();
291                actual.append(&mut result);
292            }
293            assert_eq!(test.expected, actual);
294        }
295    }
296
297    #[fuchsia::test]
298    async fn keymap_handler_initialized_with_inspect_node() {
299        let inspector = fuchsia_inspect::Inspector::default();
300        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
301        let _handler = KeymapHandler::new(&fake_handlers_node);
302        diagnostics_assertions::assert_data_tree!(inspector, root: {
303            input_handlers_node: {
304                keymap_handler: {
305                    events_received_count: 0u64,
306                    events_handled_count: 0u64,
307                    last_received_timestamp_ns: 0u64,
308                    "fuchsia.inspect.Health": {
309                        status: "STARTING_UP",
310                        // Timestamp value is unpredictable and not relevant in this context,
311                        // so we only assert that the property is present.
312                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
313                    },
314                }
315            }
316        });
317    }
318
319    #[fasync::run_singlethreaded(test)]
320    async fn keymap_handler_inspect_counts_events() {
321        let inspector = fuchsia_inspect::Inspector::default();
322        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
323        let keymap_handler = KeymapHandler::new(&fake_handlers_node);
324        let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
325            keyboard_binding::KeyboardDeviceDescriptor {
326                keys: vec![finput::Key::A, finput::Key::B],
327                ..Default::default()
328            },
329        );
330        let (_, event_time_u64) = testing_utilities::event_times();
331        let input_events = vec![
332            testing_utilities::create_keyboard_event_with_time(
333                finput::Key::A,
334                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
335                None,
336                event_time_u64,
337                &device_descriptor,
338                /* keymap= */ None,
339            ),
340            // Should not count received events that have already been handled.
341            testing_utilities::create_keyboard_event_with_handled(
342                finput::Key::B,
343                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
344                None,
345                event_time_u64,
346                &device_descriptor,
347                /* keymap= */ None,
348                /* key_meaning= */ None,
349                input_device::Handled::Yes,
350            ),
351            testing_utilities::create_keyboard_event_with_time(
352                finput::Key::A,
353                fidl_fuchsia_ui_input3::KeyEventType::Released,
354                None,
355                event_time_u64,
356                &device_descriptor,
357                /* keymap= */ None,
358            ),
359            // Should not count non-keyboard input events.
360            testing_utilities::create_fake_input_event(event_time_u64),
361            testing_utilities::create_keyboard_event_with_time(
362                finput::Key::B,
363                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
364                None,
365                event_time_u64,
366                &device_descriptor,
367                /* keymap= */ None,
368            ),
369        ];
370
371        for input_event in input_events {
372            let _ = keymap_handler.clone().handle_input_event(input_event).await;
373        }
374
375        let last_event_timestamp: u64 = event_time_u64.into_nanos().try_into().unwrap();
376
377        diagnostics_assertions::assert_data_tree!(inspector, root: {
378            input_handlers_node: {
379                keymap_handler: {
380                    events_received_count: 3u64,
381                    events_handled_count: 0u64,
382                    last_received_timestamp_ns: last_event_timestamp,
383                    "fuchsia.inspect.Health": {
384                        status: "STARTING_UP",
385                        // Timestamp value is unpredictable and not relevant in this context,
386                        // so we only assert that the property is present.
387                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
388                    },
389                }
390            }
391        });
392    }
393}