blob: 67d091ff3774439bb590f60c59e7cdc83c6b079a [file] [log] [blame]
// Copyright 2019 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.
// This declaration is required to support the `select!`.
#![recursion_limit = "256"]
#[macro_use]
mod input_testing_utilities;
mod element_repository;
mod input_device_registry_server;
mod mouse_pointer_hack;
mod pointer_hack_server;
mod touch_pointer_hack;
mod workstation_input_pipeline;
use {
crate::{
element_repository::{ElementEventHandler, ElementManagerServer, ElementRepository},
input_device_registry_server::InputDeviceRegistryServer,
pointer_hack_server::PointerHackServer,
},
anyhow::{Context as _, Error},
element_management::SimpleElementManager,
fidl::endpoints::DiscoverableService,
fidl_fuchsia_input_injection::InputDeviceRegistryRequestStream,
fidl_fuchsia_session::{
ElementManagerMarker, ElementManagerRequestStream, GraphicalPresenterMarker,
},
fidl_fuchsia_sys::LauncherMarker,
fidl_fuchsia_sys2 as fsys,
fidl_fuchsia_ui_app::ViewProviderMarker,
fidl_fuchsia_ui_policy::PresentationMarker,
fidl_fuchsia_ui_scenic::ScenicMarker,
fidl_fuchsia_ui_views as ui_views,
fidl_fuchsia_ui_views::ViewRefInstalledMarker,
fuchsia_async as fasync,
fuchsia_component::{
client::{connect_to_service, launch_with_options, App, LaunchOptions},
server::ServiceFs,
},
fuchsia_syslog::{fx_log_err, fx_log_info},
fuchsia_zircon as zx,
futures::{try_join, StreamExt},
scene_management::{self, SceneManager},
std::rc::Rc,
std::sync::{Arc, Weak},
};
enum ExposedServices {
ElementManager(ElementManagerRequestStream),
InputDeviceRegistry(InputDeviceRegistryRequestStream),
}
/// The maximum number of open requests to this component.
///
/// Currently we have this value set low because the only service we are serving
/// is the ElementManager service and we don't expect many connections to it at
/// any given time.
const NUM_CONCURRENT_REQUESTS: usize = 5;
async fn launch_ermine(
element_server: ElementManagerServer<SimpleElementManager>,
) -> Result<(App, PointerHackServer), Error> {
let launcher = connect_to_service::<LauncherMarker>()?;
let (client_chan, server_chan) = zx::Channel::create().unwrap();
let pointer_hack_server = PointerHackServer::new(server_chan, element_server);
let mut launch_options = LaunchOptions::new();
launch_options.set_additional_services(
vec![
PresentationMarker::SERVICE_NAME.to_string(),
ElementManagerMarker::SERVICE_NAME.to_string(),
],
client_chan,
);
let app = launch_with_options(
&launcher,
"fuchsia-pkg://fuchsia.com/ermine#meta/ermine.cmx".to_string(),
None,
launch_options,
)?;
Ok((app, pointer_hack_server))
}
async fn expose_services(
element_server: ElementManagerServer<SimpleElementManager>,
input_device_registry_server: InputDeviceRegistryServer,
) -> Result<(), Error> {
let mut fs = ServiceFs::new_local();
fs.dir("svc")
.add_fidl_service(ExposedServices::ElementManager)
.add_fidl_service(ExposedServices::InputDeviceRegistry);
fs.take_and_serve_directory_handle()?;
// create a reference so that we can use this within the `for_each_concurrent` generator.
// If we do not create a ref we will run into issues with the borrow checker.
let element_server_ref = &element_server;
let input_device_registry_server_ref = &input_device_registry_server;
fs.for_each_concurrent(
NUM_CONCURRENT_REQUESTS,
move |service_request: ExposedServices| async move {
match service_request {
ExposedServices::ElementManager(request_stream) => {
// TODO(47079): handle error
fx_log_info!("received incoming element manager request");
let _ = element_server_ref.handle_request(request_stream).await;
}
ExposedServices::InputDeviceRegistry(request_stream) => {
match input_device_registry_server_ref.handle_request(request_stream).await {
Ok(()) => (),
Err(e) => {
// If `handle_request()` returns `Err`, then the `unbounded_send()` call
// from `handle_request()` failed with either:
// * `TrySendError::SendErrorKind::Full`, or
// * `TrySendError::SendErrorKind::Disconnected`.
//
// These are unexpected, because:
// * `Full` can't happen, because `InputDeviceRegistryServer`
// uses an `UnboundedSender`.
// * `Disconnected` is highly unlikely, because the corresponding
// `UnboundedReceiver` lives in `main::input_fut`, and `input_fut`'s
// lifetime is nearly as long as `input_device_registry_server`'s.
//
// Nonetheless, InputDeviceRegistry isn't critical to production use.
// So we just log the error and move on.
fx_log_err!(
"failed to forward InputDeviceRegistryRequestStream: {:?}; \
must restart to enable input injection",
e
)
}
}
}
}
},
)
.await;
Ok(())
}
async fn set_view_focus(
weak_focuser: Weak<ui_views::FocuserProxy>,
mut view_ref: ui_views::ViewRef,
) -> Result<(), Error> {
// [ViewRef]'s are one-shot use only. Duplicate it for use in request_focus below.
let mut viewref_dup = fuchsia_scenic::duplicate_view_ref(&view_ref)?;
// Wait for the view_ref to signal its ready to be focused.
let view_ref_installed = connect_to_service::<ViewRefInstalledMarker>()
.context("Could not connect to ViewRefInstalledMarker")?;
let watch_result = view_ref_installed.watch(&mut view_ref).await;
match watch_result {
// Handle fidl::Errors.
Err(e) => Err(anyhow::format_err!("Failed with err: {}", e)),
// Handle ui_views::ViewRefInstalledError.
Ok(Err(value)) => Err(anyhow::format_err!("Failed with err: {:?}", value)),
Ok(_) => {
// Now set focus on the view_ref.
if let Some(focuser) = weak_focuser.upgrade() {
let focus_result = focuser.request_focus(&mut viewref_dup).await?;
match focus_result {
Ok(()) => Ok(()),
Err(e) => Err(anyhow::format_err!("Failed with err: {:?}", e)),
}
} else {
Err(anyhow::format_err!("Failed to acquire Focuser"))
}
}
}
}
#[fasync::run_singlethreaded]
async fn main() -> Result<(), Error> {
fuchsia_syslog::init_with_tags(&["workstation_session"]).expect("Failed to initialize logger.");
let realm =
connect_to_service::<fsys::RealmMarker>().context("Could not connect to Realm service.")?;
let element_manager = SimpleElementManager::new(realm);
let mut element_repository = ElementRepository::new(Rc::new(element_manager));
let (app, pointer_hack_server) = launch_ermine(element_repository.make_server()).await?;
let view_provider = app.connect_to_service::<ViewProviderMarker>()?;
let presenter = app.connect_to_service::<GraphicalPresenterMarker>()?;
let mut handler = ElementEventHandler::new(presenter);
let scenic = connect_to_service::<ScenicMarker>()?;
let mut scene_manager = scene_management::FlatSceneManager::new(scenic, None, None).await?;
// This node can be used to move the associated view around.
let view_ref =
scene_manager.add_view_to_scene(view_provider, Some("Ermine".to_string())).await?;
let set_focus_fut = set_view_focus(Arc::downgrade(&scene_manager.focuser), view_ref);
let (input_device_registry_server, input_device_registry_request_stream_receiver) =
input_device_registry_server::make_server_and_receiver();
let services_fut =
expose_services(element_repository.make_server(), input_device_registry_server);
let input_fut = workstation_input_pipeline::handle_input(
scene_manager,
&pointer_hack_server,
input_device_registry_request_stream_receiver,
);
let element_manager_fut = element_repository.run_with_handler(&mut handler);
let focus_fut = input::focus_listening::handle_focus_changes();
//TODO(47080) monitor the futures to see if they complete in an error.
let _ = try_join!(services_fut, input_fut, element_manager_fut, focus_fut, set_focus_fut);
element_repository.shutdown()?;
Ok(())
}