From 32acb7e10e3ac0306ffa14393c3a06a8d5bd6e36 Mon Sep 17 00:00:00 2001 From: Kirill Chibisov Date: Fri, 24 Jan 2025 20:17:49 +0300 Subject: [PATCH] text-input: fix active instance tracking Only one text-input must be enabled at a time, by checking the text-input id when issuing a request and comparing to the active one, however the regular `Enabled` events must be send for all of them, and as for the leave, we also send it for all the events to make it symmetrical with the `enter. Fixes: 475072d410df (text-input: Ensure only one enabled...) Fixes #1604. --- .../input_method/input_method_handle.rs | 10 +- .../input_method_keyboard_grab.rs | 2 +- src/wayland/text_input/text_input_handle.rs | 96 +++++++++++++++---- 3 files changed, 84 insertions(+), 24 deletions(-) diff --git a/src/wayland/input_method/input_method_handle.rs b/src/wayland/input_method/input_method_handle.rs index b33949c70160..88a091f1eb0d 100644 --- a/src/wayland/input_method/input_method_handle.rs +++ b/src/wayland/input_method/input_method_handle.rs @@ -211,7 +211,7 @@ where ) { match request { zwp_input_method_v2::Request::CommitString { text } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.commit_string(Some(text.clone())); }); } @@ -220,7 +220,7 @@ where cursor_begin, cursor_end, } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.preedit_string(Some(text.clone()), cursor_begin, cursor_end); }); } @@ -228,7 +228,7 @@ where before_length, after_length, } => { - data.text_input_handle.with_focused_text_input(|ti, _surface| { + data.text_input_handle.with_active_text_input(|ti, _surface| { ti.delete_surrounding_text(before_length, after_length); }); } @@ -332,8 +332,6 @@ where data: &InputMethodUserData, ) { data.handle.inner.lock().unwrap().instance = None; - data.text_input_handle.with_focused_text_input(|ti, surface| { - ti.leave(surface); - }); + data.text_input_handle.leave(); } } diff --git a/src/wayland/input_method/input_method_keyboard_grab.rs b/src/wayland/input_method/input_method_keyboard_grab.rs index 2bdedcf3ba14..24ac2a727296 100644 --- a/src/wayland/input_method/input_method_keyboard_grab.rs +++ b/src/wayland/input_method/input_method_keyboard_grab.rs @@ -54,7 +54,7 @@ where let keyboard = inner.grab.as_ref().unwrap(); inner .text_input_handle - .focused_text_input_serial_or_default(serial.0, |serial| { + .active_text_input_serial_or_default(serial.0, |serial| { keyboard.key(serial, time, keycode.raw() - 8, key_state.into()); if let Some(serialized) = modifiers.map(|m| m.serialized) { keyboard.modifiers( diff --git a/src/wayland/text_input/text_input_handle.rs b/src/wayland/text_input/text_input_handle.rs index b042f451f8e8..56b497920e6d 100644 --- a/src/wayland/text_input/text_input_handle.rs +++ b/src/wayland/text_input/text_input_handle.rs @@ -6,7 +6,6 @@ use wayland_server::backend::ClientId; use wayland_server::{protocol::wl_surface::WlSurface, Dispatch, Resource}; use crate::input::SeatHandler; -use crate::utils::IsAlive; use crate::wayland::input_method::InputMethodHandle; use super::TextInputManagerState; @@ -21,22 +20,47 @@ struct Instance { pub(crate) struct TextInput { instances: Vec, focus: Option, + active_text_input_id: Option, } impl TextInput { - fn with_focused_text_input(&mut self, mut f: F) + fn with_focused_client_all_text_inputs(&mut self, mut f: F) where F: FnMut(&ZwpTextInputV3, &WlSurface, u32), { - if let Some(ref surface) = self.focus { - if !surface.alive() { - return; - } - for ti in self.instances.iter_mut() { - if ti.instance.id().same_client_as(&surface.id()) { - f(&ti.instance, surface, ti.serial); + if let Some(surface) = self.focus.as_ref().filter(|surface| surface.is_alive()) { + for text_input in self.instances.iter() { + let instance_id = text_input.instance.id(); + if instance_id.same_client_as(&surface.id()) { + f(&text_input.instance, surface, text_input.serial); + break; } } + }; + } + + fn with_active_text_input(&mut self, mut f: F) + where + F: FnMut(&ZwpTextInputV3, &WlSurface, u32), + { + let active_id = match &self.active_text_input_id { + Some(active_text_input_id) => active_text_input_id, + None => return, + }; + + let surface = match self.focus.as_ref().filter(|surface| surface.is_alive()) { + Some(surface) => surface, + None => return, + }; + + let surface_id = surface.id(); + if let Some(text_input) = self + .instances + .iter() + .filter(|instance| instance.instance.id().same_client_as(&surface_id)) + .find(|instance| &instance.instance.id() == active_id) + { + f(&text_input.instance, surface, text_input.serial); } } } @@ -81,7 +105,10 @@ impl TextInputHandle { /// surface. pub fn leave(&self) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, focus, _| { + // Leaving clears the active text input. + inner.active_text_input_id = None; + // NOTE: we implement it in a symmetrical way with `enter`. + inner.with_focused_client_all_text_inputs(|text_input, focus, _| { text_input.leave(focus); }); } @@ -90,7 +117,9 @@ impl TextInputHandle { /// surface. pub fn enter(&self) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, focus, _| { + // NOTE: protocol states that if we have multiple text inputs enabled, `enter` must + // be send for each of them. + inner.with_focused_client_all_text_inputs(|text_input, focus, _| { text_input.enter(focus); }); } @@ -99,7 +128,7 @@ impl TextInputHandle { /// the state should be discarded and wrong serial sent. pub fn done(&self, discard_state: bool) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|text_input, _, serial| { + inner.with_active_text_input(|text_input, _, serial| { if discard_state { debug!("discarding text-input state due to serial"); // Discarding is done by sending non-matching serial. @@ -116,20 +145,31 @@ impl TextInputHandle { F: FnMut(&ZwpTextInputV3, &WlSurface), { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|ti, surface, _| { + inner.with_focused_client_all_text_inputs(|ti, surface, _| { f(ti, surface); }); } - /// Call the callback with the serial of the focused text_input or with the passed + /// Access the active text-input instance for the currently focused surface. + pub fn with_active_text_input(&self, mut f: F) + where + F: FnMut(&ZwpTextInputV3, &WlSurface), + { + let mut inner = self.inner.lock().unwrap(); + inner.with_active_text_input(|ti, surface, _| { + f(ti, surface); + }); + } + + /// Call the callback with the serial of the active text_input or with the passed /// `default` one when empty. - pub(crate) fn focused_text_input_serial_or_default(&self, default: u32, mut callback: F) + pub(crate) fn active_text_input_serial_or_default(&self, default: u32, mut callback: F) where F: FnMut(u32), { let mut inner = self.inner.lock().unwrap(); let mut should_default = true; - inner.with_focused_text_input(|_, _, serial| { + inner.with_active_text_input(|_, _, serial| { should_default = false; callback(serial); }); @@ -137,6 +177,19 @@ impl TextInputHandle { callback(default) } } + + fn set_active_text_input_id(&self, id: Option) { + self.inner.lock().unwrap().active_text_input_id = id; + } + + fn is_active_text_input_or_new(&self, id: &ObjectId) -> bool { + self.inner + .lock() + .unwrap() + .active_text_input_id + .as_ref() + .map_or(true, |active_id| active_id == id) + } } /// User data of ZwpTextInputV3 object @@ -180,11 +233,20 @@ where } }; + // text-input-v3 states that only one text_input must be enabled at the time + // and receive the events. + if !data.handle.is_active_text_input_or_new(&resource.id()) { + debug!("discarding text_input request since we already have an active one"); + return; + } + match request { zwp_text_input_v3::Request::Enable => { - data.input_method_handle.activate_input_method(state, &focus) + data.handle.set_active_text_input_id(Some(resource.id())); + data.input_method_handle.activate_input_method(state, &focus); } zwp_text_input_v3::Request::Disable => { + data.handle.set_active_text_input_id(None); data.input_method_handle.deactivate_input_method(state, false); } zwp_text_input_v3::Request::SetSurroundingText { text, cursor, anchor } => {