blob: bcccf7e32d73651738be6582138510c36787e0aa [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.
#![feature(async_await, await_macro)]
use failure::{Error, ResultExt};
use fidl::endpoints::ServiceMarker;
use fidl_fuchsia_ui_shortcut as ui_shortcut;
use fuchsia_async;
use fuchsia_component::server::ServiceFs;
use fuchsia_syslog::{fx_log_err, fx_log_info};
use futures::lock::Mutex;
use futures::{StreamExt, TryFutureExt, TryStreamExt};
use std::sync::Arc;
mod registry;
const SERVER_THREADS: usize = 2;
fn main() -> Result<(), Error> {
fuchsia_syslog::init_with_tags(&["shortcut"]).expect("shortcut syslog init should not fail");
let mut executor = fuchsia_async::Executor::new()
.context("Creating fuchsia_async executor for Shortcut failed")?;
let store = Arc::new(Mutex::new(registry::RegistryStore::default()));
let mut fs = ServiceFs::new();
fs.dir("public")
.add_fidl_service_at(ui_shortcut::RegistryMarker::NAME, {
// Capture a clone of store's Arc so the new client has a copy from
// which to make new clones.
let store = store.clone();
move |stream| {
fuchsia_async::spawn(
registry_server(stream, store.clone())
.unwrap_or_else(|e: failure::Error| fx_log_err!("couldn't run: {:?}", e)),
);
}
})
.add_fidl_service_at(ui_shortcut::ManagerMarker::NAME, {
// Capture a clone of store's Arc so the new client has a copy from
// which to make new clones.
let store = Arc::clone(&store);
move |stream| {
fuchsia_async::spawn(
manager_server(stream, store.clone())
.unwrap_or_else(|e: failure::Error| fx_log_err!("couldn't run: {:?}", e)),
);
}
});
fs.take_and_serve_directory_handle()?;
executor.run(fs.collect::<()>(), SERVER_THREADS);
Ok(())
}
async fn registry_server(
mut stream: ui_shortcut::RegistryRequestStream,
store: Arc<Mutex<registry::RegistryStore>>,
) -> Result<(), Error> {
fx_log_info!("new server connection");
// The lifetime of the shortcuts is determined by the lifetime of the connection,
// so once this registry goes out of scope, it's removed from RegistryStore.
let registry = await!(store.lock()).add_new_registry();
// TODO: clean up empty Weak refs for registries from the store
while let Some(req) = await!(stream.try_next()).context("error running registry server")? {
let mut registry = await!(registry.lock());
match req {
ui_shortcut::RegistryRequest::SetView { view_ref, listener, .. } => {
// New subscriber overrides the old one.
registry.subscriber =
Some(registry::Subscriber { view_ref, listener: listener.into_proxy()? });
}
ui_shortcut::RegistryRequest::RegisterShortcut { shortcut, .. } => {
// TODO: validation
registry.shortcuts.push(shortcut);
}
}
}
Ok(())
}
async fn manager_server(
mut stream: ui_shortcut::ManagerRequestStream,
store: Arc<Mutex<registry::RegistryStore>>,
) -> Result<(), Error> {
fx_log_info!("new manager connection");
while let Some(req) = await!(stream.try_next()).context("error running manager server")? {
let mut store = await!(store.lock());
match req {
ui_shortcut::ManagerRequest::HandleKeyEvent { event, responder } => {
// TODO: error handling
let was_handled = await!(store.handle_key_event(event))?;
responder.send(was_handled).context("error sending response")?;
}
}
}
Ok(())
}
#[cfg(test)]
mod test {
use failure::{Error, ResultExt};
use fidl_fuchsia_ui_input as ui_input;
use fidl_fuchsia_ui_shortcut as ui_shortcut;
use fidl_fuchsia_ui_views as ui_views;
use fuchsia_async;
use fuchsia_component::client::{launch, launcher};
use fuchsia_zircon as zx;
use futures::StreamExt;
static COMPONENT_URL: &str = "fuchsia-pkg://fuchsia.com/shortcut#meta/shortcut_manager.cmx";
static TEST_SHORTCUT_ID: u32 = 123;
#[fuchsia_async::run_singlethreaded(test)]
#[ignore]
async fn test_as_client() -> Result<(), Error> {
fuchsia_syslog::init_with_tags(&["shortcut"])
.expect("shortcut syslog init should not fail");
let launcher = launcher().context("Failed to open launcher service")?;
let app = launch(&launcher, COMPONENT_URL.to_string(), None)
.context("Failed to launch Shortcut manager")?;
let registry = app
.connect_to_service::<ui_shortcut::RegistryMarker>()
.context("Failed to connect to Shortcut registry service")?;
let manager = app
.connect_to_service::<ui_shortcut::ManagerMarker>()
.context("Failed to connect to Shortcut manager service")?;
let (listener_client_end, mut listener_stream) =
fidl::endpoints::create_request_stream::<ui_shortcut::ListenerMarker>()?;
// Set listener and view ref.
let (raw_event_pair, _) = zx::EventPair::create()?;
let view_ref = &mut ui_views::ViewRef { reference: raw_event_pair };
registry.set_view(view_ref, listener_client_end).expect("set_view");
// Set the shortcut.
let shortcut = ui_shortcut::Shortcut {
id: Some(TEST_SHORTCUT_ID),
modifiers: None,
key: Some(ui_input::Key::A),
use_priority: None,
};
registry.register_shortcut(shortcut).expect("register_shortcut");
// Process key event that *does not* trigger a shortcut.
let event = ui_input::KeyEvent {
key: None,
modifiers: None,
phase: Some(ui_input::KeyEventPhase::Pressed),
};
let was_handled = await!(manager.handle_key_event(event)).expect("handle_key_event false");
assert_eq!(false, was_handled);
// Process key event that triggers a shortcut.
let event = ui_input::KeyEvent {
key: Some(ui_input::Key::A),
modifiers: None,
phase: Some(ui_input::KeyEventPhase::Pressed),
};
let was_handled = manager.handle_key_event(event);
// React to one shortcut activation message from the listener stream.
if let Some(Ok(req)) = await!(listener_stream.next()) {
match req {
ui_shortcut::ListenerRequest::OnShortcut { id, responder, .. } => {
assert_eq!(id, TEST_SHORTCUT_ID);
responder.send(true).expect("responding from shortcut listener")
}
};
}
let was_handled = await!(was_handled).expect("handle_key_event true");
// Expect key event to be handled.
assert_eq!(true, was_handled);
Ok(())
}
}