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