Skip to main content

termden_core/
config.rs

1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6/// Which built-in palette to use.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
8#[serde(rename_all = "kebab-case")]
9pub enum ThemeId {
10    CatppuccinMocha,
11    Everforest,
12}
13
14impl Default for ThemeId {
15    fn default() -> Self {
16        Self::CatppuccinMocha
17    }
18}
19
20impl ThemeId {
21    pub const ALL: [ThemeId; 2] = [ThemeId::CatppuccinMocha, ThemeId::Everforest];
22
23    pub fn label(self) -> &'static str {
24        match self {
25            ThemeId::CatppuccinMocha => "Catppuccin Mocha",
26            ThemeId::Everforest => "Everforest",
27        }
28    }
29
30    pub fn next(self) -> Self {
31        match self {
32            ThemeId::CatppuccinMocha => ThemeId::Everforest,
33            ThemeId::Everforest => ThemeId::CatppuccinMocha,
34        }
35    }
36}
37
38/// Window title bar. `Auto` hides it under tiling window managers and asks
39/// the desktop for its own everywhere else.
40#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
41#[serde(rename_all = "kebab-case")]
42pub enum TitleBar {
43    #[default]
44    Auto,
45    System,
46    None,
47}
48
49impl TitleBar {
50    pub const ALL: [TitleBar; 3] = [TitleBar::Auto, TitleBar::System, TitleBar::None];
51
52    pub fn label(self) -> &'static str {
53        match self {
54            TitleBar::Auto => "Auto",
55            TitleBar::System => "System",
56            TitleBar::None => "None",
57        }
58    }
59
60    /// Whether a title bar is wanted, resolving `Auto` from the environment.
61    pub fn wanted(self) -> bool {
62        match self {
63            TitleBar::System => true,
64            TitleBar::None => false,
65            TitleBar::Auto => !tiling_wm(),
66        }
67    }
68}
69
70/// Tiling window managers place and size windows themselves; a title bar
71/// there is only clutter.
72fn tiling_wm() -> bool {
73    [
74        "HYPRLAND_INSTANCE_SIGNATURE",
75        "SWAYSOCK",
76        "I3SOCK",
77        "NIRI_SOCKET",
78    ]
79    .iter()
80    .any(|var| std::env::var_os(var).is_some_and(|v| !v.is_empty()))
81}
82
83/// User configuration, read from `$XDG_CONFIG_HOME/termden/config.toml`.
84///
85/// Every field has a default so the file can stay tiny.
86#[derive(Debug, Clone, Serialize, Deserialize)]
87#[serde(default)]
88pub struct Config {
89    pub theme: ThemeId,
90    pub ui_font: String,
91    pub mono_font: String,
92    /// Terminal text size in px (ctrl+shift +/- adjusts and saves it).
93    pub font_size: f32,
94    pub panel_width: f32,
95    pub gap: f32,
96    pub corner_radius: f32,
97    pub title_bar: TitleBar,
98}
99
100impl Default for Config {
101    fn default() -> Self {
102        Self {
103            theme: ThemeId::CatppuccinMocha,
104            ui_font: "Noto Sans".to_string(),
105            mono_font: "JetBrainsMono Nerd Font".to_string(),
106            font_size: 16.0,
107            panel_width: 268.0,
108            gap: 12.0,
109            corner_radius: 12.0,
110            title_bar: TitleBar::Auto,
111        }
112    }
113}
114
115impl Config {
116    pub fn path() -> PathBuf {
117        config_dir().join("termden").join("config.toml")
118    }
119
120    pub fn load() -> Self {
121        let path = Self::path();
122        let Ok(text) = fs::read_to_string(&path) else {
123            return Self::default();
124        };
125
126        match toml::from_str(&text) {
127            Ok(config) => config,
128            Err(error) => {
129                eprintln!("termden: could not parse {}: {error}", path.display());
130                Self::default()
131            }
132        }
133    }
134
135    pub fn save(&self) -> std::io::Result<()> {
136        let path = Self::path();
137        if let Some(parent) = path.parent() {
138            fs::create_dir_all(parent)?;
139        }
140        let text = toml::to_string_pretty(self).map_err(std::io::Error::other)?;
141        fs::write(path, text)
142    }
143}
144
145fn config_dir() -> PathBuf {
146    crate::state::xdg_dir("XDG_CONFIG_HOME", ".config")
147}