1use crate::input_handler::{Handler, InputHandlerStatus, UnhandledInputHandler};
6use crate::{input_device, metrics};
7use anyhow::{Context, Error, Result};
8use async_trait::async_trait;
9use async_utils::hanging_get::client::HangingGetStream;
10use fidl_fuchsia_input as finput;
11use fidl_fuchsia_settings as fsettings;
12use fuchsia_async as fasync;
13use fuchsia_inspect::health::Reporter;
14use futures::{TryFutureExt, TryStreamExt};
15use metrics_registry::*;
16use std::cell::RefCell;
17use std::rc::Rc;
18
19pub struct TextSettingsHandler {
23 keymap_id: RefCell<Option<finput::KeymapId>>,
27
28 pub inspect_status: InputHandlerStatus,
30
31 metrics_logger: metrics::MetricsLogger,
33}
34
35impl Handler for TextSettingsHandler {
36 fn set_handler_healthy(self: std::rc::Rc<Self>) {
37 self.inspect_status.health_node.borrow_mut().set_ok();
38 }
39
40 fn set_handler_unhealthy(self: std::rc::Rc<Self>, msg: &str) {
41 self.inspect_status.health_node.borrow_mut().set_unhealthy(msg);
42 }
43
44 fn get_name(&self) -> &'static str {
45 "TextSettingsHandler"
46 }
47
48 fn interest(&self) -> Vec<input_device::InputEventType> {
49 vec![input_device::InputEventType::Keyboard]
50 }
51}
52
53#[async_trait(?Send)]
54impl UnhandledInputHandler for TextSettingsHandler {
55 async fn handle_unhandled_input_event(
56 self: Rc<Self>,
57 unhandled_input_event: input_device::UnhandledInputEvent,
58 ) -> Vec<input_device::InputEvent> {
59 fuchsia_trace::duration!("input", "text_settings_handler");
60 match unhandled_input_event {
61 input_device::UnhandledInputEvent {
62 device_event: input_device::InputDeviceEvent::Keyboard(mut event),
63 device_descriptor,
64 event_time,
65 trace_id,
66 } => {
67 fuchsia_trace::duration!("input", "text_settings_handler[processing]");
68 if let Some(trace_id) = trace_id {
69 fuchsia_trace::flow_step!(
70 c"input",
71 c"event_in_input_pipeline",
72 trace_id.into()
73 );
74 }
75
76 self.inspect_status.count_received_event(&event_time);
77 let keymap_id = self.get_keymap_name();
78 log::debug!(
79 "text_settings_handler::Instance::handle_unhandled_input_event: keymap_id = {:?}",
80 &keymap_id
81 );
82 event = event.into_with_keymap(keymap_id);
83 vec![input_device::InputEvent {
84 device_event: input_device::InputDeviceEvent::Keyboard(event),
85 device_descriptor,
86 event_time,
87 handled: input_device::Handled::No,
88 trace_id,
89 }]
90 }
91 _ => {
93 self.metrics_logger.log_error(
94 InputPipelineErrorMetricDimensionEvent::HandlerReceivedUninterestedEvent,
95 std::format!(
96 "{} uninterested input event: {:?}",
97 self.get_name(),
98 unhandled_input_event.get_event_type()
99 ),
100 );
101 vec![input_device::InputEvent::from(unhandled_input_event)]
102 }
103 }
104 }
105}
106
107impl TextSettingsHandler {
108 pub fn new(
113 initial_keymap: Option<finput::KeymapId>,
114 input_handlers_node: &fuchsia_inspect::Node,
115 metrics_logger: metrics::MetricsLogger,
116 ) -> Rc<Self> {
117 let inspect_status = InputHandlerStatus::new(
118 input_handlers_node,
119 "text_settings_handler",
120 false,
121 );
122 Rc::new(Self { keymap_id: RefCell::new(initial_keymap), inspect_status, metrics_logger })
123 }
124
125 pub async fn process_keymap_configuration_from(
127 self: &Rc<Self>,
128 proxy: fsettings::KeyboardProxy,
129 ) -> Result<(), Error> {
130 let mut stream = HangingGetStream::new(proxy, fsettings::KeyboardProxy::watch);
131 loop {
132 match stream
133 .try_next()
134 .await
135 .context("while waiting on fuchsia.settings.Keyboard/Watch")?
136 {
137 Some(fsettings::KeyboardSettings { keymap, .. }) => {
138 self.set_keymap_id(keymap);
139 log::info!("keymap ID set to: {:?}", self.get_keymap_id());
140 }
141 e => {
142 self.metrics_logger.log_error(
143 InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerExit,
144 std::format!("exiting - unexpected response: {:?}", e),
145 );
146 break;
147 }
148 }
149 }
150 Ok(())
151 }
152
153 pub fn serve(self: Rc<Self>, proxy: fsettings::KeyboardProxy) {
155 let metrics_logger_clone = self.metrics_logger.clone();
156 fasync::Task::local(
157 async move { self.process_keymap_configuration_from(proxy).await }
158 .unwrap_or_else(move |e: anyhow::Error| {
163 metrics_logger_clone.log_warn(
164 InputPipelineErrorMetricDimensionEvent::TextSettingsHandlerCantRun,
165 std::format!("can't run: {:?}", e),
166 );
167 }),
168 )
169 .detach();
170 }
171
172 fn set_keymap_id(self: &Rc<Self>, keymap_id: Option<finput::KeymapId>) {
173 *(self.keymap_id.borrow_mut()) = keymap_id;
174 }
175
176 pub fn get_keymap_id(&self) -> Option<finput::KeymapId> {
178 self.keymap_id.borrow().clone()
179 }
180
181 fn get_keymap_name(&self) -> Option<String> {
182 match *self.keymap_id.borrow() {
184 Some(id) => match id {
185 finput::KeymapId::FrAzerty => Some("FR_AZERTY".to_owned()),
186 finput::KeymapId::UsDvorak => Some("US_DVORAK".to_owned()),
187 finput::KeymapId::UsColemak => Some("US_COLEMAK".to_owned()),
188 finput::KeymapId::UsQwerty | finput::KeymapIdUnknown!() => {
189 Some("US_QWERTY".to_owned())
190 }
191 },
192 None => Some("US_QWERTY".to_owned()),
193 }
194 }
195}
196
197#[cfg(test)]
198mod tests {
199 use super::*;
200
201 use crate::input_handler::InputHandler;
202 use crate::{keyboard_binding, testing_utilities};
203 use fuchsia_async as fasync;
204 use pretty_assertions::assert_eq;
205 use std::convert::TryFrom as _;
206
207 fn input_event_from(
208 keyboard_event: keyboard_binding::KeyboardEvent,
209 ) -> input_device::InputEvent {
210 testing_utilities::create_input_event(
211 keyboard_event,
212 &input_device::InputDeviceDescriptor::Fake,
213 zx::MonotonicInstant::from_nanos(42),
214 input_device::Handled::No,
215 )
216 }
217
218 fn key_event_with_settings(keymap: Option<String>) -> input_device::InputEvent {
219 let keyboard_event = keyboard_binding::KeyboardEvent::new(
220 fidl_fuchsia_input::Key::A,
221 fidl_fuchsia_ui_input3::KeyEventType::Pressed,
222 )
223 .into_with_keymap(keymap);
224 input_event_from(keyboard_event)
225 }
226
227 fn key_event(keymap: Option<String>) -> input_device::InputEvent {
228 let keyboard_event = keyboard_binding::KeyboardEvent::new(
229 fidl_fuchsia_input::Key::A,
230 fidl_fuchsia_ui_input3::KeyEventType::Pressed,
231 )
232 .into_with_keymap(keymap);
233 input_event_from(keyboard_event)
234 }
235
236 fn unhandled_key_event() -> input_device::UnhandledInputEvent {
237 input_device::UnhandledInputEvent::try_from(key_event(None)).unwrap()
238 }
239
240 #[fasync::run_singlethreaded(test)]
241 async fn keymap_id_setting() {
242 #[derive(Debug)]
243 struct Test {
244 keymap_id: Option<finput::KeymapId>,
245 expected: Option<String>,
246 }
247 let tests = vec![
248 Test { keymap_id: None, expected: Some("US_QWERTY".to_owned()) },
249 Test {
250 keymap_id: Some(finput::KeymapId::UsQwerty),
251 expected: Some("US_QWERTY".to_owned()),
252 },
253 Test {
254 keymap_id: Some(finput::KeymapId::FrAzerty),
255 expected: Some("FR_AZERTY".to_owned()),
256 },
257 Test {
258 keymap_id: Some(finput::KeymapId::UsDvorak),
259 expected: Some("US_DVORAK".to_owned()),
260 },
261 Test {
262 keymap_id: Some(finput::KeymapId::UsColemak),
263 expected: Some("US_COLEMAK".to_owned()),
264 },
265 ];
266 let inspector = fuchsia_inspect::Inspector::default();
267 let test_node = inspector.root().create_child("test_node");
268 for test in tests {
269 let handler = TextSettingsHandler::new(
270 test.keymap_id.clone(),
271 &test_node,
272 metrics::MetricsLogger::default(),
273 );
274 let expected = key_event(test.expected.clone());
275 let result = handler.handle_unhandled_input_event(unhandled_key_event()).await;
276 assert_eq!(vec![expected], result, "for: {:?}", &test);
277 }
278 }
279
280 fn serve_into(
281 mut server_end: fsettings::KeyboardRequestStream,
282 keymap: Option<finput::KeymapId>,
283 ) {
284 fasync::Task::local(async move {
285 if let Ok(Some(fsettings::KeyboardRequest::Watch { responder, .. })) =
286 server_end.try_next().await
287 {
288 let settings = fsettings::KeyboardSettings { keymap, ..Default::default() };
289 responder.send(&settings).expect("response sent");
290 }
291 })
292 .detach();
293 }
294
295 #[fasync::run_singlethreaded(test)]
296 async fn config_call_processing() {
297 let inspector = fuchsia_inspect::Inspector::default();
298 let test_node = inspector.root().create_child("test_node");
299 let handler = TextSettingsHandler::new(None, &test_node, metrics::MetricsLogger::default());
300
301 let (proxy, stream) =
302 fidl::endpoints::create_proxy_and_stream::<fsettings::KeyboardMarker>();
303
304 serve_into(stream, Some(finput::KeymapId::FrAzerty));
306
307 handler.clone().serve(proxy);
310
311 let deadline =
318 fuchsia_async::MonotonicInstant::after(zx::MonotonicDuration::from_seconds(5));
319 loop {
320 let result = handler.clone().handle_unhandled_input_event(unhandled_key_event()).await;
321 let expected = key_event_with_settings(Some("FR_AZERTY".to_owned()));
322 if vec![expected] == result {
323 break;
324 }
325 fuchsia_async::Timer::new(fuchsia_async::MonotonicInstant::after(
326 zx::MonotonicDuration::from_millis(10),
327 ))
328 .await;
329 let now = fuchsia_async::MonotonicInstant::now();
330 assert!(now < deadline, "the settings did not get applied, was: {:?}", &result);
331 }
332 }
333
334 #[fuchsia::test]
335 async fn text_settings_handler_initialized_with_inspect_node() {
336 let inspector = fuchsia_inspect::Inspector::default();
337 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
338 let _handler =
339 TextSettingsHandler::new(None, &fake_handlers_node, metrics::MetricsLogger::default());
340 diagnostics_assertions::assert_data_tree!(inspector, root: {
341 input_handlers_node: {
342 text_settings_handler: {
343 events_received_count: 0u64,
344 events_handled_count: 0u64,
345 last_received_timestamp_ns: 0u64,
346 "fuchsia.inspect.Health": {
347 status: "STARTING_UP",
348 start_timestamp_nanos: diagnostics_assertions::AnyProperty
351 },
352 }
353 }
354 });
355 }
356
357 #[fasync::run_singlethreaded(test)]
358 async fn text_settings_handler_inspect_counts_events() {
359 let inspector = fuchsia_inspect::Inspector::default();
360 let fake_handlers_node = inspector.root().create_child("input_handlers_node");
361 let text_settings_handler =
362 TextSettingsHandler::new(None, &fake_handlers_node, metrics::MetricsLogger::default());
363 let device_descriptor = input_device::InputDeviceDescriptor::Keyboard(
364 keyboard_binding::KeyboardDeviceDescriptor {
365 keys: vec![finput::Key::A, finput::Key::B],
366 ..Default::default()
367 },
368 );
369 let (_, event_time_u64) = testing_utilities::event_times();
370 let input_events = vec![
371 testing_utilities::create_keyboard_event_with_time(
372 finput::Key::A,
373 fidl_fuchsia_ui_input3::KeyEventType::Pressed,
374 None,
375 event_time_u64,
376 &device_descriptor,
377 None,
378 ),
379 testing_utilities::create_keyboard_event_with_handled(
381 finput::Key::B,
382 fidl_fuchsia_ui_input3::KeyEventType::Pressed,
383 None,
384 event_time_u64,
385 &device_descriptor,
386 None,
387 None,
388 input_device::Handled::Yes,
389 ),
390 testing_utilities::create_keyboard_event_with_time(
391 finput::Key::A,
392 fidl_fuchsia_ui_input3::KeyEventType::Released,
393 None,
394 event_time_u64,
395 &device_descriptor,
396 None,
397 ),
398 testing_utilities::create_fake_input_event(event_time_u64),
400 testing_utilities::create_keyboard_event_with_time(
401 finput::Key::B,
402 fidl_fuchsia_ui_input3::KeyEventType::Pressed,
403 None,
404 event_time_u64,
405 &device_descriptor,
406 None,
407 ),
408 ];
409
410 for input_event in input_events {
411 let _ = text_settings_handler.clone().handle_input_event(input_event).await;
412 }
413
414 let last_event_timestamp: u64 = event_time_u64.into_nanos().try_into().unwrap();
415
416 diagnostics_assertions::assert_data_tree!(inspector, root: {
417 input_handlers_node: {
418 text_settings_handler: {
419 events_received_count: 3u64,
420 events_handled_count: 0u64,
421 last_received_timestamp_ns: last_event_timestamp,
422 "fuchsia.inspect.Health": {
423 status: "STARTING_UP",
424 start_timestamp_nanos: diagnostics_assertions::AnyProperty
427 },
428 }
429 }
430 });
431 }
432}