Merge pull request #646 from mahkoh/jorth/simple-im
Add a simple, XCompose based input method
This commit is contained in:
commit
091918d10a
29 changed files with 977 additions and 59 deletions
|
|
@ -61,7 +61,7 @@ linearize = { version = "0.1.3", features = ["derive"] }
|
|||
png = "0.18.0"
|
||||
rustc-demangle = { version = "0.1.24", 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"] }
|
||||
regex = "1.11.1"
|
||||
cfg-if = "1.0.0"
|
||||
|
|
|
|||
|
|
@ -1026,6 +1026,24 @@ impl ConfigClient {
|
|||
self.send(&ClientMessage::SeatCopyMark { seat, src, dst });
|
||||
}
|
||||
|
||||
pub fn seat_set_simple_im_enabled(&self, seat: Seat, enabled: bool) {
|
||||
self.send(&ClientMessage::SeatSetSimpleImEnabled { seat, enabled });
|
||||
}
|
||||
|
||||
pub fn seat_get_simple_im_enabled(&self, seat: Seat) -> bool {
|
||||
let res = self.send_with_response(&ClientMessage::SeatGetSimpleImEnabled { seat });
|
||||
get_response!(res, false, SeatGetSimpleImEnabled { enabled });
|
||||
enabled
|
||||
}
|
||||
|
||||
pub fn seat_reload_simple_im(&self, seat: 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) {
|
||||
self.send(&ClientMessage::SetShowFloatPinIcon { show });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -788,6 +788,19 @@ pub enum ClientMessage<'a> {
|
|||
workspace: Workspace,
|
||||
connector: Connector,
|
||||
},
|
||||
SeatSetSimpleImEnabled {
|
||||
seat: Seat,
|
||||
enabled: bool,
|
||||
},
|
||||
SeatGetSimpleImEnabled {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatReloadSimpleIm {
|
||||
seat: Seat,
|
||||
},
|
||||
SeatEnableUnicodeInput {
|
||||
seat: Seat,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
@ -1020,6 +1033,9 @@ pub enum Response {
|
|||
GetShowBar {
|
||||
show: bool,
|
||||
},
|
||||
SeatGetSimpleImEnabled {
|
||||
enabled: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
|
|
|
|||
|
|
@ -603,6 +603,41 @@ impl Seat {
|
|||
pub fn copy_mark(self, src: u32, dst: u32) {
|
||||
get!().seat_copy_mark(self, src, dst);
|
||||
}
|
||||
|
||||
/// Sets whether the simple, XCompose based input method is enabled.
|
||||
///
|
||||
/// Regardless of this setting, this input method is not used if an external input
|
||||
/// method is running.
|
||||
///
|
||||
/// The default is `true`.
|
||||
pub fn set_simple_im_enabled(self, enabled: bool) {
|
||||
get!().seat_set_simple_im_enabled(self, enabled);
|
||||
}
|
||||
|
||||
/// Returns whether the simple, XCompose based input method is enabled.
|
||||
pub fn simple_im_enabled(self) -> bool {
|
||||
get!(true).seat_get_simple_im_enabled(self)
|
||||
}
|
||||
|
||||
/// Toggles whether the simple, XCompose based input method is enabled.
|
||||
pub fn toggle_simple_im_enabled(self) {
|
||||
let get = get!();
|
||||
get.seat_set_simple_im_enabled(self, !get.seat_get_simple_im_enabled(self));
|
||||
}
|
||||
|
||||
/// Reloads the simple, XCompose based input method.
|
||||
///
|
||||
/// This is useful if you change the XCompose files after starting the compositor.
|
||||
pub fn 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.
|
||||
|
|
|
|||
|
|
@ -83,6 +83,28 @@ pub enum SeatCommand {
|
|||
UseHardwareCursor(UseHardwareCursorArgs),
|
||||
/// Set the size of the cursor.
|
||||
SetCursorSize(SetCursorSizeArgs),
|
||||
/// Configure the simple, XCompose based input method.
|
||||
SimpleIm(SimpleImArgs),
|
||||
}
|
||||
|
||||
#[derive(Args, Debug, Clone)]
|
||||
pub struct SimpleImArgs {
|
||||
#[clap(subcommand)]
|
||||
pub command: SimpleImCommand,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Debug, Clone)]
|
||||
pub enum SimpleImCommand {
|
||||
/// Enable the simple IM.
|
||||
///
|
||||
/// Even if the IM is enabled, it will not be used if an external IM is running.
|
||||
Enable,
|
||||
/// Disable the simple IM.
|
||||
Disable,
|
||||
/// Reload the simple IM.
|
||||
///
|
||||
/// This is useful if you change the XCompose files after starting the compositor.
|
||||
Reload,
|
||||
}
|
||||
|
||||
impl Default for SeatCommand {
|
||||
|
|
@ -460,6 +482,27 @@ impl Input {
|
|||
size: a.size,
|
||||
});
|
||||
}
|
||||
SeatCommand::SimpleIm(a) => match a.command {
|
||||
SimpleImCommand::Enable | SimpleImCommand::Disable => {
|
||||
self.handle_error(input, |e| {
|
||||
eprintln!("Could not enable/disable the simple IM: {}", e);
|
||||
});
|
||||
tc.send(jay_input::SetSimpleImEnabled {
|
||||
self_id: input,
|
||||
seat: &args.seat,
|
||||
enabled: matches!(a.command, SimpleImCommand::Enable) as _,
|
||||
});
|
||||
}
|
||||
SimpleImCommand::Reload => {
|
||||
self.handle_error(input, |e| {
|
||||
eprintln!("Could not reload the simple IM: {}", e);
|
||||
});
|
||||
tc.send(jay_input::ReloadSimpleIm {
|
||||
self_id: input,
|
||||
seat: &args.seat,
|
||||
});
|
||||
}
|
||||
},
|
||||
}
|
||||
tc.round_trip().await;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2281,6 +2281,32 @@ impl ConfigProxyHandler {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_set_simple_im_enabled(&self, seat: Seat, enabled: bool) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.set_simple_im_enabled(enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_get_simple_im_enabled(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
self.respond(Response::SeatGetSimpleImEnabled {
|
||||
enabled: seat.simple_im_enabled(),
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn handle_seat_reload_simple_im(&self, seat: Seat) -> Result<(), CphError> {
|
||||
let seat = self.get_seat(seat)?;
|
||||
seat.reload_simple_im();
|
||||
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) {
|
||||
struct V;
|
||||
impl NodeVisitorBase for V {
|
||||
|
|
@ -3216,6 +3242,18 @@ impl ConfigProxyHandler {
|
|||
} => self
|
||||
.handle_show_workspace(seat, workspace, Some(connector))
|
||||
.wrn("show_workspace_on")?,
|
||||
ClientMessage::SeatSetSimpleImEnabled { seat, enabled } => self
|
||||
.handle_seat_set_simple_im_enabled(seat, enabled)
|
||||
.wrn("seat_set_simple_im_enabled")?,
|
||||
ClientMessage::SeatGetSimpleImEnabled { seat } => self
|
||||
.handle_seat_get_simple_im_enabled(seat)
|
||||
.wrn("seat_get_simple_im_enabled")?,
|
||||
ClientMessage::SeatReloadSimpleIm { seat } => self
|
||||
.handle_seat_reload_simple_im(seat)
|
||||
.wrn("seat_reload_simple_im")?,
|
||||
ClientMessage::SeatEnableUnicodeInput { seat } => self
|
||||
.handle_seat_enable_unicode_input(seat)
|
||||
.wrn("seat_enable_unicode_input")?,
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal {
|
|||
}
|
||||
|
||||
fn version(&self) -> u32 {
|
||||
21
|
||||
22
|
||||
}
|
||||
|
||||
fn required_caps(&self) -> ClientCaps {
|
||||
|
|
|
|||
|
|
@ -515,6 +515,30 @@ impl JayInputRequestHandler for JayInput {
|
|||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn set_simple_im_enabled(
|
||||
&self,
|
||||
req: SetSimpleImEnabled<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.or_error(|| {
|
||||
let seat = self.seat(req.seat)?;
|
||||
seat.set_simple_im_enabled(req.enabled != 0);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
fn reload_simple_im(
|
||||
&self,
|
||||
req: ReloadSimpleIm<'_>,
|
||||
_slf: &Rc<Self>,
|
||||
) -> Result<(), Self::Error> {
|
||||
self.or_error(|| {
|
||||
let seat = self.seat(req.seat)?;
|
||||
seat.reload_simple_im();
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object_base! {
|
||||
|
|
|
|||
|
|
@ -52,8 +52,8 @@ 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, simple_im::SimpleIm,
|
||||
zwp_text_input_v3::ZwpTextInputV3,
|
||||
},
|
||||
touch_owner::TouchOwnerHolder,
|
||||
wl_keyboard::{REPEAT_INFO_SINCE, WlKeyboard, WlKeyboardError},
|
||||
|
|
@ -216,8 +216,8 @@ pub struct WlSeatGlobal {
|
|||
last_input_usec: Cell<u64>,
|
||||
text_inputs: RefCell<AHashMap<ClientId, CopyHashMap<ZwpTextInputV3Id, Rc<ZwpTextInputV3>>>>,
|
||||
text_input: CloneCell<Option<Rc<ZwpTextInputV3>>>,
|
||||
input_method: CloneCell<Option<Rc<ZwpInputMethodV2>>>,
|
||||
input_method_grab: CloneCell<Option<Rc<ZwpInputMethodKeyboardGrabV2>>>,
|
||||
input_method: CloneCell<Option<Rc<dyn InputMethod>>>,
|
||||
input_method_grab: CloneCell<Option<Rc<dyn InputMethodKeyboardGrab>>>,
|
||||
forward: Cell<bool>,
|
||||
focus_follows_mouse: Cell<bool>,
|
||||
swipe_bindings: PerClientBindings<ZwpPointerGestureSwipeV1>,
|
||||
|
|
@ -238,6 +238,8 @@ pub struct WlSeatGlobal {
|
|||
marks: CopyHashMap<Keycode, Rc<dyn Node>>,
|
||||
modifiers_listener: EventListener<dyn LedsListener>,
|
||||
modifiers_forward: EventSource<dyn LedsListener>,
|
||||
simple_im: CloneCell<Option<Rc<SimpleIm>>>,
|
||||
simple_im_enabled: Cell<bool>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
|
|
@ -259,6 +261,7 @@ impl WlSeatGlobal {
|
|||
let cursor_user_group = CursorUserGroup::create(state);
|
||||
let cursor_user = cursor_user_group.create_user();
|
||||
cursor_user.activate();
|
||||
let simple_im = SimpleIm::new(&state.kb_ctx.ctx);
|
||||
let slf = Rc::new_cyclic(|slf: &Weak<WlSeatGlobal>| Self {
|
||||
id: state.seat_ids.next(),
|
||||
name,
|
||||
|
|
@ -306,7 +309,7 @@ impl WlSeatGlobal {
|
|||
data_control_devices: Default::default(),
|
||||
text_inputs: 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(),
|
||||
forward: Cell::new(false),
|
||||
focus_follows_mouse: Cell::new(true),
|
||||
|
|
@ -327,6 +330,8 @@ impl WlSeatGlobal {
|
|||
marks: Default::default(),
|
||||
modifiers_listener: EventListener::new(slf.clone()),
|
||||
modifiers_forward: Default::default(),
|
||||
simple_im: CloneCell::new(simple_im),
|
||||
simple_im_enabled: Cell::new(true),
|
||||
});
|
||||
slf.pointer_cursor.set_owner(slf.clone());
|
||||
slf.modifiers_listener
|
||||
|
|
@ -371,7 +376,7 @@ impl WlSeatGlobal {
|
|||
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()
|
||||
}
|
||||
|
||||
|
|
@ -693,7 +698,7 @@ impl WlSeatGlobal {
|
|||
}
|
||||
}
|
||||
if let Some(grab) = self.input_method_grab.get() {
|
||||
grab.send_repeat_info();
|
||||
grab.on_repeat_info();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1315,7 +1320,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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,16 +1,21 @@
|
|||
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::{simple_im::SimpleIm, 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,
|
||||
};
|
||||
|
||||
pub mod simple_im;
|
||||
pub mod zwp_input_method_keyboard_grab_v2;
|
||||
pub mod zwp_input_method_manager_v2;
|
||||
pub mod zwp_input_method_v2;
|
||||
|
|
@ -22,10 +27,30 @@ const MAX_TEXT_SIZE: usize = 4000;
|
|||
pub struct TextInputConnection {
|
||||
pub seat: Rc<WlSeatGlobal>,
|
||||
pub text_input: Rc<ZwpTextInputV3>,
|
||||
pub input_method: Rc<ZwpInputMethodV2>,
|
||||
pub input_method: Rc<dyn InputMethod>,
|
||||
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);
|
||||
fn is_simple(&self) -> bool;
|
||||
fn cancel_simple(&self, seat: &WlSeatGlobal);
|
||||
fn enable_unicode_input(&self);
|
||||
}
|
||||
|
||||
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,
|
||||
|
|
@ -40,6 +65,76 @@ pub enum TextDisconnectReason {
|
|||
}
|
||||
|
||||
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) {
|
||||
if self.simple_im_enabled.replace(enabled) == enabled {
|
||||
return;
|
||||
}
|
||||
if enabled {
|
||||
if self.input_method.is_none()
|
||||
&& let Some(im) = self.simple_im.get()
|
||||
{
|
||||
self.set_input_method(im);
|
||||
}
|
||||
} else {
|
||||
if let Some(im) = self.input_method.get()
|
||||
&& im.is_simple()
|
||||
{
|
||||
self.input_method.take();
|
||||
im.cancel_simple(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simple_im_enabled(&self) -> bool {
|
||||
self.simple_im_enabled.get()
|
||||
}
|
||||
|
||||
pub fn reload_simple_im(self: &Rc<Self>) {
|
||||
let im = SimpleIm::new(&self.state.kb_ctx.ctx);
|
||||
self.simple_im.set(im.clone());
|
||||
if self.simple_im_enabled.get() && self.can_set_new_im() {
|
||||
if let Some(im) = im {
|
||||
self.set_input_method(im);
|
||||
} else if let Some(old) = self.input_method.take() {
|
||||
old.cancel_simple(self);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 self.simple_im_enabled.get()
|
||||
&& let Some(im) = self.simple_im.get()
|
||||
{
|
||||
self.set_input_method(im);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_text_input_connection(self: &Rc<Self>, text_connect_reason: TextConnectReason) {
|
||||
let Some(im) = self.input_method.get() else {
|
||||
return;
|
||||
|
|
@ -67,7 +162,7 @@ impl WlSeatGlobal {
|
|||
|
||||
impl TextInputConnection {
|
||||
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.surface
|
||||
.text_input_connections
|
||||
|
|
@ -75,22 +170,27 @@ 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();
|
||||
}
|
||||
}
|
||||
if reason != TextDisconnectReason::TextInputDisabled {
|
||||
self.text_input.send_preedit_string(None, 0, 0);
|
||||
self.text_input.send_commit_string(None);
|
||||
self.text_input.send_done();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
309
src/ifs/wl_seat/text_input/simple_im.rs
Normal file
309
src/ifs/wl_seat/text_input/simple_im.rs
Normal file
|
|
@ -0,0 +1,309 @@
|
|||
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},
|
||||
fmt::Write,
|
||||
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>>,
|
||||
unicode_input: RefCell<UnicodeInput>,
|
||||
unicode_input_enabled: Cell<bool>,
|
||||
}
|
||||
|
||||
struct State {
|
||||
state: compose::State,
|
||||
char: char,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct UnicodeInput {
|
||||
text: String,
|
||||
cp: u32,
|
||||
cursor: i32,
|
||||
chars: usize,
|
||||
}
|
||||
|
||||
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,
|
||||
unicode_input: Default::default(),
|
||||
unicode_input_enabled: Default::default(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
self.unicode_input_enabled.set(false);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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;
|
||||
if self.unicode_input_enabled.get() {
|
||||
forward_to_node = false;
|
||||
}
|
||||
let states = &mut *self.states.borrow_mut();
|
||||
let ui = &mut self.unicode_input.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();
|
||||
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
|
||||
.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,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;
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ use {
|
|||
crate::{
|
||||
client::{CAP_INPUT_METHOD, Client, ClientCaps, ClientError},
|
||||
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,
|
||||
object::{Object, Version},
|
||||
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> {
|
||||
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 {
|
||||
id: req.input_method,
|
||||
client: self.client.clone(),
|
||||
|
|
@ -90,9 +90,7 @@ impl ZwpInputMethodManagerV2RequestHandler for ZwpInputMethodManagerV2 {
|
|||
if inert {
|
||||
im.send_unavailable();
|
||||
} else {
|
||||
seat.global.input_method.set(Some(im));
|
||||
seat.global
|
||||
.create_text_input_connection(TextConnectReason::InputMethodCreated);
|
||||
seat.global.set_input_method(im);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
},
|
||||
},
|
||||
|
|
@ -53,7 +53,7 @@ impl ZwpInputMethodV2 {
|
|||
}
|
||||
self.popups.clear();
|
||||
if !self.inert {
|
||||
self.seat.input_method.take();
|
||||
self.seat.remove_input_method();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -104,6 +104,52 @@ 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();
|
||||
}
|
||||
|
||||
fn is_simple(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
fn cancel_simple(&self, _seat: &WlSeatGlobal) {
|
||||
unreachable!();
|
||||
}
|
||||
|
||||
fn enable_unicode_input(&self) {
|
||||
// nothing
|
||||
}
|
||||
}
|
||||
|
||||
impl ZwpInputMethodV2RequestHandler for ZwpInputMethodV2 {
|
||||
type Error = ZwpInputMethodV2Error;
|
||||
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ impl ToolClient {
|
|||
self_id: s.registry,
|
||||
name: s.jay_compositor.0,
|
||||
interface: JayCompositor.name(),
|
||||
version: s.jay_compositor.1.min(21),
|
||||
version: s.jay_compositor.1.min(22),
|
||||
id: id.into(),
|
||||
});
|
||||
self.jay_compositor.set(Some(id));
|
||||
|
|
|
|||
|
|
@ -85,6 +85,10 @@ pub enum SimpleCommand {
|
|||
CreateMark,
|
||||
JumpToMark,
|
||||
PopMode(bool),
|
||||
EnableSimpleIm(bool),
|
||||
ToggleSimpleImEnabled,
|
||||
ReloadSimpleIm,
|
||||
EnableUnicodeInput,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
|
|
@ -446,6 +450,11 @@ pub struct Vrr {
|
|||
pub cursor_hz: Option<f64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct SimpleIm {
|
||||
pub enabled: Option<bool>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Xwayland {
|
||||
pub scaling_mode: Option<XScalingMode>,
|
||||
|
|
@ -520,6 +529,7 @@ pub struct Config {
|
|||
pub middle_click_paste: Option<bool>,
|
||||
pub input_modes: AHashMap<String, InputMode>,
|
||||
pub workspace_display_order: Option<WorkspaceDisplayOrder>,
|
||||
pub simple_im: Option<SimpleIm>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ mod output;
|
|||
mod output_match;
|
||||
mod repeat_rate;
|
||||
pub mod shortcuts;
|
||||
mod simple_im;
|
||||
mod status;
|
||||
mod tearing;
|
||||
mod theme;
|
||||
|
|
|
|||
|
|
@ -155,6 +155,11 @@ impl ActionParser<'_> {
|
|||
"jump-to-mark" => JumpToMark,
|
||||
"clear-modes" => PopMode(false),
|
||||
"pop-mode" => PopMode(true),
|
||||
"enable-simple-im" => EnableSimpleIm(true),
|
||||
"disable-simple-im" => EnableSimpleIm(false),
|
||||
"toggle-simple-im-enabled" => ToggleSimpleImEnabled,
|
||||
"reload-simple-im" => ReloadSimpleIm,
|
||||
"enable-unicode-input" => EnableUnicodeInput,
|
||||
_ => {
|
||||
return Err(
|
||||
ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span)
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ use {
|
|||
ComplexShortcutsParser, ShortcutsParser, ShortcutsParserError,
|
||||
parse_modified_keysym_str,
|
||||
},
|
||||
simple_im::SimpleImParser,
|
||||
status::StatusParser,
|
||||
tearing::TearingParser,
|
||||
theme::ThemeParser,
|
||||
|
|
@ -139,7 +140,13 @@ impl Parser for ConfigParser<'_> {
|
|||
show_bar,
|
||||
focus_history_val,
|
||||
),
|
||||
(middle_click_paste, input_modes_val, workspace_display_order_val, auto_reload),
|
||||
(
|
||||
middle_click_paste,
|
||||
input_modes_val,
|
||||
workspace_display_order_val,
|
||||
auto_reload,
|
||||
simple_im_val,
|
||||
),
|
||||
) = ext.extract((
|
||||
(
|
||||
opt(val("keymap")),
|
||||
|
|
@ -194,6 +201,7 @@ impl Parser for ConfigParser<'_> {
|
|||
opt(val("modes")),
|
||||
opt(val("workspace-display-order")),
|
||||
recover(opt(bol("auto-reload"))),
|
||||
opt(val("simple-im")),
|
||||
),
|
||||
))?;
|
||||
let mut keymap = None;
|
||||
|
|
@ -505,6 +513,15 @@ impl Parser for ConfigParser<'_> {
|
|||
}
|
||||
}
|
||||
}
|
||||
let mut simple_im = None;
|
||||
if let Some(value) = simple_im_val {
|
||||
match value.parse(&mut SimpleImParser(self.0)) {
|
||||
Ok(v) => simple_im = Some(v),
|
||||
Err(e) => {
|
||||
log::warn!("Could not parse simple IM setting: {}", self.0.error(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(Config {
|
||||
keymap,
|
||||
repeat_rate,
|
||||
|
|
@ -549,6 +566,7 @@ impl Parser for ConfigParser<'_> {
|
|||
middle_click_paste: middle_click_paste.despan(),
|
||||
input_modes,
|
||||
workspace_display_order,
|
||||
simple_im,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
44
toml-config/src/config/parsers/simple_im.rs
Normal file
44
toml-config/src/config/parsers/simple_im.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
use {
|
||||
crate::{
|
||||
config::{
|
||||
SimpleIm,
|
||||
context::Context,
|
||||
extractor::{Extractor, ExtractorError, bol, opt, recover},
|
||||
parser::{DataType, ParseResult, Parser, UnexpectedDataType},
|
||||
},
|
||||
toml::{
|
||||
toml_span::{DespanExt, Span, Spanned},
|
||||
toml_value::Value,
|
||||
},
|
||||
},
|
||||
indexmap::IndexMap,
|
||||
thiserror::Error,
|
||||
};
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
pub enum SimpleImParserError {
|
||||
#[error(transparent)]
|
||||
Expected(#[from] UnexpectedDataType),
|
||||
#[error(transparent)]
|
||||
Extract(#[from] ExtractorError),
|
||||
}
|
||||
|
||||
pub struct SimpleImParser<'a>(pub &'a Context<'a>);
|
||||
|
||||
impl Parser for SimpleImParser<'_> {
|
||||
type Value = SimpleIm;
|
||||
type Error = SimpleImParserError;
|
||||
const EXPECTED: &'static [DataType] = &[DataType::Table];
|
||||
|
||||
fn parse_table(
|
||||
&mut self,
|
||||
span: Span,
|
||||
table: &IndexMap<Spanned<String>, Spanned<Value>>,
|
||||
) -> ParseResult<Self> {
|
||||
let mut ext = Extractor::new(self.0, span, table);
|
||||
let (enabled,) = ext.extract((recover(opt(bol("enabled"))),))?;
|
||||
Ok(SimpleIm {
|
||||
enabled: enabled.despan(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +224,22 @@ impl Action {
|
|||
let state = state.clone();
|
||||
b.new(move || state.pop_mode(pop))
|
||||
}
|
||||
SimpleCommand::EnableSimpleIm(v) => {
|
||||
let persistent = state.persistent.clone();
|
||||
b.new(move || persistent.seat.set_simple_im_enabled(v))
|
||||
}
|
||||
SimpleCommand::ToggleSimpleImEnabled => {
|
||||
let persistent = state.persistent.clone();
|
||||
b.new(move || persistent.seat.toggle_simple_im_enabled())
|
||||
}
|
||||
SimpleCommand::ReloadSimpleIm => {
|
||||
let persistent = state.persistent.clone();
|
||||
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 } => {
|
||||
let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect();
|
||||
|
|
@ -1559,6 +1575,11 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc<Persistent
|
|||
if let Some(v) = config.workspace_display_order {
|
||||
set_workspace_display_order(v);
|
||||
}
|
||||
if let Some(simple_im) = config.simple_im {
|
||||
if let Some(enabled) = simple_im.enabled {
|
||||
persistent.seat.set_simple_im_enabled(enabled);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn create_command(exec: &Exec) -> Command {
|
||||
|
|
|
|||
|
|
@ -361,7 +361,7 @@
|
|||
]
|
||||
},
|
||||
{
|
||||
"description": "Sets the log level of the compositor..\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-log-level\", level = \"debug\" }\n ```\n",
|
||||
"description": "Sets the log level of the compositor.\n\n- Example:\n\n ```toml\n [shortcuts]\n alt-j = { type = \"set-log-level\", level = \"debug\" }\n ```\n",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
|
|
@ -1049,6 +1049,10 @@
|
|||
"workspace-display-order": {
|
||||
"description": "Configures the order of workspaces displayed.\n\nThe default is `manual`.\n\n- Example:\n\n ```toml\n workspace-display-order = \"sorted\"\n ```\n",
|
||||
"$ref": "#/$defs/WorkspaceDisplayOrder"
|
||||
},
|
||||
"simple-im": {
|
||||
"description": "Configures the simple, XCompose based input method.\n\nBy default, the input method is enabled. \n\nEven if the input method is enabled, it will only be used if there is no\nrunning external IM.\n\n- Example:\n\n ```toml\n [simple-im]\n enabled = false\n ```\n",
|
||||
"$ref": "#/$defs/SimpleIm"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
|
|
@ -1835,9 +1839,25 @@
|
|||
"create-mark",
|
||||
"jump-to-mark",
|
||||
"clear-modes",
|
||||
"pop-mode"
|
||||
"pop-mode",
|
||||
"enable-simple-im",
|
||||
"disable-simple-im",
|
||||
"toggle-simple-im-enabled",
|
||||
"reload-simple-im",
|
||||
"enable-unicode-input"
|
||||
]
|
||||
},
|
||||
"SimpleIm": {
|
||||
"description": "Describes the settings of the simple, XCompose based input method.\n\n- Example:\n\n ```toml\n [simple-im]\n enabled = false\n ```\n",
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"enabled": {
|
||||
"type": "boolean",
|
||||
"description": "Whether the input method is enabled.\n\nEven if the input method is enabled, it will only be used if there is no\nrunning external IM.\n"
|
||||
}
|
||||
},
|
||||
"required": []
|
||||
},
|
||||
"Status": {
|
||||
"description": "The configuration of a status program whose output will be shown in the bar.\n\n- Example:\n\n ```toml\n [status]\n format = \"i3bar\"\n exec = \"i3status\"\n ```\n",
|
||||
"type": "object",
|
||||
|
|
|
|||
|
|
@ -538,7 +538,7 @@ This table is a tagged union. The variant is determined by the `type` field. It
|
|||
|
||||
- `set-log-level`:
|
||||
|
||||
Sets the log level of the compositor..
|
||||
Sets the log level of the compositor.
|
||||
|
||||
- Example:
|
||||
|
||||
|
|
@ -2145,6 +2145,24 @@ The table has the following fields:
|
|||
|
||||
The value of this field should be a [WorkspaceDisplayOrder](#types-WorkspaceDisplayOrder).
|
||||
|
||||
- `simple-im` (optional):
|
||||
|
||||
Configures the simple, XCompose based input method.
|
||||
|
||||
By default, the input method is enabled.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
[simple-im]
|
||||
enabled = false
|
||||
```
|
||||
|
||||
The value of this field should be a [SimpleIm](#types-SimpleIm).
|
||||
|
||||
|
||||
<a name="types-Connector"></a>
|
||||
### `Connector`
|
||||
|
|
@ -4177,6 +4195,59 @@ The string should have one of the following values:
|
|||
|
||||
Pops the topmost mode from the input-mode stack.
|
||||
|
||||
- `enable-simple-im`:
|
||||
|
||||
Enables the simple, XCompose based input method.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
|
||||
- `disable-simple-im`:
|
||||
|
||||
Disables the simple, XCompose based input method.
|
||||
|
||||
- `toggle-simple-im-enabled`:
|
||||
|
||||
Toggles whether the simple, XCompose based input method is enabled.
|
||||
|
||||
- `reload-simple-im`:
|
||||
|
||||
Reloads the simple, XCompose based input method.
|
||||
|
||||
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>
|
||||
### `SimpleIm`
|
||||
|
||||
Describes the settings of the simple, XCompose based input method.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
[simple-im]
|
||||
enabled = false
|
||||
```
|
||||
|
||||
Values of this type should be tables.
|
||||
|
||||
The table has the following fields:
|
||||
|
||||
- `enabled` (optional):
|
||||
|
||||
Whether the input method is enabled.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
|
||||
The value of this field should be a boolean.
|
||||
|
||||
|
||||
<a name="types-Status"></a>
|
||||
|
|
|
|||
|
|
@ -497,7 +497,7 @@ Action:
|
|||
ref: Theme
|
||||
set-log-level:
|
||||
description: |
|
||||
Sets the log level of the compositor..
|
||||
Sets the log level of the compositor.
|
||||
|
||||
- Example:
|
||||
|
||||
|
|
@ -1033,6 +1033,28 @@ SimpleActionName:
|
|||
description: Disables all previously set input modes, clearing the input-mode stack.
|
||||
- value: pop-mode
|
||||
description: Pops the topmost mode from the input-mode stack.
|
||||
- value: enable-simple-im
|
||||
description: |
|
||||
Enables the simple, XCompose based input method.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
- value: disable-simple-im
|
||||
description: |
|
||||
Disables the simple, XCompose based input method.
|
||||
- value: toggle-simple-im-enabled
|
||||
description: |
|
||||
Toggles whether the simple, XCompose based input method is enabled.
|
||||
- value: reload-simple-im
|
||||
description: |
|
||||
Reloads the simple, XCompose based input method.
|
||||
|
||||
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:
|
||||
|
|
@ -2881,6 +2903,23 @@ Config:
|
|||
```toml
|
||||
workspace-display-order = "sorted"
|
||||
```
|
||||
simple-im:
|
||||
ref: SimpleIm
|
||||
required: false
|
||||
description: |
|
||||
Configures the simple, XCompose based input method.
|
||||
|
||||
By default, the input method is enabled.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
[simple-im]
|
||||
enabled = false
|
||||
```
|
||||
|
||||
|
||||
Idle:
|
||||
|
|
@ -4195,3 +4234,25 @@ ClientCapabilities:
|
|||
description: An array of masks that are OR'd.
|
||||
items:
|
||||
ref: ClientCapabilities
|
||||
|
||||
|
||||
SimpleIm:
|
||||
kind: table
|
||||
description: |
|
||||
Describes the settings of the simple, XCompose based input method.
|
||||
|
||||
- Example:
|
||||
|
||||
```toml
|
||||
[simple-im]
|
||||
enabled = false
|
||||
```
|
||||
fields:
|
||||
enabled:
|
||||
kind: boolean
|
||||
required: false
|
||||
description: |
|
||||
Whether the input method is enabled.
|
||||
|
||||
Even if the input method is enabled, it will only be used if there is no
|
||||
running external IM.
|
||||
|
|
|
|||
|
|
@ -134,6 +134,15 @@ request set_middle_button_emulation (since = 19) {
|
|||
enabled: u32,
|
||||
}
|
||||
|
||||
request set_simple_im_enabled (since = 22) {
|
||||
seat: str,
|
||||
enabled: u32,
|
||||
}
|
||||
|
||||
request reload_simple_im (since = 22) {
|
||||
seat: str,
|
||||
}
|
||||
|
||||
# events
|
||||
|
||||
event seat {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue