1
0
Fork 0
forked from wry/wry

simple-im: add support for unicode input

This commit is contained in:
Julian Orth 2025-10-16 15:46:49 +02:00
parent 9ac4fea594
commit 481e9b3854
13 changed files with 158 additions and 1 deletions

View file

@ -1040,6 +1040,10 @@ impl ConfigClient {
self.send(&ClientMessage::SeatReloadSimpleIm { seat }); self.send(&ClientMessage::SeatReloadSimpleIm { seat });
} }
pub fn seat_enable_unicode_input(&self, seat: Seat) {
self.send(&ClientMessage::SeatEnableUnicodeInput { seat });
}
pub fn set_show_float_pin_icon(&self, show: bool) { pub fn set_show_float_pin_icon(&self, show: bool) {
self.send(&ClientMessage::SetShowFloatPinIcon { show }); self.send(&ClientMessage::SetShowFloatPinIcon { show });
} }

View file

@ -798,6 +798,9 @@ pub enum ClientMessage<'a> {
SeatReloadSimpleIm { SeatReloadSimpleIm {
seat: Seat, seat: Seat,
}, },
SeatEnableUnicodeInput {
seat: Seat,
},
} }
#[derive(Serialize, Deserialize, Debug)] #[derive(Serialize, Deserialize, Debug)]

View file

@ -631,6 +631,13 @@ impl Seat {
pub fn reload_simple_im(self) { pub fn reload_simple_im(self) {
get!().seat_reload_simple_im(self); get!().seat_reload_simple_im(self);
} }
/// Enables Unicode input in the simple, XCompose based input method.
///
/// This has no effect if the simple IM is not currently active.
pub fn enable_unicode_input(self) {
get!().seat_enable_unicode_input(self);
}
} }
/// A focus-follows-mouse mode. /// A focus-follows-mouse mode.

View file

@ -2301,6 +2301,12 @@ impl ConfigProxyHandler {
Ok(()) Ok(())
} }
fn handle_seat_enable_unicode_input(&self, seat: Seat) -> Result<(), CphError> {
let seat = self.get_seat(seat)?;
seat.enable_unicode_input();
Ok(())
}
fn spaces_change(&self) { fn spaces_change(&self) {
struct V; struct V;
impl NodeVisitorBase for V { impl NodeVisitorBase for V {
@ -3245,6 +3251,9 @@ impl ConfigProxyHandler {
ClientMessage::SeatReloadSimpleIm { seat } => self ClientMessage::SeatReloadSimpleIm { seat } => self
.handle_seat_reload_simple_im(seat) .handle_seat_reload_simple_im(seat)
.wrn("seat_reload_simple_im")?, .wrn("seat_reload_simple_im")?,
ClientMessage::SeatEnableUnicodeInput { seat } => self
.handle_seat_enable_unicode_input(seat)
.wrn("seat_enable_unicode_input")?,
} }
Ok(()) Ok(())
} }

View file

@ -42,6 +42,7 @@ pub trait InputMethod {
fn done(self: Rc<Self>, seat: &WlSeatGlobal); fn done(self: Rc<Self>, seat: &WlSeatGlobal);
fn is_simple(&self) -> bool; fn is_simple(&self) -> bool;
fn cancel_simple(&self, seat: &WlSeatGlobal); fn cancel_simple(&self, seat: &WlSeatGlobal);
fn enable_unicode_input(&self);
} }
pub trait InputMethodKeyboardGrab { pub trait InputMethodKeyboardGrab {
@ -64,6 +65,12 @@ pub enum TextDisconnectReason {
} }
impl WlSeatGlobal { impl WlSeatGlobal {
pub fn enable_unicode_input(&self) {
if let Some(im) = self.input_method.get() {
im.enable_unicode_input();
}
}
pub fn set_simple_im_enabled(self: &Rc<Self>, enabled: bool) { pub fn set_simple_im_enabled(self: &Rc<Self>, enabled: bool) {
if self.simple_im_enabled.replace(enabled) == enabled { if self.simple_im_enabled.replace(enabled) == enabled {
return; return;

View file

@ -24,6 +24,7 @@ use {
}, },
std::{ std::{
cell::{Cell, RefCell}, cell::{Cell, RefCell},
fmt::Write,
rc::Rc, rc::Rc,
}, },
}; };
@ -36,6 +37,8 @@ pub struct SimpleIm {
table: compose::ComposeTable, table: compose::ComposeTable,
initial_state: compose::State, initial_state: compose::State,
states: RefCell<Vec<State>>, states: RefCell<Vec<State>>,
unicode_input: RefCell<UnicodeInput>,
unicode_input_enabled: Cell<bool>,
} }
struct State { struct State {
@ -43,6 +46,14 @@ struct State {
char: char, char: char,
} }
#[derive(Default)]
struct UnicodeInput {
text: String,
cp: u32,
cursor: i32,
chars: usize,
}
impl SimpleIm { impl SimpleIm {
pub fn new(ctx: &xkb::Context) -> Option<Rc<Self>> { pub fn new(ctx: &xkb::Context) -> Option<Rc<Self>> {
let table = ctx.compose_table_builder().build(WriteToLog)?; let table = ctx.compose_table_builder().build(WriteToLog)?;
@ -54,6 +65,8 @@ impl SimpleIm {
states: Default::default(), states: Default::default(),
initial_state: table.create_state(), initial_state: table.create_state(),
table, table,
unicode_input: Default::default(),
unicode_input_enabled: Default::default(),
})) }))
} }
} }
@ -94,6 +107,7 @@ impl InputMethod for SimpleIm {
self.active.set(active); self.active.set(active);
if active { if active {
self.states.borrow_mut().clear(); self.states.borrow_mut().clear();
self.unicode_input_enabled.set(false);
seat.input_method_grab.set(Some(self)); seat.input_method_grab.set(Some(self));
} else { } else {
seat.input_method_grab.take(); seat.input_method_grab.take();
@ -110,6 +124,46 @@ impl InputMethod for SimpleIm {
con.disconnect(TextDisconnectReason::InputMethodDestroyed); con.disconnect(TextDisconnectReason::InputMethodDestroyed);
} }
} }
fn enable_unicode_input(&self) {
if !self.active.get() {
return;
}
let Some(con) = self.con.get() else {
return;
};
if self.unicode_input_enabled.replace(true) {
return;
}
self.states.borrow_mut().clear();
let ui = &mut *self.unicode_input.borrow_mut();
ui.cp = 0;
ui.chars = 0;
ui.flush_preedit(&con);
}
}
impl UnicodeInput {
fn update_text(&mut self) {
self.text.clear();
if self.chars == 0 {
let _ = write!(self.text, "U+");
self.cursor = self.text.len() as _;
return;
}
let _ = write!(self.text, "U+{:x}", self.cp);
self.cursor = self.text.len() as _;
if let Some(char) = char::from_u32(self.cp) {
let _ = write!(self.text, " = {}", char);
}
}
fn flush_preedit(&mut self, con: &TextInputConnection) {
self.update_text();
con.text_input
.send_preedit_string(Some(&self.text), self.cursor, self.cursor);
con.text_input.send_done();
}
} }
impl InputMethodKeyboardGrab for SimpleIm { impl InputMethodKeyboardGrab for SimpleIm {
@ -122,7 +176,11 @@ impl InputMethodKeyboardGrab for SimpleIm {
}; };
let mut buf = [0; 4]; let mut buf = [0; 4];
let mut forward_to_node = true; let mut forward_to_node = true;
if self.unicode_input_enabled.get() {
forward_to_node = false;
}
let states = &mut *self.states.borrow_mut(); let states = &mut *self.states.borrow_mut();
let ui = &mut self.unicode_input.borrow_mut();
let lookup = kb_state.map.lookup_table.lookup( let lookup = kb_state.map.lookup_table.lookup(
kb_state.mods.group, kb_state.mods.group,
kb_state.mods.mods, kb_state.mods.mods,
@ -132,6 +190,53 @@ impl InputMethodKeyboardGrab for SimpleIm {
let is_control = mods.contains(ModifierMask::CONTROL); let is_control = mods.contains(ModifierMask::CONTROL);
for sym in lookup { for sym in lookup {
let sym = sym.keysym(); let sym = sym.keysym();
if self.unicode_input_enabled.get() {
let is_terminator = matches!(
sym,
syms::Return | syms::KP_Enter | syms::space | syms::KP_Space
);
if (is_terminator || (sym == syms::j && is_control))
&& ui.chars > 0
&& let Some(char) = char::from_u32(ui.cp)
{
self.unicode_input_enabled.set(false);
let s = char.encode_utf8(&mut buf);
con.text_input.send_preedit_string(None, 0, 0);
con.text_input.send_commit_string(Some(s));
con.text_input.send_done();
} else if sym == syms::Escape
|| (sym == syms::c && is_control)
|| (ui.chars == 0 && matches!(sym, syms::w | syms::d) && is_control)
{
self.unicode_input_enabled.set(false);
con.text_input.send_preedit_string(None, 0, 0);
con.text_input.send_done();
} else if sym == syms::BackSpace && ui.chars > 0 {
ui.chars -= 1;
ui.cp >>= 4;
ui.flush_preedit(&con);
} else if sym == syms::w && is_control {
ui.chars = 0;
ui.cp = 0;
ui.flush_preedit(&con);
} else if let Some(c) = sym.char()
&& ui.chars < 6
&& !is_control
{
let c = match c {
'0'..='9' => c as u32 - '0' as u32,
'a'..='f' => c as u32 - 'a' as u32 + 10,
'A'..='F' => c as u32 - 'A' as u32 + 10,
_ => continue,
};
ui.cp = (ui.cp << 4) | c;
if ui.cp != 0 {
ui.chars += 1;
ui.flush_preedit(&con);
}
}
continue;
}
let mut new_state = states let mut new_state = states
.last() .last()
.map(|s| s.state.clone()) .map(|s| s.state.clone())

View file

@ -144,6 +144,10 @@ impl InputMethod for ZwpInputMethodV2 {
fn cancel_simple(&self, _seat: &WlSeatGlobal) { fn cancel_simple(&self, _seat: &WlSeatGlobal) {
unreachable!(); unreachable!();
} }
fn enable_unicode_input(&self) {
// nothing
}
} }
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 { impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {

View file

@ -88,6 +88,7 @@ pub enum SimpleCommand {
EnableSimpleIm(bool), EnableSimpleIm(bool),
ToggleSimpleImEnabled, ToggleSimpleImEnabled,
ReloadSimpleIm, ReloadSimpleIm,
EnableUnicodeInput,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View file

@ -159,6 +159,7 @@ impl ActionParser<'_> {
"disable-simple-im" => EnableSimpleIm(false), "disable-simple-im" => EnableSimpleIm(false),
"toggle-simple-im-enabled" => ToggleSimpleImEnabled, "toggle-simple-im-enabled" => ToggleSimpleImEnabled,
"reload-simple-im" => ReloadSimpleIm, "reload-simple-im" => ReloadSimpleIm,
"enable-unicode-input" => EnableUnicodeInput,
_ => { _ => {
return Err( return Err(
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span) ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)

View file

@ -236,6 +236,10 @@ impl Action {
let persistent = state.persistent.clone(); let persistent = state.persistent.clone();
b.new(move || persistent.seat.reload_simple_im()) b.new(move || persistent.seat.reload_simple_im())
} }
SimpleCommand::EnableUnicodeInput => {
let persistent = state.persistent.clone();
b.new(move || persistent.seat.enable_unicode_input())
}
}, },
Action::Multi { actions } => { Action::Multi { actions } => {
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();

View file

@ -1843,7 +1843,8 @@
"enable-simple-im", "enable-simple-im",
"disable-simple-im", "disable-simple-im",
"toggle-simple-im-enabled", "toggle-simple-im-enabled",
"reload-simple-im" "reload-simple-im",
"enable-unicode-input"
] ]
}, },
"SimpleIm": { "SimpleIm": {

View file

@ -4216,6 +4216,12 @@ The string should have one of the following values:
This is useful if you change the XCompose files after starting the compositor. This is useful if you change the XCompose files after starting the compositor.
- `enable-unicode-input`:
Enables Unicode input in the simple, XCompose based input method.
This has no effect if the simple IM is not currently active.
<a name="types-SimpleIm"></a> <a name="types-SimpleIm"></a>

View file

@ -1050,6 +1050,11 @@ SimpleActionName:
Reloads the simple, XCompose based input method. Reloads the simple, XCompose based input method.
This is useful if you change the XCompose files after starting the compositor. This is useful if you change the XCompose files after starting the compositor.
- value: enable-unicode-input
description: |
Enables Unicode input in the simple, XCompose based input method.
This has no effect if the simple IM is not currently active.
Color: Color: