1
0
Fork 0
forked from wry/wry

text-input: add input method abstraction

This commit is contained in:
Julian Orth 2025-10-15 22:01:24 +02:00
parent 26988f5ce5
commit 881fb24878
8 changed files with 122 additions and 45 deletions

View file

@ -955,7 +955,7 @@ pub fn create_render_pass(
for seat in seats.values() { for seat in seats.values() {
let (x, y) = seat.pointer_cursor().position_int(); let (x, y) = seat.pointer_cursor().position_int();
if let Some(im) = seat.input_method() { if let Some(im) = seat.input_method() {
for (_, popup) in &im.popups { for (_, popup) in im.popups() {
if popup.surface.node_visible() { if popup.surface.node_visible() {
let pos = popup.surface.buffer_abs_pos.get(); let pos = popup.surface.buffer_abs_pos.get();
let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1()); let extents = popup.surface.extents.get().move_(pos.x1(), pos.y1());

View file

@ -52,8 +52,7 @@ use {
pointer_owner::PointerOwnerHolder, pointer_owner::PointerOwnerHolder,
tablet::TabletSeatData, tablet::TabletSeatData,
text_input::{ text_input::{
zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2, InputMethod, InputMethodKeyboardGrab, zwp_text_input_v3::ZwpTextInputV3,
zwp_input_method_v2::ZwpInputMethodV2, zwp_text_input_v3::ZwpTextInputV3,
}, },
touch_owner::TouchOwnerHolder, touch_owner::TouchOwnerHolder,
wl_keyboard::{REPEAT_INFO_SINCE, WlKeyboard, WlKeyboardError}, wl_keyboard::{REPEAT_INFO_SINCE, WlKeyboard, WlKeyboardError},
@ -216,8 +215,8 @@ pub struct WlSeatGlobal {
last_input_usec: Cell<u64>, last_input_usec: Cell<u64>,
text_inputs: RefCell<AHashMap<ClientId, CopyHashMap<ZwpTextInputV3Id, Rc<ZwpTextInputV3>>>>, text_inputs: RefCell<AHashMap<ClientId, CopyHashMap<ZwpTextInputV3Id, Rc<ZwpTextInputV3>>>>,
text_input: CloneCell<Option<Rc<ZwpTextInputV3>>>, text_input: CloneCell<Option<Rc<ZwpTextInputV3>>>,
input_method: CloneCell<Option<Rc<ZwpInputMethodV2>>>, input_method: CloneCell<Option<Rc<dyn InputMethod>>>,
input_method_grab: CloneCell<Option<Rc<ZwpInputMethodKeyboardGrabV2>>>, input_method_grab: CloneCell<Option<Rc<dyn InputMethodKeyboardGrab>>>,
forward: Cell<bool>, forward: Cell<bool>,
focus_follows_mouse: Cell<bool>, focus_follows_mouse: Cell<bool>,
swipe_bindings: PerClientBindings<ZwpPointerGestureSwipeV1>, swipe_bindings: PerClientBindings<ZwpPointerGestureSwipeV1>,
@ -371,7 +370,7 @@ impl WlSeatGlobal {
self.seat_kb_map.get() self.seat_kb_map.get()
} }
pub fn input_method(&self) -> Option<Rc<ZwpInputMethodV2>> { pub fn input_method(&self) -> Option<Rc<dyn InputMethod>> {
self.input_method.get() self.input_method.get()
} }
@ -693,7 +692,7 @@ impl WlSeatGlobal {
} }
} }
if let Some(grab) = self.input_method_grab.get() { 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); tl.tl_set_visible(visible);
} }
if let Some(im) = self.input_method.get() { if let Some(im) = self.input_method.get() {
for (_, popup) in &im.popups { for (_, popup) in im.popups() {
popup.update_visible(); popup.update_visible();
} }
} }

View file

@ -981,15 +981,19 @@ impl WlSeatGlobal {
} }
} }
self.send_components(&mut components_changed, &kbvm_state); self.send_components(&mut components_changed, &kbvm_state);
match self.input_method_grab.get() { let mut forward_to_node = true;
Some(g) => g.on_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state), if let Some(g) = self.input_method_grab.get() {
_ => self.keyboard_node.get().node_on_key( 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, self,
time_usec, time_usec,
kc.to_evdev(), kc.to_evdev(),
key_state, key_state,
&kbvm_state.kb_state, &kbvm_state.kb_state,
), )
} }
self.for_each_ei_seat(|ei_seat| { self.for_each_ei_seat(|ei_seat| {
ei_seat.handle_key(time_usec, kc.to_evdev(), key_state, &kbvm_state.kb_state); 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| { self.state.for_each_seat_tester(|t| {
t.send_modifiers(self.id, &kb_state.mods); t.send_modifiers(self.id, &kb_state.mods);
}); });
match self.input_method_grab.get() { let mut forward_to_node = true;
Some(g) => g.on_modifiers(kb_state), if let Some(g) = self.input_method_grab.get() {
_ => self.keyboard_node.get().node_on_mods(self, kb_state), forward_to_node = g.on_modifiers(kb_state);
}
if forward_to_node {
self.keyboard_node.get().node_on_mods(self, kb_state)
} }
} }

View file

@ -1,12 +1,13 @@
use { use {
crate::ifs::{ crate::{
wl_seat::{ backend::KeyState,
WlSeatGlobal, ifs::{
text_input::{ wl_seat::{WlSeatGlobal, text_input::zwp_text_input_v3::ZwpTextInputV3},
zwp_input_method_v2::ZwpInputMethodV2, 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, std::rc::Rc,
}; };
@ -22,10 +23,27 @@ const MAX_TEXT_SIZE: usize = 4000;
pub struct TextInputConnection { pub struct TextInputConnection {
pub seat: Rc<WlSeatGlobal>, pub seat: Rc<WlSeatGlobal>,
pub text_input: Rc<ZwpTextInputV3>, pub text_input: Rc<ZwpTextInputV3>,
pub input_method: Rc<ZwpInputMethodV2>, pub input_method: Rc<dyn InputMethod>,
pub surface: Rc<WlSurface>, pub surface: Rc<WlSurface>,
} }
pub trait InputMethod {
fn set_connection(&self, con: Option<&Rc<TextInputConnection>>);
fn popups(&self) -> &SmallMap<ZwpInputPopupSurfaceV2Id, Rc<ZwpInputPopupSurfaceV2>, 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<Self>, 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)] #[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum TextConnectReason { pub enum TextConnectReason {
TextInputEnabled, TextInputEnabled,
@ -67,7 +85,7 @@ impl WlSeatGlobal {
impl TextInputConnection { impl TextInputConnection {
fn connect(self: &Rc<Self>, reason: TextConnectReason) { fn connect(self: &Rc<Self>, 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.text_input.connection.set(Some(self.clone()));
self.surface self.surface
.text_input_connections .text_input_connections
@ -75,20 +93,20 @@ impl TextInputConnection {
self.input_method.activate(); self.input_method.activate();
if reason == TextConnectReason::InputMethodCreated { if reason == TextConnectReason::InputMethodCreated {
self.text_input.send_all_to(&self.input_method); self.text_input.send_all_to(&*self.input_method);
self.input_method.send_done(); self.input_method.clone().done(&self.seat);
} }
} }
pub fn disconnect(&self, reason: TextDisconnectReason) { pub fn disconnect(&self, reason: TextDisconnectReason) {
self.text_input.connection.take(); 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); self.surface.text_input_connections.remove(&self.seat.id);
if reason != TextDisconnectReason::InputMethodDestroyed { if reason != TextDisconnectReason::InputMethodDestroyed {
self.input_method.send_deactivate(); self.input_method.deactivate();
self.input_method.send_done(); self.input_method.clone().done(&self.seat);
for (_, popup) in &self.input_method.popups { for (_, popup) in self.input_method.popups() {
popup.update_visible(); popup.update_visible();
} }
} }

View file

@ -2,7 +2,10 @@ use {
crate::{ crate::{
backend::KeyState, backend::KeyState,
client::{Client, ClientError}, 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}, keyboard::{KeyboardState, KeyboardStateId},
leaks::Tracker, leaks::Tracker,
object::{Object, Version}, object::{Object, Version},
@ -49,7 +52,7 @@ impl ZwpInputMethodKeyboardGrabV2 {
self.kb_state_id.set(kb_state.id); 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(); let serial = self.client.next_serial();
if self.kb_state_id.get() != kb_state.id { if self.kb_state_id.get() != kb_state.id {
self.update_state(serial, kb_state); 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(); let serial = self.client.next_serial();
if self.kb_state_id.get() != kb_state.id { if self.kb_state_id.get() != kb_state.id {
self.update_state(serial, kb_state); 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 { impl ZwpInputMethodKeyboardGrabV2RequestHandler for ZwpInputMethodKeyboardGrabV2 {
type Error = ZwpInputMethodKeyboardGrabV2Error; type Error = ZwpInputMethodKeyboardGrabV2Error;

View file

@ -5,7 +5,7 @@ use {
wl_seat::{ wl_seat::{
WlSeatGlobal, WlSeatGlobal,
text_input::{ text_input::{
MAX_TEXT_SIZE, TextDisconnectReason, TextInputConnection, InputMethod, MAX_TEXT_SIZE, TextDisconnectReason, TextInputConnection,
zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2, zwp_input_method_keyboard_grab_v2::ZwpInputMethodKeyboardGrabV2,
}, },
}, },
@ -104,6 +104,40 @@ impl ZwpInputMethodV2 {
} }
} }
impl InputMethod for ZwpInputMethodV2 {
fn set_connection(&self, con: Option<&Rc<TextInputConnection>>) {
self.connection.set(con.cloned());
}
fn popups(&self) -> &SmallMap<ZwpInputPopupSurfaceV2Id, Rc<ZwpInputPopupSurfaceV2>, 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<Self>, _seat: &WlSeatGlobal) {
(*self).send_done();
}
}
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 { impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {
type Error = ZwpInputMethodV2Error; type Error = ZwpInputMethodV2Error;

View file

@ -5,8 +5,8 @@ use {
wl_seat::{ wl_seat::{
WlSeatGlobal, WlSeatGlobal,
text_input::{ text_input::{
MAX_TEXT_SIZE, TextConnectReason, TextDisconnectReason, TextInputConnection, InputMethod, MAX_TEXT_SIZE, TextConnectReason, TextDisconnectReason,
zwp_input_method_v2::ZwpInputMethodV2, TextInputConnection,
}, },
}, },
wl_surface::WlSurface, 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 state = &*self.state.borrow();
{ {
let (a, b, c) = &state.surrounding_text; 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) { pub fn send_enter(&self, surface: &WlSurface) {
@ -255,7 +255,7 @@ impl ZwpTextInputV3RequestHandler for ZwpTextInputV3 {
if state.cursor_rectangle != val if state.cursor_rectangle != val
&& let Some(con) = &con && let Some(con) = &con
{ {
for (_, popup) in &con.input_method.popups { for (_, popup) in con.input_method.popups() {
popup.schedule_positioning(); popup.schedule_positioning();
} }
} }
@ -264,26 +264,26 @@ impl ZwpTextInputV3RequestHandler for ZwpTextInputV3 {
if let Some(val) = pending.content_type { if let Some(val) = pending.content_type {
if let Some(con) = &con { if let Some(con) = &con {
sent_any = true; 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; state.content_type = val;
} }
if let Some(val) = pending.text_change_cause { if let Some(val) = pending.text_change_cause {
if let Some(con) = &con { if let Some(con) = &con {
sent_any = true; sent_any = true;
con.input_method.send_text_change_cause(val); con.input_method.text_change_cause(val);
} }
state.text_change_cause = val; state.text_change_cause = val;
} }
if let Some(val) = pending.surrounding_text { if let Some(val) = pending.surrounding_text {
if let Some(con) = &con { if let Some(con) = &con {
sent_any = true; 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; state.surrounding_text = val;
} }
if sent_any && let Some(con) = &con { if sent_any && let Some(con) = &con {
con.input_method.send_done(); con.input_method.clone().done(&self.seat);
} }
Ok(()) Ok(())
} }

View file

@ -784,7 +784,7 @@ impl WlSurface {
} }
} }
for (_, con) in &self.text_input_connections { for (_, con) in &self.text_input_connections {
for (_, popup) in &con.input_method.popups { for (_, popup) in con.input_method.popups() {
popup.schedule_positioning(); popup.schedule_positioning();
} }
} }