i3status_rs/blocks/keyboard_layout.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
//! Keyboard layout indicator
//!
//! Four drivers are available:
//! - `setxkbmap` which polls setxkbmap to get the current layout
//! - `xkbswitch` which utilizes [XkbSwitch](https://github.com/grwlf/xkb-switch) to monitor and retrieve the current layout and variant
//! - `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
//! - `sway` which can read asynchronous updates from the sway IPC
//!
//! Which of these methods is appropriate will depend on your system setup.
//!
//! # Configuration
//!
//! Key | Values | Default
//! ----|--------|--------
//! `driver` | One of `"setxkbmap"`, `"xkbswitch"`, "localebus"`, `"kbddbus"` or `"sway"`, depending on your system. | `"setxkbmap"`
//! `interval` | Update interval, in seconds. Only used by the `"setxkbmap"` driver. | `60`
//! `format` | A string to customise the output of this block. See below for available placeholders. | `" $layout "`
//! `sway_kb_identifier` | Identifier of the device you want to monitor, as found in the output of `swaymsg -t get_inputs`. | Defaults to first input found
//! `mappings` | Map `layout (variant)` to custom short name. | `None`
//!
//! Key | Value | Type
//! ---------|-------|-----
//! `layout` | Keyboard layout name | String
//! `variant`| Keyboard variant name or `N/A` if not applicable | String
//!
//! # Examples
//!
//! Check `setxkbmap` every 15 seconds:
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "setxkbmap"
//! interval = 15
//! ```
//!
//! Check `xkbswitch` every 15 seconds
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "xkbswitch"
//! interval = 15
//! ```
//!
//! Listen to D-Bus for changes:
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "localebus"
//! ```
//!
//! Listen to kbdd for changes, the text is in the following format:
//! "English (US)" - {$layout ($variant)}
//! use block.mappings to override with shorter names as shown below.
//! Also use format = " $layout ($variant) " to see the full text to map,
//! or you can use:
//! dbus-monitor interface=ru.gentoo.kbdd
//! to see the exact variant spelling
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "kbddbus"
//! [block.mappings]
//! "English (US)" = "us"
//! "Bulgarian (new phonetic)" = "bg"
//! ```
//!
//! Listen to sway for changes:
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "sway"
//! sway_kb_identifier = "1133:49706:Gaming_Keyboard_G110"
//! ```
//!
//! Listen to sway for changes and override mappings:
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "sway"
//! format = " $layout "
//! [block.mappings]
//! "English (Workman)" = "EN"
//! "Russian (N/A)" = "RU"
//! ```
mod set_xkb_map;
use set_xkb_map::SetXkbMap;
mod xkb_switch;
use xkb_switch::XkbSwitch;
mod locale_bus;
use locale_bus::LocaleBus;
mod kbdd_bus;
use kbdd_bus::KbddBus;
mod sway;
use sway::Sway;
use super::prelude::*;
#[derive(Deserialize, Debug, SmartDefault)]
#[serde(deny_unknown_fields, default)]
pub struct Config {
pub format: FormatConfig,
pub driver: KeyboardLayoutDriver,
#[default(60.into())]
pub interval: Seconds,
pub sway_kb_identifier: Option<String>,
pub mappings: Option<HashMap<String, String>>,
}
#[derive(Deserialize, Debug, SmartDefault, Clone, Copy)]
#[serde(rename_all = "lowercase")]
pub enum KeyboardLayoutDriver {
#[default]
SetXkbMap,
XkbSwitch,
LocaleBus,
KbddBus,
Sway,
}
pub async fn run(config: &Config, api: &CommonApi) -> Result<()> {
let format = config.format.with_default(" $layout ")?;
let mut backend: Box<dyn Backend> = match config.driver {
KeyboardLayoutDriver::SetXkbMap => Box::new(SetXkbMap::new(config.interval)),
KeyboardLayoutDriver::XkbSwitch => Box::new(XkbSwitch::new(config.interval)),
KeyboardLayoutDriver::LocaleBus => Box::new(LocaleBus::new().await?),
KeyboardLayoutDriver::KbddBus => Box::new(KbddBus::new().await?),
KeyboardLayoutDriver::Sway => Box::new(Sway::new(config.sway_kb_identifier.clone()).await?),
};
loop {
let Info {
mut layout,
variant,
} = backend.get_info().await?;
let variant = variant.unwrap_or_else(|| "N/A".into());
if let Some(mappings) = &config.mappings {
if let Some(mapped) = mappings.get(&format!("{layout} ({variant})")) {
layout.clone_from(mapped);
}
}
let mut widget = Widget::new().with_format(format.clone());
widget.set_values(map! {
"layout" => Value::text(layout),
"variant" => Value::text(variant),
});
api.set_widget(widget)?;
select! {
update = backend.wait_for_change() => update?,
_ = api.wait_for_update_request() => (),
}
}
}
#[async_trait]
trait Backend {
async fn get_info(&mut self) -> Result<Info>;
async fn wait_for_change(&mut self) -> Result<()>;
}
#[derive(Clone)]
struct Info {
layout: String,
variant: Option<String>,
}
impl Info {
/// Parse "layout (variant)" string
fn from_layout_variant_str(s: &str) -> Self {
if let Some((layout, rest)) = s.split_once('(') {
Self {
layout: layout.trim_end().into(),
variant: Some(rest.trim_end_matches(')').into()),
}
} else {
Self {
layout: s.into(),
variant: None,
}
}
}
}