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
&& 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 {
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,
}
}
}
}