term_detect/
detector.rs

1//! Contains code that actually detects the default terminal emulator
2//!
3//! XDG and DE-specific code is in a separate file
4
5pub use crate::xdg::detect_terminal_xdg_proposed;
6use crate::{desktops::*, DetectionError, Terminal};
7use std::{env::var, path::Path};
8
9/// Read `TERMINAL` environment variable and turn into `Terminal` if successful
10pub fn detect_terminal_env() -> Result<Terminal, DetectionError> {
11    let term = var("TERMINAL")?;
12    Ok(term.into())
13}
14
15/// Detect the DE and run the appropriate code to detect the terminal
16pub fn detect_terminal_desktop() -> Result<Terminal, DetectionError> {
17    let desktop = var("XDG_CURRENT_DESKTOP")?;
18    // NOTE: Ubuntu prepends itself before GNOME
19    let desktop = desktop.split(':').last().unwrap_or_default();
20    match desktop {
21        "Deepin" => detect_terminal_desktop_deepin(),
22        "GNOME" => detect_terminal_desktop_gnome(),
23        "KDE" => detect_terminal_desktop_kde(),
24        "LXDE" => detect_terminal_desktop_lxde(),
25        "LXQt" => detect_terminal_desktop_lxqt(),
26        "X-Cinnamon" => detect_terminal_desktop_cinnamon(),
27        "XFCE" => detect_terminal_desktop_xfce(),
28        _ => Err(DetectionError::UnsupportedDesktopError),
29    }
30}
31
32pub(crate) fn path_check(path_var: &Vec<&str>, term: &str) -> Option<String> {
33    // Check every single path in $PATH
34    for path in path_var {
35        let bin = format!("{}/{}", path, term);
36        let bin_path = Path::new(&bin);
37        if bin_path.exists() {
38            return Some(term.to_owned());
39        }
40    }
41    None
42}
43
44/// Check if a terminal from a list is in `$PATH`
45pub fn detect_terminal_path() -> Result<Terminal, DetectionError> {
46    let path_var = var("PATH").unwrap_or_default();
47    let path_var: Vec<&str> = path_var.split(':').collect();
48    for term in &[
49        "x-terminal-emulator", // Debian specific
50        "konsole",
51        "gnome-terminal",
52        "alacritty",
53        "terminator",
54        "st",
55        "urxvt",
56    ] {
57        if let Some(term) = path_check(&path_var, term) {
58            return Ok(term.into());
59        }
60    }
61    Err(DetectionError::FindError)
62}
63
64/// Run other functions in this module to find the terminal
65#[cfg(target_os = "linux")]
66pub fn detect_terminal() -> Result<Terminal, DetectionError> {
67    let mut errors = Vec::new();
68    macro_rules! return_or_add {
69        ($e:expr) => {
70            match $e {
71                Ok(term) => return Ok(term),
72                Err(e) => errors.push(e),
73            }
74        };
75    }
76
77    // Method 1: TERMINAL environment variable
78    return_or_add!(detect_terminal_env());
79    // Method 2: (proposed) XDG spec
80    return_or_add!(detect_terminal_xdg_proposed());
81    // Method 3: Desktop-specific settings
82    return_or_add!(detect_terminal_desktop());
83    // Method 4: Detection within paths
84    return_or_add!(detect_terminal_path());
85
86    Err(errors.into())
87}
88
89#[cfg(not(target_os = "linux"))]
90fn detect_terminal() -> Result<String, DetectionError> {
91    panic!("unsupported os")
92}
93
94#[cfg(test)]
95mod tests {
96    use super::*;
97
98    #[test]
99    fn env_test() {
100        let term = Terminal::from("abcterm");
101        std::env::set_var("TERMINAL", &term.0);
102        assert!(detect_terminal_env().unwrap() == term);
103    }
104    // TODO: Add more tests
105}