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