blob: ef2f693001e1f9e6786ddf0eeee0bcdeb5eb3578 [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 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(())
}