linicon_theme/
lib.rs

1//! Get the user's current icon theme on Linux
2//!
3//! There isn't a unified standard for getting the current icon theme on Linux.
4//! So linicon-theme attempts to check many places theme information might be
5//! stored.  See [`get_icon_theme`](fn.get_icon_theme.html) for more details.
6//!
7//! ## Example
8//! ```
9//! use linicon_theme::get_icon_theme;
10//!
11//! println!("Your current icon theme is: {}", get_icon_theme().unwrap());
12//! ```
13#![forbid(unsafe_code)]
14
15use freedesktop_entry_parser::parse_entry;
16use ini::Ini;
17use std::{env, ffi::OsString, process::Command, str};
18
19/// Get the user's current icon theme
20///
21/// There isn't a unified standard for getting the current icon theme on Linux.
22/// So linicon-theme attempts to check many places theme information might be
23/// stored.  The following places are checked in order.
24///
25/// - `$XDG_CONFIG_HOME/kdeglobals` -> Icons -> Theme
26/// - Output of `gsettings get org.gnome.desktop.interface icon-theme`
27/// - `$XDG_CONFIG_HOME/gtk-3.0/settings.ini` -> Settings -> gtk-icon-theme-name
28/// - `$HOME/.gtkrc-2.0` -> gtk-icon-theme-name
29/// - `$XDG_CONFIG_HOME/theme.conf` -> Settings -> icon-theme-name
30///
31/// Returns `None` if the theme can't be found for some reason.
32pub fn get_icon_theme() -> Option<String> {
33    get_icon_theme_order(&[
34        Check::KDEGlobals,
35        Check::GSettings,
36        Check::GTK3,
37        Check::GTK2,
38        Check::ThemeConf,
39    ])
40}
41
42/// The same as [`get_icon_theme`](fn.get_icon_theme.html) except
43/// you can chose which icon theme locations are checked and in
44/// what order.
45pub fn get_icon_theme_order(order: &[Check]) -> Option<String> {
46    let home_path = env::var_os("HOME")?;
47    for check in order {
48        match check {
49            Check::KDEGlobals => {
50                if let Some(s) = kde(home_path.clone()) {
51                    return Some(s);
52                }
53            }
54            Check::GSettings => {
55                if let Some(s) = gsettings() {
56                    return Some(s);
57                }
58            }
59            Check::GTK3 => {
60                if let Some(s) = gtk3(home_path.clone()) {
61                    return Some(s);
62                }
63            }
64            Check::GTK2 => {
65                if let Some(s) = gtk2(home_path.clone()) {
66                    return Some(s);
67                }
68            }
69            Check::ThemeConf => {
70                if let Some(s) = theme_conf(home_path.clone()) {
71                    return Some(s);
72                }
73            }
74        }
75    }
76    None
77}
78
79/// Select which theme store locations to check
80pub enum Check {
81    /// `$XDG_CONFIG_HOME/kdeglobals` -> Icons -> Theme
82    KDEGlobals,
83    /// Output of `gsettings get org.gnome.desktop.interface icon-theme`
84    GSettings,
85    /// `$XDG_CONFIG_HOME/gtk-3.0/settings.ini` -> Settings -> gtk-icon-theme-name
86    GTK3,
87    /// `$HOME/.gtkrc-2.0` -> gtk-icon-theme-name
88    GTK2,
89    /// `$XDG_CONFIG_HOME/theme.conf` -> Settings -> icon-theme-name
90    ThemeConf,
91}
92
93fn kde(home_path: OsString) -> Option<String> {
94    let mut path = conf_dir(home_path)?;
95    path.push("/kdeglobals");
96    let file = parse_entry(path).ok()?;
97    file.section("Icons").attr("Theme").map(|s| s.to_owned())
98}
99
100fn gtk2(mut home_path: OsString) -> Option<String> {
101    home_path.push("/.gtkrc-2.0");
102    let file = Ini::load_from_file(home_path).ok()?;
103    file.get_from::<String>(None, "gtk-icon-theme-name")
104        .map(|s| s.to_owned())
105}
106
107fn gtk3(home_path: OsString) -> Option<String> {
108    let mut path = conf_dir(home_path)?;
109    path.push("/gtk-3.0/settings.ini");
110    let file = Ini::load_from_file(path).ok()?;
111    file.get_from(Some("Settings"), "gtk-icon-theme-name")
112        .map(|s| s.to_owned())
113}
114
115fn gsettings() -> Option<String> {
116    let output = Command::new("gsettings")
117        .args(&["get", "org.gnome.desktop.interface", "icon-theme"])
118        .output()
119        .ok()?
120        .stdout;
121    let s = str::from_utf8(&output).ok()?;
122    // Remove new line and quotes if present
123    let s = match s.strip_suffix('\n') {
124        Some(s) => s,
125        None => s,
126    };
127    let s = match s.strip_prefix('\'') {
128        Some(s) => s,
129        None => s,
130    };
131    let s = match s.strip_suffix('\'') {
132        Some(s) => s,
133        None => s,
134    };
135    Some(s.to_owned())
136}
137
138fn theme_conf(home_path: OsString) -> Option<String> {
139    let mut path = conf_dir(home_path)?;
140    path.push("/theme.conf");
141    let file = Ini::load_from_file(path).ok()?;
142    file.get_from(Some("Settings"), "icon-theme-name")
143        .map(|s| s.to_owned())
144}
145
146fn conf_dir(mut home_path: OsString) -> Option<OsString> {
147    match env::var_os("XDG_CONFIG_HOME") {
148        Some(s) => Some(s),
149        None => {
150            home_path.push("/.config");
151            Some(home_path)
152        }
153    }
154}
155
156#[cfg(test)]
157mod tests {
158    use super::*;
159
160    #[test]
161    fn kde_test() {
162        let home_path = env::var_os("HOME").unwrap();
163        let target_name = env::var("TEST_THEME").unwrap();
164        assert_eq!(kde(home_path), Some(target_name));
165    }
166
167    #[test]
168    fn gtk3_test() {
169        let home_path = env::var_os("HOME").unwrap();
170        let target_name = env::var("TEST_THEME").unwrap();
171        assert_eq!(gtk3(home_path), Some(target_name));
172    }
173
174    #[test]
175    fn gtk2_test() {
176        let home_path = env::var_os("HOME").unwrap();
177        let target_name = env::var("TEST_THEME").unwrap();
178        assert_eq!(gtk2(home_path), Some(target_name));
179    }
180
181    #[test]
182    fn gsettings_test() {
183        let target_name = env::var("TEST_THEME").unwrap();
184        assert_eq!(gsettings(), Some(target_name));
185    }
186
187    #[test]
188    fn theme_conf_test() {
189        let home_path = env::var_os("HOME").unwrap();
190        let target_name = env::var("TEST_THEME").unwrap();
191        assert_eq!(theme_conf(home_path), Some(target_name));
192    }
193}