blob: e3fd71495cea029ad337e1ac2110710fa86ffb6a [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.
//! This module contains an implementation of `LegacyIme` itself.
use {
anyhow::{format_err, Context, Error, Result},
fidl_fuchsia_ui_input::{self as uii, InputMethodEditorRequest as ImeReq},
fidl_fuchsia_ui_input3 as ui_input3,
futures::{lock::Mutex, prelude::*},
std::{
collections::{HashMap, HashSet},
sync::{Arc, Weak},
},
};
use super::{
state::ImeState, HID_USAGE_KEY_BACKSPACE, HID_USAGE_KEY_DELETE, HID_USAGE_KEY_ENTER,
HID_USAGE_KEY_LEFT, HID_USAGE_KEY_RIGHT,
};
use crate::{index_convert as idx, keyboard::events, text_manager::TextManager};
/// An input method provides edits and cursor updates to a text field. This Legacy Input Method
/// provides edits to a text field over the legacy pair of interfaces `InputMethodEditor` and
/// `InputMethodEditorClient`. It can provide these edits one of two ways:
///
/// 1. If `inject_input()` is called with a `KeyboardEvent`, `LegacyIme` contains an implementation
/// of a QWERTY latin keyboard input method, and will send the appropriate edits to the text field.
/// This can also handle arrow keys with modifiers to perform selection/caret movement.
/// 2. If `bind_text_field()` is called with a `TextFieldRequestStream`, `LegacyIme` can serve the
/// new `TextField` interface, translating edits or content requests from that into requests for the
/// `InputMethodEditorClient`.
#[derive(Clone)]
pub struct LegacyIme(Arc<Mutex<ImeState>>);
impl LegacyIme {
pub fn new<I: 'static + uii::InputMethodEditorClientProxyInterface>(
keyboard_type: uii::KeyboardType,
action: uii::InputMethodAction,
initial_state: uii::TextInputState,
client: I,
text_manager: TextManager,
) -> LegacyIme {
let state = ImeState {
text_state: initial_state,
client: Box::new(client),
keyboard_type,
action,
text_manager,
revision: 0,
next_text_point_id: 0,
text_points: HashMap::new(),
keys_pressed: HashSet::new(),
};
LegacyIme(Arc::new(Mutex::new(state)))
}
pub fn downgrade(&self) -> Weak<Mutex<ImeState>> {
Arc::downgrade(&self.0)
}
pub fn upgrade(weak: &Weak<Mutex<ImeState>>) -> Option<LegacyIme> {
weak.upgrade().map(|arc| LegacyIme(arc))
}
/// Binds a `TextField` to this `LegacyIme`, replacing and unbinding any previous `TextField`.
/// All requests from the request stream will be translated into requests for
/// `InputMethodEditorClient`, and for events, vice-versa.
/// Handles all state updates passed down the `InputMethodEditorRequestStream`.
pub fn bind_ime(&self, mut stream: uii::InputMethodEditorRequestStream) {
let self_clone = self.clone();
let self_clone_2 = self.clone();
fuchsia_async::Task::spawn(
async move {
while let Some(msg) = stream
.try_next()
.await
.context("error reading value from IME request stream")?
{
self_clone
.handle_ime_message(msg)
.await
.unwrap_or_else(|e| tracing::warn!("error handling ime message: {:?}", e));
}
Ok(())
}
.unwrap_or_else(|e: anyhow::Error| tracing::error!("{:?}", e))
.then(|()| {
async move {
// this runs when IME stream closes
// clone to ensure we only hold one lock at a time
let text_manager = self_clone_2.0.lock().await.text_manager.clone();
text_manager.update_keyboard_visibility_from_ime(&self_clone_2.0, false).await;
}
}),
)
.detach();
}
/// Handles a request from the legancy IME API, an InputMethodEditorRequest.
async fn handle_ime_message(&self, msg: uii::InputMethodEditorRequest) -> Result<()> {
match msg {
ImeReq::SetKeyboardType { keyboard_type, .. } => {
let mut state = self.0.lock().await;
state.keyboard_type = keyboard_type;
Ok(())
}
ImeReq::SetState { state, .. } => {
self.set_state(state).await;
Ok(())
}
ImeReq::InjectInput { .. } => {
Err(format_err!("InjectInput is deprecated, use DispatchKey3!"))
}
ImeReq::DispatchKey3 { event, responder, .. } => {
let key_event = {
let state = self.0.lock().await;
events::KeyEvent::new(&event, state.keys_pressed.clone())
.context("error converting key")?
};
self.inject_input(key_event).await?;
self.update_keys_pressed(&event).await?;
// False since legacy API doesn't support input handling.
// Handling here depends on the hardcoded order of input processing.
// This should be reworked in future IME design.
responder.send(false).context("error sending response for DispatchKey3")
}
ImeReq::Show { .. } => {
// clone to ensure we only hold one lock at a time
let text_manager = self.0.lock().await.text_manager.clone();
text_manager.show_keyboard().await;
Ok(())
}
ImeReq::Hide { .. } => {
// clone to ensure we only hold one lock at a time
let text_manager = self.0.lock().await.text_manager.clone();
text_manager.hide_keyboard().await;
Ok(())
}
}
}
/// Sets the internal state. Expects input_state to use codeunits; automatically
/// converts to byte indices before storing.
pub async fn set_state(&self, input_state: uii::TextInputState) {
let mut state = self.0.lock().await;
state.text_state = idx::text_state_codeunit_to_byte(input_state);
// the old C++ IME implementation didn't call did_update_state here, so this second argument is false.
state.increment_revision(false);
}
/// Forwards an event to the `InputMethodEditorClient`, by sending a state update that makes no
/// changes alongside a `KeyboardEvent`. Note that this state update will always have no changes
/// — if you'd like to send a change, use `inject_input()`.
pub(crate) async fn forward_event(&self, event: &events::KeyEvent) -> Result<(), Error> {
let mut state = self.0.lock().await;
state.forward_event(event.clone())
}
/// Uses `ImeState`'s internal latin input method implementation to determine the edit
/// corresponding to `keyboard_event`, and sends this as a state update to
/// `InputMethodEditorClient`. This method can handle arrow keys to move selections, even with
/// modifier keys implemented correctly. However, it does *not* send the actual key event to the
/// client; for that, you should simultaneously call `forward_event()`.
pub(crate) async fn inject_input(&self, key_event: events::KeyEvent) -> Result<(), Error> {
let keyboard_event: uii::KeyboardEvent =
key_event.try_into().context("error converting key event to keyboard event")?;
let mut state = self.0.lock().await;
if keyboard_event.phase == uii::KeyboardEventPhase::Pressed
|| keyboard_event.phase == uii::KeyboardEventPhase::Repeat
{
if keyboard_event.code_point != 0 {
state.type_keycode(keyboard_event.code_point);
state.increment_revision(true);
} else {
match keyboard_event.hid_usage {
HID_USAGE_KEY_BACKSPACE => {
state.delete_backward();
state.increment_revision(true);
}
HID_USAGE_KEY_DELETE => {
state.delete_forward();
state.increment_revision(true);
}
HID_USAGE_KEY_LEFT => {
state.cursor_horizontal_move(keyboard_event.modifiers, false);
state.increment_revision(true);
}
HID_USAGE_KEY_RIGHT => {
state.cursor_horizontal_move(keyboard_event.modifiers, true);
state.increment_revision(true);
}
HID_USAGE_KEY_ENTER => match state.action {
// Pressing "Enter" inserts a newline when the input
// method is used to edit multi-line text. For other
// input actions, we report back to the
// client that it needs to perform some action (move
// focus, perform a search etc.).
uii::InputMethodAction::Newline => {
state.type_keycode('\n' as u32);
state.increment_revision(true);
}
_ => {
state
.client
.on_action(state.action)
.context("error sending action to ImeClient")?;
}
},
_ => {
// Not an editing key, forward the event to clients.
state.increment_revision(true);
}
}
}
}
Ok(())
}
/// Updates currently pressed keys.
async fn update_keys_pressed(&self, event: &ui_input3::KeyEvent) -> Result<(), Error> {
let type_ = event.type_.ok_or(format_err!("Expected type to be populated."))?;
let key = event.key.ok_or(format_err!("Expected key to be populated."))?;
let keys_pressed = &mut self.0.lock().await.keys_pressed;
match type_ {
ui_input3::KeyEventType::Sync | ui_input3::KeyEventType::Pressed => {
keys_pressed.insert(key);
}
ui_input3::KeyEventType::Cancel | ui_input3::KeyEventType::Released => {
keys_pressed.remove(&key);
}
};
Ok(())
}
}