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