diff --git a/anvil/src/state.rs b/anvil/src/state.rs index 8732ad9cab1d..2fb5dbea913e 100644 --- a/anvil/src/state.rs +++ b/anvil/src/state.rs @@ -278,16 +278,17 @@ impl InputMethodHandler for AnvilState { } } + fn dismiss_popup(&mut self, surface: PopupSurface) { + if let Some(parent) = surface.get_parent().map(|parent| parent.surface.clone()) { + let _ = PopupManager::dismiss_popup(&parent, &PopupKind::from(surface)); + } + } + fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { - let w = self - .space + self.space .elements() - .find(|window| window.wl_surface().as_ref() == Some(parent)); - if let Some(window) = w { - window.geometry() - } else { - Rectangle::default() - } + .find_map(|window| (window.wl_surface().as_ref() == Some(parent)).then(|| window.geometry())) + .unwrap_or_default() } } diff --git a/src/desktop/wayland/popup/manager.rs b/src/desktop/wayland/popup/manager.rs index 8f03a2811178..290e172eb7d4 100644 --- a/src/desktop/wayland/popup/manager.rs +++ b/src/desktop/wayland/popup/manager.rs @@ -182,7 +182,8 @@ impl PopupManager { }) } - pub(crate) fn dismiss_popup(surface: &WlSurface, popup: &PopupKind) -> Result<(), DeadResource> { + /// Dismiss the `popup` associated with the `surface. + pub fn dismiss_popup(surface: &WlSurface, popup: &PopupKind) -> Result<(), DeadResource> { if !surface.alive() { return Err(DeadResource); } diff --git a/src/desktop/wayland/popup/mod.rs b/src/desktop/wayland/popup/mod.rs index 9375957ae65e..b356a903af74 100644 --- a/src/desktop/wayland/popup/mod.rs +++ b/src/desktop/wayland/popup/mod.rs @@ -50,7 +50,7 @@ impl PopupKind { fn parent(&self) -> Option { match *self { PopupKind::Xdg(ref t) => t.get_parent_surface(), - PopupKind::InputMethod(ref t) => Some(t.get_parent_surface()), + PopupKind::InputMethod(ref t) => t.get_parent().map(|parent| parent.surface.clone()), } } @@ -65,7 +65,7 @@ impl PopupKind { .geometry .unwrap_or_default() }), - PopupKind::InputMethod(ref t) => t.parent_location(), + PopupKind::InputMethod(ref t) => t.get_parent().map(|parent| parent.location).unwrap_or_default(), } } diff --git a/src/wayland/input_method/input_method_handle.rs b/src/wayland/input_method/input_method_handle.rs index f07257c4c204..2dcf8bbcb057 100644 --- a/src/wayland/input_method/input_method_handle.rs +++ b/src/wayland/input_method/input_method_handle.rs @@ -9,33 +9,45 @@ use wayland_protocols_misc::zwp_input_method_v2::server::{ zwp_input_method_v2::{self, ZwpInputMethodV2}, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2, }; -use wayland_server::backend::ClientId; +use wayland_server::{backend::ClientId, protocol::wl_surface::WlSurface}; use wayland_server::{ protocol::wl_keyboard::KeymapFormat, Client, DataInit, Dispatch, DisplayHandle, Resource, }; use crate::{ input::{keyboard::KeyboardHandle, SeatHandler}, - utils::{alive_tracker::AliveTracker, Rectangle, SERIAL_COUNTER}, + utils::{alive_tracker::AliveTracker, Logical, Rectangle, SERIAL_COUNTER}, wayland::{compositor, seat::WaylandFocus, text_input::TextInputHandle}, }; use super::{ input_method_keyboard_grab::InputMethodKeyboardGrab, - input_method_popup_surface::{PopupHandle, PopupSurface}, + input_method_popup_surface::{PopupHandle, PopupParent, PopupSurface}, InputMethodHandler, InputMethodKeyboardUserData, InputMethodManagerState, - InputMethodPopupSurfaceUserData, + InputMethodPopupSurfaceUserData, INPUT_POPUP_SURFACE_ROLE, }; -const INPUT_POPUP_SURFACE_ROLE: &str = "zwp_input_popup_surface_v2"; - #[derive(Default, Debug)] pub(crate) struct InputMethod { - pub instance: Option, + pub instance: Option, pub popup_handle: PopupHandle, pub keyboard_grab: InputMethodKeyboardGrab, } +#[derive(Debug)] +pub(crate) struct Instance { + pub object: ZwpInputMethodV2, + pub serial: u32, +} + +impl Instance { + /// Send the done incrementing the serial. + pub(crate) fn done(&mut self) { + self.object.done(); + self.serial += 1; + } +} + /// Handle to an input method instance #[derive(Default, Debug, Clone)] pub struct InputMethodHandle { @@ -45,24 +57,42 @@ pub struct InputMethodHandle { impl InputMethodHandle { pub(super) fn add_instance(&self, instance: &ZwpInputMethodV2) { let mut inner = self.inner.lock().unwrap(); - if inner.instance.is_some() { - instance.unavailable() + if let Some(instance) = inner.instance.as_mut() { + instance.serial = 0; + instance.object.unavailable(); } else { - inner.instance = Some(instance.clone()); + inner.instance = Some(Instance { + object: instance.clone(), + serial: 0, + }); } } + /// Whether there's an acitve instance of input-method. + pub(crate) fn has_instance(&self) -> bool { + self.inner.lock().unwrap().instance.is_some() + } + /// Callback function to access the input method object - pub fn with_instance(&self, mut f: F) + pub(crate) fn with_instance(&self, mut f: F) where - F: FnMut(&ZwpInputMethodV2), + F: FnMut(&mut Instance), { - let inner = self.inner.lock().unwrap(); - if let Some(instance) = &inner.instance { + let mut inner = self.inner.lock().unwrap(); + if let Some(instance) = inner.instance.as_mut() { f(instance); } } + /// Callback function to access the input method. + pub(crate) fn with_input_method(&self, mut f: F) + where + F: FnMut(&mut InputMethod), + { + let mut inner = self.inner.lock().unwrap(); + f(&mut inner); + } + /// Indicates that an input method has grabbed a keyboard pub fn keyboard_grabbed(&self) -> bool { let inner = self.inner.lock().unwrap(); @@ -78,10 +108,48 @@ impl InputMethodHandle { } } - /// Convenience function to close popup surfaces - pub(crate) fn close_popup(&self) { - let mut inner = self.inner.lock().unwrap(); - inner.popup_handle.surface = None; + /// Activate input method on the given surface. + pub(crate) fn activate_input_method(&self, state: &mut D, surface: &WlSurface) { + self.with_input_method(|im| { + if let Some(instance) = im.instance.as_ref() { + instance.object.activate(); + if let Some(popup) = im.popup_handle.surface.as_mut() { + let data = instance.object.data::>().unwrap(); + let location = (data.popup_geometry_callback)(state, surface); + // Remove old popup. + (data.dismiss_popup)(state, popup.clone()); + + // Add a new one with updated parent. + let parent = PopupParent { + surface: surface.clone(), + location, + }; + popup.set_parent(Some(parent)); + (data.new_popup)(state, popup.clone()); + } + } + }); + } + + /// Deactivate the active input method. + /// + /// The `done` is required in cases where the change in state is initiated not by text-input. + pub(crate) fn deactivate_input_method(&self, state: &mut D, done: bool) { + self.with_input_method(|im| { + if let Some(instance) = im.instance.as_mut() { + instance.object.deactivate(); + if done { + instance.done(); + } + if let Some(popup) = im.popup_handle.surface.as_mut() { + let data = instance.object.data::>().unwrap(); + if popup.get_parent().is_some() { + (data.dismiss_popup)(state, popup.clone()); + } + popup.set_parent(None); + } + } + }); } } @@ -90,6 +158,9 @@ pub struct InputMethodUserData { pub(super) handle: InputMethodHandle, pub(crate) text_input_handle: TextInputHandle, pub(crate) keyboard_handle: KeyboardHandle, + pub(crate) popup_geometry_callback: fn(&D, &WlSurface) -> Rectangle, + pub(crate) new_popup: fn(&mut D, PopupSurface), + pub(crate) dismiss_popup: fn(&mut D, PopupSurface), } impl fmt::Debug for InputMethodUserData { @@ -144,22 +215,38 @@ where ti.delete_surrounding_text(before_length, after_length); }); } - zwp_input_method_v2::Request::Commit { serial: _ } => { - data.text_input_handle.done(); + zwp_input_method_v2::Request::Commit { serial } => { + let current_serial = data + .handle + .inner + .lock() + .unwrap() + .instance + .as_ref() + .map(|i| i.serial) + .unwrap_or(0); + + data.text_input_handle.done(serial != current_serial); } zwp_input_method_v2::Request::GetInputPopupSurface { id, surface } => { - if compositor::give_role(&surface, INPUT_POPUP_SURFACE_ROLE).is_err() { + if compositor::give_role(&surface, INPUT_POPUP_SURFACE_ROLE).is_err() + && compositor::get_role(&surface) != Some(INPUT_POPUP_SURFACE_ROLE) + { // Protocol requires this raise an error, but doesn't define an error enum seat.post_error(0u32, "Surface already has a role."); return; } - let parent = if let Some(parent) = data.text_input_handle.focus() { - parent - } else { - return; + let parent = match data.text_input_handle.focus().clone() { + Some(parent) => { + let location = state.parent_geometry(&parent); + Some(PopupParent { + surface: parent, + location, + }) + } + None => None, }; - let mut input_method = data.handle.inner.lock().unwrap(); let instance = data_init.init( @@ -168,15 +255,12 @@ where alive_tracker: AliveTracker::default(), }, ); - let popup = PopupSurface::new( - instance, - surface, - parent.clone(), - input_method.popup_handle.rectangle, - state.parent_geometry(&parent), - ); + let popup_rect = Arc::new(Mutex::new(input_method.popup_handle.rectangle)); + let popup = PopupSurface::new(instance, surface, popup_rect, parent); input_method.popup_handle.surface = Some(popup.clone()); - state.new_popup(popup); + if popup.get_parent().is_some() { + state.new_popup(popup); + } } zwp_input_method_v2::Request::GrabKeyboard { keyboard } => { let input_method = data.handle.inner.lock().unwrap(); diff --git a/src/wayland/input_method/input_method_keyboard_grab.rs b/src/wayland/input_method/input_method_keyboard_grab.rs index 3049a407f159..6e21fcc90df3 100644 --- a/src/wayland/input_method/input_method_keyboard_grab.rs +++ b/src/wayland/input_method/input_method_keyboard_grab.rs @@ -9,7 +9,6 @@ use wayland_protocols_misc::zwp_input_method_v2::server::zwp_input_method_keyboa use wayland_server::backend::ClientId; use wayland_server::Dispatch; -use crate::backend::input::KeyState; use crate::input::{ keyboard::{ GrabStartData as KeyboardGrabStartData, KeyboardGrab, KeyboardHandle, KeyboardInnerHandle, @@ -17,7 +16,8 @@ use crate::input::{ }, SeatHandler, }; -use crate::wayland::{seat::WaylandFocus, text_input::TextInputHandle}; +use crate::wayland::text_input::TextInputHandle; +use crate::{backend::input::KeyState, utils::Serial}; use super::InputMethodManagerState; @@ -36,7 +36,6 @@ pub struct InputMethodKeyboardGrab { impl KeyboardGrab for InputMethodKeyboardGrab where D: SeatHandler + 'static, - ::KeyboardFocus: WaylandFocus, { fn input( &mut self, @@ -45,23 +44,25 @@ where keycode: u32, key_state: KeyState, modifiers: Option, - _serial: crate::utils::Serial, + serial: Serial, time: u32, ) { let inner = self.inner.lock().unwrap(); let keyboard = inner.grab.as_ref().unwrap(); - inner.text_input_handle.focused_text_input_serial(|serial| { - keyboard.key(serial, time, keycode, key_state.into()); - if let Some(serialized) = modifiers.map(|m| m.serialized) { - keyboard.modifiers( - serial, - serialized.depressed, - serialized.latched, - serialized.locked, - serialized.layout_effective, - ) - } - }); + inner + .text_input_handle + .focused_text_input_serial_or_default(serial.0, |serial| { + keyboard.key(serial, time, keycode, key_state.into()); + if let Some(serialized) = modifiers.map(|m| m.serialized) { + keyboard.modifiers( + serial, + serialized.depressed, + serialized.latched, + serialized.locked, + serialized.layout_effective, + ) + } + }); } fn set_focus( diff --git a/src/wayland/input_method/input_method_popup_surface.rs b/src/wayland/input_method/input_method_popup_surface.rs index 75c848c9042e..dc3601b0ce57 100644 --- a/src/wayland/input_method/input_method_popup_surface.rs +++ b/src/wayland/input_method/input_method_popup_surface.rs @@ -1,3 +1,5 @@ +use std::sync::{Arc, Mutex}; + use wayland_protocols_misc::zwp_input_method_v2::server::zwp_input_popup_surface_v2::{ self, ZwpInputPopupSurfaceV2, }; @@ -5,9 +7,8 @@ use wayland_server::{backend::ClientId, protocol::wl_surface::WlSurface, Dispatc use crate::utils::{ alive_tracker::{AliveTracker, IsAlive}, - Physical, Rectangle, + Logical, Point, Rectangle, }; -use crate::utils::{Logical, Point}; use super::InputMethodManagerState; @@ -15,7 +16,7 @@ use super::InputMethodManagerState; #[derive(Debug, Clone, Default)] pub struct PopupHandle { pub surface: Option, - pub rectangle: Rectangle, + pub rectangle: Rectangle, } /// A handle to an input method popup surface @@ -24,32 +25,26 @@ pub struct PopupSurface { /// The surface role for the input method popup pub surface_role: ZwpInputPopupSurfaceV2, surface: WlSurface, - parent: WlSurface, - /// Rectangle with position and size of text cursor, used for placement of popup surface - pub rectangle: Rectangle, - parent_location: Rectangle, -} - -impl std::cmp::PartialEq for PopupSurface { - fn eq(&self, other: &Self) -> bool { - self.surface_role == other.surface_role - } + // NOTE the popup position could change at any time, so the popup manager should get updates, + // thus use shared storage for the `cloned` data to automatically apply . + /// Rectangle with position and size of text cursor, used for placement of popup surface. + pub(crate) rectangle: Arc>>, + /// Current parent of the IME popup. + parent: Option, } impl PopupSurface { pub(crate) fn new( surface_role: ZwpInputPopupSurfaceV2, surface: WlSurface, - parent: WlSurface, - rectangle: Rectangle, - parent_location: Rectangle, + rectangle: Arc>>, + parent: Option, ) -> Self { Self { surface_role, surface, - parent, rectangle, - parent_location, + parent, } } @@ -66,30 +61,49 @@ impl PopupSurface { } /// Access to the parent surface associated with this popup - pub fn get_parent_surface(&self) -> WlSurface { - self.parent.clone() + pub fn get_parent(&self) -> Option<&PopupParent> { + self.parent.as_ref() } - /// Access to the parent surface location associated with this popup - pub fn parent_location(&self) -> Rectangle { - self.parent_location + /// Set the IME popup surface parent. + pub fn set_parent(&mut self, parent: Option) { + self.parent = parent; } /// Used to access the location of an input popup surface relative to the parent pub fn location(&self) -> Point { - Point::from(( - self.rectangle.loc.x - self.rectangle.size.w, - self.rectangle.loc.y + self.rectangle.size.h, - )) + let rectangle = *self.rectangle.lock().unwrap(); + Point::from((rectangle.loc.x, rectangle.loc.y)) + } + + /// The region compositor shouldn't obscure when placing the popup within the + /// client. + pub fn protected_region(&self) -> Rectangle { + *self.rectangle.lock().unwrap() } /// Set relative location of text cursor pub fn set_rectangle(&mut self, x: i32, y: i32, width: i32, height: i32) { - self.rectangle = Rectangle::from_loc_and_size((x, y), (width, height)); + *self.rectangle.lock().unwrap() = Rectangle::from_loc_and_size((x, y), (width, height)); self.surface_role.text_input_rectangle(x, y, width, height); } } +impl std::cmp::PartialEq for PopupSurface { + fn eq(&self, other: &Self) -> bool { + self.surface_role == other.surface_role + } +} + +/// Parent surface and location for the IME popup. +#[derive(Debug, Clone)] +pub struct PopupParent { + /// The surface IME popup is present over. + pub surface: WlSurface, + /// The location of the parent surface. + pub location: Rectangle, +} + /// User data of ZwpInputPopupSurfaceV2 object #[derive(Debug)] pub struct InputMethodPopupSurfaceUserData { diff --git a/src/wayland/input_method/mod.rs b/src/wayland/input_method/mod.rs index 06190f2e9425..56b979299533 100644 --- a/src/wayland/input_method/mod.rs +++ b/src/wayland/input_method/mod.rs @@ -19,6 +19,7 @@ //! //! impl InputMethodHandler for State { //! fn new_popup(&mut self, surface: PopupSurface) {} +//! fn dismiss_popup(&mut self, surface: PopupSurface) {} //! fn parent_geometry(&self, parent: &WlSurface) -> Rectangle { //! Rectangle::default() //! } @@ -75,16 +76,22 @@ use super::text_input::TextInputHandle; const MANAGER_VERSION: u32 = 1; +/// The role of the input method popup. +pub const INPUT_POPUP_SURFACE_ROLE: &str = "zwp_input_popup_surface_v2"; + mod input_method_handle; mod input_method_keyboard_grab; mod input_method_popup_surface; -pub use input_method_popup_surface::PopupSurface; +pub use input_method_popup_surface::{PopupParent, PopupSurface}; /// Adds input method popup to compositor state pub trait InputMethodHandler { - /// Add a popup surface to compositor state + /// Add a popup surface to compositor state. fn new_popup(&mut self, surface: PopupSurface); + /// Dismiss a popup surface from the compositor state. + fn dismiss_popup(&mut self, surface: PopupSurface); + /// Sets the parent location so the popup surface can be placed correctly fn parent_geometry(&self, parent: &WlSurface) -> Rectangle; } @@ -154,7 +161,7 @@ impl Dispatch for InputMethodManagerState where D: Dispatch, D: Dispatch>, - D: SeatHandler, + D: SeatHandler + InputMethodHandler, D: 'static, { fn request( @@ -185,6 +192,9 @@ where handle: handle.clone(), text_input_handle: text_input_handle.clone(), keyboard_handle, + popup_geometry_callback: D::parent_geometry, + new_popup: D::new_popup, + dismiss_popup: D::dismiss_popup, }, ); handle.add_instance(&instance); diff --git a/src/wayland/seat/keyboard.rs b/src/wayland/seat/keyboard.rs index d98a85bf3323..889b36d772a1 100644 --- a/src/wayland/seat/keyboard.rs +++ b/src/wayland/seat/keyboard.rs @@ -144,7 +144,7 @@ fn serialize_pressed_keys(keys: Vec) -> Vec { } impl KeyboardTarget for WlSurface { - fn enter(&self, seat: &Seat, _data: &mut D, keys: Vec>, serial: Serial) { + fn enter(&self, seat: &Seat, state: &mut D, keys: Vec>, serial: Serial) { for_each_focused_kbds(seat, self, |kbd| { kbd.enter( serial.into(), @@ -152,23 +152,35 @@ impl KeyboardTarget for WlSurface { serialize_pressed_keys(keys.iter().map(|h| h.raw_code().raw() - 8).collect()), ) }); - let input_method = seat.input_method(); - input_method.with_instance(|im| { - im.activate(); - }); + let text_input = seat.text_input(); - text_input.enter(self); + let input_method = seat.input_method(); + + if input_method.has_instance() { + input_method.deactivate_input_method(state, true); + } + + // NOTE: Always set focus regardless whether the client actually has the + // text-input global bound due to clients doing lazy global binding. + text_input.set_focus(Some(self.clone())); + + // Only notify on `enter` once we have an actual IME. + if input_method.has_instance() { + text_input.enter(); + } } - fn leave(&self, seat: &Seat, _data: &mut D, serial: Serial) { + fn leave(&self, seat: &Seat, state: &mut D, serial: Serial) { for_each_focused_kbds(seat, self, |kbd| kbd.leave(serial.into(), self)); let text_input = seat.text_input(); let input_method = seat.input_method(); - input_method.close_popup(); - input_method.with_instance(|im| { - im.deactivate(); - }); - text_input.leave(self); + + if input_method.has_instance() { + input_method.deactivate_input_method(state, true); + text_input.leave(); + } + + text_input.set_focus(None); } fn key( diff --git a/src/wayland/text_input/mod.rs b/src/wayland/text_input/mod.rs index 48e8265f3aa4..8da0fe328e88 100644 --- a/src/wayland/text_input/mod.rs +++ b/src/wayland/text_input/mod.rs @@ -150,6 +150,9 @@ where }, ); handle.add_instance(&instance); + if input_method_handle.has_instance() { + handle.enter(); + } } zwp_text_input_manager_v3::Request::Destroy => { // Nothing to do diff --git a/src/wayland/text_input/text_input_handle.rs b/src/wayland/text_input/text_input_handle.rs index 6513b96bb1f3..66d77867476a 100644 --- a/src/wayland/text_input/text_input_handle.rs +++ b/src/wayland/text_input/text_input_handle.rs @@ -1,9 +1,11 @@ use std::sync::{Arc, Mutex}; +use tracing::debug; use wayland_protocols::wp::text_input::zv3::server::zwp_text_input_v3::{self, ZwpTextInputV3}; 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; @@ -13,7 +15,6 @@ use super::TextInputManagerState; struct Instance { instance: ZwpTextInputV3, serial: u32, - ready: bool, } #[derive(Default, Debug)] @@ -25,7 +26,7 @@ pub(crate) struct TextInput { impl TextInput { fn with_focused_text_input(&mut self, mut f: F) where - F: FnMut(&ZwpTextInputV3, &WlSurface, u32, &mut bool), + F: FnMut(&ZwpTextInputV3, &WlSurface, u32), { if let Some(ref surface) = self.focus { if !surface.alive() { @@ -33,7 +34,7 @@ impl TextInput { } for ti in self.instances.iter_mut() { if ti.instance.id().same_client_as(&surface.id()) { - f(&ti.instance, surface, ti.serial, &mut ti.ready); + f(&ti.instance, surface, ti.serial); } } } @@ -52,7 +53,6 @@ impl TextInputHandle { inner.instances.push(Instance { instance: instance.clone(), serial: 0, - ready: false, }); } @@ -60,53 +60,51 @@ impl TextInputHandle { let mut inner = self.inner.lock().unwrap(); for ti in inner.instances.iter_mut() { if &ti.instance == text_input { - ti.ready = true; ti.serial += 1; } } } - fn focused_instance(&self, text_input: &ZwpTextInputV3, f: F) - where - F: Fn(), - { - let mut inner = self.inner.lock().unwrap(); - for ti in inner.instances.iter_mut() { - if &ti.instance == text_input { - f(); - } - } - } - pub(crate) fn focus(&self) -> Option { self.inner.lock().unwrap().focus.clone() } - pub(crate) fn leave(&self, surface: &WlSurface) { - let inner = self.inner.lock().unwrap(); - for ti in inner.instances.iter() { - if ti.instance.id().same_client_as(&surface.id()) { - ti.instance.leave(surface); - } - } + /// Advance the focus for the client to `surface`. + /// + /// This doesn't send `enter` or `leave` events and is used to + /// tei the focus with the keyboard changes allowing sending + /// `enter`/`leave` when needed. + pub(crate) fn set_focus(&self, surface: Option) { + self.inner.lock().unwrap().focus = surface; } - pub(crate) fn enter(&self, surface: &WlSurface) { + /// Send `leave` for the currently focused text-input. + pub(crate) fn leave(&self) { let mut inner = self.inner.lock().unwrap(); - inner.focus = Some(surface.clone()); - for ti in inner.instances.iter() { - if ti.instance.id().same_client_as(&surface.id()) { - ti.instance.enter(surface); - } - } + inner.with_focused_text_input(|text_input, focus, _| { + text_input.leave(focus); + }); } - pub(crate) fn done(&self) { + /// Send `enter` for the currenty focused text-input. + pub(crate) fn enter(&self) { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|ti, _, serial, ready| { - if *ready { - *ready = false; - ti.done(serial); + inner.with_focused_text_input(|text_input, focus, _| { + text_input.enter(focus); + }); + } + + /// The `discard_state` is used when the input-method signaled that + /// the state should be discarded and wrong serial sent. + pub(crate) fn done(&self, discard_state: bool) { + let mut inner = self.inner.lock().unwrap(); + inner.with_focused_text_input(|text_input, _, serial| { + if discard_state { + debug!("discarding text-input state due to serial"); + // Discarding is done by sending non-matching serial. + text_input.done(0); + } else { + text_input.done(serial); } }); } @@ -117,19 +115,26 @@ impl TextInputHandle { F: FnMut(&ZwpTextInputV3, &WlSurface), { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|ti, surface, _, _| { + inner.with_focused_text_input(|ti, surface, _| { f(ti, surface); }); } - pub(crate) fn focused_text_input_serial(&self, mut f: F) + /// Call the `callback` with the serial of the focused 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) where F: FnMut(u32), { let mut inner = self.inner.lock().unwrap(); - inner.with_focused_text_input(|_, _, serial, _| { - f(serial); + let mut should_default = true; + inner.with_focused_text_input(|_, _, serial| { + should_default = false; + callback(serial); }); + if should_default { + callback(default) + } } } @@ -143,10 +148,11 @@ pub struct TextInputUserData { impl Dispatch for TextInputManagerState where D: Dispatch, + D: SeatHandler, D: 'static, { fn request( - _state: &mut D, + state: &mut D, _client: &wayland_server::Client, resource: &ZwpTextInputV3, request: zwp_text_input_v3::Request, @@ -154,32 +160,51 @@ where _dhandle: &wayland_server::DisplayHandle, _data_init: &mut wayland_server::DataInit<'_, D>, ) { + // Always increment serial to not desync with clients. + if matches!(request, zwp_text_input_v3::Request::Commit) { + data.handle.increment_serial(resource); + } + + // Discard requsets without any active input method instance. + if !data.input_method_handle.has_instance() { + debug!("discarding text-input request without IME running"); + return; + } + + let focus = match data.handle.focus() { + Some(focus) if focus.id().same_client_as(&resource.id()) => focus, + _ => { + debug!("discarding text-input request for unfocused client"); + return; + } + }; + match request { zwp_text_input_v3::Request::Enable => { - data.handle.focused_instance(resource, || { - data.input_method_handle - .with_instance(|input_method| input_method.activate()) - }); + data.input_method_handle.activate_input_method(state, &focus) } zwp_text_input_v3::Request::Disable => { - data.handle.focused_instance(resource, || { - data.input_method_handle - .with_instance(|input_method| input_method.deactivate()) - }); + data.input_method_handle.deactivate_input_method(state, false); } zwp_text_input_v3::Request::SetSurroundingText { text, cursor, anchor } => { data.input_method_handle.with_instance(|input_method| { - input_method.surrounding_text(text.clone(), cursor as u32, anchor as u32) + input_method + .object + .surrounding_text(text.clone(), cursor as u32, anchor as u32) }); } zwp_text_input_v3::Request::SetTextChangeCause { cause } => { data.input_method_handle.with_instance(|input_method| { - input_method.text_change_cause(cause.into_result().unwrap()) + input_method + .object + .text_change_cause(cause.into_result().unwrap()) }); } zwp_text_input_v3::Request::SetContentType { hint, purpose } => { data.input_method_handle.with_instance(|input_method| { - input_method.content_type(hint.into_result().unwrap(), purpose.into_result().unwrap()); + input_method + .object + .content_type(hint.into_result().unwrap(), purpose.into_result().unwrap()); }); } zwp_text_input_v3::Request::SetCursorRectangle { x, y, width, height } => { @@ -187,7 +212,6 @@ where .set_text_input_rectangle(x, y, width, height); } zwp_text_input_v3::Request::Commit => { - data.handle.increment_serial(resource); data.input_method_handle.with_instance(|input_method| { input_method.done(); }); @@ -199,18 +223,28 @@ where } } - fn destroyed(_state: &mut D, _client: ClientId, ti: &ZwpTextInputV3, data: &TextInputUserData) { - // Ensure IME is deactivated when text input dies. - data.input_method_handle.with_instance(|input_method| { - input_method.deactivate(); - input_method.done(); - }); - - data.handle - .inner - .lock() - .unwrap() - .instances - .retain(|i| i.instance.id() != ti.id()); + fn destroyed(state: &mut D, _client: ClientId, text_input: &ZwpTextInputV3, data: &TextInputUserData) { + let destroyed_id = text_input.id(); + let deactivate_im = { + let mut inner = data.handle.inner.lock().unwrap(); + inner.instances.retain(|inst| inst.instance.id() != destroyed_id); + let destroyed_focused = inner + .focus + .as_ref() + .map(|focus| focus.id().same_client_as(&destroyed_id)) + .unwrap_or(true); + + // Deactivate IM when we either lost focus entirely or destroyed text-input for the + // currently focused client. + destroyed_focused + && !inner + .instances + .iter() + .any(|inst| inst.instance.id().same_client_as(&destroyed_id)) + }; + + if deactivate_im { + data.input_method_handle.deactivate_input_method(state, true); + } } }