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}