1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
//! Contains code that actually detects the default terminal emulator  
//! DE-specific code is in a separate file

use crate::{desktops::*, DetectionError, Terminal};
use std::{env::var, path::Path};

/// Read `TERMINAL` environment variable and turn into `Terminal` if successful
pub fn detect_terminal_env() -> Result<Terminal, DetectionError> {
    let term = var("TERMINAL")?;
    Ok(term.into())
}

/// Detect the DE and run the appropriate code to detect the terminal
pub fn detect_terminal_desktop() -> Result<Terminal, DetectionError> {
    let desktop = var("XDG_CURRENT_DESKTOP")?;
    // NOTE: Ubuntu prepends itself before GNOME
    let desktop = desktop.split(':').last().unwrap_or_default();
    match desktop {
        "GNOME" => detect_terminal_desktop_gnome(),
        "KDE" => detect_terminal_desktop_kde(),
        "LXDE" => detect_terminal_desktop_lxde(),
        "LXQt" => detect_terminal_desktop_lxqt(),
        "X-Cinnamon" => detect_terminal_desktop_cinnamon(),
        "XFCE" => detect_terminal_desktop_xfce(),
        _ => Err(DetectionError::UnsupportedDesktopError),
    }
}

pub(crate) fn path_check(path_var: &Vec<&str>, term: &str) -> Option<String> {
    // Check every single path in $PATH
    for path in path_var {
        let bin = format!("{}/{}", path, term);
        let bin_path = Path::new(&bin);
        if bin_path.exists() {
            return Some(term.to_owned());
        }
    }
    None
}

/// Check if a terminal from a list is in `$PATH`
pub fn detect_terminal_path() -> Result<Terminal, DetectionError> {
    let path_var = var("PATH").unwrap_or_default();
    let path_var: Vec<&str> = path_var.split(':').collect();
    for term in &[
        "x-terminal-emulator", // Debian specific
        "konsole",
        "gnome-terminal",
        "alacritty",
        "terminator",
        "st",
        "urxvt",
    ] {
        if let Some(term) = path_check(&path_var, term) {
            return Ok(term.into());
        }
    }
    Err(DetectionError::FindError)
}

/// Run other functions in this module to find the terminal
#[cfg(target_os = "linux")]
pub fn detect_terminal() -> Result<Terminal, DetectionError> {
    let mut errors = Vec::new();
    macro_rules! return_or_add {
        ($e:expr) => {
            match $e {
                Ok(term) => return Ok(term),
                Err(e) => errors.push(e),
            }
        };
    }

    // Method 1: TERMINAL environment variable
    return_or_add!(detect_terminal_env());
    // Method 2: XDG
    // TODO: Add XDG method
    // Method 3: Desktop-specific settings
    return_or_add!(detect_terminal_desktop());
    // Method 4: Detection within paths
    return_or_add!(detect_terminal_path());

    Err(errors.into())
}

#[cfg(not(target_os = "linux"))]
fn detect_terminal() -> Result<String, DetectionError> {
    panic!("unsupported os")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn env_test() {
        let term = Terminal::from("abcterm");
        std::env::set_var("TERMINAL", &term.0);
        assert!(detect_terminal_env().unwrap() == term);
    }
    // TODO: Add more tests
}