seat: add a simple input method
This commit is contained in:
parent
8372f83737
commit
58b9830aaa
6 changed files with 252 additions and 9 deletions
|
|
@ -61,7 +61,7 @@ linearize = { version = "0.1.3", features = ["derive"] }
|
||||||
png = "0.18.0"
|
png = "0.18.0"
|
||||||
rustc-demangle = { version = "0.1.24", optional = true }
|
rustc-demangle = { version = "0.1.24", optional = true }
|
||||||
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
tracy-client-sys = { version = "0.24.1", features = ["ondemand", "manual-lifetime", "debuginfod", "demangle"], optional = true }
|
||||||
kbvm = "0.1.5"
|
kbvm = { version = "0.1.5", features = ["compose"] }
|
||||||
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
|
tiny-skia = { version = "0.11.4", default-features = false, features = ["std"] }
|
||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
cfg-if = "1.0.0"
|
cfg-if = "1.0.0"
|
||||||
|
|
|
||||||
|
|
@ -52,7 +52,8 @@ use {
|
||||||
pointer_owner::PointerOwnerHolder,
|
pointer_owner::PointerOwnerHolder,
|
||||||
tablet::TabletSeatData,
|
tablet::TabletSeatData,
|
||||||
text_input::{
|
text_input::{
|
||||||
InputMethod, InputMethodKeyboardGrab, zwp_text_input_v3::ZwpTextInputV3,
|
InputMethod, InputMethodKeyboardGrab, simple_im::SimpleIm,
|
||||||
|
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},
|
||||||
|
|
@ -237,6 +238,7 @@ pub struct WlSeatGlobal {
|
||||||
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
|
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
|
||||||
modifiers_listener: EventListener<dyn LedsListener>,
|
modifiers_listener: EventListener<dyn LedsListener>,
|
||||||
modifiers_forward: EventSource<dyn LedsListener>,
|
modifiers_forward: EventSource<dyn LedsListener>,
|
||||||
|
simple_im: CloneCell<Option<Rc<SimpleIm>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Copy, Clone)]
|
#[derive(Copy, Clone)]
|
||||||
|
|
@ -258,6 +260,7 @@ impl WlSeatGlobal {
|
||||||
let cursor_user_group = CursorUserGroup::create(state);
|
let cursor_user_group = CursorUserGroup::create(state);
|
||||||
let cursor_user = cursor_user_group.create_user();
|
let cursor_user = cursor_user_group.create_user();
|
||||||
cursor_user.activate();
|
cursor_user.activate();
|
||||||
|
let simple_im = SimpleIm::new(&state.kb_ctx.ctx);
|
||||||
let slf = Rc::new_cyclic(|slf: &Weak<WlSeatGlobal>| Self {
|
let slf = Rc::new_cyclic(|slf: &Weak<WlSeatGlobal>| Self {
|
||||||
id: state.seat_ids.next(),
|
id: state.seat_ids.next(),
|
||||||
name,
|
name,
|
||||||
|
|
@ -305,7 +308,7 @@ impl WlSeatGlobal {
|
||||||
data_control_devices: Default::default(),
|
data_control_devices: Default::default(),
|
||||||
text_inputs: Default::default(),
|
text_inputs: Default::default(),
|
||||||
text_input: Default::default(),
|
text_input: Default::default(),
|
||||||
input_method: Default::default(),
|
input_method: CloneCell::new(simple_im.clone().map(|im| im as _)),
|
||||||
input_method_grab: Default::default(),
|
input_method_grab: Default::default(),
|
||||||
forward: Cell::new(false),
|
forward: Cell::new(false),
|
||||||
focus_follows_mouse: Cell::new(true),
|
focus_follows_mouse: Cell::new(true),
|
||||||
|
|
@ -326,6 +329,7 @@ impl WlSeatGlobal {
|
||||||
marks: Default::default(),
|
marks: Default::default(),
|
||||||
modifiers_listener: EventListener::new(slf.clone()),
|
modifiers_listener: EventListener::new(slf.clone()),
|
||||||
modifiers_forward: Default::default(),
|
modifiers_forward: Default::default(),
|
||||||
|
simple_im: CloneCell::new(simple_im),
|
||||||
});
|
});
|
||||||
slf.pointer_cursor.set_owner(slf.clone());
|
slf.pointer_cursor.set_owner(slf.clone());
|
||||||
slf.modifiers_listener
|
slf.modifiers_listener
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ use {
|
||||||
std::rc::Rc,
|
std::rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub mod simple_im;
|
||||||
pub mod zwp_input_method_keyboard_grab_v2;
|
pub mod zwp_input_method_keyboard_grab_v2;
|
||||||
pub mod zwp_input_method_manager_v2;
|
pub mod zwp_input_method_manager_v2;
|
||||||
pub mod zwp_input_method_v2;
|
pub mod zwp_input_method_v2;
|
||||||
|
|
@ -36,6 +37,8 @@ pub trait InputMethod {
|
||||||
fn text_change_cause(&self, cause: u32);
|
fn text_change_cause(&self, cause: u32);
|
||||||
fn surrounding_text(&self, text: &str, cursor: u32, anchor: u32);
|
fn surrounding_text(&self, text: &str, cursor: u32, anchor: u32);
|
||||||
fn done(self: Rc<Self>, seat: &WlSeatGlobal);
|
fn done(self: Rc<Self>, seat: &WlSeatGlobal);
|
||||||
|
fn is_simple(&self) -> bool;
|
||||||
|
fn cancel_simple(&self, seat: &WlSeatGlobal);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait InputMethodKeyboardGrab {
|
pub trait InputMethodKeyboardGrab {
|
||||||
|
|
@ -58,6 +61,32 @@ pub enum TextDisconnectReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WlSeatGlobal {
|
impl WlSeatGlobal {
|
||||||
|
fn can_set_new_im(&self) -> bool {
|
||||||
|
match self.input_method.get() {
|
||||||
|
None => true,
|
||||||
|
Some(im) => im.is_simple(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cannot_set_new_im(&self) -> bool {
|
||||||
|
!self.can_set_new_im()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_input_method(self: &Rc<Self>, im: Rc<dyn InputMethod>) {
|
||||||
|
if let Some(old) = self.input_method.take() {
|
||||||
|
old.cancel_simple(self);
|
||||||
|
}
|
||||||
|
self.input_method.set(Some(im));
|
||||||
|
self.create_text_input_connection(TextConnectReason::InputMethodCreated);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_input_method(self: &Rc<Self>) {
|
||||||
|
self.input_method.take();
|
||||||
|
if let Some(im) = self.simple_im.get() {
|
||||||
|
self.set_input_method(im);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn create_text_input_connection(self: &Rc<Self>, text_connect_reason: TextConnectReason) {
|
fn create_text_input_connection(self: &Rc<Self>, text_connect_reason: TextConnectReason) {
|
||||||
let Some(im) = self.input_method.get() else {
|
let Some(im) = self.input_method.get() else {
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
204
src/ifs/wl_seat/text_input/simple_im.rs
Normal file
204
src/ifs/wl_seat/text_input/simple_im.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
use {
|
||||||
|
crate::{
|
||||||
|
backend::KeyState,
|
||||||
|
ifs::{
|
||||||
|
wl_seat::{
|
||||||
|
WlSeatGlobal,
|
||||||
|
text_input::{
|
||||||
|
InputMethod, InputMethodKeyboardGrab, TextDisconnectReason, TextInputConnection,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wl_surface::zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2,
|
||||||
|
},
|
||||||
|
keyboard::KeyboardState,
|
||||||
|
utils::{clonecell::CloneCell, smallmap::SmallMap},
|
||||||
|
wire::ZwpInputPopupSurfaceV2Id,
|
||||||
|
},
|
||||||
|
kbvm::{
|
||||||
|
Keycode, ModifierMask, syms,
|
||||||
|
xkb::{
|
||||||
|
self,
|
||||||
|
compose::{self, FeedResult},
|
||||||
|
diagnostic::WriteToLog,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
std::{
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
rc::Rc,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
pub struct SimpleIm {
|
||||||
|
con: CloneCell<Option<Rc<TextInputConnection>>>,
|
||||||
|
popups: SmallMap<ZwpInputPopupSurfaceV2Id, Rc<ZwpInputPopupSurfaceV2>, 1>,
|
||||||
|
active: Cell<bool>,
|
||||||
|
activate: Cell<Option<bool>>,
|
||||||
|
table: compose::ComposeTable,
|
||||||
|
initial_state: compose::State,
|
||||||
|
states: RefCell<Vec<State>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct State {
|
||||||
|
state: compose::State,
|
||||||
|
char: char,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SimpleIm {
|
||||||
|
pub fn new(ctx: &xkb::Context) -> Option<Rc<Self>> {
|
||||||
|
let table = ctx.compose_table_builder().build(WriteToLog)?;
|
||||||
|
Some(Rc::new(Self {
|
||||||
|
con: Default::default(),
|
||||||
|
popups: Default::default(),
|
||||||
|
active: Default::default(),
|
||||||
|
activate: Default::default(),
|
||||||
|
states: Default::default(),
|
||||||
|
initial_state: table.create_state(),
|
||||||
|
table,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMethod for SimpleIm {
|
||||||
|
fn set_connection(&self, con: Option<&Rc<TextInputConnection>>) {
|
||||||
|
self.con.set(con.cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn popups(&self) -> &SmallMap<ZwpInputPopupSurfaceV2Id, Rc<ZwpInputPopupSurfaceV2>, 1> {
|
||||||
|
&self.popups
|
||||||
|
}
|
||||||
|
|
||||||
|
fn activate(&self) {
|
||||||
|
self.activate.set(Some(true));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn deactivate(&self) {
|
||||||
|
self.activate.set(Some(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn content_type(&self, _hint: u32, _purpose: u32) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn text_change_cause(&self, _cause: u32) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn surrounding_text(&self, _text: &str, _cursor: u32, _anchor: u32) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
|
||||||
|
fn done(self: Rc<Self>, seat: &WlSeatGlobal) {
|
||||||
|
let Some(active) = self.activate.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
self.active.set(active);
|
||||||
|
if active {
|
||||||
|
self.states.borrow_mut().clear();
|
||||||
|
seat.input_method_grab.set(Some(self));
|
||||||
|
} else {
|
||||||
|
seat.input_method_grab.take();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_simple(&self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_simple(&self, seat: &WlSeatGlobal) {
|
||||||
|
seat.input_method_grab.take();
|
||||||
|
if let Some(con) = self.con.get() {
|
||||||
|
con.disconnect(TextDisconnectReason::InputMethodDestroyed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InputMethodKeyboardGrab for SimpleIm {
|
||||||
|
fn on_key(&self, _time_usec: u64, key: u32, state: KeyState, kb_state: &KeyboardState) -> bool {
|
||||||
|
if state != KeyState::Pressed {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
let Some(con) = self.con.get() else {
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
let mut buf = [0; 4];
|
||||||
|
let mut forward_to_node = true;
|
||||||
|
let states = &mut *self.states.borrow_mut();
|
||||||
|
let lookup = kb_state.map.lookup_table.lookup(
|
||||||
|
kb_state.mods.group,
|
||||||
|
kb_state.mods.mods,
|
||||||
|
Keycode::from_evdev(key),
|
||||||
|
);
|
||||||
|
let mods = lookup.remaining_mods();
|
||||||
|
let is_control = mods.contains(ModifierMask::CONTROL);
|
||||||
|
for sym in lookup {
|
||||||
|
let sym = sym.keysym();
|
||||||
|
let mut new_state = states
|
||||||
|
.last()
|
||||||
|
.map(|s| s.state.clone())
|
||||||
|
.unwrap_or_else(|| self.initial_state.clone());
|
||||||
|
let Some(fr) = self.table.feed(&mut new_state, sym) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
forward_to_node = false;
|
||||||
|
let mut send_preedit = |char: char| {
|
||||||
|
let s = char.encode_utf8(&mut buf);
|
||||||
|
let len = s.len() as i32;
|
||||||
|
con.text_input.send_preedit_string(Some(s), len, len);
|
||||||
|
};
|
||||||
|
match fr {
|
||||||
|
FeedResult::Pending => {
|
||||||
|
let char = sym.char().unwrap_or('·');
|
||||||
|
states.push(State {
|
||||||
|
state: new_state,
|
||||||
|
char,
|
||||||
|
});
|
||||||
|
send_preedit(char);
|
||||||
|
con.text_input.send_done();
|
||||||
|
}
|
||||||
|
FeedResult::Aborted
|
||||||
|
if sym == syms::Escape || (matches!(sym, syms::c | syms::w) && is_control) =>
|
||||||
|
{
|
||||||
|
states.clear();
|
||||||
|
con.text_input.send_preedit_string(None, 0, 0);
|
||||||
|
con.text_input.send_done();
|
||||||
|
}
|
||||||
|
FeedResult::Aborted if sym == syms::BackSpace => {
|
||||||
|
states.pop();
|
||||||
|
if let Some(state) = states.last() {
|
||||||
|
send_preedit(state.char);
|
||||||
|
} else {
|
||||||
|
con.text_input.send_preedit_string(None, 0, 0);
|
||||||
|
}
|
||||||
|
con.text_input.send_done();
|
||||||
|
}
|
||||||
|
FeedResult::Aborted => {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
FeedResult::Composed { string, keysym } => {
|
||||||
|
states.clear();
|
||||||
|
let s = if string.is_some() {
|
||||||
|
string
|
||||||
|
} else if let Some(sym) = keysym
|
||||||
|
&& let Some(char) = sym.char()
|
||||||
|
{
|
||||||
|
Some(char.encode_utf8(&mut buf) as &str)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
con.text_input.send_preedit_string(None, 0, 0);
|
||||||
|
con.text_input.send_commit_string(s);
|
||||||
|
con.text_input.send_done();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
forward_to_node
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_modifiers(&self, _kb_state: &KeyboardState) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_repeat_info(&self) {
|
||||||
|
// nothing
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ use {
|
||||||
crate::{
|
crate::{
|
||||||
client::{CAP_INPUT_METHOD, Client, ClientCaps, ClientError},
|
client::{CAP_INPUT_METHOD, Client, ClientCaps, ClientError},
|
||||||
globals::{Global, GlobalName},
|
globals::{Global, GlobalName},
|
||||||
ifs::wl_seat::text_input::{TextConnectReason, zwp_input_method_v2::ZwpInputMethodV2},
|
ifs::wl_seat::text_input::zwp_input_method_v2::ZwpInputMethodV2,
|
||||||
leaks::Tracker,
|
leaks::Tracker,
|
||||||
object::{Object, Version},
|
object::{Object, Version},
|
||||||
wire::{ZwpInputMethodManagerV2Id, zwp_input_method_manager_v2::*},
|
wire::{ZwpInputMethodManagerV2Id, zwp_input_method_manager_v2::*},
|
||||||
|
|
@ -72,7 +72,7 @@ impl ZwpInputMethodManagerV2RequestHandler for ZwpInputMethodManagerV2 {
|
||||||
|
|
||||||
fn get_input_method(&self, req: GetInputMethod, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
fn get_input_method(&self, req: GetInputMethod, _slf: &Rc<Self>) -> Result<(), Self::Error> {
|
||||||
let seat = self.client.lookup(req.seat)?;
|
let seat = self.client.lookup(req.seat)?;
|
||||||
let inert = seat.global.input_method.is_some();
|
let inert = seat.global.cannot_set_new_im();
|
||||||
let im = Rc::new(ZwpInputMethodV2 {
|
let im = Rc::new(ZwpInputMethodV2 {
|
||||||
id: req.input_method,
|
id: req.input_method,
|
||||||
client: self.client.clone(),
|
client: self.client.clone(),
|
||||||
|
|
@ -90,9 +90,7 @@ impl ZwpInputMethodManagerV2RequestHandler for ZwpInputMethodManagerV2 {
|
||||||
if inert {
|
if inert {
|
||||||
im.send_unavailable();
|
im.send_unavailable();
|
||||||
} else {
|
} else {
|
||||||
seat.global.input_method.set(Some(im));
|
seat.global.set_input_method(im);
|
||||||
seat.global
|
|
||||||
.create_text_input_connection(TextConnectReason::InputMethodCreated);
|
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@ impl ZwpInputMethodV2 {
|
||||||
}
|
}
|
||||||
self.popups.clear();
|
self.popups.clear();
|
||||||
if !self.inert {
|
if !self.inert {
|
||||||
self.seat.input_method.take();
|
self.seat.remove_input_method();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,6 +136,14 @@ impl InputMethod for ZwpInputMethodV2 {
|
||||||
fn done(self: Rc<Self>, _seat: &WlSeatGlobal) {
|
fn done(self: Rc<Self>, _seat: &WlSeatGlobal) {
|
||||||
(*self).send_done();
|
(*self).send_done();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_simple(&self) -> bool {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn cancel_simple(&self, _seat: &WlSeatGlobal) {
|
||||||
|
unreachable!();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {
|
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue