Skip to main content

ferrix_lib/
desktop.rs

1/* desktop.rs
2 *
3 * Copyright 2026 Michail Krasnov <mskrasnov07@ya.ru>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
17 *
18 * SPDX-License-Identifier: GPL-3.0-or-later
19 */
20
21//! Get information about desktop environment
22
23use crate::traits::ToJson;
24use anyhow::Result;
25use serde::{Deserialize, Serialize};
26use std::{env, fs, path::Path, process::Command};
27
28/// Session info (desktop, window manager)
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct SessionInfo {
31    /// Desktop environment name
32    pub desktop: Option<String>,
33
34    /// DE version
35    pub desktop_ver: Option<String>,
36
37    /// Window manager/compositor name
38    pub window_manager: Option<String>,
39}
40
41impl ToJson for SessionInfo {}
42
43impl SessionInfo {
44    pub fn new() -> Result<Self> {
45        let desktop = Self::get_desktop();
46        let desktop_ver = match &desktop {
47            Some(de) => Self::get_desktop_version(de),
48            _ => None,
49        };
50
51        Ok(Self {
52            desktop: if let Some(ref de) = desktop {
53                Some(Self::format_desktop_name(de))
54            } else {
55                desktop
56            },
57            desktop_ver,
58            window_manager: None,
59        })
60    }
61
62    pub fn get_desktop() -> Option<String> {
63        if let Ok(session) = env::var("DESKTOP_SESSION") {
64            if &session == "regolith" {
65                return Some("Regolith".to_string());
66            }
67        }
68
69        if let Some(de) = Self::get_de_from_env() {
70            return Some(de);
71        }
72        if let Some(de) = Self::get_de_from_xprop()
73            && env::var("DISPLAY").is_ok()
74        {
75            return Some(de);
76        }
77        None
78    }
79
80    fn get_de_from_env() -> Option<String> {
81        if let Ok(xdg) = env::var("XDG_CURRENT_DESKTOP") {
82            let mut de = xdg.replace("X-", "");
83            de = de.replace("Budgie:GNOME", "Budgie");
84            de = de.replace(":Unity7:ubuntu", "");
85            return Some(de);
86        }
87
88        if let Ok(session) = env::var("DESKTOP_SESSION") {
89            let de = Path::new(&session)
90                .file_name()
91                .and_then(|n| n.to_str())
92                .unwrap_or(&session)
93                .to_string();
94            return Some(de);
95        }
96
97        if env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
98            return Some("GNOME".to_string());
99        }
100        if env::var("MATE_DESKTOP_SESSION_ID").is_ok() {
101            return Some("MATE".to_string());
102        }
103        if env::var("TDE_FULL_SESSION").is_ok() {
104            return Some("Trinity Desktop".to_string());
105        }
106        None
107    }
108
109    fn get_de_from_xprop() -> Option<String> {
110        let out = Command::new("xprop").arg("-root").output().ok()?;
111        let stdout = String::from_utf8_lossy(&out.stdout);
112
113        for line in stdout.lines() {
114            if line.contains("KDE_SESSION_VERSION")
115                || line.contains("_MUFFIN")
116                || line.contains("xfce")
117            {
118                return Some(line.to_string());
119            }
120        }
121        None
122    }
123
124    fn format_desktop_name(de: &str) -> String {
125        let de = de.to_string();
126
127        if de.starts_with("KDE_SESSION_VERSION") {
128            if let Some(pos) = de.find(" = ") {
129                return format!("KDE{}", &de[pos + 3..]);
130            }
131        }
132
133        if de.contains("xfce4") {
134            return "Xfce4".to_string();
135        }
136        if de.contains("xfce5") {
137            return "Xfce5".to_string();
138        }
139        if de.contains("xfce") {
140            return "Xfce".to_string();
141        }
142        if de.contains("mate") {
143            return "MATE".to_string();
144        }
145        if de.contains("GNOME") {
146            return "GNOME".to_string();
147        }
148        if de.contains("MUFFIN") {
149            return "Cinnamon".to_string();
150        }
151        de
152    }
153
154    pub fn get_desktop_version(de: &str) -> Option<String> {
155        let cmd = if de.starts_with("Plasma") {
156            Some("plasmashell")
157        } else if de.starts_with("MATE") {
158            Some("mate-session")
159        } else if de.starts_with("Xfce") {
160            Some("xfce4-session")
161        } else if de.starts_with("GNOME") {
162            Some("gnome-shell")
163        } else if de.starts_with("Cinnamom") {
164            Some("cinnamon")
165        } else if de.starts_with("Budgie") {
166            Some("budgie-desktop")
167        } else if de.starts_with("LXQt") {
168            Some("lxqt-session")
169        } else if de.starts_with("Lumina") {
170            Some("lumina-desktop")
171        } else if de.starts_with("Trinity") {
172            Some("tde-config")
173        } else if de.starts_with("Unity") {
174            Some("unity")
175        } else if de.starts_with("Deepin") {
176            // WARN: Special case
177            if let Ok(contents) = fs::read_to_string("/etc/deepin-version") {
178                for line in contents.lines() {
179                    if line.starts_with("Version=") {
180                        return Some(line[8..].to_string());
181                    }
182                }
183            }
184            None
185        } else {
186            None
187        };
188
189        if let Some(cmd) = cmd {
190            let out = Command::new(cmd).arg("--version").output().ok()?;
191
192            let mut version = String::from_utf8_lossy(&out.stdout).to_string();
193            if version.is_empty() {
194                version = String::from_utf8_lossy(&out.stderr).to_string();
195            }
196
197            version = version
198                .replace("TDE:", "")
199                .replace("tde-config", "")
200                .replace("liblxqt", "")
201                .replace("Copyright", "")
202                .replace(")", "");
203
204            if let Some(last) = version.split_whitespace().last() {
205                return Some(last.trim_matches('"').to_string());
206            }
207        }
208        None
209    }
210}