Skip to main content

leenfetch_core/modules/linux/desktop/
de.rs

1use std::env;
2use std::process::Command;
3
4pub fn get_de(show_version: bool, wm: Option<&str>) -> Option<String> {
5    let mut de = detect_de_env().or_else(detect_de_fallback)?;
6
7    // Avoid false-positive where WM == DE
8    if let Some(wm_name) = wm {
9        if de.eq_ignore_ascii_case(wm_name) {
10            return None;
11        }
12    }
13
14    normalize_de_name(&mut de);
15
16    // Detect version if requested
17    if show_version {
18        if let Some(ver) = get_de_version(&de) {
19            de = format!("{} {}", de, ver);
20        }
21    }
22
23    // Tag if under Wayland
24    if env::var_os("WAYLAND_DISPLAY").is_some() {
25        de.push_str(" (Wayland)");
26    }
27
28    Some(de)
29}
30
31/// Primary DE env detection via standard XDG env vars
32fn detect_de_env() -> Option<String> {
33    if let Some(val) = env::var_os("DESKTOP_SESSION") {
34        let val_str = val.to_string_lossy();
35        return Some(if val_str.contains("regolith") {
36            "Regolith".into()
37        } else {
38            val_str.into()
39        });
40    }
41
42    if let Some(val) = env::var_os("XDG_CURRENT_DESKTOP") {
43        let val_str = val
44            .to_string_lossy()
45            .replace("X-", "")
46            .replace("Budgie:GNOME", "Budgie");
47        return Some(val_str);
48    }
49
50    None
51}
52
53/// Fallback: legacy DE-specific env vars
54fn detect_de_fallback() -> Option<String> {
55    if env::var_os("GNOME_DESKTOP_SESSION_ID").is_some() {
56        return Some("GNOME".into());
57    }
58    if env::var_os("MATE_DESKTOP_SESSION_ID").is_some() {
59        return Some("MATE".into());
60    }
61    if env::var_os("TDE_FULL_SESSION").is_some() {
62        return Some("Trinity".into());
63    }
64
65    // X11 fallback using xprop if available
66    if env::var_os("DISPLAY").is_some() && is_installed("xprop") {
67        let output = run_command("xprop", &["-root"])?;
68        if output.contains("KDE_SESSION_VERSION") {
69            return Some("KDE".into());
70        }
71        if output.contains("_MUFFIN") {
72            return Some("Cinnamon".into());
73        }
74        if output.contains("xfce") {
75            return Some("Xfce".into());
76        }
77    }
78
79    None
80}
81
82/// Normalize strings like "xfce4" -> "Xfce"
83fn normalize_de_name(de: &mut String) {
84    let lower = de.to_lowercase();
85    *de = match lower.as_str() {
86        s if s.contains("xfce4") => "Xfce4",
87        s if s.contains("xfce5") => "Xfce5",
88        s if s.contains("xfce") => "Xfce",
89        s if s.contains("mate") => "MATE",
90        s if s.contains("gnome") => "GNOME",
91        s if s.contains("muffin") => "Cinnamon",
92        s if s.contains("budgie") => "Budgie",
93        s if s.contains("lxqt") => "LXQt",
94        s if s.contains("plasma") || s.contains("kde") => "Plasma",
95        s if s.contains("unity") => "Unity",
96        _ => de.as_str(),
97    }
98    .to_string();
99}
100
101fn is_installed(cmd: &str) -> bool {
102    // Check if command exists in PATH without spawning a process
103    if let Ok(path) = std::env::var("PATH") {
104        for dir in path.split(':') {
105            let cmd_path = std::path::Path::new(dir).join(cmd);
106            if cmd_path.exists() {
107                return true;
108            }
109        }
110    }
111    false
112}
113
114fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
115    Command::new(cmd)
116        .args(args)
117        .output()
118        .ok()
119        .filter(|o| o.status.success())
120        .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
121}
122
123fn get_de_version(de: &str) -> Option<String> {
124    match de {
125        "Plasma" => parse_version("plasmashell", &["--version"]),
126        "MATE" => parse_version("mate-session", &["--version"]),
127        "Xfce" | "Xfce4" => parse_version("xfce4-session", &["--version"]),
128        "GNOME" => parse_version("gnome-shell", &["--version"]),
129        "Cinnamon" => parse_version("cinnamon", &["--version"]),
130        "Budgie" => parse_version("budgie-desktop", &["--version"]),
131        "LXQt" => parse_version("lxqt-session", &["--version"]),
132        "Lumina" => parse_version("lumina-desktop", &["--version"]),
133        "Trinity" => parse_version("tde-config", &["--version"]),
134        "Unity" => parse_version("unity", &["--version"]),
135        _ => None,
136    }
137}
138
139fn parse_version(cmd: &str, args: &[&str]) -> Option<String> {
140    run_command(cmd, args).and_then(|out| {
141        out.lines().rev().find_map(|line| {
142            line.split_whitespace()
143                .find(|s| {
144                    s.chars()
145                        .next()
146                        .map(|c| c.is_ascii_digit())
147                        .unwrap_or(false)
148                })
149                .map(|s| s.to_string())
150        })
151    })
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use crate::test_utils::EnvLock;
158
159    fn clear_env() -> EnvLock {
160        let vars = [
161            "DESKTOP_SESSION",
162            "XDG_CURRENT_DESKTOP",
163            "GNOME_DESKTOP_SESSION_ID",
164            "MATE_DESKTOP_SESSION_ID",
165            "TDE_FULL_SESSION",
166            "WAYLAND_DISPLAY",
167            "DISPLAY",
168        ];
169        let env_lock = EnvLock::acquire(&vars);
170        for var in vars {
171            env_lock.remove_var(var);
172        }
173        env_lock
174    }
175
176    // #[test]
177    // fn detects_de_from_desktop_session() {
178    //     clear_env();
179    //     env::set_var("DESKTOP_SESSION", "xfce4");
180    //     let result = get_de(false, None);
181    //     assert_eq!(result, Some("Xfce4".to_string()));
182    // }
183
184    // #[test]
185    // fn detects_regolith_special_case() {
186    //     clear_env();
187    //     env::set_var("DESKTOP_SESSION", "regolith-session");
188    //     let result = get_de(false, None);
189    //     assert_eq!(result, Some("Regolith".to_string()));
190    // }
191
192    // #[test]
193    // fn detects_de_from_xdg_current_desktop() {
194    //     clear_env();
195    //     env::set_var("XDG_CURRENT_DESKTOP", "Budgie:GNOME");
196    //     let result = get_de(false, None);
197    //     assert_eq!(result, Some("Budgie".to_string()));
198    // }
199
200    // #[test]
201    // fn detects_de_from_gnome_fallback() {
202    //     clear_env();
203    //     env::set_var("GNOME_DESKTOP_SESSION_ID", "this-is-gnome");
204    //     let result = get_de(false, None);
205    //     assert_eq!(result, Some("GNOME".to_string()));
206    // }
207
208    // #[test]
209    // fn detects_de_from_mate_fallback() {
210    //     clear_env();
211    //     env::set_var("MATE_DESKTOP_SESSION_ID", "mate-session");
212    //     let result = get_de(false, None);
213    //     assert_eq!(result, Some("MATE".to_string()));
214    // }
215
216    // #[test]
217    // fn detects_de_from_trinity_fallback() {
218    //     clear_env(); // make sure it clears ALL related env vars
219    //     env::set_var("TDE_FULL_SESSION", "true");
220    //     let result = get_de(false, None);
221    //     assert_eq!(result, Some("Trinity".to_string()));
222    // }
223
224    #[test]
225    fn excludes_wm_that_matches_de() {
226        let env_lock = clear_env();
227        env_lock.set_var("DESKTOP_SESSION", "sway");
228        let result = get_de(false, Some("sway"));
229        assert_eq!(result, None);
230        drop(env_lock);
231    }
232
233    // #[test]
234    // fn tags_wayland_session() {
235    //     clear_env();
236    //     env::set_var("DESKTOP_SESSION", "gnome");
237    //     env::set_var("WAYLAND_DISPLAY", "wayland-0");
238    //     let result = get_de(false, None);
239    //     assert_eq!(result, Some("GNOME (Wayland)".to_string()));
240    // }
241
242    #[test]
243    fn normalize_de_variants() {
244        let mut de = "xfce".to_string();
245        normalize_de_name(&mut de);
246        assert_eq!(de, "Xfce");
247
248        let mut de = "Xfce4".to_string();
249        normalize_de_name(&mut de);
250        assert_eq!(de, "Xfce4");
251
252        let mut de = "mate".to_string();
253        normalize_de_name(&mut de);
254        assert_eq!(de, "MATE");
255
256        let mut de = "gnome".to_string();
257        normalize_de_name(&mut de);
258        assert_eq!(de, "GNOME");
259
260        let mut de = "lxqt".to_string();
261        normalize_de_name(&mut de);
262        assert_eq!(de, "LXQt");
263
264        let mut de = "plasma-kde".to_string();
265        normalize_de_name(&mut de);
266        assert_eq!(de, "Plasma");
267    }
268}