term_detect/
desktops.rs

1//! DE-specific terminal detection code  
2//!
3//! TODO: Merge similar code
4
5use crate::{DetectionError, Terminal};
6use configparser::ini::Ini;
7use std::{env::var, path::Path, process::Command};
8
9/// Spawn a process and return a trimmed string from stdout
10trait SpawnCleanStringify {
11    fn spawn_clean_stringify(&mut self) -> Result<String, DetectionError>;
12}
13
14impl SpawnCleanStringify for Command {
15    fn spawn_clean_stringify(&mut self) -> Result<String, DetectionError> {
16        let child = self.stdout(std::process::Stdio::piped()).spawn()?;
17        let output = child.wait_with_output()?;
18        Ok(String::from_utf8(output.stdout)
19            .unwrap_or_default()
20            .trim()
21            .to_owned())
22    }
23}
24
25fn get_home() -> String {
26    var("HOME").unwrap_or_default()
27}
28
29/// Get the default terminal property with gsettings from a specified desktop
30fn gsettings_desktop_get(desktop_name: &str) -> Result<Terminal, DetectionError> {
31    let mut maybe_term = Command::new("gsettings")
32        .arg("get")
33        .arg(format!(
34            "{}.desktop.default-applications.terminal",
35            desktop_name
36        ))
37        .arg("exec")
38        .spawn_clean_stringify()?;
39    if maybe_term.starts_with('\'') && maybe_term.ends_with('\'') && maybe_term.len() > 1 {
40        maybe_term = maybe_term[1..maybe_term.len() - 1].to_owned();
41    }
42    if maybe_term.is_empty() {
43        Err(DetectionError::DEFindError)
44    } else {
45        Ok(maybe_term.into())
46    }
47}
48
49/// Load an ini-like file and get a key from a section
50fn ini_get<P: AsRef<Path>>(
51    path: P,
52    section: &str,
53    key: &str,
54) -> Result<Option<String>, DetectionError> {
55    let mut config = Ini::new_cs();
56    if let Err(e) = config.load(path) {
57        return Err(DetectionError::IniParseError(e));
58    };
59    Ok(config.get(section, key))
60}
61
62/// Cinnamon detector
63///
64/// Same as GNOME except the id is "org.cinnamon" instead of "org.gnome"
65pub fn detect_terminal_desktop_cinnamon() -> Result<Terminal, DetectionError> {
66    gsettings_desktop_get("org.cinnamon")
67}
68
69/// Deepin detector
70///
71/// Same as GNOME except the id is "org.deepin" instead of "org.gnome"
72pub fn detect_terminal_desktop_deepin() -> Result<Terminal, DetectionError> {
73    gsettings_desktop_get("org.deepin")
74}
75
76/// GNOME and Budgie detector
77///
78/// GNOME stored its default terminal app in GConf  
79///
80/// This also works for Budgie, since it's a GNOME fork
81///
82/// This method is deprecated in GNOME and may not work in the future  
83pub fn detect_terminal_desktop_gnome() -> Result<Terminal, DetectionError> {
84    gsettings_desktop_get("org.gnome")
85}
86
87/// KDE Plasma detector
88///
89/// KDE Plasma 5 stores its default terminal config in ~/.config/kdeglobals  
90///
91/// If konsole is set as the default, then it's empty
92pub fn detect_terminal_desktop_kde() -> Result<Terminal, DetectionError> {
93    let maybe_term = Command::new("kreadconfig5")
94        .arg("--group")
95        .arg("General")
96        .arg("--key")
97        .arg("TerminalApplication")
98        .spawn_clean_stringify()?;
99    Ok(if maybe_term.is_empty() {
100        "konsole".into()
101    } else {
102        maybe_term.into()
103    })
104}
105
106/// LXDE detector
107///
108/// LXDE stores its config in ~/.config/lxsession/**NAME**/desktop.conf
109///
110/// where **NAME** is the desktop session name ex. "Lubuntu" or "LXDE"
111pub fn detect_terminal_desktop_lxde() -> Result<Terminal, DetectionError> {
112    let filename = format!(
113        "{}/.config/lxsession/{}/desktop.conf",
114        get_home(),
115        var("XDG_SESSION_DESKTOP").unwrap_or("LXDE".to_owned()),
116    );
117    let Some(term) = ini_get(filename, "Session", "terminal_manager/command")? else {
118        return Err(DetectionError::DEFindError);
119    };
120    Ok(term.into())
121}
122
123/// LXQt detector
124///
125/// It's kind of a mess. <https://github.com/lxqt/lxqt/issues/433>  
126///
127/// LXQt stores the terminal in the env variable LXQT_TERMINAL_EMULATOR
128/// OR in ~/.config/lxqt/session.conf
129pub fn detect_terminal_desktop_lxqt() -> Result<Terminal, DetectionError> {
130    if let Ok(term) = var("LXQT_TERMINAL_EMULATOR") {
131        return Ok(term.into());
132    }
133    let filename = format!("{}/.config/lxqt/session.conf", get_home());
134    let Some(term) = ini_get(filename, "Environment", "TERM")? else {
135        return Err(DetectionError::DEFindError);
136    };
137    Ok(term.into())
138}
139
140/// XFCE detector
141///
142/// In XFCE the default terminal is stored in ~/.config/xfce4/helpers.rc
143pub fn detect_terminal_desktop_xfce() -> Result<Terminal, DetectionError> {
144    let filename = format!("{}/.config/xfce4/helpers.rc", get_home());
145    Ok(ini_get(filename, "default", "TerminalEmulator")?
146        .unwrap_or("xfce4-terminal".to_owned())
147        .into())
148}