blob: a74d3a593115d4e756746e4d5cf5cfb4fc78857f [file] [log] [blame]
// Copyright 2021 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use crate::autorepeater;
use crate::input_device;
use crate::input_handler::UnhandledInputHandler;
use anyhow::{Context, Error, Result};
use async_trait::async_trait;
use fidl_fuchsia_input as finput;
use fidl_fuchsia_settings as fsettings;
use fuchsia_async as fasync;
use fuchsia_syslog::{fx_log_debug, fx_log_err};
use futures::{TryFutureExt, TryStreamExt};
use std::cell::RefCell;
use std::rc::Rc;
use async_utils::hanging_get::client::HangingGetStream;
/// The text settings handler instance. Refer to as `text_settings_handler::TextSettingsHandler`.
/// Its task is to decorate an input event with the keymap identifier. The instance can
/// be freely cloned, each clone is thread-safely sharing data with others.
#[derive(Debug)]
pub struct TextSettingsHandler {
/// Stores the currently active keymap identifier, if present. Wrapped
/// in an refcell as it can be changed out of band through
/// `fuchsia.input.keymap.Configuration/SetLayout`.
keymap_id: RefCell<Option<finput::KeymapId>>,
/// Stores the currently active autorepeat settings.
autorepeat_settings: RefCell<Option<autorepeater::Settings>>,
}
#[async_trait(?Send)]
impl UnhandledInputHandler for TextSettingsHandler {
async fn handle_unhandled_input_event(
self: Rc<Self>,
unhandled_input_event: input_device::UnhandledInputEvent,
) -> Vec<input_device::InputEvent> {
match unhandled_input_event {
input_device::UnhandledInputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(mut event),
device_descriptor,
event_time,
trace_id: _,
} => {
let keymap_id = self.get_keymap_name();
fx_log_debug!(
"text_settings_handler::Instance::handle_unhandled_input_event: keymap_id = {:?}",
&keymap_id
);
event = event
.into_with_keymap(keymap_id)
.into_with_autorepeat_settings(self.get_autorepeat_settings());
vec![input_device::InputEvent {
device_event: input_device::InputDeviceEvent::Keyboard(event),
device_descriptor,
event_time,
handled: input_device::Handled::No,
trace_id: None,
}]
}
// Pass a non-keyboard event through.
_ => vec![input_device::InputEvent::from(unhandled_input_event)],
}
}
}
impl TextSettingsHandler {
/// Creates a new text settings handler instance.
///
/// `initial_*` contain the desired initial values to be served. Usually
/// you want the defaults.
pub fn new(
initial_keymap: Option<finput::KeymapId>,
initial_autorepeat: Option<autorepeater::Settings>,
) -> Rc<Self> {
Rc::new(Self {
keymap_id: RefCell::new(initial_keymap),
autorepeat_settings: RefCell::new(initial_autorepeat),
})
}
/// Processes requests for keymap change from `stream`.
pub async fn process_keymap_configuration_from(
self: &Rc<Self>,
proxy: fsettings::KeyboardProxy,
) -> Result<(), Error> {
let mut stream = HangingGetStream::new(proxy, fsettings::KeyboardProxy::watch);
loop {
match stream
.try_next()
.await
.context("while waiting on fuchsia.settings.Keyboard/Watch")?
{
Some(fsettings::KeyboardSettings { keymap, autorepeat, .. }) => {
self.set_keymap_id(keymap);
self.set_autorepeat_settings(autorepeat.map(|e| e.into()));
fx_log_debug!("keymap ID set to: {:?}", self.get_keymap_id());
}
e => {
fx_log_err!("exiting - unexpected response: {:?}", &e);
break;
}
}
}
Ok(())
}
/// Starts reading events from the stream. Does not block.
pub fn serve(self: Rc<Self>, proxy: fsettings::KeyboardProxy) {
fasync::Task::local(
async move { self.process_keymap_configuration_from(proxy).await }
.unwrap_or_else(|e: anyhow::Error| fx_log_err!("can't run: {:?}", e)),
)
.detach();
}
fn set_keymap_id(self: &Rc<Self>, keymap_id: Option<finput::KeymapId>) {
*(self.keymap_id.borrow_mut()) = keymap_id;
}
fn set_autorepeat_settings(self: &Rc<Self>, autorepeat: Option<autorepeater::Settings>) {
*(self.autorepeat_settings.borrow_mut()) = autorepeat.map(|s| s.into());
}
/// Gets the currently active keymap ID.
pub fn get_keymap_id(&self) -> Option<finput::KeymapId> {
self.keymap_id.borrow().clone()
}
/// Gets the currently active autorepeat settings.
pub fn get_autorepeat_settings(&self) -> Option<autorepeater::Settings> {
self.autorepeat_settings.borrow().clone()
}
fn get_keymap_name(&self) -> Option<String> {
// Maybe instead just pass in the keymap ID directly?
match *self.keymap_id.borrow() {
Some(id) => match id {
finput::KeymapId::FrAzerty => Some("FR_AZERTY".to_owned()),
finput::KeymapId::UsDvorak => Some("US_DVORAK".to_owned()),
finput::KeymapId::UsQwerty | finput::KeymapIdUnknown!() => {
Some("US_QWERTY".to_owned())
}
},
None => Some("US_QWERTY".to_owned()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::input_device;
use crate::keyboard_binding;
use crate::testing_utilities;
use fidl_fuchsia_input;
use fidl_fuchsia_ui_input3;
use fuchsia_async as fasync;
use fuchsia_zircon as zx;
use pretty_assertions::assert_eq;
use std::convert::TryFrom as _;
fn input_event_from(
keyboard_event: keyboard_binding::KeyboardEvent,
) -> input_device::InputEvent {
testing_utilities::create_input_event(
keyboard_event,
&input_device::InputDeviceDescriptor::Fake,
zx::Time::from_nanos(42),
input_device::Handled::No,
)
}
fn key_event_with_settings(
keymap: Option<String>,
settings: autorepeater::Settings,
) -> input_device::InputEvent {
let keyboard_event = keyboard_binding::KeyboardEvent::new(
fidl_fuchsia_input::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
)
.into_with_keymap(keymap)
.into_with_autorepeat_settings(Some(settings));
input_event_from(keyboard_event)
}
fn key_event(keymap: Option<String>) -> input_device::InputEvent {
let keyboard_event = keyboard_binding::KeyboardEvent::new(
fidl_fuchsia_input::Key::A,
fidl_fuchsia_ui_input3::KeyEventType::Pressed,
)
.into_with_keymap(keymap);
input_event_from(keyboard_event)
}
fn unhandled_key_event() -> input_device::UnhandledInputEvent {
input_device::UnhandledInputEvent::try_from(key_event(None)).unwrap()
}
#[fasync::run_singlethreaded(test)]
async fn keymap_id_setting() {
#[derive(Debug)]
struct Test {
keymap_id: Option<finput::KeymapId>,
expected: Option<String>,
}
let tests = vec![
Test { keymap_id: None, expected: Some("US_QWERTY".to_owned()) },
Test {
keymap_id: Some(finput::KeymapId::UsQwerty),
expected: Some("US_QWERTY".to_owned()),
},
Test {
keymap_id: Some(finput::KeymapId::FrAzerty),
expected: Some("FR_AZERTY".to_owned()),
},
Test {
keymap_id: Some(finput::KeymapId::UsDvorak),
expected: Some("US_DVORAK".to_owned()),
},
];
for test in tests {
let handler = TextSettingsHandler::new(test.keymap_id.clone(), None);
let expected = key_event(test.expected.clone());
let result = handler.handle_unhandled_input_event(unhandled_key_event()).await;
assert_eq!(vec![expected], result, "for: {:?}", &test);
}
}
fn serve_into(
mut server_end: fsettings::KeyboardRequestStream,
keymap: Option<finput::KeymapId>,
autorepeat: Option<fsettings::Autorepeat>,
) {
fasync::Task::local(async move {
if let Ok(Some(fsettings::KeyboardRequest::Watch { responder, .. })) =
server_end.try_next().await
{
let settings = fsettings::KeyboardSettings {
keymap,
autorepeat,
..fsettings::KeyboardSettings::EMPTY
};
responder.send(settings).expect("response sent");
}
})
.detach();
}
#[fasync::run_singlethreaded(test)]
async fn config_call_processing() {
let handler = TextSettingsHandler::new(None, None);
let (proxy, stream) =
fidl::endpoints::create_proxy_and_stream::<fsettings::KeyboardMarker>().unwrap();
// Serve a specific keyboard setting.
serve_into(
stream,
Some(finput::KeymapId::FrAzerty),
Some(fsettings::Autorepeat { delay: 43, period: 44 }),
);
// Start an asynchronous handler that processes keymap configuration calls
// incoming from `server_end`.
handler.clone().serve(proxy);
// Setting the keymap with a hanging get that does not synchronize with the "main"
// task of the handler inherently races with `handle_input_event`. So, the only
// way to test it correctly is to verify that we get a changed setting *eventually*
// after asking the server to hand out the modified settings. So, we loop with an
// expectation that at some point the settings get applied. To avoid a long timeout
// we quit the loop if nothing happened after a generous amount of time.
let deadline = fuchsia_async::Time::after(zx::Duration::from_seconds(5));
let autorepeat: autorepeater::Settings = Default::default();
loop {
let result = handler.clone().handle_unhandled_input_event(unhandled_key_event()).await;
let expected = key_event_with_settings(
Some("FR_AZERTY".to_owned()),
autorepeat
.clone()
.into_with_delay(zx::Duration::from_nanos(43))
.into_with_period(zx::Duration::from_nanos(44)),
);
if vec![expected] == result {
break;
}
fuchsia_async::Timer::new(fuchsia_async::Time::after(zx::Duration::from_millis(10)))
.await;
let now = fuchsia_async::Time::now();
assert!(now < deadline, "the settings did not get applied, was: {:?}", &result);
}
}
}