detect_desktop_environment/lib.rs
1#![deny(missing_docs)]
2//! Desktop environment detection
3//!
4//! This crate implements automatic detection for the current desktop environment.
5//!
6//! See [`DesktopEnvironment`] for supported desktop environments.
7//!
8//! The environment can be detected using [`DesktopEnvironment::detect`]:
9//!
10//! ```rust
11//! use detect_desktop_environment::DesktopEnvironment;
12//!
13//! match DesktopEnvironment::detect() {
14//! Some(de) => println!("detected desktop environment: {de:?}"),
15//! None => println!("failed to detect desktop environment"),
16//! }
17//! ```
18
19/// Desktop environments supported by `detect-desktop-environment`.
20// If adding new environment, please keep them sorted alphabetically and use `PascalCase`.
21#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
22#[non_exhaustive]
23pub enum DesktopEnvironment {
24 /// Cinnamon, the default desktop environment for Linux Mint.
25 ///
26 /// - <https://en.wikipedia.org/wiki/Cinnamon_(desktop_environment)>
27 Cinnamon,
28 /// COSMIC, the legacy GNOME-based desktop environment for Linux Pop!_OS.
29 ///
30 /// Note: This corresponds to the classic COSMIC based on GNOME. For the new
31 /// [COSMIC Epoch](https://github.com/pop-os/cosmic-epoch) desktop
32 /// environment built in Rust, use [`DesktopEnvironment::CosmicEpoch`].
33 ///
34 /// - <https://github.com/pop-os/cosmic>
35 Cosmic,
36 /// COSMIC Epoch
37 ///
38 /// Note: This corresponds to the new COSMIC desktop environment
39 /// built by System76 in Rust for Linux Pop!_OS.
40 ///
41 /// - <https://github.com/pop-os/cosmic-epoch>
42 CosmicEpoch,
43 /// Deepin desktop environment
44 ///
45 /// - <https://www.deepin.org/index/en>
46 Dde,
47 /// EDE Desktop
48 ///
49 /// - <https://edeproject.org/>
50 Ede,
51 /// Endless OS desktop
52 ///
53 /// - <https://www.endlessos.org/os>
54 Endless,
55 /// Enlightenment desktop environment.
56 ///
57 /// - <https://en.wikipedia.org/wiki/Enlightenment_(software)>
58 Enlightenment,
59 /// Gnome, the default environment for many major Linux distributions.
60 ///
61 /// - <https://en.wikipedia.org/wiki/GNOME>
62 Gnome,
63 /// Hyprland tiling window manager
64 ///
65 /// - <https://hyprland.org/>
66 Hyprland,
67 /// KDE Plasma, the Kool Desktop Environment.
68 ///
69 /// - <https://kde.org/plasma-desktop/>
70 Kde,
71 /// LXDE
72 ///
73 /// - <https://www.lxde.org/>
74 Lxde,
75 /// LXQt
76 ///
77 /// - <https://lxqt-project.org/>
78 Lxqt,
79 /// MacOs, the environment for Apple's OS
80 MacOs,
81 /// MATE
82 ///
83 /// - <https://mate-desktop.org/>
84 Mate,
85 /// Legacy menu systems
86 ///
87 /// Listed in [Freedesktop Desktop Environments](https://specifications.freedesktop.org/menu-spec/latest/apb.html).
88 // Please send a PR if you have more details or better ideas about how to handle this value.
89 Old,
90 /// Elementary OS Desktop Environment
91 ///
92 /// - <https://elementary.io/>
93 Pantheon,
94 /// Razor-qt
95 ///
96 /// Discontinued Desktop Environment, this is an ancestor of LXQt.
97 ///
98 ///
99 /// - <https://github.com/Razor-qt/razor-qt>
100 Razor,
101 /// ROX Desktop
102 ///
103 /// - <https://rox.sourceforge.net/desktop/>
104 Rox,
105 /// Sway tiling window manager
106 ///
107 /// - <https://swaywm.org/>
108 Sway,
109 /// TrinityDesktopEnvironment
110 ///
111 /// - <https://www.trinitydesktop.org/>
112 Tde,
113 /// Unity, the legacy desktop environment for Ubuntu
114 ///
115 /// - <https://en.wikipedia.org/wiki/Unity_%28user_interface%29>
116 Unity,
117 /// Windows, the environments for Microsoft's OS
118 Windows,
119 /// Xfce
120 ///
121 /// - <https://xfce.org/>
122 Xfce,
123}
124
125impl DesktopEnvironment {
126 /// Detect the current desktop environment
127 ///
128 /// If the current desktop environment can't be detected, `None` is returned.
129 pub fn detect() -> Option<Self> {
130 Self::detect_impl()
131 }
132
133 /// Test if the desktop environment is based on the GTK framework
134 ///
135 /// See <https://en.wikipedia.org/wiki/Category:Desktop_environments_based_on_GTK>
136 ///
137 /// ```
138 /// use detect_desktop_environment::DesktopEnvironment;
139 ///
140 /// // All matching desktop environments:
141 /// assert!(DesktopEnvironment::Cinnamon.gtk());
142 /// assert!(DesktopEnvironment::Cosmic.gtk());
143 /// assert!(DesktopEnvironment::Gnome.gtk());
144 /// assert!(DesktopEnvironment::Lxde.gtk());
145 /// assert!(DesktopEnvironment::Mate.gtk());
146 /// assert!(DesktopEnvironment::Unity.gtk());
147 /// assert!(DesktopEnvironment::Xfce.gtk());
148 /// assert!(DesktopEnvironment::Pantheon.gtk());
149 /// assert!(DesktopEnvironment::Dde.gtk());
150 ///
151 /// // Non-GTK examples
152 /// assert!(!DesktopEnvironment::Kde.gtk());
153 /// assert!(!DesktopEnvironment::Windows.gtk());
154 /// ```
155 pub const fn gtk(self) -> bool {
156 use DesktopEnvironment::*;
157 matches!(self, Cinnamon | Cosmic | Dde | Gnome | Lxde | Mate | Pantheon | Unity | Xfce)
158 }
159
160 /// Test if the desktop environment is based on the Qt framework
161 ///
162 /// ```
163 /// use detect_desktop_environment::DesktopEnvironment;
164 ///
165 /// // All matching desktop environments:
166 /// assert!(DesktopEnvironment::Kde.qt());
167 /// assert!(DesktopEnvironment::Lxqt.qt());
168 /// assert!(DesktopEnvironment::Razor.qt());
169 /// assert!(DesktopEnvironment::Tde.qt());
170 ///
171 /// // Non-Qt examples
172 /// assert!(!DesktopEnvironment::Gnome.qt());
173 /// assert!(!DesktopEnvironment::Windows.qt());
174 /// ```
175 pub const fn qt(self) -> bool {
176 use DesktopEnvironment::*;
177 matches!(self, Kde | Lxqt | Razor | Tde)
178 }
179
180 #[cfg(target_os = "macos")]
181 fn detect_impl() -> Option<Self> {
182 Some(DesktopEnvironment::MacOs)
183 }
184
185 #[cfg(target_os = "windows")]
186 fn detect_impl() -> Option<Self> {
187 Some(DesktopEnvironment::Windows)
188 }
189
190 #[cfg(not(any(target_os = "macos", target_os = "windows")))]
191 fn detect_impl() -> Option<Self> {
192 std::env::var("XDG_CURRENT_DESKTOP").ok().as_deref().and_then(Self::from_xdg_current_desktop)
193 }
194
195 /// Parse the desktop environment from the name registered with Freedesktop.org
196 ///
197 /// See <https://specifications.freedesktop.org/menu-spec/latest/apb.html>
198 ///
199 /// Returns `None` if the desktop is not registered.
200 ///
201 /// This function is strictly restricted to the DEs registered with Freedesktop, for a more
202 /// complete list use [`DesktopEnvironment::from_xdg_name`]. Note that the check follows the
203 /// spec and is case-sensitive.
204 ///
205 /// ```
206 /// use detect_desktop_environment::DesktopEnvironment;
207 ///
208 /// assert_eq!(Some(DesktopEnvironment::Kde), DesktopEnvironment::from_freedesktop("KDE"));
209 /// assert_eq!(None, DesktopEnvironment::from_freedesktop("kde")); // must be uppercase
210 /// assert_eq!(None, DesktopEnvironment::from_freedesktop("SWAY")); // not registered
211 /// assert_eq!(None, DesktopEnvironment::from_freedesktop("unknown_de"));
212 /// ```
213 pub fn from_freedesktop(name: &str) -> Option<Self> {
214 // the patterns in the match below are ordered to match the order in the freedesktop table
215 match name {
216 "COSMIC" => Some(DesktopEnvironment::CosmicEpoch),
217 "GNOME" => Some(DesktopEnvironment::Gnome),
218 "GNOME-Classic" => Some(DesktopEnvironment::Gnome),
219 "GNOME-Flashback" => Some(DesktopEnvironment::Gnome),
220 "KDE" => Some(DesktopEnvironment::Kde),
221 "LXDE" => Some(DesktopEnvironment::Lxde),
222 "LXQt" => Some(DesktopEnvironment::Lxqt),
223 "MATE" => Some(DesktopEnvironment::Mate),
224 "Razor" => Some(DesktopEnvironment::Razor),
225 "ROX" => Some(DesktopEnvironment::Rox),
226 "TDE" => Some(DesktopEnvironment::Tde),
227 "Unity" => Some(DesktopEnvironment::Unity),
228 "XFCE" => Some(DesktopEnvironment::Xfce),
229 "EDE" => Some(DesktopEnvironment::Ede),
230 "Cinnamon" => Some(DesktopEnvironment::Cinnamon),
231 "Pantheon" => Some(DesktopEnvironment::Pantheon),
232 "DDE" => Some(DesktopEnvironment::Dde),
233 "Endless" =>Some(DesktopEnvironment::Endless),
234 "Old" =>Some(DesktopEnvironment::Old),
235 _ => None,
236 }
237 }
238
239 /// Parse the XDG desktop environment name
240 ///
241 /// This is an extended variant of [`DesktopEnvironment::from_freedesktop`]. It supports all
242 /// registered Freedesktop names, as well as some extra unregistered names. This is the
243 /// recommended method to parse names from the list in the env var `XDG_CURRENT_DESKTOP`.
244 ///
245 /// Returns `None` if the name is unknown.
246 ///
247 /// ```
248 /// use detect_desktop_environment::DesktopEnvironment;
249 ///
250 /// assert_eq!(Some(DesktopEnvironment::Kde), DesktopEnvironment::from_xdg_name("KDE")); // freedesktop DE
251 /// assert_eq!(None, DesktopEnvironment::from_xdg_name("kde")); // must be uppercase
252 /// assert_eq!(Some(DesktopEnvironment::Sway), DesktopEnvironment::from_xdg_name("SWAY")); // not registered
253 /// assert_eq!(None, DesktopEnvironment::from_xdg_name("unknown_de"));
254 /// ```
255 pub fn from_xdg_name(name: &str) -> Option<Self> {
256 if let Some(de) = Self::from_freedesktop(name) {
257 return Some(de);
258 }
259
260 // keep the patterns sorted alphabetically
261 match name {
262 "ENLIGHTENMENT" => Some(DesktopEnvironment::Enlightenment),
263 "Hyprland" => Some(DesktopEnvironment::Hyprland),
264 "SWAY" => Some(DesktopEnvironment::Sway),
265 "X-Cinnamon" => Some(DesktopEnvironment::Cinnamon),
266 _ => None,
267 }
268 }
269
270 /// Retrieve the desktop environment from the format used by `XDG_CURRENT_DESKTOP`.
271 ///
272 /// `XDG_CURRENT_DESKTOP` is a colon separated list of information about the current desktop
273 /// environment.
274 /// See: <https://specifications.freedesktop.org/mime-apps-spec/1.0.1/ar01s02.html>
275 ///
276 /// Returns `None` if the resolution fails.
277 /// Duplicate entries are allowed as long as they correspond to same Desktop Environment.
278 pub fn from_xdg_current_desktop(xdg_current_desktop: &str) -> Option<Self> {
279 let mut resolved: Option<DesktopEnvironment> = None;
280
281 for part in xdg_current_desktop.split(':') {
282 let de = match Self::from_xdg_name(part) {
283 Some(de) => de,
284 None => {
285 // We ignore parsing errors as we don't really control which values are possible.
286 // Some of the entries don't even represent a DE (e.g. `ubuntu:GNOME`, where `ubuntu` is
287 // a distro, not a DE)
288 // If you want more control over this, open an issue to discuss it.
289 continue
290 },
291 };
292 match resolved {
293 None => {
294 // first successfully parsed DE, store it but keep iterating to check for conflicts
295 resolved = Some(de)
296 },
297 Some(prev) => {
298 // a DE was already parsed previously, duplicates are allowed but a conflict causes
299 // immediate rejection with `None`.
300 // If you want more control over this, open an issue to discuss it.
301 if de != prev {
302 return None
303 }
304 }
305 }
306 }
307
308 resolved
309 }
310}
311
312#[cfg(test)]
313mod tests {
314 use super::*;
315
316 #[test]
317 fn linux_tests() {
318 // Cases without colon
319 assert_eq!(
320 DesktopEnvironment::from_xdg_current_desktop("Cinnamon"),
321 Some(DesktopEnvironment::Cinnamon)
322 );
323 assert_eq!(
324 DesktopEnvironment::from_xdg_current_desktop("ENLIGHTENMENT"),
325 Some(DesktopEnvironment::Enlightenment)
326 );
327 assert_eq!(
328 DesktopEnvironment::from_xdg_current_desktop("GNOME"),
329 Some(DesktopEnvironment::Gnome)
330 );
331 assert_eq!(
332 DesktopEnvironment::from_xdg_current_desktop("KDE"),
333 Some(DesktopEnvironment::Kde)
334 );
335 assert_eq!(
336 DesktopEnvironment::from_xdg_current_desktop("LXDE"),
337 Some(DesktopEnvironment::Lxde)
338 );
339 assert_eq!(
340 DesktopEnvironment::from_xdg_current_desktop("LXQt"),
341 Some(DesktopEnvironment::Lxqt)
342 );
343 assert_eq!(
344 DesktopEnvironment::from_xdg_current_desktop("MATE"),
345 Some(DesktopEnvironment::Mate)
346 );
347 assert_eq!(
348 DesktopEnvironment::from_xdg_current_desktop("Unity"),
349 Some(DesktopEnvironment::Unity)
350 );
351 assert_eq!(
352 DesktopEnvironment::from_xdg_current_desktop("X-Cinnamon"),
353 Some(DesktopEnvironment::Cinnamon)
354 );
355 assert_eq!(
356 DesktopEnvironment::from_xdg_current_desktop("XFCE"),
357 Some(DesktopEnvironment::Xfce)
358 );
359 assert_eq!(
360 DesktopEnvironment::from_xdg_current_desktop("TDE"),
361 Some(DesktopEnvironment::Tde)
362 );
363 assert_eq!(
364 DesktopEnvironment::from_xdg_current_desktop("DDE"),
365 Some(DesktopEnvironment::Dde)
366 );
367 assert_eq!(
368 DesktopEnvironment::from_xdg_current_desktop("Pantheon"),
369 Some(DesktopEnvironment::Pantheon)
370 );
371 assert_eq!(
372 DesktopEnvironment::from_xdg_current_desktop("SWAY"),
373 Some(DesktopEnvironment::Sway)
374 );
375 assert_eq!(
376 DesktopEnvironment::from_xdg_current_desktop("Hyprland"),
377 Some(DesktopEnvironment::Hyprland)
378 );
379 assert_eq!(
380 DesktopEnvironment::from_xdg_current_desktop("COSMIC"),
381 Some(DesktopEnvironment::CosmicEpoch)
382 );
383
384 // Colon splitting
385 assert_eq!(
386 DesktopEnvironment::from_xdg_current_desktop("ubuntu:GNOME"),
387 Some(DesktopEnvironment::Gnome)
388 );
389 assert_eq!(
390 DesktopEnvironment::from_xdg_current_desktop("ubuntu:KDE"),
391 Some(DesktopEnvironment::Kde)
392 );
393 assert_eq!(
394 DesktopEnvironment::from_xdg_current_desktop("pop:GNOME"),
395 Some(DesktopEnvironment::Gnome)
396 );
397
398 // Mixed messages
399 assert_eq!(
400 DesktopEnvironment::from_xdg_current_desktop("KDE:GNOME"),
401 None
402 );
403 assert_eq!(
404 DesktopEnvironment::from_xdg_current_desktop("ubuntu:KDE:GNOME"),
405 None
406 );
407
408 // Strange cases
409 assert_eq!(
410 DesktopEnvironment::from_xdg_current_desktop("GNOME:GNOME"),
411 Some(DesktopEnvironment::Gnome)
412 );
413
414 // Empty string
415 assert_eq!(
416 DesktopEnvironment::from_xdg_current_desktop(""),
417 None
418 );
419
420 // Unknown Desktop Environment
421 assert_eq!(
422 DesktopEnvironment::from_xdg_current_desktop("foo"),
423 None
424 );
425 }
426}