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