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