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