Skip to main content

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