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    Command::new("which")
103        .arg(cmd)
104        .output()
105        .map(|o| o.status.success())
106        .unwrap_or(false)
107}
108
109fn run_command(cmd: &str, args: &[&str]) -> Option<String> {
110    Command::new(cmd)
111        .args(args)
112        .output()
113        .ok()
114        .filter(|o| o.status.success())
115        .map(|o| String::from_utf8_lossy(&o.stdout).to_string())
116}
117
118fn get_de_version(de: &str) -> Option<String> {
119    match de {
120        "Plasma" => parse_version("plasmashell", &["--version"]),
121        "MATE" => parse_version("mate-session", &["--version"]),
122        "Xfce" | "Xfce4" => parse_version("xfce4-session", &["--version"]),
123        "GNOME" => parse_version("gnome-shell", &["--version"]),
124        "Cinnamon" => parse_version("cinnamon", &["--version"]),
125        "Budgie" => parse_version("budgie-desktop", &["--version"]),
126        "LXQt" => parse_version("lxqt-session", &["--version"]),
127        "Lumina" => parse_version("lumina-desktop", &["--version"]),
128        "Trinity" => parse_version("tde-config", &["--version"]),
129        "Unity" => parse_version("unity", &["--version"]),
130        _ => None,
131    }
132}
133
134fn parse_version(cmd: &str, args: &[&str]) -> Option<String> {
135    run_command(cmd, args).and_then(|out| {
136        out.lines().rev().find_map(|line| {
137            line.split_whitespace()
138                .find(|s| {
139                    s.chars()
140                        .next()
141                        .map(|c| c.is_ascii_digit())
142                        .unwrap_or(false)
143                })
144                .map(|s| s.to_string())
145        })
146    })
147}
148
149#[cfg(test)]
150mod tests {
151    use super::*;
152    use crate::test_utils::EnvLock;
153
154    fn clear_env() -> EnvLock {
155        let vars = [
156            "DESKTOP_SESSION",
157            "XDG_CURRENT_DESKTOP",
158            "GNOME_DESKTOP_SESSION_ID",
159            "MATE_DESKTOP_SESSION_ID",
160            "TDE_FULL_SESSION",
161            "WAYLAND_DISPLAY",
162            "DISPLAY",
163        ];
164        let env_lock = EnvLock::acquire(&vars);
165        for var in vars {
166            env_lock.remove_var(var);
167        }
168        env_lock
169    }
170
171    // #[test]
172    // fn detects_de_from_desktop_session() {
173    //     clear_env();
174    //     env::set_var("DESKTOP_SESSION", "xfce4");
175    //     let result = get_de(false, None);
176    //     assert_eq!(result, Some("Xfce4".to_string()));
177    // }
178
179    // #[test]
180    // fn detects_regolith_special_case() {
181    //     clear_env();
182    //     env::set_var("DESKTOP_SESSION", "regolith-session");
183    //     let result = get_de(false, None);
184    //     assert_eq!(result, Some("Regolith".to_string()));
185    // }
186
187    // #[test]
188    // fn detects_de_from_xdg_current_desktop() {
189    //     clear_env();
190    //     env::set_var("XDG_CURRENT_DESKTOP", "Budgie:GNOME");
191    //     let result = get_de(false, None);
192    //     assert_eq!(result, Some("Budgie".to_string()));
193    // }
194
195    // #[test]
196    // fn detects_de_from_gnome_fallback() {
197    //     clear_env();
198    //     env::set_var("GNOME_DESKTOP_SESSION_ID", "this-is-gnome");
199    //     let result = get_de(false, None);
200    //     assert_eq!(result, Some("GNOME".to_string()));
201    // }
202
203    // #[test]
204    // fn detects_de_from_mate_fallback() {
205    //     clear_env();
206    //     env::set_var("MATE_DESKTOP_SESSION_ID", "mate-session");
207    //     let result = get_de(false, None);
208    //     assert_eq!(result, Some("MATE".to_string()));
209    // }
210
211    // #[test]
212    // fn detects_de_from_trinity_fallback() {
213    //     clear_env(); // make sure it clears ALL related env vars
214    //     env::set_var("TDE_FULL_SESSION", "true");
215    //     let result = get_de(false, None);
216    //     assert_eq!(result, Some("Trinity".to_string()));
217    // }
218
219    #[test]
220    fn excludes_wm_that_matches_de() {
221        let env_lock = clear_env();
222        env_lock.set_var("DESKTOP_SESSION", "sway");
223        let result = get_de(false, Some("sway"));
224        assert_eq!(result, None);
225        drop(env_lock);
226    }
227
228    // #[test]
229    // fn tags_wayland_session() {
230    //     clear_env();
231    //     env::set_var("DESKTOP_SESSION", "gnome");
232    //     env::set_var("WAYLAND_DISPLAY", "wayland-0");
233    //     let result = get_de(false, None);
234    //     assert_eq!(result, Some("GNOME (Wayland)".to_string()));
235    // }
236
237    #[test]
238    fn normalize_de_variants() {
239        let mut de = "xfce".to_string();
240        normalize_de_name(&mut de);
241        assert_eq!(de, "Xfce");
242
243        let mut de = "Xfce4".to_string();
244        normalize_de_name(&mut de);
245        assert_eq!(de, "Xfce4");
246
247        let mut de = "mate".to_string();
248        normalize_de_name(&mut de);
249        assert_eq!(de, "MATE");
250
251        let mut de = "gnome".to_string();
252        normalize_de_name(&mut de);
253        assert_eq!(de, "GNOME");
254
255        let mut de = "lxqt".to_string();
256        normalize_de_name(&mut de);
257        assert_eq!(de, "LXQt");
258
259        let mut de = "plasma-kde".to_string();
260        normalize_de_name(&mut de);
261        assert_eq!(de, "Plasma");
262    }
263}