1use std::fs;
2use std::path::PathBuf;
3
4use serde::{Deserialize, Serialize};
5
6#[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#[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 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
70fn 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#[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 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}