From 8a2631784f68a7035c9c77c049eb593504e027ae Mon Sep 17 00:00:00 2001 From: "Hugo O. Rivera" Date: Sun, 2 Jan 2022 14:25:18 -0700 Subject: [PATCH 1/3] Add support for the xkb-switch keyboard layout reader. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #1384 at https://github.com/greshake/i3status-rust/issues/1384 If you set the xkbmap layout to a multilingual string with variants, such as: ``` setxkbmap -layout us,es,ru setxkbmap -variant dvorak,,phonetic ``` the `keyboard_layout` block will not show the current layout nor the current variant. It will only show the entire multilingual layout string. This is because `i3status-rust` uses the command [`setxkbmap -query`](https://github.com/greshake/i3status-rust/blob/b6e1b0b755de9f4520f1ecf17086d62fc5f190fe/src/blocks/keyboard_layout.rs#L63) to get the current layout. ``` $ setxkbmap -query ☸ 0007-04 rules: evdev model: pc105 layout: us,es,ru variant: dvorak,,phonetic options: grp:ctrls_toggle ``` This commit uses [`xkb-switch`](https://github.com/grwlf/xkb-switch) to read the current layout and keyboard variant. Example usage: ``` [[block]] block = "keyboard_layout" driver = "xkbswitch" format = "{layout} {variant}" interval = 1 ``` --- src/blocks/keyboard_layout.rs | 61 +++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/blocks/keyboard_layout.rs b/src/blocks/keyboard_layout.rs index 312f92fee6..fbab1aec39 100644 --- a/src/blocks/keyboard_layout.rs +++ b/src/blocks/keyboard_layout.rs @@ -31,6 +31,7 @@ pub enum KeyboardLayoutDriver { SetXkbMap, LocaleBus, KbddBus, + XkbSwitch, Sway, } @@ -413,6 +414,65 @@ impl KeyboardLayoutMonitor for Sway { } } +pub struct XkbSwitch; + +impl XkbSwitch { + pub fn new() -> Result { + Command::new("xkb-switch") + .output() + .block_error("keyboard_layout", "Failed to find xkb-switch in PATH") + .map(|_| XkbSwitch) + } +} + +fn xkb_switch_show_layout_and_variant() -> Result<(String, Option)> { + // This command should return a string like "layout(variant)" or "layout" + Command::new("xkb-switch") + .args(&["-p"]) + .output() + .block_error("keyboard_layout", "Failed to execute `xkb-switch -p`.") + .and_then(|raw| { + String::from_utf8(raw.stdout).block_error("keyboard_layout", "Non-UTF8 input.") + }) + .and_then(|layout_and_variant| { + let mut components = layout_and_variant + .trim_end() + .split("("); + + let layout = components + .next() + .ok_or("") + .block_error("keyboard_layout", "Unable to find keyboard layout")? + .to_string(); + + let variant = components + .last() + // Remove the trailing parenthesis ")" + .map(|variant_str| variant_str + .split_at(variant_str.len() - 1) + .0 + .to_string()); + + Ok((layout, variant)) + }) +} + +impl KeyboardLayoutMonitor for XkbSwitch { + fn keyboard_layout(&self) -> Result { + xkb_switch_show_layout_and_variant() + .map(|layout_and_variant| layout_and_variant.0) + } + + fn keyboard_variant(&self) -> Result { + xkb_switch_show_layout_and_variant() + .map(|layout_and_variant| layout_and_variant.1.unwrap_or("".into())) + } + + fn must_poll(&self) -> bool { + true + } +} + #[derive(Deserialize, Debug, Clone)] #[serde(default, deny_unknown_fields)] pub struct KeyboardLayoutConfig { @@ -470,6 +530,7 @@ impl ConfigBlock for KeyboardLayout { monitor.monitor(id, send); Box::new(monitor) } + KeyboardLayoutDriver::XkbSwitch => Box::new(XkbSwitch::new()?), KeyboardLayoutDriver::Sway => { let monitor = Sway::new(block_config.sway_kb_identifier)?; monitor.monitor(id, send); From bab09397818fe8efb5a139c7916e7c1e9fb7785e Mon Sep 17 00:00:00 2001 From: "Hugo O. Rivera" Date: Sun, 2 Jan 2022 14:41:34 -0700 Subject: [PATCH 2/3] Fix formatting and linter errors. - Less newlines. - Use unwrap_or_else instead of running unwrap_or with a function call. This improves efficiency by making the function call lazy. - Use a character instead of a single-character string for split(). --- src/blocks/keyboard_layout.rs | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/blocks/keyboard_layout.rs b/src/blocks/keyboard_layout.rs index fbab1aec39..92a0772714 100644 --- a/src/blocks/keyboard_layout.rs +++ b/src/blocks/keyboard_layout.rs @@ -435,9 +435,7 @@ fn xkb_switch_show_layout_and_variant() -> Result<(String, Option)> { String::from_utf8(raw.stdout).block_error("keyboard_layout", "Non-UTF8 input.") }) .and_then(|layout_and_variant| { - let mut components = layout_and_variant - .trim_end() - .split("("); + let mut components = layout_and_variant.trim_end().split('('); let layout = components .next() @@ -448,10 +446,7 @@ fn xkb_switch_show_layout_and_variant() -> Result<(String, Option)> { let variant = components .last() // Remove the trailing parenthesis ")" - .map(|variant_str| variant_str - .split_at(variant_str.len() - 1) - .0 - .to_string()); + .map(|variant_str| variant_str.split_at(variant_str.len() - 1).0.to_string()); Ok((layout, variant)) }) @@ -459,13 +454,12 @@ fn xkb_switch_show_layout_and_variant() -> Result<(String, Option)> { impl KeyboardLayoutMonitor for XkbSwitch { fn keyboard_layout(&self) -> Result { - xkb_switch_show_layout_and_variant() - .map(|layout_and_variant| layout_and_variant.0) + xkb_switch_show_layout_and_variant().map(|layout_and_variant| layout_and_variant.0) } fn keyboard_variant(&self) -> Result { xkb_switch_show_layout_and_variant() - .map(|layout_and_variant| layout_and_variant.1.unwrap_or("".into())) + .map(|layout_and_variant| layout_and_variant.1.unwrap_or_else(|| "".into())) } fn must_poll(&self) -> bool { From 9501521e0e8a860569e30d73816e6e91e80e0187 Mon Sep 17 00:00:00 2001 From: "Hugo O. Rivera" Date: Sun, 2 Jan 2022 16:32:05 -0700 Subject: [PATCH 3/3] Add documentation --- doc/blocks.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/doc/blocks.md b/doc/blocks.md index 8722a719a7..83cf52905b 100644 --- a/doc/blocks.md +++ b/doc/blocks.md @@ -944,6 +944,7 @@ Four drivers are available: - `setxkbmap` which polls setxkbmap to get the current layout - `localebus` which can read asynchronous updates from the systemd `org.freedesktop.locale1` D-Bus path - `kbddbus` which uses [kbdd](https://github.com/qnikst/kbdd) to monitor per-window layout changes via DBus +- `xkbswitch` which uses [xkb-switch](https://github.com/grwlf/xkb-switch) to show the current layout and variant. This works when `setxkbmap` is used to set a comma separated list of layouts, such as `us,es,fr`. - `sway` which can read asynchronous updates from the sway IPC Which of these methods is appropriate will depend on your system setup. @@ -985,6 +986,17 @@ block = "keyboard_layout" driver = "kbddbus" ``` +Poll `xkb-switch` for current layout and variant: + +```toml +[[block]] +block = "keyboard_layout" +driver = "xkbswitch" +on_click = "xkb-switch -n" +format = "{layout} {variant}" +interval = 1 +``` + Listen to sway for changes: ```toml