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    pub desktop: Option<String>,
32    pub desktop_ver: Option<String>,
33    pub window_manager: Option<String>,
34}
35
36impl ToJson for SessionInfo {}
37
38impl SessionInfo {
39    pub fn new() -> Result<Self> {
40        let desktop = Self::get_desktop();
41        let desktop_ver = match &desktop {
42            Some(de) => Self::get_desktop_version(de),
43            _ => None,
44        };
45
46        Ok(Self {
47            desktop: if let Some(ref de) = desktop {
48                Some(Self::format_desktop_name(de))
49            } else {
50                desktop
51            },
52            desktop_ver,
53            window_manager: None,
54        })
55    }
56
57    pub fn get_desktop() -> Option<String> {
58        if let Ok(session) = env::var("DESKTOP_SESSION") {
59            if &session == "regolith" {
60                return Some("Regolith".to_string());
61            }
62        }
63
64        if let Some(de) = Self::get_de_from_env() {
65            return Some(de);
66        }
67        if let Some(de) = Self::get_de_from_xprop()
68            && env::var("DISPLAY").is_ok()
69        {
70            return Some(de);
71        }
72        None
73    }
74
75    fn get_de_from_env() -> Option<String> {
76        if let Ok(xdg) = env::var("XDG_CURRENT_DESKTOP") {
77            let mut de = xdg.replace("X-", "");
78            de = de.replace("Budgie:GNOME", "Budgie");
79            de = de.replace(":Unity7:ubuntu", "");
80            return Some(de);
81        }
82
83        if let Ok(session) = env::var("DESKTOP_SESSION") {
84            let de = Path::new(&session)
85                .file_name()
86                .and_then(|n| n.to_str())
87                .unwrap_or(&session)
88                .to_string();
89            return Some(de);
90        }
91
92        if env::var("GNOME_DESKTOP_SESSION_ID").is_ok() {
93            return Some("GNOME".to_string());
94        }
95        if env::var("MATE_DESKTOP_SESSION_ID").is_ok() {
96            return Some("MATE".to_string());
97        }
98        if env::var("TDE_FULL_SESSION").is_ok() {
99            return Some("Trinity Desktop".to_string());
100        }
101        None
102    }
103
104    fn get_de_from_xprop() -> Option<String> {
105        let out = Command::new("xprop").arg("-root").output().ok()?;
106        let stdout = String::from_utf8_lossy(&out.stdout);
107
108        for line in stdout.lines() {
109            if line.contains("KDE_SESSION_VERSION")
110                || line.contains("_MUFFIN")
111                || line.contains("xfce")
112            {
113                return Some(line.to_string());
114            }
115        }
116        None
117    }
118
119    fn format_desktop_name(de: &str) -> String {
120        let de = de.to_string();
121
122        if de.starts_with("KDE_SESSION_VERSION") {
123            if let Some(pos) = de.find(" = ") {
124                return format!("KDE{}", &de[pos + 3..]);
125            }
126        }
127
128        if de.contains("xfce4") {
129            return "Xfce4".to_string();
130        }
131        if de.contains("xfce5") {
132            return "Xfce5".to_string();
133        }
134        if de.contains("xfce") {
135            return "Xfce".to_string();
136        }
137        if de.contains("mate") {
138            return "MATE".to_string();
139        }
140        if de.contains("GNOME") {
141            return "GNOME".to_string();
142        }
143        if de.contains("MUFFIN") {
144            return "Cinnamon".to_string();
145        }
146        de
147    }
148
149    pub fn get_desktop_version(de: &str) -> Option<String> {
150        let cmd = if de.starts_with("Plasma") {
151            Some("plasmashell")
152        } else if de.starts_with("MATE") {
153            Some("mate-session")
154        } else if de.starts_with("Xfce") {
155            Some("xfce4-session")
156        } else if de.starts_with("GNOME") {
157            Some("gnome-shell")
158        } else if de.starts_with("Cinnamom") {
159            Some("cinnamon")
160        } else if de.starts_with("Budgie") {
161            Some("budgie-desktop")
162        } else if de.starts_with("LXQt") {
163            Some("lxqt-session")
164        } else if de.starts_with("Lumina") {
165            Some("lumina-desktop")
166        } else if de.starts_with("Trinity") {
167            Some("tde-config")
168        } else if de.starts_with("Unity") {
169            Some("unity")
170        } else if de.starts_with("Deepin") {
171            // WARN: Special case
172            if let Ok(contents) = fs::read_to_string("/etc/deepin-version") {
173                for line in contents.lines() {
174                    if line.starts_with("Version=") {
175                        return Some(line[8..].to_string());
176                    }
177                }
178            }
179            None
180        } else {
181            None
182        };
183
184        if let Some(cmd) = cmd {
185            let out = Command::new(cmd).arg("--version").output().ok()?;
186
187            let mut version = String::from_utf8_lossy(&out.stdout).to_string();
188            if version.is_empty() {
189                version = String::from_utf8_lossy(&out.stderr).to_string();
190            }
191
192            version = version
193                .replace("TDE:", "")
194                .replace("tde-config", "")
195                .replace("liblxqt", "")
196                .replace("Copyright", "")
197                .replace(")", "");
198
199            if let Some(last) = version.split_whitespace().last() {
200                return Some(last.trim_matches('"').to_string());
201            }
202        }
203        None
204    }
205}