Skip to main content

sway_groups_config/
lib.rs

1use std::path::PathBuf;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
6#[serde(default)]
7#[derive(Default)]
8pub struct SwaygConfig {
9    pub defaults: DefaultsConfig,
10    pub bar: BarConfig,
11    /// Assignment rules: when the daemon sees a new workspace whose name
12    /// matches a rule, it assigns the workspace to the specified groups
13    /// and/or marks it global — instead of adding it to the active group.
14    #[serde(default)]
15    pub assign: Vec<AssignRule>,
16}
17
18/// Controls how [`AssignRule::match_pattern`] is interpreted.
19#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Default)]
20#[serde(rename_all = "lowercase")]
21pub enum MatchType {
22    #[default]
23    Exact,
24    Regex,
25}
26
27/// A rule that controls which groups a newly created workspace is added to.
28#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
29pub struct AssignRule {
30    /// Pattern to match against the workspace name.
31    #[serde(rename = "match")]
32    pub match_pattern: String,
33    /// How to interpret `match_pattern`. Default: `"exact"`.
34    #[serde(default)]
35    pub match_type: MatchType,
36    /// Groups to add the workspace to. If non-empty, replaces the default
37    /// "add to active group" behaviour.
38    #[serde(default)]
39    pub groups: Vec<String>,
40    /// Whether to mark the workspace as global.
41    #[serde(default)]
42    pub global: bool,
43}
44
45#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
46#[serde(default)]
47pub struct DefaultsConfig {
48    pub default_group: String,
49    pub default_workspace: String,
50}
51
52#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
53#[serde(default)]
54pub struct BarConfig {
55    pub workspaces: BarSectionConfig,
56    pub groups: BarSectionConfig,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60#[serde(default)]
61pub struct BarSectionConfig {
62    pub socket_instance: String,
63    pub display: BarDisplay,
64    pub show_global: bool,
65    pub show_empty: bool,
66}
67
68#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
69#[serde(rename_all = "lowercase")]
70pub enum BarDisplay {
71    All,
72    Active,
73    None,
74}
75
76
77impl Default for DefaultsConfig {
78    fn default() -> Self {
79        Self {
80            default_group: "0".to_string(),
81            default_workspace: "0".to_string(),
82        }
83    }
84}
85
86impl Default for BarConfig {
87    fn default() -> Self {
88        Self {
89            workspaces: BarSectionConfig {
90                socket_instance: "swayg_workspaces".to_string(),
91                display: BarDisplay::All,
92                show_global: true,
93                show_empty: true,
94            },
95            groups: BarSectionConfig {
96                socket_instance: "swayg_groups".to_string(),
97                display: BarDisplay::All,
98                show_global: true,
99                show_empty: true,
100            },
101        }
102    }
103}
104
105impl Default for BarSectionConfig {
106    fn default() -> Self {
107        Self {
108            socket_instance: String::new(),
109            display: BarDisplay::All,
110            show_global: true,
111            show_empty: true,
112        }
113    }
114}
115
116impl SwaygConfig {
117    /// Return all assignment rules whose pattern matches the given workspace name.
118    pub fn matching_rules(&self, ws_name: &str) -> Vec<&AssignRule> {
119        self.assign
120            .iter()
121            .filter(|rule| match rule.match_type {
122                MatchType::Exact => rule.match_pattern == ws_name,
123                MatchType::Regex => regex::Regex::new(&rule.match_pattern)
124                    .map(|re| re.is_match(ws_name))
125                    .unwrap_or(false),
126            })
127            .collect()
128    }
129
130    pub fn config_path() -> Option<PathBuf> {
131        let dirs = directories::ProjectDirs::from("com", "swayg", "swayg")?;
132        Some(dirs.config_dir().join("config.toml"))
133    }
134
135    pub fn load() -> anyhow::Result<Self> {
136        if let Ok(env_path) = std::env::var("SWAYG_CONFIG") {
137            return Self::load_from(std::path::Path::new(&env_path));
138        }
139        let path = Self::config_path()
140            .ok_or_else(|| anyhow::anyhow!("Could not determine config directory"))?;
141        Self::load_from(&path)
142    }
143
144    pub fn load_from(path: &std::path::Path) -> anyhow::Result<Self> {
145        if !path.exists() {
146            return Ok(Self::default());
147        }
148        let content = std::fs::read_to_string(path)?;
149        let config: SwaygConfig = toml::from_str(&content)?;
150        Ok(config)
151    }
152
153    pub fn dump(&self) -> anyhow::Result<String> {
154        let mut output = String::new();
155        output.push_str("# swayg configuration\n");
156        output.push_str("# Place at: ~/.config/swayg/config.toml\n\n");
157        output.push_str(&toml::to_string_pretty(self)?);
158        Ok(output)
159    }
160
161    pub fn dump_to(&self, path: &std::path::Path) -> anyhow::Result<()> {
162        if let Some(parent) = path.parent() {
163            std::fs::create_dir_all(parent)?;
164        }
165        let content = self.dump()?;
166        std::fs::write(path, content)?;
167        Ok(())
168    }
169}