blob: 3027debcb212796ecb75b0e8431459401c8bef30 [file] [log] [blame]
// Copyright 2020 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::{modern_backend::input_device::InputDevice, synthesizer},
anyhow::{format_err, Error},
fidl::endpoints,
fidl_fuchsia_input::Key,
fidl_fuchsia_input_injection::InputDeviceRegistryProxy,
fidl_fuchsia_input_report::{
DeviceDescriptor, InputDeviceMarker, KeyboardDescriptor, KeyboardInputDescriptor,
},
};
/// Implements the `synthesizer::InputDeviceRegistry` trait, and the client side
/// of the `fuchsia.input.injection.InputDeviceRegistry` protocol.
pub struct InputDeviceRegistry {
proxy: InputDeviceRegistryProxy,
}
impl synthesizer::InputDeviceRegistry for self::InputDeviceRegistry {
fn add_touchscreen_device(
&mut self,
_width: u32,
_height: u32,
) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
Err(format_err!("TODO: implement touchscreen support"))
}
fn add_keyboard_device(&mut self) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
// Generate a `Vec` of all known keys.
// * Because there is no direct way to iterate over enum values, we iterate
// over the primitives corresponding to `Key::A` and `Key::MediaVolumeDecrement`.
// * Some primitive values in the range have no corresponding enum value. For
// example, the value 0x00070065 sits between `NonUsBackslash` (0x00070064), and
// `KeypadEquals` (0x00070067). Such primitives are removed by `filter_map()`.
let all_keys: Vec<Key> = (Key::A.into_primitive()
..=Key::MediaVolumeDecrement.into_primitive())
.filter_map(Key::from_primitive)
.collect();
self.add_device(DeviceDescriptor {
keyboard: Some(KeyboardDescriptor {
input: Some(KeyboardInputDescriptor {
keys3: Some(all_keys.clone()),
..KeyboardInputDescriptor::EMPTY
}),
..KeyboardDescriptor::EMPTY
}),
..DeviceDescriptor::EMPTY
})
}
fn add_media_buttons_device(&mut self) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
Err(format_err!("TODO: implement media buttons support"))
}
}
impl InputDeviceRegistry {
pub fn new(proxy: InputDeviceRegistryProxy) -> Self {
Self { proxy }
}
/// Adds a device to the `InputDeviceRegistry` FIDL server connected to this
/// `InputDeviceRegistry` struct.
///
/// # Returns
/// A `synthesizer::InputDevice`, which can be used to send events to the
/// `fuchsia.input.report.InputDevice` that has been registered with the
/// `fuchsia.input.injection.InputDeviceRegistry` service.
fn add_device(
&self,
descriptor: DeviceDescriptor,
) -> Result<Box<dyn synthesizer::InputDevice>, Error> {
let (client_end, request_stream) = endpoints::create_request_stream::<InputDeviceMarker>()?;
self.proxy.register(client_end)?;
Ok(Box::new(InputDevice::new(request_stream, descriptor)))
}
}
#[cfg(test)]
mod tests {
use {
super::{synthesizer::InputDeviceRegistry as _, *},
anyhow::Context as _,
fidl_fuchsia_input_injection::{InputDeviceRegistryMarker, InputDeviceRegistryRequest},
fuchsia_async as fasync,
futures::StreamExt,
matches::assert_matches,
};
#[fasync::run_until_stalled(test)]
async fn add_keyboard_device_invokes_fidl_register_method_exactly_once() -> Result<(), Error> {
let (proxy, request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceRegistryMarker>()
.context("failed to create proxy and stream for InputDeviceRegistry")?;
InputDeviceRegistry { proxy }.add_keyboard_device().context("adding keyboard")?;
assert_matches!(
request_stream.collect::<Vec<_>>().await.as_slice(),
[Ok(InputDeviceRegistryRequest::Register { .. })]
);
Ok(())
}
#[fasync::run_until_stalled(test)]
async fn add_keyboard_device_registers_a_keyboard() -> Result<(), Error> {
// Create an `InputDeviceRegistry`, and add a keyboard to it.
let (registry_proxy, mut registry_request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceRegistryMarker>()
.context("failed to create proxy and stream for InputDeviceRegistry")?;
let mut input_device_registry = InputDeviceRegistry { proxy: registry_proxy };
let input_device =
input_device_registry.add_keyboard_device().context("adding keyboard")?;
// `input_device_registry` should send a `Register` messgage to `registry_request_stream`.
// Use `registry_request_stream` to grab the `ClientEnd` of the keyboard added above,
// and convert the `ClientEnd` into an `InputDeviceProxy`.
let input_device_proxy = match registry_request_stream
.next()
.await
.context("stream read should yield Some")?
.context("fidl read")?
{
InputDeviceRegistryRequest::Register { device, .. } => device,
}
.into_proxy()
.context("converting client_end to proxy")?;
// Send a `GetDescriptor` request to `input_device`, and verify that the device
// is as keyboard.
let input_device_get_descriptor_fut = input_device_proxy.get_descriptor();
let input_device_server_fut = input_device.serve_reports();
std::mem::drop(input_device_proxy); // Terminate stream served by `input_device_server_fut`.
assert_matches!(
futures::future::join(input_device_server_fut, input_device_get_descriptor_fut).await,
(_, Ok(DeviceDescriptor { keyboard: Some(_), .. }))
);
Ok(())
}
// Because `input_synthesis` is a library, unimplemented features should yield `Error`s,
// rather than panic!()-ing.
mod unimplemented_methods {
use super::*;
#[test]
fn add_touchscreen_device_yields_error() -> Result<(), Error> {
let _executor = fuchsia_async::Executor::new(); // Create TLS executor used by `endpoints`.
let (proxy, _request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceRegistryMarker>()
.context("internal error creating InputDevice proxy and stream")?;
assert!(InputDeviceRegistry { proxy }.add_touchscreen_device(0, 0).is_err());
Ok(())
}
#[test]
fn add_media_buttons_device_yields_error() -> Result<(), Error> {
let _executor = fuchsia_async::Executor::new(); // Create TLS executor used by `endpoints`.
let (proxy, _request_stream) =
endpoints::create_proxy_and_stream::<InputDeviceRegistryMarker>()
.context("internal error creating InputDevice proxy and stream")?;
assert!(InputDeviceRegistry { proxy }.add_media_buttons_device().is_err());
Ok(())
}
}
}