leenfetch_core/modules/linux/desktop/
theme.rs1use std::{env, fs, process::Command};
2
3pub fn get_theme(de: Option<&str>) -> Option<String> {
4 if env::var_os("DISPLAY").is_none() {
5 return None;
6 }
7
8 let de = de.unwrap_or("").to_lowercase();
9 let home = env::var("HOME").unwrap_or_default();
10
11 let mut gtk2 = None;
12 let mut gtk3 = None;
13 let mut gtk4 = None;
14 let mut kde = None;
15 let mut qt = None;
16
17 if de.contains("kde") || de.contains("plasma") {
19 let kde_paths = [
20 format!("{home}/.config/kdeglobals"),
21 "/etc/xdg/kdeglobals".to_string(),
22 ];
23
24 for path in kde_paths {
25 if let Ok(content) = fs::read_to_string(&path) {
26 if let Some(line) = content.lines().find(|l| l.starts_with("Name=")) {
27 kde = Some(line.trim_start_matches("Name=").trim().to_string());
28 break;
29 }
30 }
31 }
32 }
33
34 if let Ok(output) = Command::new("gsettings")
36 .args(["get", "org.gnome.desktop.interface", "gtk-theme"])
37 .output()
38 {
39 if output.status.success() {
40 let val = String::from_utf8_lossy(&output.stdout)
41 .trim()
42 .trim_matches('\'')
43 .to_string();
44 if !val.is_empty() {
45 gtk3 = Some(val.clone());
46 gtk2 = Some(val.clone()); }
48 }
49 }
50
51 let gtk4_path = format!("{home}/.config/gtk-4.0/settings.ini");
53 if let Ok(content) = fs::read_to_string(>k4_path) {
54 for line in content.lines() {
55 if let Some(val) = line.trim().strip_prefix("gtk-theme-name=") {
56 gtk4 = Some(val.trim_matches('"').to_string());
57 break;
58 }
59 }
60 }
61
62 if gtk2.is_none() {
64 let gtk2_paths = [
65 format!("{home}/.gtkrc-2.0"),
66 "/etc/gtk-2.0/gtkrc".into(),
67 "/usr/share/gtk-2.0/gtkrc".into(),
68 ];
69 for path in gtk2_paths {
70 if let Ok(content) = fs::read_to_string(&path) {
71 for line in content.lines() {
72 if let Some((_, val)) = line
73 .trim_start()
74 .strip_prefix("gtk-theme-name")
75 .and_then(|l| l.split_once('='))
76 {
77 gtk2 = Some(val.trim().trim_matches('"').to_string());
78 break;
79 }
80 }
81 }
82 if gtk2.is_some() {
83 break;
84 }
85 }
86 }
87
88 let qt_paths = [
90 format!("{home}/.config/qt5ct/qt5ct.conf"),
91 format!("{home}/.config/qt6ct/qt6ct.conf"),
92 ];
93 for path in qt_paths {
94 if let Ok(content) = fs::read_to_string(&path) {
95 for line in content.lines() {
96 if let Some(val) = line.trim().strip_prefix("style=") {
97 qt = Some(val.trim().to_string());
98 break;
99 }
100 }
101 }
102 if qt.is_some() {
103 break;
104 }
105 }
106
107 let mut result = Vec::new();
109
110 if let Some(val) = kde {
111 result.push(format!("{val} [KDE]"));
112 }
113
114 if let Some(val) = qt {
115 result.push(format!("{val} [Qt]"));
116 }
117
118 match (>k2, >k3) {
119 (Some(g2), Some(g3)) if g2 == g3 => result.push(format!("{g3} [GTK2/3]")),
120 (Some(g2), Some(g3)) => {
121 result.push(format!("{g2} [GTK2]"));
122 result.push(format!("{g3} [GTK3]"));
123 }
124 (Some(g2), None) => result.push(format!("{g2} [GTK2]")),
125 (None, Some(g3)) => result.push(format!("{g3} [GTK3]")),
126 _ => {}
127 }
128
129 if let Some(val) = gtk4 {
130 result.push(format!("{val} [GTK4]"));
131 }
132
133 if result.is_empty() {
134 None
135 } else {
136 Some(result.join(", "))
137 }
138}
139
140#[cfg(test)]
141mod tests {
142 use super::*;
143 use crate::test_utils::EnvLock;
144 use std::fs;
145 use std::time::{SystemTime, UNIX_EPOCH};
146
147 #[test]
148 fn returns_none_without_display() {
149 let env_lock = EnvLock::acquire(&["DISPLAY"]);
150 env_lock.remove_var("DISPLAY");
151 assert!(get_theme(None).is_none());
152 drop(env_lock);
153 }
154
155 #[test]
156 fn collects_theme_from_config_files() {
157 let env_lock = EnvLock::acquire(&["DISPLAY", "HOME", "PATH"]);
158 env_lock.set_var("DISPLAY", ":0");
159 env_lock.set_var("PATH", "/nonexistent"); let unique = SystemTime::now()
162 .duration_since(UNIX_EPOCH)
163 .unwrap()
164 .as_nanos();
165 let temp_home = std::env::temp_dir().join(format!("leenfetch_theme_test_{unique}"));
166 fs::create_dir_all(temp_home.join(".config/gtk-4.0")).unwrap();
167 fs::create_dir_all(temp_home.join(".config/qt5ct")).unwrap();
168
169 fs::write(
170 temp_home.join(".config/kdeglobals"),
171 "Name=Catppuccin Plasma\n",
172 )
173 .unwrap();
174 fs::write(
175 temp_home.join(".config/gtk-4.0/settings.ini"),
176 "[Settings]\ngtk-theme-name=Nordic\n",
177 )
178 .unwrap();
179 fs::write(temp_home.join(".gtkrc-2.0"), "gtk-theme-name=\"Adwaita\"\n").unwrap();
180 fs::write(temp_home.join(".config/qt5ct/qt5ct.conf"), "style=Breeze\n").unwrap();
181
182 env_lock.set_var("HOME", temp_home.to_str().unwrap());
183
184 let result = get_theme(Some("plasma")).expect("expected theme output");
185 assert!(
186 result.contains("[KDE]"),
187 "Expected KDE theme marker in {result}"
188 );
189 assert!(
190 result.contains("[GTK2]") || result.contains("[GTK2/3]"),
191 "Expected GTK theme marker in {result}"
192 );
193 assert!(
194 result.contains("[GTK4]"),
195 "Expected GTK4 theme marker in {result}"
196 );
197 assert!(
198 result.contains("[Qt]"),
199 "Expected Qt theme marker in {result}"
200 );
201
202 fs::remove_dir_all(&temp_home).unwrap();
203 drop(env_lock);
204 }
205}