icon_loader/
theme_name_provider.rs

1pub mod error;
2
3use std::{borrow::ToOwned, error::Error as StdError};
4
5use error::{Error, Result};
6
7/// Enum that provides a theme name to [`IconLoader`](crate::IconLoader).
8/// It can either load the system theme name from the KDE or GTK config files
9/// or provide a fixed string or a theme name yielded by a completely customizable function.
10/// The last option allows users to load their own config files for example.
11pub enum ThemeNameProvider {
12    /// Use the '~/.config/kdeglobals' file to determine the theme name.
13    #[cfg(feature = "kde")]
14    KDE,
15
16    /// Use the '~/.config/gtk-3.0/settings.ini' file to determine the theme name.
17    #[cfg(feature = "gtk")]
18    GTK,
19
20    /// A theme name provided by the user.
21    User(String),
22
23    /// A custom function that returns a theme name or an error.
24    Custom(
25        Box<dyn Fn() -> std::result::Result<String, Box<dyn StdError + Send + Sync>> + Send + Sync>,
26    ),
27}
28
29impl ThemeNameProvider {
30    /// Creates a new `ThemeNameProvider` that provides the given string as theme name.
31    pub fn user(string: impl Into<String>) -> Self {
32        ThemeNameProvider::User(string.into())
33    }
34
35    /// Creates a new custom `ThemeNameProvider` from the given function.
36    pub fn custom<F, S, E>(f: F) -> Self
37    where
38        F: Fn() -> std::result::Result<S, E> + Send + Sync + 'static,
39        S: Into<String>,
40        E: StdError + Send + Sync + 'static,
41    {
42        ThemeNameProvider::Custom(Box::new(move || f().map(Into::into).map_err(Into::into)))
43    }
44
45    pub(crate) fn theme_name(&self) -> Result<String> {
46        match self {
47            #[cfg(feature = "kde")]
48            ThemeNameProvider::KDE => {
49                let base_dirs = xdg::BaseDirectories::new();
50
51                if base_dirs.find_config_file("kdeglobals").is_none() {
52                    return Err(Error::ConfigNotFound);
53                }
54
55                for config_path in base_dirs.find_config_files("kdeglobals") {
56                    let config = ini::Ini::load_from_file(config_path)?;
57
58                    for (category, properties) in config.iter() {
59                        if let Some("Icons") = category {
60                            for (key, value) in properties.iter() {
61                                if key == "Theme" {
62                                    return Ok(value.to_string());
63                                }
64                            }
65                        }
66                    }
67                }
68
69                Err(Error::ConfigMissingThemeName)
70            }
71
72            #[cfg(feature = "gtk")]
73            ThemeNameProvider::GTK => {
74                let base_dirs = xdg::BaseDirectories::new();
75
76                if base_dirs.find_config_file("gtk-3.0/settings.ini").is_none() {
77                    return Err(Error::ConfigNotFound);
78                }
79
80                for config_path in base_dirs.find_config_files("gtk-3.0/settings.ini") {
81                    let config = ini::Ini::load_from_file(config_path)?;
82
83                    for (category, properties) in config.iter() {
84                        if let Some("Settings") = category {
85                            for (key, value) in properties.iter() {
86                                if key == "gtk-icon-theme-name" {
87                                    return Ok(value.to_string());
88                                }
89                            }
90                        }
91                    }
92                }
93
94                Err(Error::ConfigMissingThemeName)
95            }
96
97            ThemeNameProvider::User(string) => Ok(string.clone()),
98            ThemeNameProvider::Custom(func) => func().map_err(|source| Error::Custom { source }),
99        }
100    }
101}
102
103impl std::fmt::Debug for ThemeNameProvider {
104    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
105        match self {
106            #[cfg(feature = "kde")]
107            ThemeNameProvider::KDE => write!(f, "ThemeNameProvider::KDE"),
108
109            #[cfg(feature = "gtk")]
110            ThemeNameProvider::GTK => write!(f, "ThemeNameProvider::GTK"),
111
112            ThemeNameProvider::User(string) => write!(f, "ThemeNameProvider::User({})", string),
113            ThemeNameProvider::Custom(_) => write!(f, "ThemeNameProvider::Custom"),
114        }
115    }
116}
117
118impl Default for ThemeNameProvider {
119    fn default() -> Self {
120        ThemeNameProvider::User(String::from("hicolor"))
121    }
122}
123
124impl PartialEq for ThemeNameProvider {
125    fn eq(&self, other: &Self) -> bool {
126        use std::mem::discriminant;
127
128        if discriminant(self) != discriminant(other) {
129            return false;
130        }
131
132        if let ThemeNameProvider::Custom(_) = self {
133            return false;
134        }
135
136        if let (ThemeNameProvider::User(string), ThemeNameProvider::User(other_string)) =
137            (self, other)
138        {
139            return string == other_string;
140        }
141
142        true
143    }
144}
145
146impl<F, S, E> From<F> for ThemeNameProvider
147where
148    F: Fn() -> std::result::Result<S, E> + Send + Sync + 'static,
149    S: Into<String>,
150    E: StdError + Send + Sync + 'static,
151{
152    fn from(f: F) -> Self {
153        ThemeNameProvider::custom(f)
154    }
155}
156
157impl From<&str> for ThemeNameProvider {
158    fn from(string: &str) -> Self {
159        ThemeNameProvider::User(string.to_owned())
160    }
161}
162
163impl From<String> for ThemeNameProvider {
164    fn from(string: String) -> Self {
165        ThemeNameProvider::User(string)
166    }
167}