Skip to main content

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}