use serde::Deserialize;
use serde_xml_rs as xml;
use std::fs::File;
use std::io::{self, BufReader};
use std::path::PathBuf;
#[cfg(target_os = "linux")]
const RULES_FILE: &str = "evdev.xml";
#[cfg(target_os = "linux")]
const X11_BASE_RULES: &str = "/usr/share/X11/xkb/rules/evdev.xml";
#[cfg(target_os = "linux")]
const X11_EXTRAS_RULES: &str = "/usr/share/X11/xkb/rules/evdev.extras.xml";
#[cfg(not(target_os = "linux"))]
const RULES_FILE: &str = "base.xml";
#[cfg(not(target_os = "linux"))]
const X11_BASE_RULES: &str = "/usr/share/X11/xkb/rules/base.xml";
#[cfg(not(target_os = "linux"))]
const X11_EXTRAS_RULES: &str = "/usr/share/X11/xkb/rules/base.extras.xml";
#[derive(Debug, Deserialize, Clone)]
pub struct KeyboardLayouts {
#[serde(rename = "layoutList")]
pub layout_list: LayoutList,
}
impl KeyboardLayouts {
pub fn layouts(&self) -> &[KeyboardLayout] {
&self.layout_list.layout
}
pub fn layouts_mut(&mut self) -> &mut [KeyboardLayout] {
&mut self.layout_list.layout
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct LayoutList {
pub layout: Vec<KeyboardLayout>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct KeyboardLayout {
#[serde(rename = "configItem")]
pub config_item: ConfigItem,
#[serde(rename = "variantList")]
pub variant_list: Option<VariantList>,
}
impl KeyboardLayout {
pub fn name(&self) -> &str {
&self.config_item.name
}
pub fn description(&self) -> &str {
&self.config_item.description
}
pub fn variants(&self) -> Option<&Vec<KeyboardVariant>> {
self.variant_list.as_ref().and_then(|x| x.variant.as_ref())
}
}
#[derive(Debug, Deserialize, Clone)]
pub struct ConfigItem {
pub name: String,
#[serde(rename = "shortDescription")]
pub short_description: Option<String>,
pub description: String,
}
#[derive(Debug, Deserialize, Clone)]
pub struct VariantList {
pub variant: Option<Vec<KeyboardVariant>>,
}
#[derive(Debug, Deserialize, Clone)]
pub struct KeyboardVariant {
#[serde(rename = "configItem")]
pub config_item: ConfigItem,
}
impl KeyboardVariant {
pub fn name(&self) -> &str {
&self.config_item.name
}
pub fn description(&self) -> &str {
&self.config_item.description
}
}
pub fn get_keyboard_layouts(path: &str) -> io::Result<KeyboardLayouts> {
xml::from_reader(BufReader::new(File::open(path)?))
.map_err(|why| io::Error::new(io::ErrorKind::InvalidData, format!("{}", why)))
}
pub fn keyboard_layouts() -> io::Result<KeyboardLayouts> {
if let Ok(x11_base_rules_xml) = std::env::var("X11_BASE_RULES_XML") {
get_keyboard_layouts(&x11_base_rules_xml)
} else {
get_keyboard_layouts(X11_BASE_RULES)
}
}
pub fn extra_keyboard_layouts() -> io::Result<KeyboardLayouts> {
if let Ok(x11_extra_rules_xml) = std::env::var("X11_EXTRA_RULES_XML") {
get_keyboard_layouts(&x11_extra_rules_xml)
} else {
get_keyboard_layouts(X11_EXTRAS_RULES)
}
}
pub fn user_keyboard_layouts() -> io::Result<KeyboardLayouts> {
let paths = user_xkb_rules_paths();
let layout_lists: Vec<LayoutList> = paths
.into_iter()
.filter_map(|path| get_keyboard_layouts(path.to_string_lossy().as_ref()).ok())
.map(|layouts| layouts.layout_list)
.collect();
Ok(KeyboardLayouts {
layout_list: concat_layout_lists(layout_lists),
})
}
fn user_xkb_rules_paths() -> Vec<PathBuf> {
let mut paths = Vec::new();
if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
let mut path = PathBuf::from(xdg_config_home);
path.push("xkb/rules");
path.push(RULES_FILE);
if path.exists() {
paths.push(path);
}
} else if let Ok(home) = std::env::var("HOME") {
let mut path = PathBuf::from(home);
path.push(".config/xkb/rules");
path.push(RULES_FILE);
if path.exists() {
paths.push(path);
}
}
if let Ok(home) = std::env::var("HOME") {
let mut path = PathBuf::from(home);
path.push(".xkb/rules");
path.push(RULES_FILE);
if path.exists() {
paths.push(path);
}
}
paths
}
pub fn all_keyboard_layouts() -> io::Result<KeyboardLayouts> {
let base_rules = keyboard_layouts()?;
let extras_rules = extra_keyboard_layouts()?;
let user_rules = user_keyboard_layouts().unwrap_or(KeyboardLayouts {
layout_list: LayoutList { layout: vec![] },
});
let layout_lists = vec![
base_rules.layout_list,
extras_rules.layout_list,
user_rules.layout_list,
];
Ok(KeyboardLayouts {
layout_list: concat_layout_lists(layout_lists),
})
}
fn merge_rules(base: KeyboardLayouts, extras: KeyboardLayouts) -> KeyboardLayouts {
KeyboardLayouts {
layout_list: concat_layout_lists(vec![base.layout_list, extras.layout_list]),
}
}
fn concat_layout_lists(layouts: Vec<LayoutList>) -> LayoutList {
let mut new_layouts = vec![];
for layout_list in layouts.into_iter() {
new_layouts.extend(layout_list.layout);
}
return LayoutList {
layout: new_layouts,
};
}