blob: 597556c866be54349a984296f634acd0c74e64ba [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.
use crate::pointerinjector_config::{
InjectorViewportChangeFn, InjectorViewportHangingGet, InjectorViewportPublisher,
InjectorViewportSpec, InjectorViewportSubscriber,
};
use crate::{DisplayMetrics, ViewingDistance};
use anyhow::{Context, Error, Result};
use async_trait::async_trait;
use async_utils::hanging_get::server as hanging_get;
use fidl::endpoints::{create_proxy, Proxy};
use fidl_fuchsia_ui_composition::{self as ui_comp, ContentId, TransformId};
use fidl_fuchsia_ui_pointerinjector_configuration::{
SetupRequest as PointerInjectorConfigurationSetupRequest,
SetupRequestStream as PointerInjectorConfigurationSetupRequestStream,
};
use flatland_frame_scheduling_lib::*;
use fuchsia_sync::Mutex;
use futures::channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
use futures::channel::oneshot;
use futures::prelude::*;
use input_pipeline::Size;
use std::collections::VecDeque;
use std::ffi::CStr;
use std::process;
use std::sync::{Arc, Weak};
use tracing::{error, info, warn};
use {
fidl_fuchsia_accessibility_scene as a11y_scene, fidl_fuchsia_math as math,
fidl_fuchsia_ui_app as ui_app, fidl_fuchsia_ui_display_singleton as singleton_display,
fidl_fuchsia_ui_views as ui_views, fuchsia_async as fasync, fuchsia_scenic as scenic,
fuchsia_trace as trace, fuchsia_zircon as zx, math as fmath,
};
/// Presentation messages.
pub enum PresentationMessage {
/// Request a present call.
RequestPresent,
// Requests a present call; also, provides a channel that will get a ping back
// when the next frame has been presented on screen.
RequestPresentWithPingback(oneshot::Sender<()>),
}
/// Unbounded sender used for presentation messages.
pub type PresentationSender = UnboundedSender<PresentationMessage>;
/// Unbounded receiver used for presentation messages.
pub type PresentationReceiver = UnboundedReceiver<PresentationMessage>;
const _CURSOR_SIZE: (u32, u32) = (18, 29);
const CURSOR_HOTSPOT: (u32, u32) = (2, 4);
// TODO(https://fxbug.dev/42158302): Remove hardcoded scale when Flatland provides
// what is needed to determine the cursor scale factor.
const CURSOR_SCALE_MULTIPLIER: u32 = 5;
const CURSOR_SCALE_DIVIDER: u32 = 4;
// Converts a cursor size to physical pixels.
fn physical_cursor_size(value: u32) -> u32 {
(CURSOR_SCALE_MULTIPLIER * value) / CURSOR_SCALE_DIVIDER
}
pub type FlatlandPtr = Arc<Mutex<ui_comp::FlatlandProxy>>;
#[derive(Clone)]
struct TransformContentIdPair {
transform_id: TransformId,
content_id: ContentId,
}
/// FlatlandInstance encapsulates a FIDL connection to a Flatland instance, along with some other
/// state resulting from initializing the instance in a standard way; see FlatlandInstance::new().
/// For example, a view is created during initialization, and so FlatlandInstance stores the
/// corresponding ViewRef and a ParentViewportWatcher FIDL connection.
struct FlatlandInstance {
// TODO(https://fxbug.dev/42168246): Arc<Mutex<>>, yuck.
flatland: FlatlandPtr,
view_ref: ui_views::ViewRef,
root_transform_id: TransformId,
parent_viewport_watcher: ui_comp::ParentViewportWatcherProxy,
focuser: ui_views::FocuserProxy,
}
impl FlatlandInstance {
fn new(
flatland: ui_comp::FlatlandProxy,
view_creation_token: ui_views::ViewCreationToken,
id_generator: &mut scenic::flatland::IdGenerator,
) -> Result<FlatlandInstance, Error> {
let (parent_viewport_watcher, parent_viewport_watcher_request) =
create_proxy::<ui_comp::ParentViewportWatcherMarker>()?;
let (focuser, focuser_request) = create_proxy::<ui_views::FocuserMarker>()?;
let view_bound_protocols = ui_comp::ViewBoundProtocols {
view_focuser: Some(focuser_request),
..Default::default()
};
let view_identity = ui_views::ViewIdentityOnCreation::from(scenic::ViewRefPair::new()?);
let view_ref = scenic::duplicate_view_ref(&view_identity.view_ref)?;
flatland.create_view2(
view_creation_token,
view_identity,
view_bound_protocols,
parent_viewport_watcher_request,
)?;
let root_transform_id = id_generator.next_transform_id();
flatland.create_transform(&root_transform_id)?;
flatland.set_root_transform(&root_transform_id)?;
Ok(FlatlandInstance {
flatland: Arc::new(Mutex::new(flatland)),
view_ref,
root_transform_id,
parent_viewport_watcher,
focuser,
})
}
}
fn request_present_with_pingback(
presentation_sender: &PresentationSender,
) -> Result<oneshot::Receiver<()>, Error> {
let (sender, receiver) = oneshot::channel::<()>();
presentation_sender.unbounded_send(PresentationMessage::RequestPresentWithPingback(sender))?;
Ok(receiver)
}
/// SceneManager manages the platform/framework-controlled part of the global Scenic scene
/// graph, with the fundamental goal of connecting the physical display to the product-defined user
/// shell. The part of the scene graph managed by the scene manager is split between three Flatland
/// instances, which are linked by view/viewport pairs.
//
// The scene graph looks like this:
//
// FD FD: FlatlandDisplay
// |
// R* R*: root transform of |root_flatland|,
// | and also the corresponding view/view-ref (see below)
// / \
// / \ Rc: transform holding whatever is necessary to render the cursor
// Rpi Rc
// | \ Rpi: transform with viewport linking to |pointerinjector_flatland|
// | (etc.) (see docs on struct field for rationale)
// |
// P* P*: root transform of |pointerinjector_flatland|,
// | and also the corresponding view/view-ref (see below)
// |
// Pa Pa: transform with viewport linking to an external Flatland instance
// | owned by a11y manager.
// |
// A* A*: root transform of |a11y_flatland| (owned by a11y manager),
// | and also the corresponding view/view-ref (see below).
// |
// As As: transform with viewport linking to |scene_flatland|.
// |
// |
// S* S*: root transform of |scene_flatland|,
// | and also the corresponding view/view-ref (see below)
// |
// (user shell) The session uses the SceneManager.SetRootView() FIDL API to attach the user
// shell to the scene graph depicted above.
//
// There is a reason why the "corresponding view/view-refs" are called out in the diagram above.
// When registering an input device with the fuchsia.ui.pointerinjector.Registry API, the Config
// must specify two ViewRefs, the "context" and the "target"; the former must be a strict ancestor
// or the former (the target denotes the first eligible view to receive input; it will always be
// the root of the focus chain). The context ViewRef is R* and the target ViewRef is P*. Note that
// the possibly-inserted accessiblity view is the direct descendant of |pointerinjector_flatland|.
// This gives the accessiblity manager the ability to give itself focus, and therefore receive all
// input.
pub struct SceneManager {
// Flatland connection between the physical display and the rest of the scene graph.
_display: ui_comp::FlatlandDisplayProxy,
// The size that will ultimately be assigned to the View created with the
// `fuchsia.session.scene.Manager` protocol.
client_viewport_size: math::SizeU,
// Flatland instance that connects to |display|. Hosts a viewport which connects it to
// to a view in |pointerinjector_flatland|.
//
// See the above diagram of SceneManager's scene graph topology.
root_flatland: FlatlandInstance,
// Flatland instance that sits beneath |root_flatland| in the scene graph. The reason that this
// exists is that two different ViewRefs must be provided when configuring the input pipeline to
// inject pointer events into Scenic via fuchsia.ui.pointerinjector.Registry; since a Flatland
// instance can have only a single view, we add an additional Flatland instance into the scene
// graph to obtain the second view (the "target" view; the "context" view is obtained from
// |root_flatland|).
//
// See the above diagram of SceneManager's scene graph topology.
_pointerinjector_flatland: FlatlandInstance,
// Flatland instance that embeds the system shell (i.e. via the SetRootView() FIDL API). Its
// root view is attached to a viewport owned by the accessibility manager (via
// fuchsia.accessibility.scene.Provider/CreateView()).
scene_flatland: FlatlandInstance,
// These are the ViewRefs returned by get_pointerinjection_view_refs(). They are used to
// configure input-pipeline handlers for pointer events.
context_view_ref: ui_views::ViewRef,
target_view_ref: ui_views::ViewRef,
// Used to sent presentation requests for |root_flatand| and |scene_flatland|, respectively.
root_flatland_presentation_sender: PresentationSender,
_pointerinjector_flatland_presentation_sender: PresentationSender,
scene_flatland_presentation_sender: PresentationSender,
// Holds a pair of IDs that are used to embed the system shell inside |scene_flatland|, a
// TransformId identifying a transform in the scene graph, and a ContentId which identifies a
// a viewport that is set as the content of that transform.
scene_root_viewport_ids: Option<TransformContentIdPair>,
// Generates a sequential stream of ContentIds and TransformIds. By guaranteeing
// uniqueness across all Flatland instances, we avoid potential confusion during debugging.
id_generator: scenic::flatland::IdGenerator,
// Supports callers of fuchsia.ui.pointerinjector.configuration.setup.WatchViewport(), allowing
// each invocation to subscribe to changes in the viewport region.
viewport_hanging_get: Arc<Mutex<InjectorViewportHangingGet>>,
// Used to publish viewport changes to subscribers of |viewport_hanging_get|.
// TODO(https://fxbug.dev/42168647): use this to publish changes to screen resolution.
_viewport_publisher: Arc<Mutex<InjectorViewportPublisher>>,
// Used to position the cursor.
cursor_transform_id: Option<TransformId>,
// Used to track cursor visibility.
cursor_visibility: bool,
// Used to track the display metrics for the root scene.
display_metrics: DisplayMetrics,
// Used to convert between logical and physical pixels.
//
// (physical pixel) = (device_pixel_ratio) * (logical pixel)
device_pixel_ratio: f32,
}
/// A [SceneManager] manages a Scenic scene graph, and allows clients to add views to it.
/// Each [`SceneManager`] can choose how to configure the scene, including lighting, setting the
/// frames of added views, etc.
///
/// # Example
///
/// ```
/// let view_provider = some_apips.connect_to_service::<ViewProviderMarker>()?;
///
/// let scenic = connect_to_service::<ScenicMarker>()?;
/// let mut scene_manager = scene_management::FlatSceneManager::new(scenic).await?;
/// scene_manager.set_root_view(viewport_token).await?;
///
/// ```
#[async_trait]
pub trait SceneManagerTrait: Send {
/// Sets the root view for the scene.
///
/// ViewRef will be unset for Flatland views.
///
/// Removes any previous root view, as well as all of its descendants.
async fn set_root_view(
&mut self,
viewport_creation_token: ui_views::ViewportCreationToken,
view_ref: Option<ui_views::ViewRef>,
) -> Result<(), Error>;
/// DEPRECATED: Use ViewportToken version above.
/// Sets the root view for the scene.
///
/// Removes any previous root view, as well as all of its descendants.
async fn set_root_view_deprecated(
&mut self,
view_provider: ui_app::ViewProviderProxy,
) -> Result<ui_views::ViewRef, Error>;
/// Requests a new frame be presented in the scene.
fn present_root_view(&self);
/// Sets the position of the cursor in the current scene. If no cursor has been created it will
/// create one using default settings.
///
/// # Parameters
/// - `position_physical_px`: A [`Position`] struct representing the cursor position, in physical
/// pixels.
///
/// # Notes
/// If a custom cursor has not been set using `set_cursor_image` or `set_cursor_shape` a default
/// cursor will be created and added to the scene. The implementation of the `SceneManager` trait
/// is responsible for translating the raw input position into "pips".
fn set_cursor_position(&mut self, position_physical_px: input_pipeline::Position);
/// Sets the visibility of the cursor in the current scene. The cursor is visible by default.
///
/// # Parameters
/// - `visible`: Boolean value indicating if the cursor should be visible.
fn set_cursor_visibility(&mut self, visible: bool);
// Supports the implementation of fuchsia.ui.pointerinjector.configurator.Setup.GetViewRefs()
fn get_pointerinjection_view_refs(&self) -> (ui_views::ViewRef, ui_views::ViewRef);
/// Input pipeline handlers such as TouchInjectorHandler require the display size in order to be
/// instantiated. This method exposes that information.
fn get_pointerinjection_display_size(&self) -> input_pipeline::Size;
// Support the hanging get implementation of
// fuchsia.ui.pointerinjector.configurator.Setup.WatchViewport().
fn get_pointerinjector_viewport_watcher_subscription(&self) -> InjectorViewportSubscriber;
fn get_display_metrics(&self) -> &DisplayMetrics;
}
#[async_trait]
impl SceneManagerTrait for SceneManager {
/// Sets the root view for the scene.
///
/// ViewRef will be unset for Flatland views.
///
/// Removes any previous root view, as well as all of its descendants.
async fn set_root_view(
&mut self,
viewport_creation_token: ui_views::ViewportCreationToken,
_view_ref: Option<ui_views::ViewRef>,
) -> Result<(), Error> {
self.set_root_view_internal(viewport_creation_token).await.map(|_view_ref| {})
}
/// DEPRECATED: Use ViewportToken version above.
/// Sets the root view for the scene.
///
/// Removes any previous root view, as well as all of its descendants.
async fn set_root_view_deprecated(
&mut self,
view_provider: ui_app::ViewProviderProxy,
) -> Result<ui_views::ViewRef, Error> {
let link_token_pair = scenic::flatland::ViewCreationTokenPair::new()?;
// Use view provider to initiate creation of the view which will be connected to the
// viewport that we create below.
view_provider.create_view2(ui_app::CreateView2Args {
view_creation_token: Some(link_token_pair.view_creation_token),
..Default::default()
})?;
self.set_root_view_internal(link_token_pair.viewport_creation_token).await
}
/// Requests a new frame be presented in the scene.
fn present_root_view(&self) {
self.root_flatland_presentation_sender
.unbounded_send(PresentationMessage::RequestPresent)
.expect("send failed");
}
// Supports the implementation of fuchsia.ui.pointerinjector.configurator.Setup.GetViewRefs()
fn get_pointerinjection_view_refs(&self) -> (ui_views::ViewRef, ui_views::ViewRef) {
(
scenic::duplicate_view_ref(&self.context_view_ref).expect("failed to copy ViewRef"),
scenic::duplicate_view_ref(&self.target_view_ref).expect("failed to copy ViewRef"),
)
}
/// Sets the position of the cursor in the current scene. If no cursor has been created it will
/// create one using default settings.
///
/// # Parameters
/// - `position_physical_px`: A [`Position`] struct representing the cursor position, in physical
/// pixels.
///
/// # Notes
/// If a custom cursor has not been set using `set_cursor_image` or `set_cursor_shape` a default
/// cursor will be created and added to the scene. The implementation of the `SceneManager` trait
/// is responsible for translating the raw input position into "pips".
fn set_cursor_position(&mut self, position_physical_px: input_pipeline::Position) {
if let Some(cursor_transform_id) = self.cursor_transform_id {
let position_logical = position_physical_px / self.device_pixel_ratio;
let x =
position_logical.x.round() as i32 - physical_cursor_size(CURSOR_HOTSPOT.0) as i32;
let y =
position_logical.y.round() as i32 - physical_cursor_size(CURSOR_HOTSPOT.1) as i32;
let flatland = self.root_flatland.flatland.lock();
flatland
.set_translation(&cursor_transform_id, &fmath::Vec_ { x, y })
.expect("fidl error");
self.root_flatland_presentation_sender
.unbounded_send(PresentationMessage::RequestPresent)
.expect("send failed");
}
}
/// Sets the visibility of the cursor in the current scene. The cursor is visible by default.
///
/// # Parameters
/// - `visible`: Boolean value indicating if the cursor should be visible.
fn set_cursor_visibility(&mut self, visible: bool) {
if let Some(cursor_transform_id) = self.cursor_transform_id {
if self.cursor_visibility != visible {
self.cursor_visibility = visible;
let flatland = self.root_flatland.flatland.lock();
if visible {
flatland
.add_child(&self.root_flatland.root_transform_id, &cursor_transform_id)
.expect("failed to add cursor to scene");
} else {
flatland
.remove_child(&self.root_flatland.root_transform_id, &cursor_transform_id)
.expect("failed to remove cursor from scene");
}
self.root_flatland_presentation_sender
.unbounded_send(PresentationMessage::RequestPresent)
.expect("send failed");
}
}
}
/// Input pipeline handlers such as TouchInjectorHandler require the display size in order to be
/// instantiated. This method exposes that information.
fn get_pointerinjection_display_size(&self) -> Size {
// Input pipeline expects size in physical pixels.
self.display_metrics.size_in_pixels()
}
// Support the hanging get implementation of
// fuchsia.ui.pointerinjector.configurator.Setup.WatchViewport().
fn get_pointerinjector_viewport_watcher_subscription(&self) -> InjectorViewportSubscriber {
self.viewport_hanging_get.lock().new_subscriber()
}
fn get_display_metrics(&self) -> &DisplayMetrics {
&self.display_metrics
}
}
const ROOT_VIEW_DEBUG_NAME: &str = "SceneManager Display";
const POINTER_INJECTOR_DEBUG_NAME: &str = "SceneManager PointerInjector";
const SCENE_DEBUG_NAME: &str = "SceneManager Scene";
const ROOT_VIEW_PRESENT_TRACING_NAME: &CStr = c"Flatland::PerAppPresent[SceneManager Display]";
const POINTER_INJECTOR_PRESENT_TRACING_NAME: &CStr =
c"Flatland::PerAppPresent[SceneManager PointerInjector]";
const SCENE_TRACING_NAME: &CStr = c"Flatland::PerAppPresent[SceneManager Scene]";
impl SceneManager {
pub async fn new(
display: ui_comp::FlatlandDisplayProxy,
singleton_display_info: singleton_display::InfoProxy,
root_flatland: ui_comp::FlatlandProxy,
pointerinjector_flatland: ui_comp::FlatlandProxy,
scene_flatland: ui_comp::FlatlandProxy,
a11y_view_provider: a11y_scene::ProviderProxy,
display_rotation: u64,
display_pixel_density: Option<f32>,
viewing_distance: Option<ViewingDistance>,
) -> Result<Self, Error> {
// If scenic closes, all the Scenic connections become invalid. This task exits the
// process in response.
start_exit_on_scenic_closed_task(display.clone());
let mut id_generator = scenic::flatland::IdGenerator::new();
// Generate unique transform/content IDs that will be used to create the sub-scenegraphs
// in the Flatland instances managed by SceneManager.
let pointerinjector_viewport_transform_id = id_generator.next_transform_id();
let pointerinjector_viewport_content_id = id_generator.next_content_id();
let a11y_viewport_transform_id = id_generator.next_transform_id();
let a11y_viewport_content_id = id_generator.next_content_id();
root_flatland.set_debug_name(ROOT_VIEW_DEBUG_NAME)?;
pointerinjector_flatland.set_debug_name(POINTER_INJECTOR_DEBUG_NAME)?;
scene_flatland.set_debug_name(SCENE_DEBUG_NAME)?;
let root_view_creation_pair = scenic::flatland::ViewCreationTokenPair::new()?;
let root_flatland = FlatlandInstance::new(
root_flatland,
root_view_creation_pair.view_creation_token,
&mut id_generator,
)?;
let pointerinjector_view_creation_pair = scenic::flatland::ViewCreationTokenPair::new()?;
let pointerinjector_flatland = FlatlandInstance::new(
pointerinjector_flatland,
pointerinjector_view_creation_pair.view_creation_token,
&mut id_generator,
)?;
let scene_view_creation_pair = scenic::flatland::ViewCreationTokenPair::new()?;
let scene_flatland = FlatlandInstance::new(
scene_flatland,
scene_view_creation_pair.view_creation_token,
&mut id_generator,
)?;
// Create display metrics, and set the device pixel ratio of FlatlandDisplay.
let info = singleton_display_info.get_metrics().await?;
let extent_in_px =
info.extent_in_px.ok_or(anyhow::anyhow!("Did not receive display size"))?;
let display_metrics = DisplayMetrics::new(
Size { width: extent_in_px.width as f32, height: extent_in_px.height as f32 },
display_pixel_density,
viewing_distance,
None,
);
display.set_device_pixel_ratio(&fmath::VecF {
x: display_metrics.pixels_per_pip(),
y: display_metrics.pixels_per_pip(),
})?;
// Connect the FlatlandDisplay to |root_flatland|'s view.
{
// We don't need to watch the child view, since we also own it. So, we discard the
// client end of the the channel pair.
let (_, child_view_watcher_request) =
create_proxy::<ui_comp::ChildViewWatcherMarker>()?;
display.set_content(
root_view_creation_pair.viewport_creation_token,
child_view_watcher_request,
)?;
}
// Obtain layout info from FlatlandDisplay. Logical size may be different from the
// display size if DPR is applied.
let layout_info = root_flatland.parent_viewport_watcher.get_layout().await?;
let root_viewport_size = layout_info
.logical_size
.ok_or(anyhow::anyhow!("Did not receive layout info from the display"))?;
let (
display_rotation_enum,
injector_viewport_translation,
flip_injector_viewport_dimensions,
) = match display_rotation % 360 {
0 => Ok((ui_comp::Orientation::Ccw0Degrees, math::Vec_ { x: 0, y: 0 }, false)),
90 => Ok((
// Rotation is specified in the opposite winding direction to the
// specified |display_rotation| value. Winding in the opposite direction is equal
// to -90 degrees, which is equivalent to 270.
ui_comp::Orientation::Ccw270Degrees,
math::Vec_ { x: root_viewport_size.width as i32, y: 0 },
true,
)),
180 => Ok((
ui_comp::Orientation::Ccw180Degrees,
math::Vec_ {
x: root_viewport_size.width as i32,
y: root_viewport_size.height as i32,
},
false,
)),
270 => Ok((
// Rotation is specified in the opposite winding direction to the
// specified |display_rotation| value. Winding in the opposite direction is equal
// to -270 degrees, which is equivalent to 90.
ui_comp::Orientation::Ccw90Degrees,
math::Vec_ { x: 0, y: root_viewport_size.height as i32 },
true,
)),
_ => Err(anyhow::anyhow!("Invalid display rotation; must be {{0,90,180,270}}")),
}?;
let client_viewport_size = match flip_injector_viewport_dimensions {
true => {
math::SizeU { width: root_viewport_size.height, height: root_viewport_size.width }
}
false => {
math::SizeU { width: root_viewport_size.width, height: root_viewport_size.height }
}
};
// Create the pointerinjector view and embed it as a child of the root view.
{
let flatland = root_flatland.flatland.lock();
flatland.create_transform(&pointerinjector_viewport_transform_id)?;
flatland.add_child(
&root_flatland.root_transform_id,
&pointerinjector_viewport_transform_id,
)?;
flatland
.set_orientation(&pointerinjector_viewport_transform_id, display_rotation_enum)?;
flatland.set_translation(
&pointerinjector_viewport_transform_id,
&injector_viewport_translation,
)?;
let link_properties = ui_comp::ViewportProperties {
logical_size: Some(client_viewport_size),
..Default::default()
};
let (_, child_view_watcher_request) =
create_proxy::<ui_comp::ChildViewWatcherMarker>()?;
flatland.create_viewport(
&pointerinjector_viewport_content_id,
pointerinjector_view_creation_pair.viewport_creation_token,
&link_properties,
child_view_watcher_request,
)?;
flatland.set_content(
&pointerinjector_viewport_transform_id,
&pointerinjector_viewport_content_id,
)?;
}
let a11y_view_creation_pair = scenic::flatland::ViewCreationTokenPair::new()?;
// Bridge the pointerinjector and a11y Flatland instances.
let (a11y_view_watcher, a11y_view_watcher_request) =
create_proxy::<ui_comp::ChildViewWatcherMarker>()?;
{
let flatland = pointerinjector_flatland.flatland.lock();
flatland.create_transform(&a11y_viewport_transform_id)?;
flatland.add_child(
&pointerinjector_flatland.root_transform_id,
&a11y_viewport_transform_id,
)?;
let link_properties = ui_comp::ViewportProperties {
logical_size: Some(client_viewport_size),
..Default::default()
};
flatland.create_viewport(
&a11y_viewport_content_id,
a11y_view_creation_pair.viewport_creation_token,
&link_properties,
a11y_view_watcher_request,
)?;
flatland.set_content(&a11y_viewport_transform_id, &a11y_viewport_content_id)?;
}
// Request for the a11y manager to create its view.
a11y_view_provider.create_view(
a11y_view_creation_pair.view_creation_token,
scene_view_creation_pair.viewport_creation_token,
)?;
// Start Present() loops for both Flatland instances, and request that both be presented.
let (root_flatland_presentation_sender, root_receiver) = unbounded();
start_flatland_presentation_loop(
root_receiver,
Arc::downgrade(&root_flatland.flatland),
ROOT_VIEW_DEBUG_NAME.to_string(),
);
let (pointerinjector_flatland_presentation_sender, pointerinjector_receiver) = unbounded();
start_flatland_presentation_loop(
pointerinjector_receiver,
Arc::downgrade(&pointerinjector_flatland.flatland),
POINTER_INJECTOR_DEBUG_NAME.to_string(),
);
let (scene_flatland_presentation_sender, scene_receiver) = unbounded();
start_flatland_presentation_loop(
scene_receiver,
Arc::downgrade(&scene_flatland.flatland),
SCENE_DEBUG_NAME.to_string(),
);
let mut pingback_channels = Vec::new();
pingback_channels.push(request_present_with_pingback(&root_flatland_presentation_sender)?);
pingback_channels
.push(request_present_with_pingback(&pointerinjector_flatland_presentation_sender)?);
pingback_channels.push(request_present_with_pingback(&scene_flatland_presentation_sender)?);
// Wait for a11y view to attach before proceeding.
let a11y_view_status = a11y_view_watcher.get_status().await?;
match a11y_view_status {
ui_comp::ChildViewStatus::ContentHasPresented => {}
}
// Read device pixel ratio from layout info.
let device_pixel_ratio = display_metrics.pixels_per_pip();
let viewport_hanging_get: Arc<Mutex<InjectorViewportHangingGet>> =
create_viewport_hanging_get({
InjectorViewportSpec {
width: display_metrics.width_in_pixels() as f32,
height: display_metrics.height_in_pixels() as f32,
scale: 1. / device_pixel_ratio,
x_offset: 0.,
y_offset: 0.,
}
});
let viewport_publisher = Arc::new(Mutex::new(viewport_hanging_get.lock().new_publisher()));
let context_view_ref = scenic::duplicate_view_ref(&root_flatland.view_ref)?;
let target_view_ref = scenic::duplicate_view_ref(&pointerinjector_flatland.view_ref)?;
// Wait for all pingbacks to ensure the scene is fully set up before returning.
for receiver in pingback_channels {
_ = receiver.await;
}
Ok(SceneManager {
_display: display,
client_viewport_size,
root_flatland,
_pointerinjector_flatland: pointerinjector_flatland,
scene_flatland,
context_view_ref,
target_view_ref,
root_flatland_presentation_sender,
_pointerinjector_flatland_presentation_sender:
pointerinjector_flatland_presentation_sender,
scene_flatland_presentation_sender,
scene_root_viewport_ids: None,
id_generator,
viewport_hanging_get,
_viewport_publisher: viewport_publisher,
cursor_transform_id: None,
cursor_visibility: true,
display_metrics,
device_pixel_ratio,
})
}
async fn set_root_view_internal(
&mut self,
viewport_creation_token: ui_views::ViewportCreationToken,
) -> Result<ui_views::ViewRef> {
// Remove any existing viewport.
if let Some(ids) = &self.scene_root_viewport_ids {
let locked = self.scene_flatland.flatland.lock();
locked
.set_content(&ids.transform_id, &ContentId { value: 0 })
.context("could not set content")?;
locked.remove_child(&self.scene_flatland.root_transform_id, &ids.transform_id)?;
locked.release_transform(&ids.transform_id).context("could not release transform")?;
let _ = locked.release_viewport(&ids.content_id);
self.scene_root_viewport_ids = None;
}
// Create new viewport.
let ids = TransformContentIdPair {
transform_id: self.id_generator.next_transform_id(),
content_id: self.id_generator.next_content_id(),
};
let (child_view_watcher, child_view_watcher_request) =
create_proxy::<ui_comp::ChildViewWatcherMarker>()
.context("could not connect to ChildViewWatcher")?;
{
let locked = self.scene_flatland.flatland.lock();
let viewport_properties = ui_comp::ViewportProperties {
logical_size: Some(self.client_viewport_size),
..Default::default()
};
locked.create_viewport(
&ids.content_id,
viewport_creation_token,
&viewport_properties,
child_view_watcher_request,
)?;
locked.create_transform(&ids.transform_id).context("could not create transform")?;
locked.add_child(&self.scene_flatland.root_transform_id, &ids.transform_id)?;
locked
.set_content(&ids.transform_id, &ids.content_id)
.context("could not set content #2")?;
}
self.scene_root_viewport_ids = Some(ids);
// Present the previous scene graph mutations. This MUST be done before awaiting the result
// of get_view_ref() below, because otherwise the view won't become attached to the global
// scene graph topology, and the awaited ViewRef will never come.
let mut pingback_channels = Vec::new();
pingback_channels.push(
request_present_with_pingback(&self.scene_flatland_presentation_sender)
.context("could not request present with pingback")?,
);
let _child_status =
child_view_watcher.get_status().await.context("could not call get_status")?;
let child_view_ref =
child_view_watcher.get_view_ref().await.context("could not get view_ref")?;
let child_view_ref_copy =
scenic::duplicate_view_ref(&child_view_ref).context("could not duplicate view_ref")?;
let request_focus_result = self.root_flatland.focuser.request_focus(child_view_ref).await;
match request_focus_result {
Err(e) => warn!("Request focus failed with err: {}", e),
Ok(Err(value)) => warn!("Request focus failed with err: {:?}", value),
Ok(_) => {}
}
pingback_channels.push(
request_present_with_pingback(&self.root_flatland_presentation_sender)
.context("could not request present with pingback #2")?,
);
// Wait for all pingbacks to ensure the scene is fully set up before returning.
for receiver in pingback_channels {
_ = receiver.await;
}
Ok(child_view_ref_copy)
}
}
pub fn create_viewport_hanging_get(
initial_spec: InjectorViewportSpec,
) -> Arc<Mutex<InjectorViewportHangingGet>> {
let notify_fn: InjectorViewportChangeFn = Box::new(|viewport_spec, responder| {
if let Err(fidl_error) = responder.send(&(*viewport_spec).into()) {
info!("Viewport hanging get notification, FIDL error: {}", fidl_error);
}
// TODO(https://fxbug.dev/42168817): the HangingGet docs don't explain what value to return.
true
});
Arc::new(Mutex::new(hanging_get::HangingGet::new(initial_spec, notify_fn)))
}
pub fn start_exit_on_scenic_closed_task(flatland_proxy: ui_comp::FlatlandDisplayProxy) {
fasync::Task::local(async move {
let _ = flatland_proxy.on_closed().await;
info!("Scenic died, closing SceneManager too.");
process::exit(1);
})
.detach()
}
pub fn start_flatland_presentation_loop(
mut receiver: PresentationReceiver,
weak_flatland: Weak<Mutex<ui_comp::FlatlandProxy>>,
debug_name: String,
) {
fasync::Task::local(async move {
let mut present_count = 0;
let scheduler = ThroughputScheduler::new();
let mut flatland_event_stream = {
if let Some(flatland) = weak_flatland.upgrade() {
flatland.lock().take_event_stream()
} else {
warn!(
"Failed to upgrade Flatand weak ref; exiting presentation loop for {debug_name}"
);
return;
}
};
let mut channels_awaiting_pingback = VecDeque::from([Vec::new()]);
loop {
futures::select! {
message = receiver.next() => {
match message {
Some(PresentationMessage::RequestPresent) => {
scheduler.request_present();
}
Some(PresentationMessage::RequestPresentWithPingback(channel)) => {
channels_awaiting_pingback.back_mut().unwrap().push(channel);
scheduler.request_present();
}
None => {}
}
}
flatland_event = flatland_event_stream.next() => {
match flatland_event {
Some(Ok(ui_comp::FlatlandEvent::OnNextFrameBegin{ values })) => {
trace::duration!(c"scene_manager", c"SceneManager::OnNextFrameBegin",
"debug_name" => &*debug_name);
let credits = values
.additional_present_credits
.expect("Present credits must exist");
let infos = values
.future_presentation_infos
.expect("Future presentation infos must exist")
.iter()
.map(
|x| PresentationInfo{
latch_point: zx::Time::from_nanos(x.latch_point.unwrap()),
presentation_time: zx::Time::from_nanos(
x.presentation_time.unwrap())
})
.collect();
scheduler.on_next_frame_begin(credits, infos);
}
Some(Ok(ui_comp::FlatlandEvent::OnFramePresented{ frame_presented_info })) => {
trace::duration!(c"scene_manager", c"SceneManager::OnFramePresented",
"debug_name" => &*debug_name);
let actual_presentation_time =
zx::Time::from_nanos(frame_presented_info.actual_presentation_time);
let presented_infos: Vec<PresentedInfo> =
frame_presented_info.presentation_infos
.into_iter()
.map(|x| x.into())
.collect();
// Pingbacks for presented updates. For each presented frame, drain all
// of the corresponding pingback channels
for _ in 0..presented_infos.len() {
for channel in channels_awaiting_pingback.pop_back().unwrap() {
_ = channel.send(());
}
}
scheduler.on_frame_presented(actual_presentation_time, presented_infos);
}
Some(Ok(ui_comp::FlatlandEvent::OnError{ error })) => {
error!(
"Received FlatlandError code: {}; exiting listener loop for {debug_name}",
error.into_primitive()
);
return;
}
_ => {}
}
}
present_parameters = scheduler.wait_to_update().fuse() => {
trace::duration!(c"scene_manager", c"SceneManager::Present",
"debug_name" => &*debug_name);
match debug_name.as_str() {
ROOT_VIEW_DEBUG_NAME => {
trace::flow_begin!(c"gfx", ROOT_VIEW_PRESENT_TRACING_NAME, present_count.into());
}
POINTER_INJECTOR_DEBUG_NAME => {
trace::flow_begin!(c"gfx", POINTER_INJECTOR_PRESENT_TRACING_NAME, present_count.into());
}
SCENE_DEBUG_NAME => {
trace::flow_begin!(c"gfx", SCENE_TRACING_NAME, present_count.into());
}
_ => {
warn!("SceneManager::Present with unknown debug_name {:?}", debug_name);
}
}
present_count += 1;
channels_awaiting_pingback.push_front(Vec::new());
if let Some(flatland) = weak_flatland.upgrade() {
flatland
.lock()
.present(present_parameters.into())
.expect("Present failed for {debug_name}");
} else {
warn!(
"Failed to upgrade Flatand weak ref; exiting listener loop for {debug_name}"
);
return;
}
}
}
}})
.detach()
}
pub fn handle_pointer_injector_configuration_setup_request_stream(
mut request_stream: PointerInjectorConfigurationSetupRequestStream,
scene_manager: Arc<futures::lock::Mutex<dyn SceneManagerTrait>>,
) {
fasync::Task::local(async move {
let subscriber =
scene_manager.lock().await.get_pointerinjector_viewport_watcher_subscription();
loop {
let request = request_stream.try_next().await;
match request {
Ok(Some(PointerInjectorConfigurationSetupRequest::GetViewRefs { responder })) => {
let (context_view_ref, target_view_ref) =
scene_manager.lock().await.get_pointerinjection_view_refs();
if let Err(e) = responder.send(context_view_ref, target_view_ref) {
warn!("Failed to send GetViewRefs() response: {}", e);
}
}
Ok(Some(PointerInjectorConfigurationSetupRequest::WatchViewport { responder })) => {
if let Err(e) = subscriber.register(responder) {
warn!("Failed to register WatchViewport() subscriber: {}", e);
}
}
Ok(None) => {
return;
}
Err(e) => {
error!("Error obtaining SetupRequest: {}", e);
return;
}
}
}
})
.detach()
}