| // 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()) |
| } |
| } |