Skip to main content

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