xdgkit/icon_theme.rs
1/*!
2# Icon Theme
3
4This rustifies the freedesktop specifications for icon themes
5*/
6
7// icon_theme.rs
8// Rusified in 2021 Copyright Israel Dahl. All rights reserved.
9//
10// /VVVV\
11// /V V\
12// /V V\
13// / 0 0 \
14// \|\|\</\/\>/|/|/
15// \_/\_/
16//
17// This program is free software; you can redistribute it and/or modify
18// it under the terms of the GNU General Public License version 2 as
19// published by the Free Software Foundation.
20//
21// This program is distributed in the hope that it will be useful,
22// but WITHOUT ANY WARRANTY; without even the implied warranty of
23// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
24// GNU General Public License for more details.
25//
26// You should have received a copy of the GNU General Public License
27// along with this program; if not, write to the Free Software
28// Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
29
30
31extern crate tini;
32use tini::Ini;
33use std::path::PathBuf;
34
35use crate::utils::to_bool;
36use crate::utils::to_int;
37//use std::fmt;
38
39/// ## Type
40///
41/// (a.k.a DirectoryType/xdg_type)
42///
43/// The type of icon sizes for the icons in this directory.
44/// Valid types are:
45/// * Fixed
46/// * Scalable
47/// * Threshold
48///
49/// The type decides what other keys in the section are used. If not specified, the **default is Threshold**.
50#[derive(Debug, Clone, Copy)]
51#[allow(dead_code)]
52pub enum DirectoryType {
53 Fixed,
54 Scalable,
55 Threshold,
56}
57
58/// ## Context
59///
60/// The Context allows the designer to group icons on a conceptual level. It doesn't act as a namespace in the file system, such that icons can have identical names, but allows implementations to categorize and sort by it, for example.
61///
62/// These are the available contexts:
63/// * Actions. Icons representing actions which the user initiates, such as Save As.
64/// * Devices. Icons representing real world devices, such as printers and mice. It's not for file system nodes such as character or block devices.
65/// * FileSystems. Icons for objects which are represented as part of the file system. This is for example, the local network, “Home”, and “Desktop” folders.
66/// * MimeTypes. Icons representing MIME types.
67#[derive(Debug, Clone, Copy)]
68#[allow(dead_code)]
69pub enum IconContext {
70 Actions,
71 Devices,
72 FileSystems,
73 MimeTypes,
74 /// There is no `default` for Context here, so I'll make an `Unknown` similar to Desktop Entry's `type`
75 Unknown,
76}
77/// # Overview
78///
79/// An icon theme is a set of icons that share a common look and feel. The user can then select the icon theme that they want to use, and all apps use icons from the theme. The initial user of icon themes is the icon field of the desktop file specification, but in the future it can have other uses (such as mimetype icons).
80///
81/// From a programmer perspective an icon theme is just a mapping. Given a set of directories to look for icons in and a theme name it maps from icon name and nominal icon size to an icon filename.
82/// ## Definitions
83///
84/// #### Icon Theme
85///
86/// An icon theme is a named set of icons. It is used to map from an iconname and size to a file. Themes may inherit from other themes as a way to extend them.
87///
88/// #### Icon file
89///
90/// An icon file is an image that can be loaded and used as an icon. The supported image file formats are PNG, XPM and SVG. PNG is the recommended bitmap format, and SVG is for vectorized icons. XPM is supported due to backwards compability reasons, and it is not recommended that new themes use XPM files. Support for SVGs is optional.
91///
92/// #### Base Directory
93///
94/// Icons and themes are searched for in a set of directories, called base directories. The themes are stored in subdirectories of the base directories.
95///
96/// #### Icon scale
97///
98/// On very high density (high dpi) screens the UI is often scaled to avoid the UI being so small it is hard to see. In order to support this icons can have a target scale, describing what scale factor they are designed for.
99///
100/// For instance, an icon with a directory size of 48 but scale 2x would be 96x96 pixels, but designed to have the same level of detail as a 48x48 icon at scale 1x. This can be used on high density displays where a 48x48 icon would be too small (or ugly upscaled) and a normal 96x96 icon would have a lot of detail that is hard to see.
101/// ## Directory Layout
102///
103/// Icons and themes are looked for in a set of directories. By default, apps should look in `$HOME/.icons` (for backwards compatibility), in `$XDG_DATA_DIRS/icons` and in `/usr/share/pixmaps` (in that order). Applications may further add their own icon directories to this list, and users may extend or change the list (in application/desktop specific ways).In each of these directories themes are stored as subdirectories. A theme can be spread across several base directories by having subdirectories of the same name. This way users can extend and override system themes.
104///
105/// In order to have a place for third party applications to install their icons there should always exist a theme called ["hicolor"](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#ftn.idm44938525353648). The data for the hicolor theme is available for download at: <http://www.freedesktop.org/software/icon-theme/>. Implementations are required to look in the "hicolor" theme if an icon was not found in the current theme.
106///
107/// Each theme is stored as subdirectories of the base directories. The internal name of the theme is the name of the subdirectory, although the user-visible name as specified by the theme may be different. Hence, theme names are case sensitive, and are limited to ASCII characters. Theme names may also not contain comma or space.
108///
109/// In at least one of the theme directories there must be a file called index.theme that describes the theme. The first index.theme found while searching the base directories in order is used. This file describes the general attributes of the theme.
110///
111/// In the theme directory are also a set of subdirectories containing image files. Each directory contains icons designed for a certain nominal icon size and scale, as described by the index.theme file. The subdirectories are allowed to be several levels deep, e.g. the subdirectory "48x48/apps" in the theme "hicolor" would end up at $basedir/hicolor/48x48/apps.
112///
113/// The image files must be one of the types: PNG, XPM, or SVG, and the extension must be ".png", ".xpm", or ".svg" (lower case). The support for SVG files is optional. Implementations that do not support SVGs should just ignore any ".svg" files. In addition to this there may be an additional file with extra icon-data for each file. It should have the same basename as the image file, with the extension ".icon". e.g. if the icon file is called "mime_source_c.png" the corresponding file would be named "mime_source_c.icon".
114/// #File Formats
115/// Both the icon theme description file and the icon data files are ini-style text files, as described in the desktop file specification. They don't have any encoding field. Instead, they must **always be stored in UTF-8 encoding**.
116///
117/// The `index.theme` file must start with a section called `[Icon Theme]`, with contents according to the items below. All lists in the ini file, are to be comma-separated.
118#[derive(Debug, Clone)]
119#[allow(dead_code)]
120pub struct Directory {
121 /// **[REQUIRED BY SPECS]** directory name
122 pub name:Option<String>,
123 /// Nominal (unscaled) size of the icons in this directory.
124 pub size:Option<i32>,
125 /// **[REQUIRED BY SPECS]** Target scale of of the icons in this directory. Defaults to the value `1` if not present. Any directory with a scale other than `1` should be listed in the `scaled_directories` list rather than `directories` for backwards compatibility.
126 pub scale:Option<i32>,
127 /// The context the icon is normally used in. See: [Context](https://specifications.freedesktop.org/icon-theme-spec/icon-theme-spec-latest.html#context)
128 pub context:Option<IconContext>,
129 /// The type of icon sizes for the icons in this directory. Valid types are `Fixed`, `Scalable` and `Threshold`. The type decides what other keys in the section are used. If not specified, the default is `Threshold`.
130 pub xdg_type:Option<DirectoryType>,
131 /// Specifies the maximum (unscaled) size that the icons in this directory can be scaled to. Defaults to the value of `size` if not present.
132 pub max_size:Option<i32>,
133 /// Specifies the minimum (unscaled) size that the icons in this directory can be scaled to. Defaults to the value of `size` if not present.
134 pub min_size:Option<i32>,
135 /// The icons in this directory can be used if the size differ at most this much from the desired (unscaled) size. Defaults to `2` if not present.
136 pub threshold:Option<i32>,
137}
138impl Directory{
139 /// Convert an `Option<String>` to a `DirectoryType`
140 #[allow(dead_code)]
141 pub fn convert_xdg_type(directory_type:Option<String>)->Option<DirectoryType> {
142 if let Some(dt) = directory_type {
143 if dt == "Fixed" {
144 return Some(DirectoryType::Fixed)
145 }
146 else if dt == "Scalable" {
147 return Some(DirectoryType::Scalable)
148 }
149 }
150 Some(DirectoryType::Threshold)
151 }
152
153 /// This function will return a Some(String) from a DirectoryType **Threshold is returned by default**
154 #[allow(dead_code)]
155 pub fn string_xdg_type(dt:DirectoryType)->Option<String> {
156 match dt {
157 DirectoryType::Fixed => Some(String::from("Fixed")),
158 DirectoryType::Scalable => Some(String::from("Scalable")),
159 _ => Some(String::from("Threshold")),
160 }
161 }
162 /// Convert an `Option<String>` to an `Option<IconContext>`
163 #[allow(dead_code)]
164 pub fn context(icon_context:Option<String>)->Option<IconContext> {
165 if let Some(ic) = icon_context {
166 if ic == "Actions" {
167 return Some(IconContext::Actions)
168 }
169 else if ic == "Devices" {
170 return Some(IconContext::Devices)
171 }
172 else if ic == "FileSystems" {
173 return Some(IconContext::FileSystems)
174 }
175 else if ic == "MimeTypes" {
176 return Some(IconContext::MimeTypes)
177 }
178 }
179 // This was added to mimic Desktop Entry behavior
180 Some(IconContext::Unknown)
181 }
182}
183/// Makes an `Option<Vec<Directory>>` from an `Option<Vec<String>>` of directories in the `Directories=` field
184#[allow(dead_code)]
185pub fn make_directories(dirs:Option<Vec<String>>, file_string:String)->Option<Vec<Directory>> {
186 let mut result:Vec<Directory> = Vec::new();
187 dirs.as_ref()?;
188 let test_ini = Ini::from_string(file_string);
189 if test_ini.is_err() {
190 return None
191 }
192 let conf = test_ini.unwrap();
193 let directories = dirs.unwrap();
194
195 for dir in directories{
196 if !dir.is_empty() {
197 let section = dir.as_str();
198 let mut nom:Option<String> = conf.get(section, "Name");
199 if nom.is_none() {
200 nom = Some(String::from(section));
201 }
202 let sz:Option<String> = conf.get(section, "Size");
203 let scl:Option<String> = conf.get(section, "Scale");
204 let cntxt:Option<String> = conf.get(section, "Context");
205 let x_type:Option<String> = conf.get(section, "Type");
206 let max_sz:Option<String> = conf.get(section, "MaxSize");
207 let min_sz:Option<String> = conf.get(section, "MinSize");
208 let thresh:Option<String> = conf.get(section, "Threshold");
209 result.push(
210 Directory {
211 name:nom,
212 size:to_int(sz),
213 scale:to_int(scl),
214 context:Directory::context(cntxt),
215 xdg_type:Directory::convert_xdg_type(x_type),
216 max_size:to_int(max_sz),
217 min_size:to_int(min_sz),
218 threshold:to_int(thresh),
219 }
220 );
221 }
222 }
223 Some(result)
224}
225#[derive(Debug, Clone)]
226#[allow(dead_code)]
227pub struct IconTheme {
228 /// **[REQUIRED BY SPECS]** short name of the icon theme, used in e.g. lists when selecting themes.
229 pub name:Option<String>,
230 /// **[REQUIRED BY SPECS]** longer string describing the theme
231 pub comment:Option<String>,
232 /// The name of the theme that this theme inherits from. If an icon name is not found in the current theme, it is searched for in the inherited theme (and recursively in all the inherited themes). If no theme is specified implementations are required to add the "hicolor" theme to the inheritance tree. An implementation may optionally add other default themes in between the last specified theme and the hicolor theme.
233 pub inherits:Option<Vec<String>>,
234 /// **[REQUIRED BY SPECS]** list of subdirectories for this theme. For every subdirectory there must be a section in the `index.theme` file describing that directory.
235 pub directories:Option<Vec<Directory>>,
236 /// Additional list of subdirectories for this theme, in addition to the ones in Directories. These directories should only be read by implementations supporting scaled directories and was added to keep compatibility with old implementations that don't support these.
237 pub scaled_directories:Option<Vec<String>>,
238 /// Whether to hide the theme in a theme selection user interface. This is used for things such as fallback-themes that are not supposed to be visible to the user.
239 pub hidden:Option<bool>,
240 /// The name of an icon that should be used as an example of how this theme looks.
241 pub example:Option<String>,
242}
243
244
245
246/// Implementations
247impl IconTheme {
248 /// Creates a blank theme, **not according to specs**
249 ///
250 /// You **must** specify a `name`,`comment`, and `directories` to be inline with the specs
251 #[allow(dead_code)]
252 pub fn empty()->Self where Self:Sized {
253 IconTheme {
254 name:None,
255 comment:None,
256 inherits:None,
257 directories:None,
258 scaled_directories:None,
259 hidden:None,
260 example:None,
261 }
262 }
263 pub fn from_pathbuff(file_name:PathBuf)->Self where Self:Sized {
264 let filename:String = match file_name.as_path().to_str() {
265 Some(t) => String::from(t),
266 None => String::from(""),
267 };
268 Self::new(filename)
269 }
270
271 #[allow(dead_code)]
272 /// Creates a new struct from a full desktop file path
273 pub fn new(file_name:String)->Self where Self:Sized {
274 if file_name.is_empty() { return Self::empty() }
275 let test_ini = Ini::from_file(&file_name);
276 if test_ini.is_err() {
277 println!("___________________\nERROR: {:?}\nin file:{}\n___________________",test_ini,file_name);
278 return Self::empty()
279 }
280 let conf = test_ini.unwrap();
281
282 let section = "Icon Theme";
283 //Populate our struct
284 let nom:Option<String> = conf.get(section, "Name");
285 let comm:Option<String> = conf.get(section, "Comment");
286 let hid:Option<String> = conf.get(section, "Hidden");
287 let ex:Option<String> = conf.get(section, "Example");
288 let inh:Option<Vec<String>> = conf.get_vec_with_sep(section, "Inherits",",");
289 let dirs:Option<Vec<String>> = conf.get_vec_with_sep(section, "Directories",",");
290 let scaled:Option<Vec<String>> = conf.get_vec_with_sep(section, "ScaledDirectories",",");
291
292 IconTheme {
293 name:nom,
294 comment:comm,
295 inherits:inh,
296 directories:make_directories(dirs, conf.to_string()),
297 scaled_directories:scaled,
298 hidden:to_bool(hid),
299 example:ex,
300 }
301 }
302}