1use 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
17pub struct TextSettingsHandler {
21 keymap_id: RefCell<Option<finput::KeymapId>>,
25
26 autorepeat_settings: RefCell<Option<autorepeater::Settings>>,
28
29 pub inspect_status: InputHandlerStatus,
31
32 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 _ => 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 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 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 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 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 .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 pub fn get_keymap_id(&self) -> Option<finput::KeymapId> {
163 self.keymap_id.borrow().clone()
164 }
165
166 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 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_into(
304 stream,
305 Some(finput::KeymapId::FrAzerty),
306 Some(fsettings::Autorepeat { delay: 43, period: 44 }),
307 );
308
309 handler.clone().serve(proxy);
312
313 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 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 None,
395 ),
396 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 None,
404 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 None,
414 ),
415 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 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 start_timestamp_nanos: diagnostics_assertions::AnyProperty
444 },
445 }
446 }
447 });
448 }
449}