From 881fb2487873538d4c23dcf9171dd80adf84a76c Mon Sep 17 00:00:00 2001 From: Julian Orth Date: Wed, 15 Oct 2025 22:01:24 +0200 Subject: [PATCH] text-input: add input method abstraction --- src/gfx_api.rs | 2 +- src/ifs/wl_seat.rs | 13 +++-- src/ifs/wl_seat/event_handling.rs | 21 +++++--- src/ifs/wl_seat/text_input.rs | 48 +++++++++++++------ .../zwp_input_method_keyboard_grab_v2.rs | 25 ++++++++-- .../wl_seat/text_input/zwp_input_method_v2.rs | 36 +++++++++++++- .../wl_seat/text_input/zwp_text_input_v3.rs | 20 ++++---- src/ifs/wl_surface.rs | 2 +- 8 files changed, 122 insertions(+), 45 deletions(-) diff --git a/src/gfx_api.rs b/src/gfx_api.rs index 810bdd81..5193f40b 100644 --- a/src/gfx_api.rs +++ b/src/gfx_api.rs @@ -955,7 +955,7 @@ pub fn create_render_pass( for seat in seats.values() { let (x, y) = seat.pointer_cursor().position_int(); if let Some(im) = seat.input_method() { - for (_, popup) in &im.popups { + for (_, popup) in im.popups() { if popup.surface.node_visible() { let pos = popup.surface.buffer_abs_pos.get(); let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1()); diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index c89853d9..596d5dcc 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -52,8 +52,7 @@ use { pointer_owner::PointerOwnerHolder, tablet::TabletSeatData, text_input::{ - zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2, - zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3, + InputMethod, InputMethodKeyboardGrab, zwp_text_input_v3::ZwpTextInputV3, }, touch_owner::TouchOwnerHolder, wl_keyboard::{REPEAT_INFO_SINCE, WlKeyboard, WlKeyboardError}, @@ -216,8 +215,8 @@ pub struct WlSeatGlobal { last_input_usec: Cell, text_inputs: RefCell>>>, text_input: CloneCell>>, - input_method: CloneCell>>, - input_method_grab: CloneCell>>, + input_method: CloneCell>>, + input_method_grab: CloneCell>>, forward: Cell, focus_follows_mouse: Cell, swipe_bindings: PerClientBindings, @@ -371,7 +370,7 @@ impl WlSeatGlobal { self.seat_kb_map.get() } - pub fn input_method(&self) -> Option> { + pub fn input_method(&self) -> Option> { self.input_method.get() } @@ -693,7 +692,7 @@ impl WlSeatGlobal { } } if let Some(grab) = self.input_method_grab.get() { - grab.send_repeat_info(); + grab.on_repeat_info(); } } @@ -1315,7 +1314,7 @@ impl WlSeatGlobal { tl.tl_set_visible(visible); } if let Some(im) = self.input_method.get() { - for (_, popup) in &im.popups { + for (_, popup) in im.popups() { popup.update_visible(); } } diff --git a/src/ifs/wl_seat/event_handling.rs b/src/ifs/wl_seat/event_handling.rs index 616c9d3b..8cb052b7 100644 --- a/src/ifs/wl_seat/event_handling.rs +++ b/src/ifs/wl_seat/event_handling.rs @@ -981,15 +981,19 @@ impl WlSeatGlobal { } } self.send_components(&mut components_changed, &kbvm_state); - match self.input_method_grab.get() { - Some(g) => g.on_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state), - _ => self.keyboard_node.get().node_on_key( + let mut forward_to_node = true; + if let Some(g) = self.input_method_grab.get() { + forward_to_node = + g.on_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state); + } + if forward_to_node { + self.keyboard_node.get().node_on_key( self, time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state, - ), + ) } self.for_each_ei_seat(|ei_seat| { ei_seat.handle_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state); @@ -1064,9 +1068,12 @@ impl WlSeatGlobal { self.state.for_each_seat_tester(|t| { t.send_modifiers(self.id, &kb_state.mods); }); - match self.input_method_grab.get() { - Some(g) => g.on_modifiers(kb_state), - _ => self.keyboard_node.get().node_on_mods(self, kb_state), + let mut forward_to_node = true; + if let Some(g) = self.input_method_grab.get() { + forward_to_node = g.on_modifiers(kb_state); + } + if forward_to_node { + self.keyboard_node.get().node_on_mods(self, kb_state) } } diff --git a/src/ifs/wl_seat/text_input.rs b/src/ifs/wl_seat/text_input.rs index 4f4f8418..4ec1516a 100644 --- a/src/ifs/wl_seat/text_input.rs +++ b/src/ifs/wl_seat/text_input.rs @@ -1,12 +1,13 @@ use { - crate::ifs::{ - wl_seat::{ - WlSeatGlobal, - text_input::{ - zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3, - }, + crate::{ + backend::KeyState, + ifs::{ + wl_seat::{WlSeatGlobal, text_input::zwp_text_input_v3::ZwpTextInputV3}, + wl_surface::{WlSurface, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2}, }, - wl_surface::WlSurface, + keyboard::KeyboardState, + utils::smallmap::SmallMap, + wire::ZwpInputPopupSurfaceV2Id, }, std::rc::Rc, }; @@ -22,10 +23,27 @@ const MAX_TEXT_SIZE: usize = 4000; pub struct TextInputConnection { pub seat: Rc, pub text_input: Rc, - pub input_method: Rc, + pub input_method: Rc, pub surface: Rc, } +pub trait InputMethod { + fn set_connection(&self, con: Option<&Rc>); + fn popups(&self) -> &SmallMap, 1>; + fn activate(&self); + fn deactivate(&self); + fn content_type(&self, hint: u32, purpose: u32); + fn text_change_cause(&self, cause: u32); + fn surrounding_text(&self, text: &str, cursor: u32, anchor: u32); + fn done(self: Rc, seat: &WlSeatGlobal); +} + +pub trait InputMethodKeyboardGrab { + fn on_key(&self, time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) -> bool; + fn on_modifiers(&self, kb_state: &KeyboardState) -> bool; + fn on_repeat_info(&self); +} + #[derive(Copy, Clone, Debug, Eq, PartialEq)] pub enum TextConnectReason { TextInputEnabled, @@ -67,7 +85,7 @@ impl WlSeatGlobal { impl TextInputConnection { fn connect(self: &Rc, reason: TextConnectReason) { - self.input_method.connection.set(Some(self.clone())); + self.input_method.set_connection(Some(self)); self.text_input.connection.set(Some(self.clone())); self.surface .text_input_connections @@ -75,20 +93,20 @@ impl TextInputConnection { self.input_method.activate(); if reason == TextConnectReason::InputMethodCreated { - self.text_input.send_all_to(&self.input_method); - self.input_method.send_done(); + self.text_input.send_all_to(&*self.input_method); + self.input_method.clone().done(&self.seat); } } pub fn disconnect(&self, reason: TextDisconnectReason) { self.text_input.connection.take(); - self.input_method.connection.take(); + self.input_method.set_connection(None); self.surface.text_input_connections.remove(&self.seat.id); if reason != TextDisconnectReason::InputMethodDestroyed { - self.input_method.send_deactivate(); - self.input_method.send_done(); - for (_, popup) in &self.input_method.popups { + self.input_method.deactivate(); + self.input_method.clone().done(&self.seat); + for (_, popup) in self.input_method.popups() { popup.update_visible(); } } diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs index adedd3b3..53de655d 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_keyboard_grab_v2.rs @@ -2,7 +2,10 @@ use { crate::{ backend::KeyState, client::{Client, ClientError}, - ifs::wl_seat::{text_input::zwp_input_method_v2::ZwpInputMethodV2, wl_keyboard}, + ifs::wl_seat::{ + text_input::{InputMethodKeyboardGrab, zwp_input_method_v2::ZwpInputMethodV2}, + wl_keyboard, + }, keyboard::{KeyboardState, KeyboardStateId}, leaks::Tracker, object::{Object, Version}, @@ -49,7 +52,7 @@ impl ZwpInputMethodKeyboardGrabV2 { self.kb_state_id.set(kb_state.id); } - pub fn on_key(&self, time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) { + fn on_key(&self, time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) { let serial = self.client.next_serial(); if self.kb_state_id.get() != kb_state.id { self.update_state(serial, kb_state); @@ -70,7 +73,7 @@ impl ZwpInputMethodKeyboardGrabV2 { }) } - pub fn on_modifiers(&self, kb_state: &KeyboardState) { + fn on_modifiers(&self, kb_state: &KeyboardState) { let serial = self.client.next_serial(); if self.kb_state_id.get() != kb_state.id { self.update_state(serial, kb_state); @@ -99,6 +102,22 @@ impl ZwpInputMethodKeyboardGrabV2 { } } +impl InputMethodKeyboardGrab for ZwpInputMethodKeyboardGrabV2 { + fn on_key(&self, time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) -> bool { + self.on_key(time_usec, key, state, kb_state); + false + } + + fn on_modifiers(&self, kb_state: &KeyboardState) -> bool { + self.on_modifiers(kb_state); + false + } + + fn on_repeat_info(&self) { + self.send_repeat_info(); + } +} + impl ZwpInputMethodKeyboardGrabV2RequestHandler for ZwpInputMethodKeyboardGrabV2 { type Error = ZwpInputMethodKeyboardGrabV2Error; diff --git a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs index 3848bc56..5f6e3b15 100644 --- a/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs +++ b/src/ifs/wl_seat/text_input/zwp_input_method_v2.rs @@ -5,7 +5,7 @@ use { wl_seat::{ WlSeatGlobal, text_input::{ - MAX_TEXT_SIZE, TextDisconnectReason, TextInputConnection, + InputMethod, MAX_TEXT_SIZE, TextDisconnectReason, TextInputConnection, zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2, }, }, @@ -104,6 +104,40 @@ impl ZwpInputMethodV2 { } } +impl InputMethod for ZwpInputMethodV2 { + fn set_connection(&self, con: Option<&Rc>) { + self.connection.set(con.cloned()); + } + + fn popups(&self) -> &SmallMap, 1> { + &self.popups + } + + fn activate(&self) { + self.activate(); + } + + fn deactivate(&self) { + self.send_deactivate(); + } + + fn content_type(&self, hint: u32, purpose: u32) { + self.send_content_type(hint, purpose); + } + + fn text_change_cause(&self, cause: u32) { + self.send_text_change_cause(cause); + } + + fn surrounding_text(&self, text: &str, cursor: u32, anchor: u32) { + self.send_surrounding_text(text, cursor, anchor); + } + + fn done(self: Rc, _seat: &WlSeatGlobal) { + (*self).send_done(); + } +} + impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 { type Error = ZwpInputMethodV2Error; diff --git a/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs b/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs index 54211b16..f831ac33 100644 --- a/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs +++ b/src/ifs/wl_seat/text_input/zwp_text_input_v3.rs @@ -5,8 +5,8 @@ use { wl_seat::{ WlSeatGlobal, text_input::{ - MAX_TEXT_SIZE, TextConnectReason, TextDisconnectReason, TextInputConnection, - zwp_input_method_v2::ZwpInputMethodV2, + InputMethod, MAX_TEXT_SIZE, TextConnectReason, TextDisconnectReason, + TextInputConnection, }, }, wl_surface::WlSurface, @@ -72,13 +72,13 @@ impl ZwpTextInputV3 { } } - pub fn send_all_to(&self, im: &ZwpInputMethodV2) { + pub fn send_all_to(&self, im: &dyn InputMethod) { let state = &*self.state.borrow(); { let (a, b, c) = &state.surrounding_text; - im.send_surrounding_text(a, *b, *c); + im.surrounding_text(a, *b, *c); } - im.send_content_type(state.content_type.0, state.content_type.1); + im.content_type(state.content_type.0, state.content_type.1); } pub fn send_enter(&self, surface: &WlSurface) { @@ -255,7 +255,7 @@ impl ZwpTextInputV3RequestHandler for ZwpTextInputV3 { if state.cursor_rectangle != val && let Some(con) = &con { - for (_, popup) in &con.input_method.popups { + for (_, popup) in con.input_method.popups() { popup.schedule_positioning(); } } @@ -264,26 +264,26 @@ impl ZwpTextInputV3RequestHandler for ZwpTextInputV3 { if let Some(val) = pending.content_type { if let Some(con) = &con { sent_any = true; - con.input_method.send_content_type(val.0, val.1); + con.input_method.content_type(val.0, val.1); } state.content_type = val; } if let Some(val) = pending.text_change_cause { if let Some(con) = &con { sent_any = true; - con.input_method.send_text_change_cause(val); + con.input_method.text_change_cause(val); } state.text_change_cause = val; } if let Some(val) = pending.surrounding_text { if let Some(con) = &con { sent_any = true; - con.input_method.send_surrounding_text(&val.0, val.1, val.2); + con.input_method.surrounding_text(&val.0, val.1, val.2); } state.surrounding_text = val; } if sent_any && let Some(con) = &con { - con.input_method.send_done(); + con.input_method.clone().done(&self.seat); } Ok(()) } diff --git a/src/ifs/wl_surface.rs b/src/ifs/wl_surface.rs index 282a7696..a108b7f4 100644 --- a/src/ifs/wl_surface.rs +++ b/src/ifs/wl_surface.rs @@ -784,7 +784,7 @@ impl WlSurface { } } for (_, con) in &self.text_input_connections { - for (_, popup) in &con.input_method.popups { + for (_, popup) in con.input_method.popups() { popup.schedule_positioning(); } }