blob: 64dd932b9c8e0581d89eeab868ca8e1e869d7fb3 [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 {
::input_pipeline::{text_settings_handler::TextSettingsHandler, CursorMessage},
anyhow::{Context, Error},
fidl_fuchsia_input_injection::InputDeviceRegistryRequestStream,
fidl_fuchsia_settings as fsettings,
fidl_fuchsia_ui_input_config::FeaturesRequestStream as InputConfigFeaturesRequestStream,
fidl_fuchsia_ui_pointerinjector_configuration::SetupProxy,
fidl_fuchsia_ui_shortcut as ui_shortcut, fuchsia_async as fasync,
fuchsia_component::client::connect_to_protocol,
fuchsia_inspect as inspect,
fuchsia_syslog::{fx_log_err, fx_log_warn},
fuchsia_zircon as zx,
futures::{lock::Mutex, StreamExt},
icu_data,
input_pipeline::{
self, dead_keys_handler,
ime_handler::ImeHandler,
immersive_mode_shortcut_handler::ImmersiveModeShortcutHandler,
input_device,
input_pipeline::{InputDeviceBindingHashMap, InputPipeline, InputPipelineAssembly},
keymap_handler,
mouse_injector_handler::MouseInjectorHandler,
shortcut_handler::ShortcutHandler,
touch_injector_handler::TouchInjectorHandler,
},
scene_management::{self, SceneManager},
std::sync::Arc,
};
/// Begins handling input events. The returned future will complete when
/// input events are no longer being handled.
///
/// # Parameters
/// - `scene_manager`: The scene manager used by the session.
/// - `input_config_request_stream_receiver`: A receiving end of a MPSC channel for
/// `InputConfig` messages.
/// - `input_device_registry_request_stream_receiver`: A receiving end of a MPSC channel for
/// `InputDeviceRegistry` messages.
/// - `node`: The inspect node to insert individual inspect handler nodes into.
pub async fn handle_input(
// If this is false, it means we're using the legacy Scenic Gfx API, instead of the
// new Flatland API.
use_flatland: bool,
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
input_config_request_stream_receiver: futures::channel::mpsc::UnboundedReceiver<
InputConfigFeaturesRequestStream,
>,
input_device_registry_request_stream_receiver: futures::channel::mpsc::UnboundedReceiver<
InputDeviceRegistryRequestStream,
>,
icu_data_loader: icu_data::Loader,
node: &inspect::Node,
display_ownership_event: zx::Event,
) -> Result<InputPipeline, Error> {
let input_pipeline = InputPipeline::new(
vec![
input_device::InputDeviceType::Mouse,
input_device::InputDeviceType::Touch,
input_device::InputDeviceType::Keyboard,
],
build_input_pipeline_assembly(
use_flatland,
scene_manager,
icu_data_loader,
node,
display_ownership_event,
)
.await,
)
.context("Failed to create InputPipeline.")?;
let input_device_registry_fut = handle_input_device_registry_request_streams(
input_device_registry_request_stream_receiver,
input_pipeline.input_device_types().clone(),
input_pipeline.input_event_sender().clone(),
input_pipeline.input_device_bindings().clone(),
);
fasync::Task::local(input_device_registry_fut).detach();
let input_config_fut = handle_input_config_request_streams(
input_config_request_stream_receiver,
input_pipeline.input_device_bindings().clone(),
);
fasync::Task::local(input_config_fut).detach();
Ok(input_pipeline)
}
fn setup_pointer_injector_config_request_stream(
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
) -> SetupProxy {
let (setup_proxy, setup_request_stream) = fidl::endpoints::create_proxy_and_stream::<
fidl_fuchsia_ui_pointerinjector_configuration::SetupMarker,
>()
.expect("Failed to create pointerinjector.configuration.Setup channel.");
scene_management::handle_pointer_injector_configuration_setup_request_stream(
setup_request_stream,
scene_manager,
);
setup_proxy
}
async fn add_touch_handler(
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
mut assembly: InputPipelineAssembly,
) -> InputPipelineAssembly {
let setup_proxy = setup_pointer_injector_config_request_stream(scene_manager.clone());
let size = scene_manager.lock().await.get_pointerinjection_display_size();
let touch_handler = TouchInjectorHandler::new_with_config_proxy(setup_proxy, size).await;
match touch_handler {
Ok(touch_handler) => {
fasync::Task::local(touch_handler.clone().watch_viewport()).detach();
assembly = assembly.add_handler(touch_handler);
}
Err(e) => fx_log_err!(
"build_input_pipeline_assembly(): Touch injector handler was not installed: {:?}",
e
),
};
assembly
}
async fn add_mouse_handler(
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
mut assembly: InputPipelineAssembly,
sender: futures::channel::mpsc::Sender<CursorMessage>,
) -> InputPipelineAssembly {
let setup_proxy = setup_pointer_injector_config_request_stream(scene_manager.clone());
let size = scene_manager.lock().await.get_pointerinjection_display_size();
let mouse_handler =
MouseInjectorHandler::new_with_config_proxy(setup_proxy, size, sender).await;
match mouse_handler {
Ok(mouse_handler) => {
fasync::Task::local(mouse_handler.clone().watch_viewport()).detach();
assembly = assembly.add_handler(mouse_handler);
}
Err(e) => fx_log_err!(
"build_input_pipeline_assembly(): Mouse injector handler was not installed: {:?}",
e
),
};
assembly
}
// Maximum pointer movement during a clickpad press for the gesture to
// be guaranteed to be interpreted as a click. For movement greater than
// this value, upper layers may, e.g., interpret the gesture as a drag.
//
// This value has been tuned for Atlas, and may need further tuning or
// configuration for other devices.
const CLICK_TO_DRAG_THRESHOLD: f32 = 16.0;
async fn build_input_pipeline_assembly(
use_flatland: bool,
scene_manager: Arc<Mutex<Box<dyn SceneManager>>>,
icu_data_loader: icu_data::Loader,
node: &inspect::Node,
display_ownership_event: zx::Event,
) -> InputPipelineAssembly {
let mut assembly = InputPipelineAssembly::new();
let (sender, mut receiver) = futures::channel::mpsc::channel(0);
{
// Keep this handler first because it keeps performance measurement counters
// for the rest of the pipeline at entry.
assembly = add_inspect_handler(node.create_child("input_pipeline_entry"), assembly);
assembly = assembly.add_display_ownership(display_ownership_event);
assembly = add_modifier_handler(assembly);
// Add the text settings handler early in the pipeline to use the
// keymap settings in the remainder of the pipeline.
assembly = add_text_settings_handler(assembly);
assembly = add_keymap_handler(assembly);
assembly = assembly.add_autorepeater();
assembly = add_dead_keys_handler(assembly, icu_data_loader);
assembly = add_immersive_mode_shortcut_handler(assembly);
// Shortcut needs to go before IME.
assembly = add_shortcut_handler(assembly).await;
assembly = add_ime(assembly).await;
// Add the click-drag handler before the mouse handler, to allow
// the click-drag handler to filter events seen by the mouse
// handler.
assembly = add_click_drag_handler(assembly);
// Add handler to scale pointer motion based on speed of sensor
// motion. This allows touchpads and mice to be easily used for
// both precise pointing, and quick motion across the width
// (or height) of the screen.
//
// This handler must come before the PointerMotionDisplayScaleHandler.
// Otherwise the display scale will be applied quadratically in some
// cases.
assembly = add_pointer_motion_sensor_scale_handler(assembly);
// Add handler to scale pointer motion on high-DPI displays.
//
// * This handler is added _after_ the click-drag handler, since the
// motion denoising done by click drag handler is a property solely
// of the trackpad, and not of the display.
//
// * This handler is added _before_ the mouse handler, since _all_
// mouse events should be scaled.
let pointer_scale =
scene_manager.lock().await.get_display_metrics().physical_pixel_ratio().max(1.0);
assembly = add_pointer_motion_display_scale_handler(assembly, pointer_scale);
assembly = add_touch_handler(scene_manager.clone(), assembly).await;
if use_flatland {
assembly = add_mouse_handler(scene_manager.clone(), assembly, sender).await;
} else {
// We don't have mouse support for GFX. But that's okay,
// because the devices still using GFX don't support mice
// anyway.
}
// Keep this handler last because it keeps performance measurement counters
// for the rest of the pipeline at exit. We compare these values to the
// values at entry.
assembly = add_inspect_handler(node.create_child("input_pipeline_exit"), assembly);
// Forward focus.
// This requires `fuchsia.ui.focus.FocusChainListenerRegistry`
assembly = assembly.add_focus_listener();
}
{
let scene_manager = scene_manager.clone();
fasync::Task::spawn(async move {
while let Some(message) = receiver.next().await {
let mut scene_manager = scene_manager.lock().await;
match message {
CursorMessage::SetPosition(position) => {
scene_manager.set_cursor_position(position)
}
CursorMessage::SetVisibility(visible) => {
scene_manager.set_cursor_visibility(visible)
}
}
}
})
.detach();
}
assembly
}
/// Hooks up the modifier keys handler.
fn add_modifier_handler(assembly: InputPipelineAssembly) -> InputPipelineAssembly {
assembly.add_handler(input_pipeline::modifier_handler::ModifierHandler::new())
}
/// Hooks up the inspect handler.
fn add_inspect_handler(
node: inspect::Node,
assembly: InputPipelineAssembly,
) -> InputPipelineAssembly {
assembly.add_handler(input_pipeline::inspect_handler::InspectHandler::new(node))
}
/// Hooks up the text settings handler.
fn add_text_settings_handler(assembly: InputPipelineAssembly) -> InputPipelineAssembly {
let proxy = connect_to_protocol::<fsettings::KeyboardMarker>()
.expect("needs a connection to fuchsia.settings.Keyboard");
let text_handler = TextSettingsHandler::new(None, None);
text_handler.clone().serve(proxy);
assembly.add_handler(text_handler)
}
/// Hooks up the keymapper. The keymapper requires the text settings handler to
/// be added as well to support keymapping. Otherwise, it defaults to applying
/// the US QWERTY keymap.
fn add_keymap_handler(assembly: InputPipelineAssembly) -> InputPipelineAssembly {
assembly.add_handler(keymap_handler::KeymapHandler::new())
}
/// Hooks up the dead keys handler. This allows us to input accented characters by composing a
/// diacritic and a character.
fn add_dead_keys_handler(
assembly: InputPipelineAssembly,
loader: icu_data::Loader,
) -> InputPipelineAssembly {
assembly.add_handler(dead_keys_handler::Handler::new(loader))
}
async fn add_shortcut_handler(mut assembly: InputPipelineAssembly) -> InputPipelineAssembly {
if let Ok(manager) = connect_to_protocol::<ui_shortcut::ManagerMarker>() {
if let Ok(shortcut_handler) = ShortcutHandler::new(manager) {
assembly = assembly.add_handler(shortcut_handler);
}
}
assembly
}
async fn add_ime(mut assembly: InputPipelineAssembly) -> InputPipelineAssembly {
if let Ok(ime_handler) = ImeHandler::new().await {
assembly = assembly.add_handler(ime_handler);
}
assembly
}
fn add_click_drag_handler(assembly: InputPipelineAssembly) -> InputPipelineAssembly {
assembly.add_handler(input_pipeline::click_drag_handler::ClickDragHandler::new(
CLICK_TO_DRAG_THRESHOLD,
))
}
fn add_pointer_motion_display_scale_handler(
assembly: InputPipelineAssembly,
scale_factor: f32,
) -> InputPipelineAssembly {
match input_pipeline::pointer_motion_display_scale_handler::PointerMotionDisplayScaleHandler::new(scale_factor)
{
Ok(handler) => assembly.add_handler(handler),
Err(e) => {
fx_log_err!("Failed to install pointer scaler: {}", e);
assembly
}
}
}
fn add_pointer_motion_sensor_scale_handler(
assembly: InputPipelineAssembly,
) -> InputPipelineAssembly {
assembly.add_handler(
input_pipeline::pointer_motion_sensor_scale_handler::PointerMotionSensorScaleHandler::new(),
)
}
fn add_immersive_mode_shortcut_handler(assembly: InputPipelineAssembly) -> InputPipelineAssembly {
assembly.add_handler(ImmersiveModeShortcutHandler::new())
}
pub async fn handle_input_config_request_streams(
mut stream_receiver: futures::channel::mpsc::UnboundedReceiver<
InputConfigFeaturesRequestStream,
>,
input_device_bindings: InputDeviceBindingHashMap,
) {
while let Some(stream) = stream_receiver.next().await {
match InputPipeline::handle_input_config_request_stream(stream, &input_device_bindings)
.await
{
Ok(()) => (),
Err(e) => {
fx_log_warn!(
"failure while serving InputConfig.Features: {}; \
will continue serving other clients",
e
);
}
}
}
}
pub async fn handle_input_device_registry_request_streams(
mut stream_receiver: futures::channel::mpsc::UnboundedReceiver<
InputDeviceRegistryRequestStream,
>,
input_device_types: Vec<input_device::InputDeviceType>,
input_event_sender: futures::channel::mpsc::Sender<input_device::InputEvent>,
input_device_bindings: InputDeviceBindingHashMap,
) {
// Use a high value device id to avoid conflicting device ids.
let mut device_id = u32::MAX;
while let Some(stream) = stream_receiver.next().await {
match InputPipeline::handle_input_device_registry_request_stream(
stream,
&input_device_types,
&input_event_sender,
&input_device_bindings,
device_id,
)
.await
{
Ok(()) => (),
Err(e) => {
fx_log_warn!(
"failure while serving InputDeviceRegistry: {}; \
will continue serving other clients",
e
);
}
}
device_id -= 1;
}
}
#[cfg(test)]
mod tests {
use fuchsia_async as fasync;
#[fasync::run_singlethreaded(test)]
async fn test_placeholder() {
// TODO(fxb/73643): Add tests that verify the construction of the input pipeline.
}
}