diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index 936b8b5c..898ddd2e 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -1026,6 +1026,20 @@ 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 set_show_float_pin_icon(&self, show: bool) { self.send(&ClientMessage::SetShowFloatPinIcon { show }); } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index 02e9fbfd..b1e91ec4 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -788,6 +788,16 @@ pub enum ClientMessage<'a> { workspace: Workspace, connector: Connector, }, + SeatSetSimpleImEnabled { + seat: Seat, + enabled: bool, + }, + SeatGetSimpleImEnabled { + seat: Seat, + }, + SeatReloadSimpleIm { + seat: Seat, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -1020,6 +1030,9 @@ pub enum Response { GetShowBar { show: bool, }, + SeatGetSimpleImEnabled { + enabled: bool, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/input.rs b/jay-config/src/input.rs index 61b6c484..1956791b 100644 --- a/jay-config/src/input.rs +++ b/jay-config/src/input.rs @@ -603,6 +603,34 @@ 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); + } } /// A focus-follows-mouse mode. diff --git a/src/config/handler.rs b/src/config/handler.rs index 8573532e..c1cd7759 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -2281,6 +2281,26 @@ 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 spaces_change(&self) { struct V; impl NodeVisitorBase for V { @@ -3216,6 +3236,15 @@ 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")?, } Ok(()) } diff --git a/src/ifs/wl_seat.rs b/src/ifs/wl_seat.rs index e54758fc..a2e3200e 100644 --- a/src/ifs/wl_seat.rs +++ b/src/ifs/wl_seat.rs @@ -239,6 +239,7 @@ pub struct WlSeatGlobal { modifiers_listener: EventListener, modifiers_forward: EventSource, simple_im: CloneCell>>, + simple_im_enabled: Cell, } #[derive(Copy, Clone)] @@ -330,6 +331,7 @@ impl WlSeatGlobal { 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 diff --git a/src/ifs/wl_seat/text_input.rs b/src/ifs/wl_seat/text_input.rs index 3802afa6..2cdf08bf 100644 --- a/src/ifs/wl_seat/text_input.rs +++ b/src/ifs/wl_seat/text_input.rs @@ -2,7 +2,10 @@ use { crate::{ backend::KeyState, ifs::{ - wl_seat::{WlSeatGlobal, text_input::zwp_text_input_v3::ZwpTextInputV3}, + wl_seat::{ + WlSeatGlobal, + text_input::{simple_im::SimpleIm, zwp_text_input_v3::ZwpTextInputV3}, + }, wl_surface::{WlSurface, zwp_input_popup_surface_v2::ZwpInputPopupSurfaceV2}, }, keyboard::KeyboardState, @@ -61,6 +64,42 @@ pub enum TextDisconnectReason { } impl WlSeatGlobal { + pub fn set_simple_im_enabled(self: &Rc, 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) { + 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, @@ -82,7 +121,9 @@ impl WlSeatGlobal { fn remove_input_method(self: &Rc) { self.input_method.take(); - if let Some(im) = self.simple_im.get() { + if self.simple_im_enabled.get() + && let Some(im) = self.simple_im.get() + { self.set_input_method(im); } } diff --git a/toml-config/src/config.rs b/toml-config/src/config.rs index 6d240d96..5c7785e8 100644 --- a/toml-config/src/config.rs +++ b/toml-config/src/config.rs @@ -85,6 +85,9 @@ pub enum SimpleCommand { CreateMark, JumpToMark, PopMode(bool), + EnableSimpleIm(bool), + ToggleSimpleImEnabled, + ReloadSimpleIm, } #[derive(Debug, Clone)] @@ -446,6 +449,11 @@ pub struct Vrr { pub cursor_hz: Option, } +#[derive(Debug, Clone)] +pub struct SimpleIm { + pub enabled: Option, +} + #[derive(Debug, Clone)] pub struct Xwayland { pub scaling_mode: Option, @@ -520,6 +528,7 @@ pub struct Config { pub middle_click_paste: Option, pub input_modes: AHashMap, pub workspace_display_order: Option, + pub simple_im: Option, } #[derive(Debug, Error)] diff --git a/toml-config/src/config/parsers.rs b/toml-config/src/config/parsers.rs index 46ec09f2..c786c71d 100644 --- a/toml-config/src/config/parsers.rs +++ b/toml-config/src/config/parsers.rs @@ -39,6 +39,7 @@ mod output; mod output_match; mod repeat_rate; pub mod shortcuts; +mod simple_im; mod status; mod tearing; mod theme; diff --git a/toml-config/src/config/parsers/action.rs b/toml-config/src/config/parsers/action.rs index 10a2efcd..1f1bd327 100644 --- a/toml-config/src/config/parsers/action.rs +++ b/toml-config/src/config/parsers/action.rs @@ -155,6 +155,10 @@ 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, _ => { return Err( ActionParserError::UnknownSimpleAction(string.to_string()).spanned(span) diff --git a/toml-config/src/config/parsers/config.rs b/toml-config/src/config/parsers/config.rs index 59268800..03cefe0f 100644 --- a/toml-config/src/config/parsers/config.rs +++ b/toml-config/src/config/parsers/config.rs @@ -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, }) } } diff --git a/toml-config/src/config/parsers/simple_im.rs b/toml-config/src/config/parsers/simple_im.rs new file mode 100644 index 00000000..b4c08095 --- /dev/null +++ b/toml-config/src/config/parsers/simple_im.rs @@ -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>, + ) -> ParseResult { + let mut ext = Extractor::new(self.0, span, table); + let (enabled,) = ext.extract((recover(opt(bol("enabled"))),))?; + Ok(SimpleIm { + enabled: enabled.despan(), + }) + } +} diff --git a/toml-config/src/lib.rs b/toml-config/src/lib.rs index cf5bd21b..7ba79aeb 100644 --- a/toml-config/src/lib.rs +++ b/toml-config/src/lib.rs @@ -224,6 +224,18 @@ 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()) + } }, Action::Multi { actions } => { let actions: Vec<_> = actions.into_iter().map(|a| a.into_fn(state)).collect(); @@ -1559,6 +1571,11 @@ fn load_config(initial_load: bool, auto_reload: bool, persistent: &Rc Command { diff --git a/toml-spec/spec/spec.generated.json b/toml-spec/spec/spec.generated.json index 0de5ae68..a2fa94e9 100644 --- a/toml-spec/spec/spec.generated.json +++ b/toml-spec/spec/spec.generated.json @@ -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,24 @@ "create-mark", "jump-to-mark", "clear-modes", - "pop-mode" + "pop-mode", + "enable-simple-im", + "disable-simple-im", + "toggle-simple-im-enabled", + "reload-simple-im" ] }, + "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", diff --git a/toml-spec/spec/spec.generated.md b/toml-spec/spec/spec.generated.md index 41a72d2e..e4666c24 100644 --- a/toml-spec/spec/spec.generated.md +++ b/toml-spec/spec/spec.generated.md @@ -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). + ### `Connector` @@ -4177,6 +4195,53 @@ 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. + + + + +### `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. diff --git a/toml-spec/spec/spec.yaml b/toml-spec/spec/spec.yaml index 8932c35d..4262deff 100644 --- a/toml-spec/spec/spec.yaml +++ b/toml-spec/spec/spec.yaml @@ -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,23 @@ 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. Color: @@ -2881,6 +2898,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 +4229,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.