1use serde::Deserialize;
5use serde_xml_rs as xml;
6use std::fs::File;
7use std::io::{self, BufReader};
8use std::path::PathBuf;
9
10#[cfg(target_os = "linux")]
12const RULES_FILE: &str = "evdev.xml";
13#[cfg(target_os = "linux")]
14const X11_BASE_RULES: &str = "/usr/share/X11/xkb/rules/evdev.xml";
15#[cfg(target_os = "linux")]
16const X11_EXTRAS_RULES: &str = "/usr/share/X11/xkb/rules/evdev.extras.xml";
17#[cfg(not(target_os = "linux"))]
19const RULES_FILE: &str = "base.xml";
20#[cfg(not(target_os = "linux"))]
21const X11_BASE_RULES: &str = "/usr/share/X11/xkb/rules/base.xml";
22#[cfg(not(target_os = "linux"))]
23const X11_EXTRAS_RULES: &str = "/usr/share/X11/xkb/rules/base.extras.xml";
24
25#[derive(Debug, Deserialize, Clone)]
27pub struct KeyboardLayouts {
28 #[serde(rename = "layoutList")]
29 pub layout_list: LayoutList,
30}
31
32impl KeyboardLayouts {
33 pub fn layouts(&self) -> &[KeyboardLayout] {
35 &self.layout_list.layout
36 }
37
38 pub fn layouts_mut(&mut self) -> &mut [KeyboardLayout] {
40 &mut self.layout_list.layout
41 }
42}
43
44#[derive(Debug, Deserialize, Clone)]
46pub struct LayoutList {
47 pub layout: Vec<KeyboardLayout>,
48}
49
50#[derive(Debug, Deserialize, Clone)]
52pub struct KeyboardLayout {
53 #[serde(rename = "configItem")]
54 pub config_item: ConfigItem,
55 #[serde(rename = "variantList")]
56 pub variant_list: Option<VariantList>,
57}
58
59impl KeyboardLayout {
60 pub fn name(&self) -> &str {
62 &self.config_item.name
63 }
64
65 pub fn description(&self) -> &str {
67 &self.config_item.description
68 }
69
70 pub fn variants(&self) -> Option<&Vec<KeyboardVariant>> {
72 self.variant_list.as_ref().and_then(|x| x.variant.as_ref())
73 }
74}
75
76#[derive(Debug, Deserialize, Clone)]
78pub struct ConfigItem {
79 pub name: String,
80 #[serde(rename = "shortDescription")]
81 pub short_description: Option<String>,
82 pub description: String,
83}
84
85#[derive(Debug, Deserialize, Clone)]
87pub struct VariantList {
88 pub variant: Option<Vec<KeyboardVariant>>,
89}
90
91#[derive(Debug, Deserialize, Clone)]
93pub struct KeyboardVariant {
94 #[serde(rename = "configItem")]
95 pub config_item: ConfigItem,
96}
97
98impl KeyboardVariant {
99 pub fn name(&self) -> &str {
101 &self.config_item.name
102 }
103
104 pub fn description(&self) -> &str {
106 &self.config_item.description
107 }
108}
109
110pub fn get_keyboard_layouts(path: &str) -> io::Result<KeyboardLayouts> {
112 xml::from_reader(BufReader::new(File::open(path)?))
113 .map_err(|why| io::Error::new(io::ErrorKind::InvalidData, format!("{}", why)))
114}
115
116pub fn keyboard_layouts() -> io::Result<KeyboardLayouts> {
118 if let Ok(x11_base_rules_xml) = std::env::var("X11_BASE_RULES_XML") {
119 get_keyboard_layouts(&x11_base_rules_xml)
120 } else {
121 get_keyboard_layouts(X11_BASE_RULES)
122 }
123}
124
125pub fn extra_keyboard_layouts() -> io::Result<KeyboardLayouts> {
127 if let Ok(x11_extra_rules_xml) = std::env::var("X11_EXTRA_RULES_XML") {
128 get_keyboard_layouts(&x11_extra_rules_xml)
129 } else {
130 get_keyboard_layouts(X11_EXTRAS_RULES)
131 }
132}
133
134pub fn user_keyboard_layouts() -> io::Result<KeyboardLayouts> {
141 let paths = user_xkb_rules_paths();
142
143 let layout_lists: Vec<LayoutList> = paths
144 .into_iter()
145 .filter_map(|path| get_keyboard_layouts(path.to_string_lossy().as_ref()).ok())
146 .map(|layouts| layouts.layout_list)
147 .collect();
148
149 Ok(KeyboardLayouts {
150 layout_list: concat_layout_lists(layout_lists),
151 })
152}
153
154fn user_xkb_rules_paths() -> Vec<PathBuf> {
156 let mut paths = Vec::new();
157
158 if let Ok(xdg_config_home) = std::env::var("XDG_CONFIG_HOME") {
160 let mut path = PathBuf::from(xdg_config_home);
161 path.push("xkb/rules");
162 path.push(RULES_FILE);
163 if path.exists() {
164 paths.push(path);
165 }
166 } else if let Ok(home) = std::env::var("HOME") {
167 let mut path = PathBuf::from(home);
168 path.push(".config/xkb/rules");
169 path.push(RULES_FILE);
170 if path.exists() {
171 paths.push(path);
172 }
173 }
174
175 if let Ok(home) = std::env::var("HOME") {
177 let mut path = PathBuf::from(home);
178 path.push(".xkb/rules");
179 path.push(RULES_FILE);
180 if path.exists() {
181 paths.push(path);
182 }
183 }
184
185 paths
186}
187
188pub fn all_keyboard_layouts() -> io::Result<KeyboardLayouts> {
191 let base_rules = keyboard_layouts()?;
192 let extras_rules = extra_keyboard_layouts()?;
193 let user_rules = user_keyboard_layouts().unwrap_or(KeyboardLayouts {
194 layout_list: LayoutList { layout: vec![] },
195 });
196
197 let layout_lists = vec![
198 base_rules.layout_list,
199 extras_rules.layout_list,
200 user_rules.layout_list,
201 ];
202
203 Ok(KeyboardLayouts {
204 layout_list: concat_layout_lists(layout_lists),
205 })
206}
207
208fn merge_rules(base: KeyboardLayouts, extras: KeyboardLayouts) -> KeyboardLayouts {
209 KeyboardLayouts {
210 layout_list: concat_layout_lists(vec![base.layout_list, extras.layout_list]),
211 }
212}
213
214fn concat_layout_lists(layouts: Vec<LayoutList>) -> LayoutList {
215 let mut new_layouts = vec![];
216 for layout_list in layouts.into_iter() {
217 new_layouts.extend(layout_list.layout);
218 }
219 return LayoutList {
220 layout: new_layouts,
221 };
222}