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
//! Keyboard layout indicator
//!
//! Six drivers are available:
//! - `xkbevent` which can read asynchronous updates from the x11 events
//! - `setxkbmap` (alias for `xkbevent`) *DEPRECATED*
//! - `xkbswitch` (alias for `xkbevent`) *DEPRECATED*
//! - `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
//!
//! `setxkbmap` and `xkbswitch` are deprecated and will be removed in v0.35.0.
//!
//! Which of these methods is appropriate will depend on your system setup.
//!
//! # Configuration
//!
//! Key | Values | Default
//! ----|--------|--------
//! `driver` | One of `"xkbevent"`, `"setxkbmap"`, `"xkbswitch"`, `"localebus"`, `"kbddbus"` or `"sway"`, depending on your system. | `"xkbevent"`
//! `interval` *DEPRECATED* | 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`
//!
//! `interval` is deprecated and will be removed in v0.35.0.
//!
//! Key | Value | Type
//! ---------|-------|-----
//! `layout` | Keyboard layout name | String
//! `variant`| Keyboard variant name or `N/A` if not applicable | String
//!
//! # Examples
//!
//! 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"
//! ```
//!
//! Listen to xkb events for changes:
//!
//! ```toml
//! [[block]]
//! block = "keyboard_layout"
//! driver = "xkbevent"
//! ```
mod locale_bus;
use locale_bus::LocaleBus;
mod kbdd_bus;
use kbdd_bus::KbddBus;
mod sway;
use sway::Sway;
mod xkb_event;
use xkb_event::XkbEvent;
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]
XkbEvent,
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::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?),
KeyboardLayoutDriver::XkbEvent
| KeyboardLayoutDriver::SetXkbMap
| KeyboardLayoutDriver::XkbSwitch => Box::new(XkbEvent::new().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)?;
backend.wait_for_change().await?;
}
}
#[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,
}
}
}
}