xkb_data/
lib.rs

1// Copyright 2023 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4use serde::Deserialize;
5use serde_xml_rs as xml;
6use std::fs::File;
7use std::io::{self, BufReader};
8use std::path::PathBuf;
9
10// As per XKB official configuration guide, on Linux we use `evdev.xml`
11#[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// when not on Linux, we use `base.xml`
18#[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/// A list of keyboard layouts parsed from X11_BASE_RULES.
26#[derive(Debug, Deserialize, Clone)]
27pub struct KeyboardLayouts {
28    #[serde(rename = "layoutList")]
29    pub layout_list: LayoutList,
30}
31
32impl KeyboardLayouts {
33    /// Fetch the layouts from the layout list.
34    pub fn layouts(&self) -> &[KeyboardLayout] {
35        &self.layout_list.layout
36    }
37
38    /// Fetch the layouts from the layout list.
39    pub fn layouts_mut(&mut self) -> &mut [KeyboardLayout] {
40        &mut self.layout_list.layout
41    }
42}
43
44/// A list of keyboard layouts.
45#[derive(Debug, Deserialize, Clone)]
46pub struct LayoutList {
47    pub layout: Vec<KeyboardLayout>,
48}
49
50/// A keyboard layout, which contains an optional list of variants, a name, and a description.
51#[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    /// Fetches the name of the keyboard layout.
61    pub fn name(&self) -> &str {
62        &self.config_item.name
63    }
64
65    /// Fetches a description of the layout.
66    pub fn description(&self) -> &str {
67        &self.config_item.description
68    }
69
70    /// Fetches a list of possible layout variants.
71    pub fn variants(&self) -> Option<&Vec<KeyboardVariant>> {
72        self.variant_list.as_ref().and_then(|x| x.variant.as_ref())
73    }
74}
75
76/// Contains the name and description of a keyboard layout.
77#[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/// A list of possible variants of a keyboard layout.
86#[derive(Debug, Deserialize, Clone)]
87pub struct VariantList {
88    pub variant: Option<Vec<KeyboardVariant>>,
89}
90
91/// A variant of a keyboard layout.
92#[derive(Debug, Deserialize, Clone)]
93pub struct KeyboardVariant {
94    #[serde(rename = "configItem")]
95    pub config_item: ConfigItem,
96}
97
98impl KeyboardVariant {
99    /// The name of this variant of a keybaord layout.
100    pub fn name(&self) -> &str {
101        &self.config_item.name
102    }
103
104    /// A description of this variant of a keyboard layout.
105    pub fn description(&self) -> &str {
106        &self.config_item.description
107    }
108}
109
110/// Fetches a list of keyboard layouts from a path.
111pub 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
116/// Fetches a list of keyboard layouts from the X11_BASE_RULES const or the file defined in the X11_BASE_RULES_XML environment variable.
117pub 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
125/// Fetches a list of keyboard layouts from the X11_EXTRAS_RULES const or the file defined in the X11_EXTRA_RULES_XML environment variable.
126pub 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
134/// Fetches user-specific keyboard layouts from XDG config paths.
135/// On Linux, looks for `evdev.xml`, on other systems looks for `base.xml`.
136/// Returns all layouts found from these paths:
137/// 1. `$XDG_CONFIG_HOME/xkb/rules/` (defaults to `$HOME/.config/xkb/rules/`)
138/// 2. `$HOME/.xkb/rules/`
139/// Returns empty layouts if no files are found.
140pub 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
154/// Returns the list of user XKB rules paths to check for a given rules file.
155fn user_xkb_rules_paths() -> Vec<PathBuf> {
156    let mut paths = Vec::new();
157
158    // Defaults to $HOME/.config/xkb/rules/ if XDG_CONFIG_HOME is not set
159    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    // Second priority: $HOME/.xkb/rules/
176    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
188/// Fetches a list of keyboard layouts from the system rules files and user config paths,
189/// merging them together.
190pub 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}