diff --git a/jay-config/src/_private/client.rs b/jay-config/src/_private/client.rs index c8fbf901..31172667 100644 --- a/jay-config/src/_private/client.rs +++ b/jay-config/src/_private/client.rs @@ -19,7 +19,7 @@ use { acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod, }, keyboard::{ - Keymap, + Group, Keymap, mods::{Modifiers, RELEASE}, syms::KeySym, }, @@ -1385,6 +1385,23 @@ impl ConfigClient { keymap } + pub fn keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option<&[Group<'_>]>, + options: Option<&[&str]>, + ) -> Keymap { + let res = self.send_with_response(&ClientMessage::KeymapFromNames { + rules, + model, + groups: groups.map(|v| v.to_vec()), + options: options.map(|v| v.to_vec()), + }); + get_response!(res, Keymap(0), KeymapFromNames { keymap }); + keymap + } + pub fn set_ei_socket_enabled(&self, enabled: bool) { self.send(&ClientMessage::SetEiSocketEnabled { enabled }) } diff --git a/jay-config/src/_private/ipc.rs b/jay-config/src/_private/ipc.rs index f9e1889c..1b89c8b9 100644 --- a/jay-config/src/_private/ipc.rs +++ b/jay-config/src/_private/ipc.rs @@ -7,7 +7,7 @@ use { FocusFollowsMouseMode, InputDevice, LayerDirection, Seat, SwitchEvent, Timeline, acceleration::AccelProfile, capability::Capability, clickmethod::ClickMethod, }, - keyboard::{Keymap, mods::Modifiers, syms::KeySym}, + keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel, theme::{BarPosition, Color, colors::Colorable, sized::Resizable}, timer::Timer, @@ -820,6 +820,12 @@ pub enum ClientMessage<'a> { connector: Connector, use_native_gamut: bool, }, + KeymapFromNames { + rules: Option<&'a str>, + model: Option<&'a str>, + groups: Option>>, + options: Option>, + }, } #[derive(Serialize, Deserialize, Debug)] @@ -1067,6 +1073,9 @@ pub enum Response { GetBarPosition { position: BarPosition, }, + KeymapFromNames { + keymap: Keymap, + }, } #[derive(Serialize, Deserialize, Debug)] diff --git a/jay-config/src/keyboard/mod.rs b/jay-config/src/keyboard/mod.rs index 8630926c..95a51b1f 100644 --- a/jay-config/src/keyboard/mod.rs +++ b/jay-config/src/keyboard/mod.rs @@ -70,6 +70,15 @@ impl Keymap { } } +/// An RMLVO group consisting of a layout and a variant. +#[derive(Serialize, Deserialize, Copy, Clone, Debug, Eq, PartialEq, Hash)] +pub struct Group<'a> { + /// The layout of the group. + pub layout: &'a str, + /// The variant of the group. Can be an empty string. + pub variant: &'a str, +} + /// Parses a keymap. /// /// The returned keymap can later be used to set the keymap of a seat. If the keymap cannot @@ -109,3 +118,39 @@ impl Keymap { pub fn parse_keymap(keymap: &str) -> Keymap { get!(Keymap::INVALID).parse_keymap(keymap) } + +/// Creates a keymap from RMLVO names. +/// +/// If a parameter is not given, a value from the environment or a default is used: +/// +/// | name | default | +/// | ---------------------- | ---------------------- | +/// | `XKB_DEFAULT_RULES` | `evdev` | +/// | `XKB_DEFAULT_MODEL` | `pc105` | +/// | `XKB_DEFAULT_LAYOUT` | `us` | +/// | `XKB_DEFAULT_VARIANTS` | | +/// | `XKB_DEFAULT_OPTIONS` | | +/// +/// `XKB_DEFAULT_LAYOUT` and `XKB_DEFAULT_VARIANTS` are parsed into the `groups` parameter like this example: +/// ``` +/// XKB_DEFAULT_LAYOUT = "us,il,ru,de,jp" +/// XKB_DEFAULT_VARIANTS = ",,phonetic,neo" +/// ``` +/// produces: +/// ``` +/// [ +/// Group { layout: "us", variant: "" }, +/// Group { layout: "il", variant: "" }, +/// Group { layout: "ru", variant: "phonetic" }, +/// Group { layout: "de", variant: "neo" }, +/// Group { layout: "jp", variant: "" }, +/// ] +/// ``` +pub fn keymap_from_names( + rules: Option<&str>, + model: Option<&str>, + groups: Option<&[Group<'_>]>, + options: Option<&[&str]>, +) -> Keymap { + get!(Keymap::INVALID).keymap_from_names(rules, model, groups, options) +} diff --git a/src/cli/input.rs b/src/cli/input.rs index ee253ebf..2506cbb0 100644 --- a/src/cli/input.rs +++ b/src/cli/input.rs @@ -77,6 +77,8 @@ pub enum SeatCommand { SetRepeatRate(SetRepeatRateArgs), /// Set the keymap. SetKeymap(SetKeymapArgs), + /// Set the keymap from RMLVO names. + SetKeymapFromNames(SetKeymapFromNamesArgs), /// Retrieve the keymap. Keymap, /// Configure whether this seat uses the hardware cursor. @@ -145,6 +147,8 @@ pub enum DeviceCommand { SetTransformMatrix(SetTransformMatrixArgs), /// Set the keymap of this device. SetKeymap(SetKeymapArgs), + /// Set the keymap of this device from RMLVO names. + SetKeymapFromNames(SetKeymapFromNamesArgs), /// Retrieve the keymap of this device. Keymap, /// Attach the device to a seat. @@ -293,6 +297,25 @@ pub struct SetKeymapArgs { pub file: Option, } +#[derive(Args, Debug, Clone)] +pub struct SetKeymapFromNamesArgs { + /// The rules file. + #[clap(short, long)] + pub rules: Option, + /// The model name. + #[clap(short, long)] + pub model: Option, + /// The comma-separated list of layouts. + #[clap(short, long)] + pub layout: Option, + /// The comma-separated list of layout variants. + #[clap(short, long)] + pub variant: Option, + /// The comma-separated list of options. + #[clap(short, long)] + pub options: Option, +} + #[derive(Args, Debug, Clone)] pub struct UseHardwareCursorArgs { /// Whether the seat uses the hardware cursor. @@ -503,6 +526,20 @@ impl Input { }); } }, + SeatCommand::SetKeymapFromNames(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set keymap: {}", e); + }); + tc.send(jay_input::SetKeymapFromNames { + self_id: input, + seat: &args.seat, + rules: a.rules.as_deref(), + model: a.model.as_deref(), + layout: a.layout.as_deref(), + variant: a.variant.as_deref(), + options: a.options.as_deref(), + }); + } } tc.round_trip().await; } @@ -725,6 +762,20 @@ impl Input { enabled: a.middle_button_emulation as _, }); } + DeviceCommand::SetKeymapFromNames(a) => { + self.handle_error(input, |e| { + eprintln!("Could not set keymap: {}", e); + }); + tc.send(jay_input::SetDeviceKeymapFromNames { + self_id: input, + id: args.device, + rules: a.rules.as_deref(), + model: a.model.as_deref(), + layout: a.layout.as_deref(), + variant: a.variant.as_deref(), + options: a.options.as_deref(), + }); + } } tc.round_trip().await; } diff --git a/src/config/handler.rs b/src/config/handler.rs index 639b8cfc..4625fe35 100644 --- a/src/config/handler.rs +++ b/src/config/handler.rs @@ -65,7 +65,7 @@ use { CLICK_METHOD_BUTTON_AREAS, CLICK_METHOD_CLICKFINGER, CLICK_METHOD_NONE, ClickMethod, }, }, - keyboard::{Keymap, mods::Modifiers, syms::KeySym}, + keyboard::{Group, Keymap, mods::Modifiers, syms::KeySym}, logging::LogLevel, theme::{BarPosition, colors::Colorable, sized::Resizable}, timer::Timer as JayTimer, @@ -316,6 +316,39 @@ impl ConfigProxyHandler { res } + fn handle_keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option>>, + options: Option>, + ) -> Result<(), CphError> { + let kbvm_groups = groups.map(|groups| { + groups + .iter() + .map(|g| kbvm::xkb::rmlvo::Group { + layout: g.layout, + variant: g.variant, + }) + .collect::>() + }); + let (keymap, res) = match self.state.kb_ctx.keymap_from_names( + rules, + model, + kbvm_groups.as_deref(), + options.as_deref(), + ) { + Ok(keymap) => { + let id = Keymap(self.id()); + self.keymaps.set(id, keymap); + (id, Ok(())) + } + Err(e) => (Keymap::INVALID, Err(CphError::ParseKeymapError(e))), + }; + self.respond(Response::KeymapFromNames { keymap }); + res + } + fn handle_get_connectors( &self, dev: Option, @@ -3332,6 +3365,14 @@ impl ConfigProxyHandler { } => self .handle_connector_set_use_native_gamut(connector, use_native_gamut) .wrn("connector_set_use_native_gamut")?, + ClientMessage::KeymapFromNames { + rules, + model, + groups, + options, + } => self + .handle_keymap_from_names(rules, model, groups, options) + .wrn("keymap_from_names")?, } Ok(()) } diff --git a/src/ifs/jay_compositor.rs b/src/ifs/jay_compositor.rs index 46aa4393..611ab54c 100644 --- a/src/ifs/jay_compositor.rs +++ b/src/ifs/jay_compositor.rs @@ -79,7 +79,7 @@ impl Global for JayCompositorGlobal { } fn version(&self) -> u32 { - 23 + 24 } fn required_caps(&self) -> ClientCaps { diff --git a/src/ifs/jay_input.rs b/src/ifs/jay_input.rs index 531d2cd3..a06a2505 100644 --- a/src/ifs/jay_input.rs +++ b/src/ifs/jay_input.rs @@ -16,6 +16,7 @@ use { utils::errorfmt::ErrorFmt, wire::{JayInputId, jay_input::*}, }, + kbvm::xkb::rmlvo::Group, std::rc::Rc, thiserror::Error, uapi::OwnedFd, @@ -212,6 +213,44 @@ impl JayInput { Ok(()) }) } + + fn set_keymap_from_names_impl( + &self, + rules: Option<&str>, + model: Option<&str>, + layout: Option<&str>, + variant: Option<&str>, + options: Option<&str>, + f: F, + ) -> Result<(), JayInputError> + where + F: FnOnce(&Rc) -> Result<(), JayInputError>, + { + self.or_error(|| { + let mut groups = None::>; + if layout.is_some() || variant.is_some() { + groups = Some( + Group::from_layouts_and_variants( + layout.unwrap_or_default(), + variant.unwrap_or_default(), + ) + .collect(), + ); + } + let mut options_vec = None::>; + if let Some(options) = options { + options_vec = Some(options.split(",").collect()); + } + let keymap = self.client.state.kb_ctx.keymap_from_names( + rules, + model, + groups.as_deref(), + options_vec.as_deref(), + )?; + f(&keymap)?; + Ok(()) + }) + } } impl JayInputRequestHandler for JayInput { @@ -539,6 +578,44 @@ impl JayInputRequestHandler for JayInput { Ok(()) }) } + + fn set_keymap_from_names( + &self, + req: SetKeymapFromNames<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.set_keymap_from_names_impl( + req.rules, + req.model, + req.layout, + req.variant, + req.options, + |map| { + let seat = self.seat(req.seat)?; + seat.set_seat_keymap(&map); + Ok(()) + }, + ) + } + + fn set_device_keymap_from_names( + &self, + req: SetDeviceKeymapFromNames<'_>, + _slf: &Rc, + ) -> Result<(), Self::Error> { + self.set_keymap_from_names_impl( + req.rules, + req.model, + req.layout, + req.variant, + req.options, + |map| { + let dev = self.device(req.id)?; + dev.set_keymap(Some(map.clone())); + Ok(()) + }, + ) + } } object_base! { diff --git a/src/kbvm.rs b/src/kbvm.rs index 969a8846..1b751711 100644 --- a/src/kbvm.rs +++ b/src/kbvm.rs @@ -92,6 +92,25 @@ impl KbvmContext { .ctx .keymap_from_bytes(WriteToLog, None, keymap) .map_err(KbvmError::CouldNotParseKeymap)?; + let id = KbvmMapId(*blake3::hash(keymap).as_bytes()); + self.create_keymap(id, map) + } + + pub fn keymap_from_names( + &self, + rules: Option<&str>, + model: Option<&str>, + groups: Option<&[xkb::rmlvo::Group<'_>]>, + options: Option<&[&str]>, + ) -> Result, KbvmError> { + let map = self + .ctx + .keymap_from_names(WriteToLog, rules, model, groups, options); + let id = KbvmMapId(*blake3::hash(map.format().to_string().as_bytes()).as_bytes()); + self.create_keymap(id, map) + } + + fn create_keymap(&self, id: KbvmMapId, map: Keymap) -> Result, KbvmError> { let mut has_indicators = false; let mut num_lock = None; let mut caps_lock = None; @@ -111,7 +130,7 @@ impl KbvmContext { } let builder = map.to_builder(); Ok(Rc::new(KbvmMap { - id: KbvmMapId(*blake3::hash(keymap).as_bytes()), + id, state_machine: builder.build_state_machine(), map: create_keymap_memfd(&map, false).map_err(KbvmError::KeymapMemfd)?, xwayland_map: create_keymap_memfd(&map, true).map_err(KbvmError::KeymapMemfd)?, diff --git a/src/tools/tool_client.rs b/src/tools/tool_client.rs index 20b0eb48..32b307ec 100644 --- a/src/tools/tool_client.rs +++ b/src/tools/tool_client.rs @@ -335,7 +335,7 @@ impl ToolClient { self_id: s.registry, name: s.jay_compositor.0, interface: JayCompositor.name(), - version: s.jay_compositor.1.min(23), + version: s.jay_compositor.1.min(24), id: id.into(), }); self.jay_compositor.set(Some(id)); diff --git a/wire/jay_input.txt b/wire/jay_input.txt index 2f81bb75..73b0b65c 100644 --- a/wire/jay_input.txt +++ b/wire/jay_input.txt @@ -143,6 +143,24 @@ request reload_simple_im (since = 22) { seat: str, } +request set_keymap_from_names (since = 24) { + seat: str, + rules: optstr, + model: optstr, + layout: optstr, + variant: optstr, + options: optstr, +} + +request set_device_keymap_from_names (since = 24) { + id: u32, + rules: optstr, + model: optstr, + layout: optstr, + variant: optstr, + options: optstr, +} + # events event seat {