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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
//! Get the user's current icon theme on Linux
//!
//! There isn't a unified standard for getting the current icon theme on Linux.
//! So linicon-theme attempts to check many places theme information might be
//! stored.  See [`get_icon_theme`](fn.get_icon_theme.html) for more details.
//!
//! ## Example
//! ```
//! use linicon_theme::get_icon_theme;
//! 
//! println!("Your current icon theme is: {}", get_icon_theme().unwrap());
//! ```
#![forbid(unsafe_code)]

use freedesktop_entry_parser::parse_entry;
use ini::Ini;
use std::{env, ffi::OsString, process::Command, str};

/// Get the user's current icon theme
///
/// There isn't a unified standard for getting the current icon theme on Linux.
/// So linicon-theme attempts to check many places theme information might be
/// stored.  The following places are checked in order.
///
/// - `$XDG_CONFIG_HOME/kdeglobals` -> Icons -> Theme
/// - Output of `gsettings get org.gnome.desktop.interface icon-theme`
/// - `$XDG_CONFIG_HOME/gtk-3.0/settings.ini` -> Settings -> gtk-icon-theme-name
/// - `$HOME/.gtkrc-2.0` -> gtk-icon-theme-name
/// - `$XDG_CONFIG_HOME/theme.conf` -> Settings -> icon-theme-name
///
/// Returns `None` if the theme can't be found for some reason.
pub fn get_icon_theme() -> Option<String> {
    get_icon_theme_order(&[
        Check::KDEGlobals,
        Check::GSettings,
        Check::GTK3,
        Check::GTK2,
        Check::ThemeConf,
    ])
}

/// The same as [`get_icon_theme`](fn.get_icon_theme.html) except
/// you can chose which icon theme locations are checked and in
/// what order.
pub fn get_icon_theme_order(order: &[Check]) -> Option<String> {
    let home_path = env::var_os("HOME")?;
    for check in order {
        match check {
            Check::KDEGlobals => match kde(home_path.clone()) {
                Some(s) => return Some(s),
                None => (),
            },
            Check::GSettings => match gsettings() {
                Some(s) => return Some(s),
                None => (),
            },
            Check::GTK3 => match gtk3(home_path.clone()) {
                Some(s) => return Some(s),
                None => (),
            },
            Check::GTK2 => match gtk2(home_path.clone()) {
                Some(s) => return Some(s),
                None => (),
            },
            Check::ThemeConf => match theme_conf(home_path.clone()) {
                Some(s) => return Some(s),
                None => (),
            },
        }
    }
    None
}

/// Select which theme store locations to check
pub enum Check {
    /// `$XDG_CONFIG_HOME/kdeglobals` -> Icons -> Theme
    KDEGlobals,
    /// Output of `gsettings get org.gnome.desktop.interface icon-theme`
    GSettings,
    /// `$XDG_CONFIG_HOME/gtk-3.0/settings.ini` -> Settings -> gtk-icon-theme-name
    GTK3,
    /// `$HOME/.gtkrc-2.0` -> gtk-icon-theme-name
    GTK2,
    /// `$XDG_CONFIG_HOME/theme.conf` -> Settings -> icon-theme-name
    ThemeConf,
}

fn kde(home_path: OsString) -> Option<String> {
    let mut path = conf_dir(home_path)?;
    path.push("/kdeglobals");
    let file = parse_entry(path).ok()?;
    file.section("Icons").attr("Theme").map(|s| s.to_owned())
}

fn gtk2(mut home_path: OsString) -> Option<String> {
    home_path.push("/.gtkrc-2.0");
    let file = Ini::load_from_file(home_path).ok()?;
    file.get_from::<String>(None, "gtk-icon-theme-name")
        .map(|s| s.to_owned())
}

fn gtk3(home_path: OsString) -> Option<String> {
    let mut path = conf_dir(home_path)?;
    path.push("/gtk-3.0/settings.ini");
    let file = Ini::load_from_file(path).ok()?;
    file.get_from(Some("Settings"), "gtk-icon-theme-name")
        .map(|s| s.to_owned())
}

fn gsettings() -> Option<String> {
    let output = Command::new("gsettings")
        .args(&["get", "org.gnome.desktop.interface", "icon-theme"])
        .output()
        .ok()?
        .stdout;
    let s = str::from_utf8(&output).ok()?;
    // Remove new line and quotes if present
    let s = match s.strip_suffix('\n') {
        Some(s) => s,
        None => s,
    };
    let s = match s.strip_prefix('\'') {
        Some(s) => s,
        None => s,
    };
    let s = match s.strip_suffix('\'') {
        Some(s) => s,
        None => s,
    };
    Some(s.to_owned())
}

fn theme_conf(home_path: OsString) -> Option<String> {
    let mut path = conf_dir(home_path)?;
    path.push("/theme.conf");
    let file = Ini::load_from_file(path).ok()?;
    file.get_from(Some("Settings"), "icon-theme-name")
        .map(|s| s.to_owned())
}

fn conf_dir(mut home_path: OsString) -> Option<OsString> {
    match env::var_os("XDG_CONFIG_HOME") {
        Some(s) => Some(s),
        None => {
            home_path.push("/.config");
            Some(home_path)
        }
    }
}

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

    #[test]
    fn kde_test() {
        let home_path = env::var_os("HOME").unwrap();
        let target_name = env::var("TEST_THEME").unwrap();
        assert_eq!(kde(home_path), Some(target_name));
    }

    #[test]
    fn gtk3_test() {
        let home_path = env::var_os("HOME").unwrap();
        let target_name = env::var("TEST_THEME").unwrap();
        assert_eq!(gtk3(home_path), Some(target_name));
    }

    #[test]
    fn gtk2_test() {
        let home_path = env::var_os("HOME").unwrap();
        let target_name = env::var("TEST_THEME").unwrap();
        assert_eq!(gtk2(home_path), Some(target_name));
    }

    #[test]
    fn gsettings_test() {
        let target_name = env::var("TEST_THEME").unwrap();
        assert_eq!(gsettings(), Some(target_name));
    }

    #[test]
    fn theme_conf_test() {
        let home_path = env::var_os("HOME").unwrap();
        let target_name = env::var("TEST_THEME").unwrap();
        assert_eq!(theme_conf(home_path), Some(target_name));
    }
}