atagen

monolith

public
Star0
Watch0
fe835aed6c8d3486105d5f898a774d4078ee6284
1
ssh://brewery@pls.lobotomise.me:33/atagen/monolith.git

Blame: crates/config/src/lib.rs

fe835aed6c8datagen2026-10-16 10:05 UTC1
//! The TOML config. Parsing and validation only, the compositor applies it.
57f44a63d99fatagen2026-10-16 05:34 UTC2
fe835aed6c8datagen2026-10-16 10:05 UTC3
use monolith_ipc::Command;
fe835aed6c8datagen2026-10-16 10:05 UTC4
use regex::Regex;
fe835aed6c8datagen2026-10-16 10:05 UTC5
use serde::Deserialize;
fe835aed6c8datagen2026-10-16 10:05 UTC6
use std::collections::BTreeMap;
fe835aed6c8datagen2026-10-16 10:05 UTC7
use std::fmt;
fe835aed6c8datagen2026-10-16 10:05 UTC8
use std::path::{Path, PathBuf};
fe835aed6c8datagen2026-10-16 10:05 UTC9
fe835aed6c8datagen2026-10-16 10:05 UTC10
pub const DEFAULT: &str = include_str!("../default.toml");
fe835aed6c8datagen2026-10-16 10:05 UTC11
fe835aed6c8datagen2026-10-16 10:05 UTC12
#[derive(Debug, Clone)]
fe835aed6c8datagen2026-10-16 10:05 UTC13
pub struct Config {
fe835aed6c8datagen2026-10-16 10:05 UTC14
    pub general: General,
fe835aed6c8datagen2026-10-16 10:05 UTC15
    pub keyboard: Keyboard,
fe835aed6c8datagen2026-10-16 10:05 UTC16
    pub binds: Vec<Bind>,
fe835aed6c8datagen2026-10-16 10:05 UTC17
    pub rules: Vec<Rule>,
fe835aed6c8datagen2026-10-16 10:05 UTC18
    pub startup: Vec<Vec<String>>,
fe835aed6c8datagen2026-10-16 10:05 UTC19
}
fe835aed6c8datagen2026-10-16 10:05 UTC20
fe835aed6c8datagen2026-10-16 10:05 UTC21
#[derive(Deserialize, Debug, Clone, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC22
#[serde(default, deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC23
pub struct General {
fe835aed6c8datagen2026-10-16 10:05 UTC24
    /// What `Mod` means in binds.
fe835aed6c8datagen2026-10-16 10:05 UTC25
    #[serde(rename = "mod")]
fe835aed6c8datagen2026-10-16 10:05 UTC26
    pub mod_key: Modifier,
fe835aed6c8datagen2026-10-16 10:05 UTC27
    pub gaps: Gaps,
fe835aed6c8datagen2026-10-16 10:05 UTC28
}
fe835aed6c8datagen2026-10-16 10:05 UTC29
fe835aed6c8datagen2026-10-16 10:05 UTC30
impl Default for General {
fe835aed6c8datagen2026-10-16 10:05 UTC31
    fn default() -> Self {
fe835aed6c8datagen2026-10-16 10:05 UTC32
        Self {
fe835aed6c8datagen2026-10-16 10:05 UTC33
            mod_key: Modifier::Super,
fe835aed6c8datagen2026-10-16 10:05 UTC34
            gaps: Gaps::default(),
fe835aed6c8datagen2026-10-16 10:05 UTC35
        }
fe835aed6c8datagen2026-10-16 10:05 UTC36
    }
fe835aed6c8datagen2026-10-16 10:05 UTC37
}
fe835aed6c8datagen2026-10-16 10:05 UTC38
fe835aed6c8datagen2026-10-16 10:05 UTC39
#[derive(Deserialize, Debug, Clone, Copy, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC40
#[serde(default, deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC41
pub struct Gaps {
fe835aed6c8datagen2026-10-16 10:05 UTC42
    pub inner: f64,
fe835aed6c8datagen2026-10-16 10:05 UTC43
    pub outer: f64,
fe835aed6c8datagen2026-10-16 10:05 UTC44
}
fe835aed6c8datagen2026-10-16 10:05 UTC45
fe835aed6c8datagen2026-10-16 10:05 UTC46
impl Default for Gaps {
fe835aed6c8datagen2026-10-16 10:05 UTC47
    fn default() -> Self {
fe835aed6c8datagen2026-10-16 10:05 UTC48
        Self {
fe835aed6c8datagen2026-10-16 10:05 UTC49
            inner: 8.0,
fe835aed6c8datagen2026-10-16 10:05 UTC50
            outer: 8.0,
fe835aed6c8datagen2026-10-16 10:05 UTC51
        }
fe835aed6c8datagen2026-10-16 10:05 UTC52
    }
fe835aed6c8datagen2026-10-16 10:05 UTC53
}
fe835aed6c8datagen2026-10-16 10:05 UTC54
fe835aed6c8datagen2026-10-16 10:05 UTC55
#[derive(Deserialize, Debug, Clone, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC56
#[serde(default, deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC57
pub struct Keyboard {
fe835aed6c8datagen2026-10-16 10:05 UTC58
    pub rules: String,
fe835aed6c8datagen2026-10-16 10:05 UTC59
    pub model: String,
fe835aed6c8datagen2026-10-16 10:05 UTC60
    pub layout: String,
fe835aed6c8datagen2026-10-16 10:05 UTC61
    pub variant: String,
fe835aed6c8datagen2026-10-16 10:05 UTC62
    pub options: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC63
    pub repeat_delay: i32,
fe835aed6c8datagen2026-10-16 10:05 UTC64
    pub repeat_rate: i32,
fe835aed6c8datagen2026-10-16 10:05 UTC65
}
fe835aed6c8datagen2026-10-16 10:05 UTC66
fe835aed6c8datagen2026-10-16 10:05 UTC67
impl Default for Keyboard {
fe835aed6c8datagen2026-10-16 10:05 UTC68
    fn default() -> Self {
fe835aed6c8datagen2026-10-16 10:05 UTC69
        Self {
fe835aed6c8datagen2026-10-16 10:05 UTC70
            rules: String::new(),
fe835aed6c8datagen2026-10-16 10:05 UTC71
            model: String::new(),
fe835aed6c8datagen2026-10-16 10:05 UTC72
            layout: String::new(),
fe835aed6c8datagen2026-10-16 10:05 UTC73
            variant: String::new(),
fe835aed6c8datagen2026-10-16 10:05 UTC74
            options: None,
fe835aed6c8datagen2026-10-16 10:05 UTC75
            repeat_delay: 200,
fe835aed6c8datagen2026-10-16 10:05 UTC76
            repeat_rate: 25,
fe835aed6c8datagen2026-10-16 10:05 UTC77
        }
fe835aed6c8datagen2026-10-16 10:05 UTC78
    }
fe835aed6c8datagen2026-10-16 10:05 UTC79
}
fe835aed6c8datagen2026-10-16 10:05 UTC80
fe835aed6c8datagen2026-10-16 10:05 UTC81
#[derive(Deserialize, Debug, Clone, Copy, PartialEq, Eq)]
fe835aed6c8datagen2026-10-16 10:05 UTC82
#[serde(rename_all = "lowercase")]
fe835aed6c8datagen2026-10-16 10:05 UTC83
pub enum Modifier {
fe835aed6c8datagen2026-10-16 10:05 UTC84
    Super,
fe835aed6c8datagen2026-10-16 10:05 UTC85
    Alt,
fe835aed6c8datagen2026-10-16 10:05 UTC86
    Ctrl,
fe835aed6c8datagen2026-10-16 10:05 UTC87
    Shift,
fe835aed6c8datagen2026-10-16 10:05 UTC88
}
fe835aed6c8datagen2026-10-16 10:05 UTC89
fe835aed6c8datagen2026-10-16 10:05 UTC90
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
fe835aed6c8datagen2026-10-16 10:05 UTC91
pub struct Mods {
fe835aed6c8datagen2026-10-16 10:05 UTC92
    pub logo: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC93
    pub alt: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC94
    pub ctrl: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC95
    pub shift: bool,
fe835aed6c8datagen2026-10-16 10:05 UTC96
}
fe835aed6c8datagen2026-10-16 10:05 UTC97
fe835aed6c8datagen2026-10-16 10:05 UTC98
impl Mods {
fe835aed6c8datagen2026-10-16 10:05 UTC99
    fn set(&mut self, m: Modifier) {
fe835aed6c8datagen2026-10-16 10:05 UTC100
        match m {
fe835aed6c8datagen2026-10-16 10:05 UTC101
            Modifier::Super => self.logo = true,
fe835aed6c8datagen2026-10-16 10:05 UTC102
            Modifier::Alt => self.alt = true,
fe835aed6c8datagen2026-10-16 10:05 UTC103
            Modifier::Ctrl => self.ctrl = true,
fe835aed6c8datagen2026-10-16 10:05 UTC104
            Modifier::Shift => self.shift = true,
fe835aed6c8datagen2026-10-16 10:05 UTC105
        }
fe835aed6c8datagen2026-10-16 10:05 UTC106
    }
fe835aed6c8datagen2026-10-16 10:05 UTC107
}
fe835aed6c8datagen2026-10-16 10:05 UTC108
fe835aed6c8datagen2026-10-16 10:05 UTC109
#[derive(Debug, Clone, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC110
pub struct Bind {
fe835aed6c8datagen2026-10-16 10:05 UTC111
    pub mods: Mods,
fe835aed6c8datagen2026-10-16 10:05 UTC112
    /// An xkb keysym name, resolved by the compositor.
fe835aed6c8datagen2026-10-16 10:05 UTC113
    pub key: String,
fe835aed6c8datagen2026-10-16 10:05 UTC114
    pub command: Command,
fe835aed6c8datagen2026-10-16 10:05 UTC115
}
fe835aed6c8datagen2026-10-16 10:05 UTC116
fe835aed6c8datagen2026-10-16 10:05 UTC117
#[derive(Debug, Clone)]
fe835aed6c8datagen2026-10-16 10:05 UTC118
pub struct Rule {
fe835aed6c8datagen2026-10-16 10:05 UTC119
    pub app_id: Option<Regex>,
fe835aed6c8datagen2026-10-16 10:05 UTC120
    pub title: Option<Regex>,
fe835aed6c8datagen2026-10-16 10:05 UTC121
    pub tag: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC122
    pub output: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC123
    pub floating: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC124
    pub sticky: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC125
    pub scratchpad: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC126
}
fe835aed6c8datagen2026-10-16 10:05 UTC127
fe835aed6c8datagen2026-10-16 10:05 UTC128
/// Everything the matching rules say about a window, later rules winning.
fe835aed6c8datagen2026-10-16 10:05 UTC129
#[derive(Debug, Clone, Default, PartialEq)]
fe835aed6c8datagen2026-10-16 10:05 UTC130
pub struct Placement {
fe835aed6c8datagen2026-10-16 10:05 UTC131
    pub tag: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC132
    pub output: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC133
    pub floating: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC134
    pub sticky: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC135
    pub scratchpad: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC136
}
fe835aed6c8datagen2026-10-16 10:05 UTC137
fe835aed6c8datagen2026-10-16 10:05 UTC138
#[derive(Debug)]
fe835aed6c8datagen2026-10-16 10:05 UTC139
pub enum Error {
fe835aed6c8datagen2026-10-16 10:05 UTC140
    Io(PathBuf, std::io::Error),
fe835aed6c8datagen2026-10-16 10:05 UTC141
    Toml(toml::de::Error),
fe835aed6c8datagen2026-10-16 10:05 UTC142
    Bind(String, String),
fe835aed6c8datagen2026-10-16 10:05 UTC143
    Regex(String, regex::Error),
fe835aed6c8datagen2026-10-16 10:05 UTC144
}
fe835aed6c8datagen2026-10-16 10:05 UTC145
fe835aed6c8datagen2026-10-16 10:05 UTC146
impl fmt::Display for Error {
fe835aed6c8datagen2026-10-16 10:05 UTC147
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fe835aed6c8datagen2026-10-16 10:05 UTC148
        match self {
fe835aed6c8datagen2026-10-16 10:05 UTC149
            Error::Io(p, e) => write!(f, "{}: {e}", p.display()),
fe835aed6c8datagen2026-10-16 10:05 UTC150
            Error::Toml(e) => write!(f, "{e}"),
fe835aed6c8datagen2026-10-16 10:05 UTC151
            Error::Bind(k, e) => write!(f, "bind {k:?}: {e}"),
fe835aed6c8datagen2026-10-16 10:05 UTC152
            Error::Regex(r, e) => write!(f, "rule pattern {r:?}: {e}"),
fe835aed6c8datagen2026-10-16 10:05 UTC153
        }
fe835aed6c8datagen2026-10-16 10:05 UTC154
    }
fe835aed6c8datagen2026-10-16 10:05 UTC155
}
fe835aed6c8datagen2026-10-16 10:05 UTC156
fe835aed6c8datagen2026-10-16 10:05 UTC157
impl std::error::Error for Error {}
fe835aed6c8datagen2026-10-16 10:05 UTC158
fe835aed6c8datagen2026-10-16 10:05 UTC159
#[derive(Deserialize)]
fe835aed6c8datagen2026-10-16 10:05 UTC160
#[serde(deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC161
struct Raw {
fe835aed6c8datagen2026-10-16 10:05 UTC162
    #[serde(default)]
fe835aed6c8datagen2026-10-16 10:05 UTC163
    startup: Vec<Vec<String>>,
fe835aed6c8datagen2026-10-16 10:05 UTC164
    #[serde(default)]
fe835aed6c8datagen2026-10-16 10:05 UTC165
    general: General,
fe835aed6c8datagen2026-10-16 10:05 UTC166
    #[serde(default)]
fe835aed6c8datagen2026-10-16 10:05 UTC167
    keyboard: Keyboard,
fe835aed6c8datagen2026-10-16 10:05 UTC168
    #[serde(default)]
fe835aed6c8datagen2026-10-16 10:05 UTC169
    binds: BTreeMap<String, Words>,
fe835aed6c8datagen2026-10-16 10:05 UTC170
    #[serde(default)]
fe835aed6c8datagen2026-10-16 10:05 UTC171
    rules: Vec<RawRule>,
fe835aed6c8datagen2026-10-16 10:05 UTC172
}
fe835aed6c8datagen2026-10-16 10:05 UTC173
fe835aed6c8datagen2026-10-16 10:05 UTC174
#[derive(Deserialize)]
fe835aed6c8datagen2026-10-16 10:05 UTC175
#[serde(untagged)]
fe835aed6c8datagen2026-10-16 10:05 UTC176
enum Words {
fe835aed6c8datagen2026-10-16 10:05 UTC177
    Line(String),
fe835aed6c8datagen2026-10-16 10:05 UTC178
    List(Vec<String>),
fe835aed6c8datagen2026-10-16 10:05 UTC179
}
fe835aed6c8datagen2026-10-16 10:05 UTC180
fe835aed6c8datagen2026-10-16 10:05 UTC181
#[derive(Deserialize)]
fe835aed6c8datagen2026-10-16 10:05 UTC182
#[serde(deny_unknown_fields)]
fe835aed6c8datagen2026-10-16 10:05 UTC183
struct RawRule {
fe835aed6c8datagen2026-10-16 10:05 UTC184
    app_id: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC185
    title: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC186
    tag: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC187
    output: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC188
    floating: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC189
    sticky: Option<bool>,
fe835aed6c8datagen2026-10-16 10:05 UTC190
    scratchpad: Option<String>,
fe835aed6c8datagen2026-10-16 10:05 UTC191
}
fe835aed6c8datagen2026-10-16 10:05 UTC192
fe835aed6c8datagen2026-10-16 10:05 UTC193
impl Config {
fe835aed6c8datagen2026-10-16 10:05 UTC194
    /// `$MONOLITH_CONFIG`, else `$XDG_CONFIG_HOME/monolith/config.toml`.
fe835aed6c8datagen2026-10-16 10:05 UTC195
    pub fn path() -> Option<PathBuf> {
fe835aed6c8datagen2026-10-16 10:05 UTC196
        if let Some(p) = std::env::var_os("MONOLITH_CONFIG") {
fe835aed6c8datagen2026-10-16 10:05 UTC197
            return Some(p.into());
fe835aed6c8datagen2026-10-16 10:05 UTC198
        }
fe835aed6c8datagen2026-10-16 10:05 UTC199
        let base = std::env::var_os("XDG_CONFIG_HOME")
fe835aed6c8datagen2026-10-16 10:05 UTC200
            .map(PathBuf::from)
fe835aed6c8datagen2026-10-16 10:05 UTC201
            .or_else(|| std::env::var_os("HOME").map(|h| PathBuf::from(h).join(".config")))?;
fe835aed6c8datagen2026-10-16 10:05 UTC202
        Some(base.join("monolith/config.toml"))
fe835aed6c8datagen2026-10-16 10:05 UTC203
    }
fe835aed6c8datagen2026-10-16 10:05 UTC204
fe835aed6c8datagen2026-10-16 10:05 UTC205
    /// Loads `path`, or the built-in default when it does not exist.
fe835aed6c8datagen2026-10-16 10:05 UTC206
    pub fn load(path: Option<&Path>) -> Result<Self, Error> {
fe835aed6c8datagen2026-10-16 10:05 UTC207
        match path {
fe835aed6c8datagen2026-10-16 10:05 UTC208
            Some(p) => match std::fs::read_to_string(p) {
fe835aed6c8datagen2026-10-16 10:05 UTC209
                Ok(text) => Self::parse(&text),
fe835aed6c8datagen2026-10-16 10:05 UTC210
                Err(e) if e.kind() == std::io::ErrorKind::NotFound => Self::parse(DEFAULT),
fe835aed6c8datagen2026-10-16 10:05 UTC211
                Err(e) => Err(Error::Io(p.to_owned(), e)),
fe835aed6c8datagen2026-10-16 10:05 UTC212
            },
fe835aed6c8datagen2026-10-16 10:05 UTC213
            None => Self::parse(DEFAULT),
fe835aed6c8datagen2026-10-16 10:05 UTC214
        }
fe835aed6c8datagen2026-10-16 10:05 UTC215
    }
fe835aed6c8datagen2026-10-16 10:05 UTC216
fe835aed6c8datagen2026-10-16 10:05 UTC217
    pub fn parse(text: &str) -> Result<Self, Error> {
fe835aed6c8datagen2026-10-16 10:05 UTC218
        let raw: Raw = toml::from_str(text).map_err(Error::Toml)?;
fe835aed6c8datagen2026-10-16 10:05 UTC219
        let mod_key = raw.general.mod_key;
fe835aed6c8datagen2026-10-16 10:05 UTC220
        let binds = raw
fe835aed6c8datagen2026-10-16 10:05 UTC221
            .binds
fe835aed6c8datagen2026-10-16 10:05 UTC222
            .into_iter()
fe835aed6c8datagen2026-10-16 10:05 UTC223
            .map(|(combo, words)| parse_bind(&combo, words, mod_key))
fe835aed6c8datagen2026-10-16 10:05 UTC224
            .collect::<Result<_, _>>()?;
fe835aed6c8datagen2026-10-16 10:05 UTC225
        let regex = |p: Option<String>| {
fe835aed6c8datagen2026-10-16 10:05 UTC226
            p.map(|p| Regex::new(&p).map_err(|e| Error::Regex(p, e)))
fe835aed6c8datagen2026-10-16 10:05 UTC227
                .transpose()
fe835aed6c8datagen2026-10-16 10:05 UTC228
        };
fe835aed6c8datagen2026-10-16 10:05 UTC229
        let rules = raw
fe835aed6c8datagen2026-10-16 10:05 UTC230
            .rules
fe835aed6c8datagen2026-10-16 10:05 UTC231
            .into_iter()
fe835aed6c8datagen2026-10-16 10:05 UTC232
            .map(|r| {
fe835aed6c8datagen2026-10-16 10:05 UTC233
                Ok(Rule {
fe835aed6c8datagen2026-10-16 10:05 UTC234
                    app_id: regex(r.app_id)?,
fe835aed6c8datagen2026-10-16 10:05 UTC235
                    title: regex(r.title)?,
fe835aed6c8datagen2026-10-16 10:05 UTC236
                    tag: r.tag,
fe835aed6c8datagen2026-10-16 10:05 UTC237
                    output: r.output,
fe835aed6c8datagen2026-10-16 10:05 UTC238
                    floating: r.floating,
fe835aed6c8datagen2026-10-16 10:05 UTC239
                    sticky: r.sticky,
fe835aed6c8datagen2026-10-16 10:05 UTC240
                    scratchpad: r.scratchpad,
fe835aed6c8datagen2026-10-16 10:05 UTC241
                })
fe835aed6c8datagen2026-10-16 10:05 UTC242
            })
fe835aed6c8datagen2026-10-16 10:05 UTC243
            .collect::<Result<_, Error>>()?;
fe835aed6c8datagen2026-10-16 10:05 UTC244
        Ok(Self {
fe835aed6c8datagen2026-10-16 10:05 UTC245
            general: raw.general,
fe835aed6c8datagen2026-10-16 10:05 UTC246
            keyboard: raw.keyboard,
fe835aed6c8datagen2026-10-16 10:05 UTC247
            binds,
fe835aed6c8datagen2026-10-16 10:05 UTC248
            rules,
fe835aed6c8datagen2026-10-16 10:05 UTC249
            startup: raw.startup,
fe835aed6c8datagen2026-10-16 10:05 UTC250
        })
fe835aed6c8datagen2026-10-16 10:05 UTC251
    }
fe835aed6c8datagen2026-10-16 10:05 UTC252
fe835aed6c8datagen2026-10-16 10:05 UTC253
    pub fn placement(&self, app_id: &str, title: &str) -> Placement {
fe835aed6c8datagen2026-10-16 10:05 UTC254
        let mut p = Placement::default();
fe835aed6c8datagen2026-10-16 10:05 UTC255
        let hit = |re: &Option<Regex>, s: &str| re.as_ref().is_none_or(|re| re.is_match(s));
fe835aed6c8datagen2026-10-16 10:05 UTC256
        for r in &self.rules {
fe835aed6c8datagen2026-10-16 10:05 UTC257
            if !hit(&r.app_id, app_id) || !hit(&r.title, title) {
fe835aed6c8datagen2026-10-16 10:05 UTC258
                continue;
fe835aed6c8datagen2026-10-16 10:05 UTC259
            }
fe835aed6c8datagen2026-10-16 10:05 UTC260
            p.tag = r.tag.clone().or(p.tag);
fe835aed6c8datagen2026-10-16 10:05 UTC261
            p.output = r.output.clone().or(p.output);
fe835aed6c8datagen2026-10-16 10:05 UTC262
            p.floating = r.floating.or(p.floating);
fe835aed6c8datagen2026-10-16 10:05 UTC263
            p.sticky = r.sticky.or(p.sticky);
fe835aed6c8datagen2026-10-16 10:05 UTC264
            p.scratchpad = r.scratchpad.clone().or(p.scratchpad);
fe835aed6c8datagen2026-10-16 10:05 UTC265
        }
fe835aed6c8datagen2026-10-16 10:05 UTC266
        p
fe835aed6c8datagen2026-10-16 10:05 UTC267
    }
fe835aed6c8datagen2026-10-16 10:05 UTC268
}
fe835aed6c8datagen2026-10-16 10:05 UTC269
fe835aed6c8datagen2026-10-16 10:05 UTC270
fn parse_bind(combo: &str, words: Words, mod_key: Modifier) -> Result<Bind, Error> {
fe835aed6c8datagen2026-10-16 10:05 UTC271
    let bad = |m: String| Error::Bind(combo.to_owned(), m);
fe835aed6c8datagen2026-10-16 10:05 UTC272
    let parts: Vec<&str> = combo.split('+').map(str::trim).collect();
fe835aed6c8datagen2026-10-16 10:05 UTC273
    let Some((&key, mods)) = parts.split_last().filter(|(k, _)| !k.is_empty()) else {
fe835aed6c8datagen2026-10-16 10:05 UTC274
        return Err(bad("no key".into()));
fe835aed6c8datagen2026-10-16 10:05 UTC275
    };
fe835aed6c8datagen2026-10-16 10:05 UTC276
    let mut set = Mods::default();
fe835aed6c8datagen2026-10-16 10:05 UTC277
    for m in mods {
fe835aed6c8datagen2026-10-16 10:05 UTC278
        let m = match m.to_ascii_lowercase().as_str() {
fe835aed6c8datagen2026-10-16 10:05 UTC279
            "mod" => mod_key,
fe835aed6c8datagen2026-10-16 10:05 UTC280
            "super" | "logo" | "win" => Modifier::Super,
fe835aed6c8datagen2026-10-16 10:05 UTC281
            "alt" => Modifier::Alt,
fe835aed6c8datagen2026-10-16 10:05 UTC282
            "ctrl" | "control" => Modifier::Ctrl,
fe835aed6c8datagen2026-10-16 10:05 UTC283
            "shift" => Modifier::Shift,
fe835aed6c8datagen2026-10-16 10:05 UTC284
            other => return Err(bad(format!("unknown modifier {other}"))),
fe835aed6c8datagen2026-10-16 10:05 UTC285
        };
fe835aed6c8datagen2026-10-16 10:05 UTC286
        set.set(m);
fe835aed6c8datagen2026-10-16 10:05 UTC287
    }
fe835aed6c8datagen2026-10-16 10:05 UTC288
    let words: Vec<String> = match words {
fe835aed6c8datagen2026-10-16 10:05 UTC289
        Words::Line(s) => s.split_whitespace().map(str::to_owned).collect(),
fe835aed6c8datagen2026-10-16 10:05 UTC290
        Words::List(v) => v,
fe835aed6c8datagen2026-10-16 10:05 UTC291
    };
fe835aed6c8datagen2026-10-16 10:05 UTC292
    let command = Command::parse(&words).map_err(|e| bad(e.0))?;
fe835aed6c8datagen2026-10-16 10:05 UTC293
    Ok(Bind {
fe835aed6c8datagen2026-10-16 10:05 UTC294
        mods: set,
fe835aed6c8datagen2026-10-16 10:05 UTC295
        key: key.to_owned(),
fe835aed6c8datagen2026-10-16 10:05 UTC296
        command,
fe835aed6c8datagen2026-10-16 10:05 UTC297
    })
fe835aed6c8datagen2026-10-16 10:05 UTC298
}
fe835aed6c8datagen2026-10-16 10:05 UTC299
fe835aed6c8datagen2026-10-16 10:05 UTC300
#[cfg(test)]
fe835aed6c8datagen2026-10-16 10:05 UTC301
mod tests {
fe835aed6c8datagen2026-10-16 10:05 UTC302
    #[test]
fe835aed6c8datagen2026-10-16 10:05 UTC303
    fn the_default_config_parses() {
fe835aed6c8datagen2026-10-16 10:05 UTC304
        let c = super::Config::parse(super::DEFAULT).unwrap();
fe835aed6c8datagen2026-10-16 10:05 UTC305
        assert!(c.binds.len() > 40);
fe835aed6c8datagen2026-10-16 10:05 UTC306
    }
fe835aed6c8datagen2026-10-16 10:05 UTC307
}