blob: 47fd9834c81d6cd6345d47de7db70f2202cee07d [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 {
anyhow::{Context as _, Error},
core::convert::TryInto,
fidl::endpoints::{ClientEnd, RequestStream, ServerEnd},
fidl_fuchsia_ui_input as uii, fidl_fuchsia_ui_text as txt,
fuchsia_syslog::fx_log_err,
futures::{lock::Mutex, prelude::*},
std::sync::{Arc, Weak},
};
use crate::{
keyboard::events::KeyEvent,
legacy_ime::{ImeState, LegacyIme},
multiplex::TextFieldMultiplexer,
};
pub struct ImeServiceState {
pub keyboard_visible: bool,
pub active_ime: Option<Weak<Mutex<ImeState>>>,
pub visibility_listeners: Vec<uii::ImeVisibilityServiceControlHandle>,
pub multiplexer: Option<TextFieldMultiplexer>,
/// `TextInputContext` is a service provided to input methods that want to edit text. Whenever
/// a new text field is focused, we provide a TextField interface to any connected `TextInputContext`s,
/// which are listed here.
pub text_input_context_clients: Vec<txt::TextInputContextLegacyControlHandle>,
}
/// The internal state of the IMEService, usually held behind an Arc<Mutex>
/// so it can be accessed from multiple places.
impl ImeServiceState {
pub fn update_keyboard_visibility(&mut self, visible: bool) {
self.keyboard_visible = visible;
self.visibility_listeners.retain(|listener| {
// drop listeners if they error on send
listener.send_on_keyboard_visibility_changed(visible).is_ok()
});
}
}
/// Serves several public FIDL services: `ImeService`, `ImeVisibilityService`, and
/// `TextInputContext`.
#[derive(Clone)]
pub struct ImeService {
state: Arc<Mutex<ImeServiceState>>,
}
impl ImeService {
pub fn new() -> ImeService {
ImeService {
state: Arc::new(Mutex::new(ImeServiceState {
keyboard_visible: false,
active_ime: None,
multiplexer: None,
visibility_listeners: Vec::new(),
text_input_context_clients: Vec::new(),
})),
}
}
/// Only updates the keyboard visibility if IME passed in is active
pub async fn update_keyboard_visibility_from_ime<'a>(
&'a self,
check_ime: &'a Arc<Mutex<ImeState>>,
visible: bool,
) {
let mut state = self.state.lock().await;
let active_ime_weak = match &state.active_ime {
Some(val) => val,
None => return,
};
let active_ime = match active_ime_weak.upgrade() {
Some(val) => val,
None => return,
};
if Arc::ptr_eq(check_ime, &active_ime) {
state.update_keyboard_visibility(visible);
}
}
pub async fn get_input_method_editor(
&mut self,
keyboard_type: uii::KeyboardType,
action: uii::InputMethodAction,
initial_state: uii::TextInputState,
client: ClientEnd<uii::InputMethodEditorClientMarker>,
editor: ServerEnd<uii::InputMethodEditorMarker>,
) {
let client_proxy = match client.into_proxy() {
Ok(v) => v,
Err(_) => return,
};
let ime = LegacyIme::new(keyboard_type, action, initial_state, client_proxy, self.clone());
let mut state = self.state.lock().await;
let editor_stream = match editor.into_stream() {
Ok(v) => v,
Err(e) => {
fx_log_err!("Failed to create stream: {}", e);
return;
}
};
let (txt_proxy, txt_request_stream) =
match fidl::endpoints::create_proxy_and_stream::<txt::TextFieldLegacyMarker>() {
Ok(v) => v,
Err(e) => {
fx_log_err!("Failed to create TextField proxy and stream: {}", e);
return;
}
};
state.active_ime = Some(ime.downgrade());
ime.bind_ime(editor_stream);
ime.bind_text_field(txt_request_stream);
let multiplexer = TextFieldMultiplexer::new(txt_proxy);
state.text_input_context_clients.retain(|listener| {
// drop listeners if they error on send
bind_new_text_field(&multiplexer, &listener).is_ok()
});
state.multiplexer = Some(multiplexer);
}
pub async fn show_keyboard(&self) {
self.state.lock().await.update_keyboard_visibility(true);
}
pub async fn hide_keyboard(&self) {
self.state.lock().await.update_keyboard_visibility(false);
}
/// This is called by the operating system when input from the physical keyboard comes in.
/// It also is called by legacy onscreen keyboards that just simulate physical keyboard input.
pub(crate) async fn inject_input(&mut self, event: KeyEvent) -> Result<(), Error> {
let mut state = self.state.lock().await;
let ime = {
let active_ime_weak = match state.active_ime {
Some(ref v) => v,
None => return Ok(()), // no currently active IME
};
match LegacyIme::upgrade(active_ime_weak) {
Some(active_ime) => active_ime,
None => return Ok(()), // IME no longer exists
}
};
// Send the legacy ime a keystroke event to forward to connected clients. Even if a v2 input
// method is connected, this ensures legacy text fields are able to still see key events;
// something not yet provided by the new `TextField` API.
ime.forward_event(&event).await?;
// Send the key event to any listening `TextInputContext` clients. If at least one still
// exists, we assume it handled it and converted it into an edit sent via its handle to the
// `TextField` protocol.
state.text_input_context_clients.retain(|listener| {
// drop listeners if they error on send
listener.send_on_key3_event(event.clone().try_into().unwrap()).is_ok()
});
// If no `TextInputContext` clients handled the input event, or if there are none connected,
// we allow the internal input method inside of `LegacyIme` to convert this key event into
// an edit.
if state.text_input_context_clients.len() == 0 {
ime.inject_input(event).await?;
}
Ok(())
}
pub async fn handle_ime_service_msg(
&mut self,
msg: uii::ImeServiceRequest,
) -> Result<(), Error> {
match msg {
uii::ImeServiceRequest::GetInputMethodEditor {
keyboard_type,
action,
initial_state,
client,
editor,
..
} => {
self.get_input_method_editor(keyboard_type, action, initial_state, client, editor)
.await;
}
uii::ImeServiceRequest::ShowKeyboard { .. } => {
self.show_keyboard().await;
}
uii::ImeServiceRequest::HideKeyboard { .. } => {
self.hide_keyboard().await;
}
uii::ImeServiceRequest::DispatchKey { .. } => {
// Transitional: DispatchKey should be handled by keyboard/Service.
// See Service.spawn_ime_service() for handing DispatchKey.
// In future, Keyboard service will receive keys directly.
panic!("Should be handled by keyboard service");
}
}
Ok(())
}
pub fn bind_ime_visibility_service(&self, stream: uii::ImeVisibilityServiceRequestStream) {
let self_clone = self.clone();
fuchsia_async::Task::spawn(
async move {
let control_handle = stream.control_handle();
let mut state = self_clone.state.lock().await;
if control_handle
.send_on_keyboard_visibility_changed(state.keyboard_visible)
.is_ok()
{
state.visibility_listeners.push(control_handle);
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| fx_log_err!("{:?}", e)),
)
.detach();
}
pub fn bind_text_input_context(&self, mut stream: txt::TextInputContextLegacyRequestStream) {
let self_clone = self.clone();
fuchsia_async::Task::spawn(
async move {
let control_handle = stream.control_handle();
{
let mut state = self_clone.state.lock().await;
if let Some(multiplexer) = &state.multiplexer {
if let Err(e) = bind_new_text_field(multiplexer, &control_handle) {
fx_log_err!("Error when binding text field for newly connected TextInputContext: {}", e);
}
}
state.text_input_context_clients.push(control_handle)
}
while let Some(msg) = stream.try_next().await
.context("error reading value from text input context request stream")?
{
match msg {
txt::TextInputContextLegacyRequest::HideKeyboard { .. } => {
self_clone.hide_keyboard().await;
}
}
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| fx_log_err!("{:?}", e)),
).detach();
}
}
pub fn bind_new_text_field(
multiplexer: &TextFieldMultiplexer,
control_handle: &txt::TextInputContextLegacyControlHandle,
) -> Result<(), fidl::Error> {
let (client_end, request_stream) =
fidl::endpoints::create_request_stream::<txt::TextFieldLegacyMarker>()
.expect("Failed to create text field request stream");
multiplexer.add_request_stream(request_stream);
control_handle.send_on_focus(client_end)
}