input_pipeline/
text_settings_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
5use crate::input_handler::{InputHandlerStatus, UnhandledInputHandler};
6use crate::{autorepeater, input_device, metrics};
7use anyhow::{Context, Error, Result};
8use async_trait::async_trait;
9use async_utils::hanging_get::client::HangingGetStream;
10use fuchsia_inspect::health::Reporter;
11use futures::{TryFutureExt, TryStreamExt};
12use metrics_registry::*;
13use std::cell::RefCell;
14use std::rc::Rc;
15use {fidl_fuchsia_input as finput, fidl_fuchsia_settings as fsettings, fuchsia_async as fasync};
16
17/// The text settings handler instance. Refer to as `text_settings_handler::TextSettingsHandler`.
18/// Its task is to decorate an input event with the keymap identifier.  The instance can
19/// be freely cloned, each clone is thread-safely sharing data with others.
20pub struct TextSettingsHandler {
21    /// Stores the currently active keymap identifier, if present.  Wrapped
22    /// in an refcell as it can be changed out of band through
23    /// `fuchsia.input.keymap.Configuration/SetLayout`.
24    keymap_id: RefCell<Option<finput::KeymapId>>,
25
26    /// Stores the currently active autorepeat settings.
27    autorepeat_settings: RefCell<Option<autorepeater::Settings>>,
28
29    /// The inventory of this handler's Inspect status.
30    pub inspect_status: InputHandlerStatus,
31
32    /// The metrics logger.
33    metrics_logger: metrics::MetricsLogger,
34}
35
36#[async_trait(?Send)]
37impl UnhandledInputHandler for TextSettingsHandler {
38    async fn handle_unhandled_input_event(
39        self: Rc<Self>,
40        unhandled_input_event: input_device::UnhandledInputEvent,
41    ) -> Vec<input_device::InputEvent> {
42        match unhandled_input_event.clone() {
43            input_device::UnhandledInputEvent {
44                device_event: input_device::InputDeviceEvent::Keyboard(mut event),
45                device_descriptor,
46                event_time,
47                trace_id,
48            } => {
49                fuchsia_trace::duration!(c"input", c"text_settings_handler");
50                if let Some(trace_id) = trace_id {
51                    fuchsia_trace::flow_step!(
52                        c"input",
53                        c"event_in_input_pipeline",
54                        trace_id.into()
55                    );
56                }
57
58                self.inspect_status.count_received_event(&event_time);
59                let keymap_id = self.get_keymap_name();
60                log::debug!(
61                    "text_settings_handler::Instance::handle_unhandled_input_event: keymap_id = {:?}",
62                    &keymap_id
63                );
64                event = event
65                    .into_with_keymap(keymap_id)
66                    .into_with_autorepeat_settings(self.get_autorepeat_settings());
67                vec![input_device::InputEvent {
68                    device_event: input_device::InputDeviceEvent::Keyboard(event),
69                    device_descriptor,
70                    event_time,
71                    handled: input_device::Handled::No,
72                    trace_id,
73                }]
74            }
75            // Pass a non-keyboard event through.
76            _ => vec![input_device::InputEvent::from(unhandled_input_event)],
77        }
78    }
79
80    fn set_handler_healthy(self: std::rc::Rc<Self>) {
81        self.inspect_status.health_node.borrow_mut().set_ok();
82    }
83
84    fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
85        self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
86    }
87}
88
89impl TextSettingsHandler {
90    /// Creates a new text settings handler instance.
91    ///
92    /// `initial_*` contain the desired initial values to be served.  Usually
93    /// you want the defaults.
94    pub fn new(
95        initial_keymap: Option<finput::KeymapId>,
96        initial_autorepeat: Option<autorepeater::Settings>,
97        input_handlers_node: &fuchsia_inspect::Node,
98        metrics_logger: metrics::MetricsLogger,
99    ) -> Rc<Self> {
100        let inspect_status = InputHandlerStatus::new(
101            input_handlers_node,
102            "text_settings_handler",
103            /* generates_events */ false,
104        );
105        Rc::new(Self {
106            keymap_id: RefCell::new(initial_keymap),
107            autorepeat_settings: RefCell::new(initial_autorepeat),
108            inspect_status,
109            metrics_logger,
110        })
111    }
112
113    /// Processes requests for keymap change from `stream`.
114    pub async fn process_keymap_configuration_from(
115        self: &Rc<Self>,
116        proxy: fsettings::KeyboardProxy,
117    ) -> Result<(), Error> {
118        let mut stream = HangingGetStream::new(proxy, fsettings::KeyboardProxy::watch);
119        loop {
120            match stream
121                .try_next()
122                .await
123                .context("while waiting on fuchsia.settings.Keyboard/Watch")?
124            {
125                Some(fsettings::KeyboardSettings { keymap, autorepeat, .. }) => {
126                    self.set_keymap_id(keymap);
127                    self.set_autorepeat_settings(autorepeat.map(|e| e.into()));
128                    log::info!("keymap ID set to: {:?}", self.get_keymap_id());
129                }
130                e => {
131                    self.metrics_logger.log_error(
132                        InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerExit,
133                        std::format!("exiting - unexpected response: {:?}", e),
134                    );
135                    break;
136                }
137            }
138        }
139        Ok(())
140    }
141
142    /// Starts reading events from the stream.  Does not block.
143    pub fn serve(self: Rc<Self>, proxy: fsettings::KeyboardProxy) {
144        let metrics_logger_clone = self.metrics_logger.clone();
145        fasync::Task::local(
146            async move { self.process_keymap_configuration_from(proxy).await }
147                // In most tests, this message is not fatal. It indicates that keyboard
148                // settings won't run, but that only means you can't configure keyboard
149                // settings. If your tests does not need to change keymaps, or adjust
150                // key autorepeat rates, these are not relevant.
151                .unwrap_or_else(move |e: anyhow::Error| {
152                    metrics_logger_clone.log_warn(
153                        InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerCantRun,
154                        std::format!("can't run: {:?}", e),
155                    );
156                }),
157        )
158        .detach();
159    }
160
161    fn set_keymap_id(self: &Rc<Self>, keymap_id: Option<finput::KeymapId>) {
162        *(self.keymap_id.borrow_mut()) = keymap_id;
163    }
164
165    fn set_autorepeat_settings(self: &Rc<Self>, autorepeat: Option<autorepeater::Settings>) {
166        *(self.autorepeat_settings.borrow_mut()) = autorepeat.map(|s| s.into());
167    }
168
169    /// Gets the currently active keymap ID.
170    pub fn get_keymap_id(&self) -> Option<finput::KeymapId> {
171        self.keymap_id.borrow().clone()
172    }
173
174    /// Gets the currently active autorepeat settings.
175    pub fn get_autorepeat_settings(&self) -> Option<autorepeater::Settings> {
176        self.autorepeat_settings.borrow().clone()
177    }
178
179    fn get_keymap_name(&self) -> Option<String> {
180        // Maybe instead just pass in the keymap ID directly?
181        match *self.keymap_id.borrow() {
182            Some(id) => match id {
183                finput::KeymapId::FrAzerty => Some("FR_AZERTY".to_owned()),
184                finput::KeymapId::UsDvorak => Some("US_DVORAK".to_owned()),
185                finput::KeymapId::UsColemak => Some("US_COLEMAK".to_owned()),
186                finput::KeymapId::UsQwerty | finput::KeymapIdUnknown!() => {
187                    Some("US_QWERTY".to_owned())
188                }
189            },
190            None => Some("US_QWERTY".to_owned()),
191        }
192    }
193}
194
195#[cfg(test)]
196mod tests {
197    use super::*;
198
199    use crate::input_handler::InputHandler;
200    use crate::{keyboard_binding, testing_utilities};
201    use fuchsia_async as fasync;
202    use pretty_assertions::assert_eq;
203    use std::convert::TryFrom as _;
204
205    fn input_event_from(
206        keyboard_event: keyboard_binding::KeyboardEvent,
207    ) -> input_device::InputEvent {
208        testing_utilities::create_input_event(
209            keyboard_event,
210            &input_device::InputDeviceDescriptor::Fake,
211            zx::MonotonicInstant::from_nanos(42),
212            input_device::Handled::No,
213        )
214    }
215
216    fn key_event_with_settings(
217        keymap: Option<String>,
218        settings: autorepeater::Settings,
219    ) -> input_device::InputEvent {
220        let keyboard_event = keyboard_binding::KeyboardEvent::new(
221            fidl_fuchsia_input::Key::A,
222            fidl_fuchsia_ui_input3::KeyEventType::Pressed,
223        )
224        .into_with_keymap(keymap)
225        .into_with_autorepeat_settings(Some(settings));
226        input_event_from(keyboard_event)
227    }
228
229    fn key_event(keymap: Option<String>) -> input_device::InputEvent {
230        let keyboard_event = keyboard_binding::KeyboardEvent::new(
231            fidl_fuchsia_input::Key::A,
232            fidl_fuchsia_ui_input3::KeyEventType::Pressed,
233        )
234        .into_with_keymap(keymap);
235        input_event_from(keyboard_event)
236    }
237
238    fn unhandled_key_event() -> input_device::UnhandledInputEvent {
239        input_device::UnhandledInputEvent::try_from(key_event(None)).unwrap()
240    }
241
242    #[fasync::run_singlethreaded(test)]
243    async fn keymap_id_setting() {
244        #[derive(Debug)]
245        struct Test {
246            keymap_id: Option<finput::KeymapId>,
247            expected: Option<String>,
248        }
249        let tests = vec![
250            Test { keymap_id: None, expected: Some("US_QWERTY".to_owned()) },
251            Test {
252                keymap_id: Some(finput::KeymapId::UsQwerty),
253                expected: Some("US_QWERTY".to_owned()),
254            },
255            Test {
256                keymap_id: Some(finput::KeymapId::FrAzerty),
257                expected: Some("FR_AZERTY".to_owned()),
258            },
259            Test {
260                keymap_id: Some(finput::KeymapId::UsDvorak),
261                expected: Some("US_DVORAK".to_owned()),
262            },
263            Test {
264                keymap_id: Some(finput::KeymapId::UsColemak),
265                expected: Some("US_COLEMAK".to_owned()),
266            },
267        ];
268        let inspector = fuchsia_inspect::Inspector::default();
269        let test_node = inspector.root().create_child("test_node");
270        for test in tests {
271            let handler = TextSettingsHandler::new(
272                test.keymap_id.clone(),
273                None,
274                &test_node,
275                metrics::MetricsLogger::default(),
276            );
277            let expected = key_event(test.expected.clone());
278            let result = handler.handle_unhandled_input_event(unhandled_key_event()).await;
279            assert_eq!(vec![expected], result, "for: {:?}", &test);
280        }
281    }
282
283    fn serve_into(
284        mut server_end: fsettings::KeyboardRequestStream,
285        keymap: Option<finput::KeymapId>,
286        autorepeat: Option<fsettings::Autorepeat>,
287    ) {
288        fasync::Task::local(async move {
289            if let Ok(Some(fsettings::KeyboardRequest::Watch { responder, .. })) =
290                server_end.try_next().await
291            {
292                let settings =
293                    fsettings::KeyboardSettings { keymap, autorepeat, ..Default::default() };
294                responder.send(&settings).expect("response sent");
295            }
296        })
297        .detach();
298    }
299
300    #[fasync::run_singlethreaded(test)]
301    async fn config_call_processing() {
302        let inspector = fuchsia_inspect::Inspector::default();
303        let test_node = inspector.root().create_child("test_node");
304        let handler =
305            TextSettingsHandler::new(None, None, &test_node, metrics::MetricsLogger::default());
306
307        let (proxy, stream) =
308            fidl::endpoints::create_proxy_and_stream::<fsettings::KeyboardMarker>();
309
310        // Serve a specific keyboard setting.
311        serve_into(
312            stream,
313            Some(finput::KeymapId::FrAzerty),
314            Some(fsettings::Autorepeat { delay: 43, period: 44 }),
315        );
316
317        // Start an asynchronous handler that processes keymap configuration calls
318        // incoming from `server_end`.
319        handler.clone().serve(proxy);
320
321        // Setting the keymap with a hanging get that does not synchronize with the "main"
322        // task of the handler inherently races with `handle_input_event`.  So, the only
323        // way to test it correctly is to verify that we get a changed setting *eventually*
324        // after asking the server to hand out the modified settings.  So, we loop with an
325        // expectation that at some point the settings get applied.  To avoid a long timeout
326        // we quit the loop if nothing happened after a generous amount of time.
327        let deadline =
328            fuchsia_async::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(5));
329        let autorepeat: autorepeater::Settings = Default::default();
330        loop {
331            let result = handler.clone().handle_unhandled_input_event(unhandled_key_event()).await;
332            let expected = key_event_with_settings(
333                Some("FR_AZERTY".to_owned()),
334                autorepeat
335                    .clone()
336                    .into_with_delay(zx::MonotonicDuration::from_nanos(43))
337                    .into_with_period(zx::MonotonicDuration::from_nanos(44)),
338            );
339            if vec![expected] == result {
340                break;
341            }
342            fuchsia_async::Timer::new(fuchsia_async::MonotonicInstant::after(
343                zx::MonotonicDuration::from_millis(10),
344            ))
345            .await;
346            let now = fuchsia_async::MonotonicInstant::now();
347            assert!(now < deadline, "the settings did not get applied, was: {:?}", &result);
348        }
349    }
350
351    #[fuchsia::test]
352    async fn text_settings_handler_initialized_with_inspect_node() {
353        let inspector = fuchsia_inspect::Inspector::default();
354        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
355        let _handler = TextSettingsHandler::new(
356            None,
357            None,
358            &fake_handlers_node,
359            metrics::MetricsLogger::default(),
360        );
361        diagnostics_assertions::assert_data_tree!(inspector, root: {
362            input_handlers_node: {
363                text_settings_handler: {
364                    events_received_count: 0u64,
365                    events_handled_count: 0u64,
366                    last_received_timestamp_ns: 0u64,
367                    "fuchsia.inspect.Health": {
368                        status: "STARTING_UP",
369                        // Timestamp value is unpredictable and not relevant in this context,
370                        // so we only assert that the property is present.
371                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
372                    },
373                }
374            }
375        });
376    }
377
378    #[fasync::run_singlethreaded(test)]
379    async fn text_settings_handler_inspect_counts_events() {
380        let inspector = fuchsia_inspect::Inspector::default();
381        let fake_handlers_node = inspector.root().create_child("input_handlers_node");
382        let text_settings_handler = TextSettingsHandler::new(
383            None,
384            None,
385            &fake_handlers_node,
386            metrics::MetricsLogger::default(),
387        );
388        let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
389            keyboard_binding::KeyboardDeviceDescriptor {
390                keys: vec![finput::Key::A, finput::Key::B],
391                ..Default::default()
392            },
393        );
394        let (_, event_time_u64) = testing_utilities::event_times();
395        let input_events = vec![
396            testing_utilities::create_keyboard_event_with_time(
397                finput::Key::A,
398                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
399                None,
400                event_time_u64,
401                &device_descriptor,
402                /* keymap= */ None,
403            ),
404            // Should not count received events that have already been handled.
405            testing_utilities::create_keyboard_event_with_handled(
406                finput::Key::B,
407                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
408                None,
409                event_time_u64,
410                &device_descriptor,
411                /* keymap= */ None,
412                /* key_meaning= */ None,
413                input_device::Handled::Yes,
414            ),
415            testing_utilities::create_keyboard_event_with_time(
416                finput::Key::A,
417                fidl_fuchsia_ui_input3::KeyEventType::Released,
418                None,
419                event_time_u64,
420                &device_descriptor,
421                /* keymap= */ None,
422            ),
423            // Should not count non-keyboard input events.
424            testing_utilities::create_fake_input_event(event_time_u64),
425            testing_utilities::create_keyboard_event_with_time(
426                finput::Key::B,
427                fidl_fuchsia_ui_input3::KeyEventType::Pressed,
428                None,
429                event_time_u64,
430                &device_descriptor,
431                /* keymap= */ None,
432            ),
433        ];
434
435        for input_event in input_events {
436            let _ = text_settings_handler.clone().handle_input_event(input_event).await;
437        }
438
439        let last_event_timestamp: u64 = event_time_u64.into_nanos().try_into().unwrap();
440
441        diagnostics_assertions::assert_data_tree!(inspector, root: {
442            input_handlers_node: {
443                text_settings_handler: {
444                    events_received_count: 3u64,
445                    events_handled_count: 0u64,
446                    last_received_timestamp_ns: last_event_timestamp,
447                    "fuchsia.inspect.Health": {
448                        status: "STARTING_UP",
449                        // Timestamp value is unpredictable and not relevant in this context,
450                        // so we only assert that the property is present.
451                        start_timestamp_nanos: diagnostics_assertions::AnyProperty
452                    },
453                }
454            }
455        });
456    }
457}