| // 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 failure::{Error, ResultExt}; |
| use fidl::endpoints::{ |
| create_endpoints, create_proxy, ClientEnd, RequestStream, ServerEnd, ServiceMarker, |
| }; |
| use fidl_fuchsia_developer_tiles as tiles; |
| use fidl_fuchsia_math::SizeF; |
| use fidl_fuchsia_modular::{ |
| SessionShellContextMarker, SessionShellContextProxy, SessionShellMarker, SessionShellRequest, |
| SessionShellRequestStream, StoryProviderProxy, StoryProviderWatcherMarker, |
| StoryProviderWatcherRequest, StoryState, |
| }; |
| use fidl_fuchsia_ui_input::{ |
| KeyboardEvent, KeyboardEventPhase, MODIFIER_LEFT_SUPER, MODIFIER_RIGHT_SUPER, |
| }; |
| use fidl_fuchsia_ui_policy::{ |
| KeyboardCaptureListenerHackMarker, KeyboardCaptureListenerHackRequest, PresentationProxy, |
| }; |
| use fidl_fuchsia_ui_viewsv1::{ |
| ViewManagerMarker, ViewManagerProxy, ViewProviderMarker, ViewProviderRequest::CreateView, |
| ViewProviderRequestStream, |
| }; |
| use fidl_fuchsia_ui_viewsv1token::ViewOwnerMarker; |
| use fuchsia_app::{self as component, client::connect_to_service}; |
| use fuchsia_async as fasync; |
| use fuchsia_zircon as zx; |
| use futures::{future::ready as fready, TryFutureExt, TryStreamExt}; |
| use lazy_static::lazy_static; |
| use parking_lot::Mutex; |
| use std::sync::Arc; |
| |
| mod ask_box; |
| mod view; |
| |
| use crate::view::{ErmineView, ErmineViewPtr}; |
| |
| pub struct App { |
| view_manager: ViewManagerProxy, |
| views: Vec<ErmineViewPtr>, |
| session_shell_context: SessionShellContextProxy, |
| story_provider: StoryProviderProxy, |
| presentation_proxy: Option<PresentationProxy>, |
| next_key: u32, |
| } |
| |
| pub type AppPtr = Arc<Mutex<App>>; |
| |
| lazy_static! { |
| pub static ref APP: AppPtr = App::new().expect("Unable to create ermine app"); |
| } |
| |
| impl App { |
| pub fn new() -> Result<AppPtr, Error> { |
| let view_manager = connect_to_service::<ViewManagerMarker>()?; |
| let session_shell_context = connect_to_service::<SessionShellContextMarker>()?; |
| let (story_provider, story_provider_end) = create_proxy()?; |
| session_shell_context |
| .clone() |
| .get_story_provider(story_provider_end)?; |
| |
| let app = Arc::new(Mutex::new(App { |
| view_manager, |
| views: vec![], |
| session_shell_context, |
| story_provider, |
| presentation_proxy: None, |
| next_key: 1, |
| })); |
| app.lock().spawn_story_watcher()?; |
| app.lock().setup_keyboard_hack()?; |
| Ok(app) |
| } |
| |
| pub fn spawn_view_provider_server(chan: fasync::Channel) { |
| fasync::spawn( |
| ViewProviderRequestStream::from_channel(chan) |
| .try_for_each(move |req| { |
| let CreateView { view_owner, .. } = req; |
| APP.lock() |
| .create_view(view_owner) |
| .expect("Create view failed"); |
| futures::future::ready(Ok(())) |
| }) |
| .unwrap_or_else(|e| eprintln!("error running view_provider server: {:?}", e)), |
| ) |
| } |
| |
| pub fn create_view(&mut self, req: ServerEnd<ViewOwnerMarker>) -> Result<(), Error> { |
| let (view, view_server_end) = create_proxy()?; |
| let (view_listener, view_listener_server) = zx::Channel::create()?; |
| let view_listener_request = ServerEnd::new(view_listener_server); |
| let (mine, theirs) = zx::EventPair::create()?; |
| self.view_manager.create_view( |
| view_server_end, |
| req, |
| ClientEnd::new(view_listener), |
| theirs, |
| None, |
| )?; |
| let (scenic, scenic_request) = create_proxy()?; |
| self.view_manager.get_scenic(scenic_request)?; |
| let view_ptr = ErmineView::new(view_listener_request, view, mine, scenic)?; |
| self.views.push(view_ptr); |
| Ok(()) |
| } |
| |
| pub fn watch_for_key_event(&mut self, code_point: u32, modifiers: u32) -> Result<(), Error> { |
| let mut hotkey_event = KeyboardEvent { |
| event_time: 0, |
| device_id: 0, |
| phase: KeyboardEventPhase::Released, |
| hid_usage: 0, |
| code_point: code_point, |
| modifiers: modifiers, |
| }; |
| let (event_listener_client, event_listener_server) = zx::Channel::create()?; |
| let event_listener = ClientEnd::new(event_listener_client); |
| let event_listener_request = |
| ServerEnd::<KeyboardCaptureListenerHackMarker>::new(event_listener_server); |
| |
| self.presentation_proxy |
| .clone() |
| .unwrap() |
| .capture_keyboard_event_hack(&mut hotkey_event, event_listener)?; |
| |
| fasync::spawn( |
| event_listener_request |
| .into_stream() |
| .unwrap() |
| .try_for_each(move |event| match event { |
| KeyboardCaptureListenerHackRequest::OnEvent { event, .. } => { |
| APP.lock().handle_hot_key(&event).expect("handle hot key"); |
| futures::future::ready(Ok(())) |
| } |
| }) |
| .unwrap_or_else(|e| eprintln!("keyboard hack listener error: {:?}", e)), |
| ); |
| |
| Ok(()) |
| } |
| |
| pub fn setup_keyboard_hack(&mut self) -> Result<(), Error> { |
| let (presentation_proxy, presentation_request) = create_proxy()?; |
| self.session_shell_context |
| .clone() |
| .get_presentation(presentation_request)?; |
| self.presentation_proxy = Some(presentation_proxy); |
| |
| self.watch_for_key_event(0x20, MODIFIER_LEFT_SUPER)?; |
| self.watch_for_key_event(0x20, MODIFIER_RIGHT_SUPER)?; |
| |
| Ok(()) |
| } |
| |
| fn next_story_key(&mut self) -> u32 { |
| let next_key = self.next_key; |
| self.next_key += 1; |
| next_key |
| } |
| |
| pub fn request_start_story(&mut self, story_id: String) -> Result<(), Error> { |
| let (story_controller, story_controller_end) = create_proxy()?; |
| self.story_provider |
| .get_controller(&story_id, story_controller_end)?; |
| story_controller.request_start()?; |
| Ok(()) |
| } |
| |
| pub fn remove_view_for_story(&mut self, story_id: String) -> Result<(), Error> { |
| self.views[0].lock().remove_view_for_story(&story_id) |
| } |
| |
| pub fn remove_story(&mut self, key: u32) { |
| self.views[0].lock().remove_story(key); |
| } |
| |
| pub fn list_stories(&self) -> (Vec<u32>, Vec<String>, Vec<SizeF>, Vec<bool>) { |
| self.views[0].lock().list_stories() |
| } |
| |
| pub fn add_child_view_for_story_attach( |
| &mut self, story_id: String, view_owner: ClientEnd<ViewOwnerMarker>, |
| ) { |
| let key_to_use = self.next_story_key(); |
| self.views[0] |
| .lock() |
| .add_child_view_for_story_attach(key_to_use, story_id, view_owner); |
| } |
| |
| pub fn spawn_tiles_server(chan: fasync::Channel) { |
| fasync::spawn( |
| tiles::ControllerRequestStream::from_channel(chan) |
| .try_for_each(move |req| match req { |
| tiles::ControllerRequest::AddTileFromUrl { responder, .. } => { |
| eprintln!("error AddTileFromUrl no longr supported"); |
| responder.control_handle().shutdown(); |
| fready(Ok(())) |
| } |
| tiles::ControllerRequest::AddTileFromViewProvider { responder, .. } => { |
| eprintln!("error AddTileFromViewProvider no longr supported"); |
| responder.control_handle().shutdown(); |
| fready(Ok(())) |
| } |
| tiles::ControllerRequest::RemoveTile { key, .. } => { |
| APP.lock().remove_story(key); |
| fready(Ok(())) |
| } |
| tiles::ControllerRequest::ListTiles { responder } => { |
| let (mut keys, mut urls, mut sizes, mut focusabilties) = |
| APP.lock().list_stories(); |
| fready(responder.send( |
| &mut keys.iter_mut().map(|a| *a), |
| &mut urls.iter_mut().map(|a| &**a), |
| &mut sizes.iter_mut(), |
| &mut focusabilties.iter_mut().map(|a| *a), |
| )) |
| } |
| tiles::ControllerRequest::Quit { control_handle: _ } => { |
| ::std::process::exit(0) |
| } |
| }) |
| .unwrap_or_else(|e| eprintln!("error running Tiles controller server: {:?}", e)), |
| ) |
| } |
| |
| pub fn spawn_session_shell_server(chan: fasync::Channel) { |
| fasync::spawn( |
| SessionShellRequestStream::from_channel(chan) |
| .try_for_each(move |req| match req { |
| SessionShellRequest::AttachView { |
| view_id, |
| view_owner, |
| .. |
| } => { |
| println!("AttachView {:?}", view_id.story_id); |
| APP.lock() |
| .add_child_view_for_story_attach(view_id.story_id, view_owner); |
| fready(Ok(())) |
| } |
| SessionShellRequest::DetachView { view_id, responder } => { |
| println!("DetachView {:?}", view_id.story_id); |
| fready(responder.send()) |
| } |
| }) |
| .unwrap_or_else(|e| eprintln!("error running SessionShell server: {:?}", e)), |
| ) |
| } |
| |
| pub fn spawn_story_watcher(&mut self) -> Result<(), Error> { |
| let (story_watcher, story_watcher_request) = |
| create_endpoints::<StoryProviderWatcherMarker>()?; |
| |
| fasync::spawn( |
| story_watcher_request |
| .into_stream() |
| .unwrap() |
| .map_ok(move |request| match request { |
| StoryProviderWatcherRequest::OnChange { |
| story_info, |
| story_state, |
| .. |
| } => { |
| if story_state == StoryState::Stopped { |
| APP.lock() |
| .request_start_story(story_info.id.to_string()) |
| .unwrap_or_else(|e| { |
| eprintln!("error adding story {}: {:?}", story_info.id, e); |
| }); |
| } |
| } |
| StoryProviderWatcherRequest::OnDelete { story_id, .. } => { |
| APP.lock() |
| .remove_view_for_story(story_id.to_string()) |
| .unwrap_or_else(|e| { |
| eprintln!("error removing story {}: {:?}", story_id, e); |
| }); |
| } |
| }) |
| .try_collect::<()>() |
| .unwrap_or_else(|e| eprintln!("story watcher error: {:?}", e)), |
| ); |
| |
| let f = self.story_provider.get_stories(Some(story_watcher)); |
| fasync::spawn( |
| f.map_ok(move |r| { |
| for story in r { |
| APP.lock() |
| .request_start_story(story.id.to_string()) |
| .unwrap_or_else(|e| { |
| eprintln!("error adding view for initial story {}: {:?}", story.id, e); |
| }); |
| } |
| }) |
| .unwrap_or_else(|e| eprintln!("get_stories error: {:?}", e)), |
| ); |
| Ok(()) |
| } |
| |
| pub fn handle_hot_key(&mut self, event: &KeyboardEvent) -> Result<(), Error> { |
| let key_to_use = self.next_story_key(); |
| self.views[0].lock().handle_hot_key(event, key_to_use) |
| } |
| |
| pub fn handle_suggestion(&mut self, text: Option<&str>) -> Result<(), Error> { |
| let mut view = self.views[0].lock(); |
| if let Some(text) = text { |
| view.handle_suggestion(text) |
| .unwrap_or_else(|e| eprintln!("handle_suggestion error: {:?}", e)); |
| } |
| view.remove_ask_box(); |
| Ok(()) |
| } |
| } |
| |
| fn main() -> Result<(), Error> { |
| let mut executor = fasync::Executor::new().context("Error creating executor")?; |
| |
| let fut = component::server::ServicesServer::new() |
| .add_service((ViewProviderMarker::NAME, move |chan| { |
| App::spawn_view_provider_server(chan); |
| })) |
| .add_service((tiles::ControllerMarker::NAME, move |chan| { |
| App::spawn_tiles_server(chan); |
| })) |
| .add_service((SessionShellMarker::NAME, move |chan| { |
| App::spawn_session_shell_server(chan); |
| })) |
| .start() |
| .context("Error starting services server")?; |
| |
| executor.run_singlethreaded(fut)?; |
| |
| Ok(()) |
| } |