| // 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. |
| } |
| } |