blob: 4bc72f899564afb38968092d23eb216fbbc02571 [file] [log] [blame]
// Copyright 2018 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::{
message::Message,
view::{ViewAssistantPtr, ViewController, ViewKey},
};
use failure::{bail, Error, ResultExt};
use fidl::endpoints::{create_endpoints, create_proxy};
use fidl_fuchsia_ui_app::{ViewProviderRequest, ViewProviderRequestStream};
use fidl_fuchsia_ui_scenic::{ScenicMarker, ScenicProxy, SessionListenerRequest};
use fidl_fuchsia_ui_views::ViewToken;
use fuchsia_async as fasync;
use fuchsia_component::{self as component, client::connect_to_service};
use fuchsia_scenic::{Session, SessionPtr};
use fuchsia_zircon as zx;
use futures::{Future, StreamExt, TryFutureExt, TryStreamExt};
use std::{
cell::RefCell,
collections::BTreeMap,
mem,
sync::atomic::{AtomicBool, Ordering},
};
/// Trait that a mod author must implement. Currently responsible for creating
/// a view assistant when the Fuchsia view framework requests that the mod create
/// a view.
pub trait AppAssistant {
/// This method is responsible for setting up the AppAssistant implementation.
/// _It's not clear if this is going to so useful, as anything that isn't
/// initialized in the creation of the structure implementing AppAssistant
/// is going to have to be represented as an `Option`, which is awkward._
fn setup(&mut self) -> Result<(), Error>;
/// Called when the Fuchsia view system requests that a view be created.
fn create_view_assistant(
&mut self,
key: ViewKey,
session: &SessionPtr,
) -> Result<ViewAssistantPtr, Error>;
/// Return the list of names of services this app wants to provide
fn outgoing_services_names(&self) -> Vec<&'static str> {
Vec::new()
}
/// Handle a request to connect to a service provided by this app
fn handle_service_connection_request(
&mut self,
_service_name: &str,
_channel: fasync::Channel,
) -> Result<(), Error> {
bail!("handle_service_connection_request not implemented")
}
}
pub type AppAssistantPtr = Box<dyn AppAssistant>;
/// Struct that implements module-wide responsibilities, currently limited
/// to creating views on request.
pub struct App {
pub(crate) scenic: ScenicProxy,
view_controllers: BTreeMap<ViewKey, ViewController>,
next_key: ViewKey,
assistant: Option<AppAssistantPtr>,
messages: Vec<(ViewKey, Message)>,
}
/// Reference to the singleton app. _This type is likely to change in the future so
/// using this type alias might make for easier forward migration._
type AppPtr = RefCell<App>;
static DID_APP_INIT: AtomicBool = AtomicBool::new(false);
thread_local! {
/// Singleton reference to the running application
static APP: AppPtr = {
if DID_APP_INIT.fetch_or(true, Ordering::SeqCst) {
panic!("App::with() may only be called on the first thread that calls App::run()");
}
App::new().expect("Failed to create app")
};
}
impl App {
fn new() -> Result<AppPtr, Error> {
let scenic = connect_to_service::<ScenicMarker>()?;
Ok(RefCell::new(App {
scenic,
view_controllers: BTreeMap::new(),
next_key: 0,
assistant: None,
messages: Vec::new(),
}))
}
/// Starts an application based on Carnelian. The `assistant` parameter will
/// be used to create new views when asked to do so by the Fuchsia view system.
pub fn run(assistant: Box<AppAssistant>) -> Result<(), Error> {
let mut executor = fasync::Executor::new().context("Error creating executor")?;
let fut = App::with(|app| {
app.set_assistant(assistant);
let fut = App::start_services(app);
app.assistant.as_mut().unwrap().setup()?;
fut
})?;
executor.run_singlethreaded(fut);
Ok(())
}
/// Function to get a mutable reference to the singleton app struct, useful
/// in callbacks.
pub fn with<F, R>(f: F) -> R
where
F: FnOnce(&mut App) -> R,
{
APP.with(|app| {
let mut app_ref = app
.try_borrow_mut()
.expect("Attempted to call App::with() while already in a call to App::with()");
let r = f(&mut app_ref);
// Replace app's messages with an empty list before
// sending them. This isn't strictly needed now as
// queueing messages during `with` is not possible
// but it will be in the future.
let mut messages = Vec::new();
mem::swap(&mut messages, &mut app_ref.messages);
for (key, msg) in messages {
app_ref.send_message(key, msg);
}
r
})
}
/// Function to get a mutable reference to a view controller for a particular
/// view, by view key. It is a fatal error to pass a view id that doesn't
/// have a corresponding view controller.
pub fn with_view<F>(&mut self, view_key: ViewKey, f: F)
where
F: FnOnce(&mut ViewController),
{
if let Some(view) = self.view_controllers.get_mut(&view_key) {
f(view)
} else {
panic!("Could not find view controller for {}", view_key);
}
}
/// Send a message to a specific view controller. Messages not handled by the ViewController
/// will be forwarded to the `ViewControllerAssistant`.
fn send_message(&mut self, target: ViewKey, msg: Message) {
if let Some(view) = self.view_controllers.get_mut(&target) {
view.send_message(msg);
}
}
fn set_assistant(&mut self, assistant: AppAssistantPtr) {
self.assistant = Some(assistant);
}
/// Send a message to a specific view controller. Messages not handled by the ViewController
/// will be forwarded to the `ViewControllerAssistant`.
pub fn queue_message(&mut self, target: ViewKey, msg: Message) {
self.messages.push((target, msg));
}
fn setup_session(&self) -> Result<SessionPtr, Error> {
let (session_listener, session_listener_request) = create_endpoints()?;
let (session_proxy, session_request) = create_proxy()?;
let view_id = self.next_key;
self.scenic.create_session(session_request, Some(session_listener))?;
fasync::spawn_local(
session_listener_request
.into_stream()?
.map_ok(move |request| match request {
SessionListenerRequest::OnScenicEvent { events, .. } => App::with(|app| {
app.with_view(view_id, |view| {
view.handle_session_events(events);
})
}),
_ => (),
})
.try_collect::<()>()
.unwrap_or_else(|e| eprintln!("view listener error: {:?}", e)),
);
Ok(Session::new(session_proxy))
}
fn create_view_assistant(&mut self, session: &SessionPtr) -> Result<ViewAssistantPtr, Error> {
Ok(self.assistant.as_mut().unwrap().create_view_assistant(self.next_key, session)?)
}
fn create_view(&mut self, view_token: ViewToken) -> Result<(), Error> {
let session = self.setup_session()?;
let view_assistant = self.create_view_assistant(&session)?;
let mut view_controller =
ViewController::new(self.next_key, view_token, session, view_assistant)?;
view_controller.setup_animation_mode();
view_controller.present();
self.view_controllers.insert(self.next_key, view_controller);
self.next_key += 1;
Ok(())
}
fn spawn_view_provider_server(stream: ViewProviderRequestStream) {
fasync::spawn_local(
stream
.try_for_each(move |req| {
let ViewProviderRequest::CreateView { token, .. } = req;
let view_token = ViewToken { value: token };
App::with(|app| {
app.create_view(view_token)
.unwrap_or_else(|e| eprintln!("create_view error: {:?}", e));
});
futures::future::ready(Ok(()))
})
.unwrap_or_else(|e| eprintln!("error running ViewProvider server: {:?}", e)),
)
}
fn pass_connection_to_assistant(channel: zx::Channel, service_name: &'static str) {
match fasync::Channel::from_channel(channel) {
Ok(channel) => {
App::with(|app| {
app.assistant
.as_mut()
.unwrap()
.handle_service_connection_request(service_name, channel)
.unwrap_or_else(|e| {
eprintln!("error running {} server: {:?}", service_name, e)
});
});
}
Err(e) => eprintln!("error asyncifying channel: {:?}", e),
}
}
fn start_services(app: &mut App) -> Result<impl Future<Output = ()>, Error> {
let outgoing_services_names = app.assistant.as_ref().unwrap().outgoing_services_names();
let mut fs = component::server::ServiceFs::new();
let mut public = fs.dir("public");
public.add_fidl_service(Self::spawn_view_provider_server);
for name in outgoing_services_names {
public.add_service_at(name, move |channel| {
Self::pass_connection_to_assistant(channel, name);
None
});
}
fs.take_and_serve_directory_handle()?;
Ok(fs.collect())
}
}