diff --git a/garnet/bin/ui/ime/src/fidl_helpers.rs b/garnet/bin/ui/ime/src/fidl_helpers.rs index b2243751cf77fe06afe3835d9c2c83b299a81b09..9a623a7d815f4f10bfbcae18e7809058b67eff25 100644 --- a/garnet/bin/ui/ime/src/fidl_helpers.rs +++ b/garnet/bin/ui/ime/src/fidl_helpers.rs @@ -26,3 +26,14 @@ pub fn clone_state(state: &uii::TextInputState) -> uii::TextInputState { composing: uii::TextRange { start: state.composing.start, end: state.composing.end }, } } + +pub fn clone_keyboard_event(ev: &uii::KeyboardEvent) -> uii::KeyboardEvent { + uii::KeyboardEvent { + event_time: ev.event_time, + device_id: ev.device_id, + phase: ev.phase, + hid_usage: ev.hid_usage, + code_point: ev.code_point, + modifiers: ev.modifiers, + } +} diff --git a/garnet/bin/ui/ime/src/ime_service.rs b/garnet/bin/ui/ime/src/ime_service.rs index dac94321b4155a3ef6eb8069c83eac81c1947e2d..829c0bf85e18bf26e7dde36da0264a54cb08c317 100644 --- a/garnet/bin/ui/ime/src/ime_service.rs +++ b/garnet/bin/ui/ime/src/ime_service.rs @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +use crate::fidl_helpers::clone_keyboard_event; use crate::legacy_ime::Ime; use crate::legacy_ime::ImeState; use failure::ResultExt; @@ -109,6 +110,10 @@ impl ImeService { /// 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. async fn inject_input(&mut self, mut event: uii::InputEvent) { + let keyboard_event = match &event { + uii::InputEvent::Keyboard(e) => clone_keyboard_event(e), + _ => return, + }; let mut state = await!(self.0.lock()); let ime = { let active_ime_weak = match state.active_ime { @@ -120,6 +125,10 @@ impl ImeService { None => return, // IME no longer exists } }; + + // send the legacy ime a keystroke event to forward + await!(ime.forward_event(clone_keyboard_event(&keyboard_event))); + state.text_input_context_clients.retain(|listener| { // drop listeners if they error on send listener.send_on_input_event(&mut event).is_ok() @@ -127,7 +136,7 @@ impl ImeService { // only use the default text input handler in ime.rs if there are no text_input_context_clients // attached to handle it if state.text_input_context_clients.len() == 0 { - await!(ime.inject_input(event)); + await!(ime.inject_input(keyboard_event)); } } @@ -250,6 +259,27 @@ mod test { use fuchsia_async as fasync; use pin_utils::pin_mut; + async fn get_state_update(editor_stream: &mut uii::InputMethodEditorClientRequestStream) -> (uii::TextInputState, Option<uii::KeyboardEvent>) { + let msg = await!(editor_stream.try_next()) + .expect("expected working event stream") + .expect("ime should have sent message"); + if let uii::InputMethodEditorClientRequest::DidUpdateState { + state, event, .. + } = msg + { + let keyboard_event = event.map(|e| { + if let uii::InputEvent::Keyboard(keyboard_event) = *e { + keyboard_event + } else { + panic!("expected DidUpdateState to only send Keyboard events"); + } + }); + (state, keyboard_event) + } else { + panic!("request should be DidUpdateState"); + } + } + fn async_service_test<T, F>(test_fn: T) where T: FnOnce(uii::ImeServiceProxy, uii::ImeVisibilityServiceProxy) -> F, @@ -377,35 +407,53 @@ mod test { // type 'a' simulate_keypress(&ime_service, 'a'.into(), 0); - let msg = await!(editor_stream.try_next()) - .expect("expected working event stream") - .expect("ime should have sent message"); - if let uii::InputMethodEditorClientRequest::DidUpdateState { - state, event: _, .. - } = msg - { - assert_eq!(state.text, "a"); - assert_eq!(state.selection.base, 1); - assert_eq!(state.selection.extent, 1); - } else { - panic!("request should be DidUpdateState"); - } + + // get first message with keypress event but no state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + let event = event.expect("expected event to be set"); + assert_eq!(event.phase, uii::KeyboardEventPhase::Pressed); + assert_eq!(event.code_point, 97); + assert_eq!(state.text, ""); + + // get second message with state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + assert!(event.is_none()); + assert_eq!(state.text, "a"); + assert_eq!(state.selection.base, 1); + assert_eq!(state.selection.extent, 1); + + // get third message with keyrelease event but no state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + let event = event.expect("expected event to be set"); + assert_eq!(event.phase, uii::KeyboardEventPhase::Released); + assert_eq!(event.code_point, 97); + assert_eq!(state.text, "a"); // press left arrow simulate_keypress(&ime_service, 0, HID_USAGE_KEY_LEFT); - let msg = await!(editor_stream.try_next()) - .expect("expected working event stream") - .expect("ime should have sent message"); - if let uii::InputMethodEditorClientRequest::DidUpdateState { - state, event: _, .. - } = msg - { - assert_eq!(state.text, "a"); - assert_eq!(state.selection.base, 0); - assert_eq!(state.selection.extent, 0); - } else { - panic!("request should be DidUpdateState"); - } + + // get first message with keypress event but no state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + let event = event.expect("expected event to be set"); + assert_eq!(event.phase, uii::KeyboardEventPhase::Pressed); + assert_eq!(event.code_point, 0); + assert_eq!(event.hid_usage, HID_USAGE_KEY_LEFT); + assert_eq!(state.text, "a"); + + // get second message with state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + assert!(event.is_none()); + assert_eq!(state.text, "a"); + assert_eq!(state.selection.base, 0); + assert_eq!(state.selection.extent, 0); + + // get first message with keyrelease event but no state update + let (state, event) = await!(get_state_update(&mut editor_stream)); + let event = event.expect("expected event to be set"); + assert_eq!(event.phase, uii::KeyboardEventPhase::Released); + assert_eq!(event.code_point, 0); + assert_eq!(event.hid_usage, HID_USAGE_KEY_LEFT); + assert_eq!(state.text, "a"); } }); } @@ -415,7 +463,18 @@ mod test { async_service_test(|ime_service, _visibility_service| { async move { let (_ime, mut editor_stream) = bind_ime_for_test(&ime_service); + + // send key events simulate_keypress(&ime_service, 0, HID_USAGE_KEY_ENTER); + + // get first message with keypress event + let (_state, event) = await!(get_state_update(&mut editor_stream)); + let event = event.expect("expected event to be set"); + assert_eq!(event.phase, uii::KeyboardEventPhase::Pressed); + assert_eq!(event.code_point, 0); + assert_eq!(event.hid_usage, HID_USAGE_KEY_ENTER); + + // get second message with onaction event let msg = await!(editor_stream.try_next()) .expect("expected working event stream") .expect("ime should have sent message"); diff --git a/garnet/bin/ui/ime/src/legacy_ime/handler.rs b/garnet/bin/ui/ime/src/legacy_ime/handler.rs index 41555ca06d8b4fb20e43581be2945f4916064a9b..934616bb6991458d85676465253dfe592c8f6f3f 100644 --- a/garnet/bin/ui/ime/src/legacy_ime/handler.rs +++ b/garnet/bin/ui/ime/src/legacy_ime/handler.rs @@ -201,7 +201,7 @@ impl Ime { } let res = if ime_state.apply_transaction() { let res = responder.send(txt::Error::Ok); - ime_state.increment_revision(None, true); + ime_state.increment_revision(true); res } else { responder.send(txt::Error::BadRequest) @@ -240,7 +240,11 @@ impl Ime { await!(self.set_state(idx::text_state_codeunit_to_byte(state))); } ImeReq::InjectInput { event, .. } => { - await!(self.inject_input(event)); + let keyboard_event = match event { + uii::InputEvent::Keyboard(e) => e, + _ => return, + }; + await!(self.inject_input(keyboard_event)); } ImeReq::Show { .. } => { // clone to ensure we only hold one lock at a time @@ -261,39 +265,40 @@ impl Ime { let mut state = await!(self.0.lock()); 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(None, false); + state.increment_revision(false); } - pub async fn inject_input(&self, event: uii::InputEvent) { + pub async fn forward_event(&self, keyboard_event: uii::KeyboardEvent) { + let mut state = await!(self.0.lock()); + state.forward_event(keyboard_event); + } + + pub async fn inject_input(&self, keyboard_event: uii::KeyboardEvent) { let mut state = await!(self.0.lock()); - let keyboard_event = match event { - uii::InputEvent::Keyboard(e) => e, - _ => return, - }; 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(Some(keyboard_event), true) + state.increment_revision(true) } else { match keyboard_event.hid_usage { HID_USAGE_KEY_BACKSPACE => { state.delete_backward(); - state.increment_revision(Some(keyboard_event), true); + state.increment_revision(true); } HID_USAGE_KEY_DELETE => { state.delete_forward(); - state.increment_revision(Some(keyboard_event), true); + state.increment_revision(true); } HID_USAGE_KEY_LEFT => { state.cursor_horizontal_move(keyboard_event.modifiers, false); - state.increment_revision(Some(keyboard_event), true); + state.increment_revision(true); } HID_USAGE_KEY_RIGHT => { state.cursor_horizontal_move(keyboard_event.modifiers, true); - state.increment_revision(Some(keyboard_event), true); + state.increment_revision(true); } HID_USAGE_KEY_ENTER => { state.client.on_action(state.action).unwrap_or_else(|e| { @@ -302,7 +307,7 @@ impl Ime { } _ => { // Not an editing key, forward the event to clients. - state.increment_revision(Some(keyboard_event), true); + state.increment_revision(true); } } } diff --git a/garnet/bin/ui/ime/src/legacy_ime/state.rs b/garnet/bin/ui/ime/src/legacy_ime/state.rs index 091ee598b92a8157084ab1da65bfc68a078b0772..32b648c76c2a973d4b0fc54ef42a21aa43e72260 100644 --- a/garnet/bin/ui/ime/src/legacy_ime/state.rs +++ b/garnet/bin/ui/ime/src/legacy_ime/state.rs @@ -88,15 +88,20 @@ pub fn get_range( } impl ImeState { + /// Forwards a keyboard event to any listening clients without changing the actual state of the + /// IME at all. + pub fn forward_event(&mut self, ev: uii::KeyboardEvent) { + let mut state = idx::text_state_byte_to_codeunit(clone_state(&self.text_state)); + self.client + .did_update_state(&mut state, Some(OutOfLine(&mut uii::InputEvent::Keyboard(ev)))) + .unwrap_or_else(|e| fx_log_warn!("error sending state update to ImeClient: {:?}", e)); + } + /// Any time the state is updated, this method is called, which allows ImeState to inform any /// listening clients (either TextField or InputMethodEditorClientProxy) that state has updated. /// If InputMethodEditorClient caused the update with SetState, set call_did_update_state so that /// we don't send its own edit back to it. Otherwise, set to true. - pub fn increment_revision( - &mut self, - e: Option<uii::KeyboardEvent>, - call_did_update_state: bool, - ) { + pub fn increment_revision(&mut self, call_did_update_state: bool) { self.revision += 1; self.text_points = HashMap::new(); let state = self.as_text_field_state(); @@ -108,20 +113,9 @@ impl ImeState { if call_did_update_state { let mut state = idx::text_state_byte_to_codeunit(clone_state(&self.text_state)); - if let Some(ev) = e { - self.client - .did_update_state( - &mut state, - Some(OutOfLine(&mut uii::InputEvent::Keyboard(ev))), - ) - .unwrap_or_else(|e| { - fx_log_warn!("error sending state update to ImeClient: {:?}", e) - }); - } else { - self.client.did_update_state(&mut state, None).unwrap_or_else(|e| { - fx_log_warn!("error sending state update to ImeClient: {:?}", e) - }); - } + self.client.did_update_state(&mut state, None).unwrap_or_else(|e| { + fx_log_warn!("error sending state update to ImeClient: {:?}", e) + }); } } diff --git a/garnet/bin/ui/ime/src/legacy_ime/tests.rs b/garnet/bin/ui/ime/src/legacy_ime/tests.rs index f174a12e81f095e7bf6a061a88ddcd99d7827cde..a2a6d498020676b6fea9a6f4d45ff9528e1d4371 100644 --- a/garnet/bin/ui/ime/src/legacy_ime/tests.rs +++ b/garnet/bin/ui/ime/src/legacy_ime/tests.rs @@ -48,22 +48,22 @@ async fn simulate_keypress<K: Into<u32> + Copy + 'static>( ) { let hid_usage = if hid_key { key.into() } else { 0 }; let code_point = if hid_key { 0 } else { key.into() }; - await!(ime.inject_input(uii::InputEvent::Keyboard(uii::KeyboardEvent { + await!(ime.inject_input(uii::KeyboardEvent { event_time: 0, device_id: 0, phase: uii::KeyboardEventPhase::Pressed, hid_usage, code_point, modifiers, - }))); - await!(ime.inject_input(uii::InputEvent::Keyboard(uii::KeyboardEvent { + })); + await!(ime.inject_input(uii::KeyboardEvent { event_time: 0, device_id: 0, phase: uii::KeyboardEventPhase::Released, hid_usage, code_point, modifiers, - }))); + })); } struct MockImeClient {