Skip to content

Commit

Permalink
text-input: fix active instance tracking
Browse files Browse the repository at this point in the history
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: 475072d (text-input: Ensure only one enabled...)
Fixes #1604.
  • Loading branch information
kchibisov committed Jan 24, 2025
1 parent 7b46d1d commit 964cce6
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 35 deletions.
10 changes: 4 additions & 6 deletions src/wayland/input_method/input_method_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()));
});
}
Expand All @@ -220,15 +220,15 @@ 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);
});
}
zwp_input_method_v2::Request::DeleteSurroundingText {
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);
});
}
Expand Down Expand Up @@ -332,8 +332,6 @@ where
data: &InputMethodUserData<D>,
) {
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();
}
}
2 changes: 1 addition & 1 deletion src/wayland/input_method/input_method_keyboard_grab.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
105 changes: 77 additions & 28 deletions src/wayland/text_input/text_input_handle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ use wayland_server::backend::{ClientId, ObjectId};
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;
Expand All @@ -21,26 +20,47 @@ struct Instance {
pub(crate) struct TextInput {
instances: Vec<Instance>,
focus: Option<WlSurface>,
enabled_resource_id: Option<ObjectId>,
active_text_input_id: Option<ObjectId>,
}

impl TextInput {
fn with_focused_text_input<F>(&mut self, mut f: F)
fn with_focused_client_all_text_inputs<F>(&mut self, mut f: F)
where
F: FnMut(&ZwpTextInputV3, &WlSurface, u32),
{
if let (Some(surface), Some(enabled_resource_id)) = (&self.focus, &self.enabled_resource_id) {
if !surface.alive() {
return;
}

for ti in self.instances.iter_mut() {
let instance_id = ti.instance.id();
if instance_id.same_client_as(&surface.id()) && instance_id.eq(enabled_resource_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<F>(&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);
}
}
}
Expand Down Expand Up @@ -81,18 +101,14 @@ impl TextInputHandle {
self.inner.lock().unwrap().focus = surface;
}

fn set_enabled_resource_id(&self, resource_id: Option<ObjectId>) {
let mut inner = self.inner.lock().unwrap();
if inner.enabled_resource_id.is_some() != resource_id.is_some() {
inner.enabled_resource_id = resource_id;
}
}

/// Send `leave` on the text-input instance for the currently focused
/// 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);
});
}
Expand All @@ -101,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);
});
}
Expand All @@ -110,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.
Expand All @@ -127,27 +145,51 @@ 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);
});
}

/// Access the active text-input instance for the currently focused surface.
pub fn with_active_text_input<F>(&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 focused text_input or with the passed
/// 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<F>(&self, default: u32, mut callback: F)
pub(crate) fn active_text_input_serial_or_default<F>(&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);
});
if should_default {
callback(default)
}
}

fn set_active_text_input_id(&self, id: Option<ObjectId>) {
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
Expand Down Expand Up @@ -191,13 +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.handle.set_enabled_resource_id(Some(resource.id()));
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_enabled_resource_id(None);
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 } => {
Expand Down

0 comments on commit 964cce6

Please sign in to comment.