feat: implement base freedesktop-icon lookup
This commit is contained in:
parent
d70d90dcf8
commit
6515a02881
6 changed files with 426 additions and 92 deletions
|
|
@ -9,7 +9,9 @@ edition = "2021"
|
||||||
dirs = "4.0.0"
|
dirs = "4.0.0"
|
||||||
rust-ini = "0.18.0"
|
rust-ini = "0.18.0"
|
||||||
thiserror = "1.0.31"
|
thiserror = "1.0.31"
|
||||||
|
once_cell = "1.10.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
speculoos = "0.9.0"
|
speculoos = "0.9.0"
|
||||||
anyhow = "1.0.57"
|
anyhow = "1.0.57"
|
||||||
|
linicon = "2.3.0"
|
||||||
53
src/lib.rs
53
src/lib.rs
|
|
@ -1 +1,54 @@
|
||||||
|
use crate::theme::{try_build_icon_path, FALL_BACK_THEMES, THEMES};
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
pub mod theme;
|
pub mod theme;
|
||||||
|
|
||||||
|
pub fn lookup(name: &str, size: u16, scale: u16, theme: &str) -> Option<PathBuf> {
|
||||||
|
if let Some(theme) = THEMES.get(theme) {
|
||||||
|
let icon = theme.try_get_icon(name, size, scale);
|
||||||
|
if icon.is_some() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for theme in FALL_BACK_THEMES.iter() {
|
||||||
|
let icon = theme.try_get_icon(name, size, scale);
|
||||||
|
if icon.is_some() {
|
||||||
|
return icon;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try_build_icon_path(name, "/usr/share/pixmaps")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::lookup;
|
||||||
|
use speculoos::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compare_to_linincon() {
|
||||||
|
let lin_wireshark = linicon::lookup_icon("wireshark")
|
||||||
|
.next()
|
||||||
|
.unwrap()
|
||||||
|
.unwrap()
|
||||||
|
.path;
|
||||||
|
|
||||||
|
let wireshark = lookup("wireshark", 16, 1, "Papirus");
|
||||||
|
|
||||||
|
assert_that!(wireshark).is_some().is_equal_to(lin_wireshark)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn compare_to_linicon_in_pixmap() {
|
||||||
|
let archlinux_logo = linicon::lookup_icon("archlinux-logo").next();
|
||||||
|
|
||||||
|
assert_that!(archlinux_logo).is_some();
|
||||||
|
|
||||||
|
let archlinux_logo = lookup("archlinux-logo", 16, 1, "Papirus");
|
||||||
|
|
||||||
|
assert_that!(archlinux_logo)
|
||||||
|
.is_some()
|
||||||
|
.has_file_name("/usr/share/pixmaps/archlinux-logo.png");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
84
src/theme/directories.rs
Normal file
84
src/theme/directories.rs
Normal file
|
|
@ -0,0 +1,84 @@
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Directory<'a> {
|
||||||
|
pub name: &'a str,
|
||||||
|
pub size: i16,
|
||||||
|
pub scale: i16,
|
||||||
|
pub context: Option<&'a str>,
|
||||||
|
pub type_: DirectoryType,
|
||||||
|
pub maxsize: i16,
|
||||||
|
pub minsize: i16,
|
||||||
|
pub threshold: i16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Directory<'_> {
|
||||||
|
pub fn match_size(&self, size: u16, scale: u16) -> bool {
|
||||||
|
let scale = scale as i16;
|
||||||
|
let size = size as i16;
|
||||||
|
|
||||||
|
if self.scale != scale {
|
||||||
|
false
|
||||||
|
} else {
|
||||||
|
match self.type_ {
|
||||||
|
DirectoryType::Fixed => self.size == size,
|
||||||
|
DirectoryType::Scalable => self.minsize <= size && size <= self.maxsize,
|
||||||
|
DirectoryType::Threshold => {
|
||||||
|
self.size - self.threshold <= size && size <= self.size + self.threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn directory_size_distance(&self, size: u16, scale: u16) -> i16 {
|
||||||
|
let scaled_size = self.size * self.scale;
|
||||||
|
let min_scaled_size = self.minsize * self.scale;
|
||||||
|
let max_scaled_size = self.maxsize * self.scale;
|
||||||
|
let scale = scale as i16;
|
||||||
|
let size = size as i16;
|
||||||
|
let scaled_requested_size = size * scale;
|
||||||
|
|
||||||
|
match self.type_ {
|
||||||
|
DirectoryType::Fixed => scaled_size - scaled_requested_size,
|
||||||
|
DirectoryType::Scalable => {
|
||||||
|
if scaled_requested_size < min_scaled_size {
|
||||||
|
min_scaled_size - scaled_requested_size
|
||||||
|
} else if scaled_requested_size < max_scaled_size {
|
||||||
|
scaled_requested_size - max_scaled_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DirectoryType::Threshold => {
|
||||||
|
if scaled_requested_size < (self.size - self.threshold) * scale {
|
||||||
|
min_scaled_size - scaled_requested_size
|
||||||
|
} else if scaled_requested_size > (self.size + self.threshold) * scale {
|
||||||
|
scaled_requested_size - max_scaled_size
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum DirectoryType {
|
||||||
|
Fixed,
|
||||||
|
Scalable,
|
||||||
|
Threshold,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DirectoryType {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Threshold
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for DirectoryType {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
match value {
|
||||||
|
"Fixed" => DirectoryType::Fixed,
|
||||||
|
"Scalable" => DirectoryType::Scalable,
|
||||||
|
_ => DirectoryType::Threshold,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
229
src/theme/mod.rs
229
src/theme/mod.rs
|
|
@ -1,123 +1,172 @@
|
||||||
use crate::theme::error::ThemeError;
|
use crate::theme::error::ThemeError;
|
||||||
use dirs::{data_dir, home_dir};
|
use crate::theme::paths::{ThemePath, FALLBACK_PATHS};
|
||||||
use ini::Ini;
|
use ini::Ini;
|
||||||
use std::borrow::Cow;
|
use once_cell::sync::Lazy;
|
||||||
|
use paths::BASE_PATHS;
|
||||||
|
use std::collections::BTreeMap;
|
||||||
use std::fmt::{Debug, Formatter};
|
use std::fmt::{Debug, Formatter};
|
||||||
use std::io;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::PathBuf;
|
|
||||||
|
|
||||||
|
pub mod directories;
|
||||||
pub mod error;
|
pub mod error;
|
||||||
|
pub mod parse;
|
||||||
|
pub mod paths;
|
||||||
|
|
||||||
type Result<T> = std::result::Result<T, error::ThemeError>;
|
type Result<T> = std::result::Result<T, ThemeError>;
|
||||||
|
|
||||||
const HICOLOR: &str = "/usr/share/pixmaps";
|
pub static THEMES: Lazy<BTreeMap<String, Theme>> =
|
||||||
|
Lazy::new(|| get_all_themes().expect("Failed to get theme paths"));
|
||||||
|
|
||||||
#[derive(Debug)]
|
pub static FALL_BACK_THEMES: Lazy<Vec<Theme>> =
|
||||||
struct ThemePath(PathBuf);
|
Lazy::new(|| fallback_themes().expect("Failed to get theme paths"));
|
||||||
|
|
||||||
struct ThemeIndex(Ini);
|
pub struct Theme {
|
||||||
|
path: ThemePath,
|
||||||
impl Debug for ThemeIndex {
|
index: Ini,
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
|
||||||
let mut content = vec![];
|
|
||||||
self.0.write_to(&mut content).expect("Write error");
|
|
||||||
let content = String::from_utf8_lossy(&content);
|
|
||||||
writeln!(f, "ThemeIndex({content:?})")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ThemePath {
|
impl Theme {
|
||||||
fn name(&self) -> Cow<'_, str> {
|
pub fn try_get_icon(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
||||||
// Unwrapping is safe here, we just got the path from [`list_icon_themes`]
|
self.try_get_icon_exact_size(name, size, scale)
|
||||||
self.0.file_name().unwrap().to_string_lossy()
|
.or(self.try_get_icon_closest_size(name, size, scale))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn index(&self) -> Result<ThemeIndex> {
|
fn try_get_icon_exact_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
||||||
let index = self.0.join("index.theme");
|
self.match_size(size, scale)
|
||||||
|
.iter()
|
||||||
if !index.exists() {
|
.find_map(|path| try_build_icon_path(name, path))
|
||||||
return Err(ThemeError::ThemeIndexNotFound(index));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let index = Ini::load_from_file(index)?;
|
fn match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
||||||
|
let dirs = self.get_all_directories();
|
||||||
|
|
||||||
Ok(ThemeIndex(index))
|
dirs.iter()
|
||||||
}
|
.filter(|directory| directory.match_size(size, scale))
|
||||||
}
|
.map(|dir| dir.name)
|
||||||
/// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons and in /usr/share/pixmaps (in that order).
|
.map(|dir| self.path().join(dir))
|
||||||
/// Paths that are not found are filtered out.
|
|
||||||
fn icon_theme_base_paths() -> Vec<PathBuf> {
|
|
||||||
let home_icon_dir = home_dir().expect("No $HOME directory").join(".icons");
|
|
||||||
let usr_data_dir = data_dir().expect("No $XDG_DATA_DIR").join("icons");
|
|
||||||
let xdg_data_dirs_local = PathBuf::from("/usr/local/share/").join("icons");
|
|
||||||
let xdg_data_dirs = PathBuf::from("/usr/share/").join("icons");
|
|
||||||
|
|
||||||
[
|
|
||||||
home_icon_dir,
|
|
||||||
usr_data_dir,
|
|
||||||
xdg_data_dirs_local,
|
|
||||||
xdg_data_dirs,
|
|
||||||
]
|
|
||||||
.into_iter()
|
|
||||||
.filter(|p| p.exists())
|
|
||||||
.collect()
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_get_icon_closest_size(&self, name: &str, size: u16, scale: u16) -> Option<PathBuf> {
|
||||||
|
self.closest_match_size(size, scale)
|
||||||
|
.iter()
|
||||||
|
.find_map(|path| try_build_icon_path(name, path))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn closest_match_size(&self, size: u16, scale: u16) -> Vec<PathBuf> {
|
||||||
|
let dirs = self.get_all_directories();
|
||||||
|
|
||||||
|
dirs.iter()
|
||||||
|
.filter(|directory| directory.directory_size_distance(size, scale) < i16::MAX)
|
||||||
|
.map(|dir| dir.name)
|
||||||
|
.map(|dir| self.path().join(dir))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn path(&self) -> &PathBuf {
|
||||||
|
&self.path.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn try_build_icon_path<P: AsRef<Path>>(name: &str, path: P) -> Option<PathBuf> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
let png = path.join(format!("{name}.png"));
|
||||||
|
if png.exists() {
|
||||||
|
return Some(png);
|
||||||
|
}
|
||||||
|
|
||||||
|
let svg = path.join(format!("{name}.svg"));
|
||||||
|
if svg.exists() {
|
||||||
|
return Some(svg);
|
||||||
|
}
|
||||||
|
let xmp = path.join(format!("{name}.xmp"));
|
||||||
|
|
||||||
|
if xmp.exists() {
|
||||||
|
return Some(xmp);
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
// Iter through the base paths and get all theme directories
|
// Iter through the base paths and get all theme directories
|
||||||
fn list_icon_themes() -> io::Result<Vec<ThemePath>> {
|
fn get_all_themes() -> Result<BTreeMap<String, Theme>> {
|
||||||
let mut icon_theme_path = vec![];
|
let mut icon_themes = BTreeMap::new();
|
||||||
for theme_base_dir in icon_theme_base_paths().iter() {
|
for theme_base_dir in BASE_PATHS.iter() {
|
||||||
for entry in theme_base_dir.read_dir()? {
|
for entry in theme_base_dir.read_dir()? {
|
||||||
let entry = entry?;
|
let entry = entry?;
|
||||||
let has_index = entry.path().join("index.theme").exists();
|
if let Some(theme) = Theme::from_path(entry.path()) {
|
||||||
if entry.path().is_dir() && has_index {
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
icon_theme_path.push(ThemePath(entry.path()));
|
icon_themes.insert(name, theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(icon_theme_path)
|
Ok(icon_themes)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fallback_themes() -> Result<Vec<Theme>> {
|
||||||
|
let mut icon_themes = vec![];
|
||||||
|
for theme_base_dir in FALLBACK_PATHS.iter() {
|
||||||
|
for entry in theme_base_dir.read_dir()? {
|
||||||
|
let entry = entry?;
|
||||||
|
if let Some(theme) = Theme::from_path(entry.path()) {
|
||||||
|
icon_themes.push(theme);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(icon_themes)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn theme_names() -> Vec<&'static str> {
|
||||||
|
THEMES
|
||||||
|
.values()
|
||||||
|
.map(|path| &path.index)
|
||||||
|
.filter_map(|index| {
|
||||||
|
index
|
||||||
|
.section(Some("Icon Theme"))
|
||||||
|
.and_then(|section| section.get("Name").map(|s| s))
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
fn from_path<P: AsRef<Path>>(path: P) -> Option<Self> {
|
||||||
|
let path = path.as_ref();
|
||||||
|
|
||||||
|
let has_index = path.join("index.theme").exists();
|
||||||
|
|
||||||
|
if !has_index || !path.is_dir() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let path = ThemePath(path.into());
|
||||||
|
|
||||||
|
match path.index() {
|
||||||
|
Ok(index) => Some(Theme { path, index }),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for Theme {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let mut content = vec![];
|
||||||
|
self.index.write_to(&mut content).expect("Write error");
|
||||||
|
let content = String::from_utf8_lossy(&content);
|
||||||
|
writeln!(f, "ThemeIndex{{path: {:?}, index: {content:?}}}", self.path)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use crate::theme::{icon_theme_base_paths, list_icon_themes};
|
use crate::THEMES;
|
||||||
use anyhow::Result;
|
|
||||||
use speculoos::prelude::*;
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn should_get_theme_paths_ordered() {
|
fn get_one_icon() {
|
||||||
let base_paths = icon_theme_base_paths();
|
let theme = THEMES.get("Adwaita").unwrap();
|
||||||
|
println!(
|
||||||
assert_that!(base_paths).is_not_empty()
|
"{:?}",
|
||||||
}
|
theme.try_get_icon_exact_size("edit-delete-symbolic", 24, 1)
|
||||||
|
);
|
||||||
#[test]
|
|
||||||
fn should_get_icon_theme_paths_ordered() -> Result<()> {
|
|
||||||
let themes = list_icon_themes()?;
|
|
||||||
|
|
||||||
assert_that!(themes).is_not_empty();
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_read_theme_index() -> Result<()> {
|
|
||||||
let paths = list_icon_themes()?;
|
|
||||||
|
|
||||||
for theme_path in paths {
|
|
||||||
assert_that!(theme_path.index()).is_ok();
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn should_get_theme_name() -> Result<()> {
|
|
||||||
let paths = list_icon_themes()?;
|
|
||||||
|
|
||||||
for theme_path in paths {
|
|
||||||
assert_that!(theme_path.name().len()).is_greater_than(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
74
src/theme/parse.rs
Normal file
74
src/theme/parse.rs
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
use crate::theme::directories::{Directory, DirectoryType};
|
||||||
|
use crate::theme::Theme;
|
||||||
|
use ini::Properties;
|
||||||
|
|
||||||
|
impl Theme {
|
||||||
|
pub(super) fn get_all_directories(&self) -> Vec<Directory> {
|
||||||
|
let dir_names = self.directories().unwrap_or(vec![]);
|
||||||
|
let mut dirs = vec![];
|
||||||
|
for dir in dir_names {
|
||||||
|
let dir = self.get_directory(dir);
|
||||||
|
if let Some(dir) = dir {
|
||||||
|
dirs.push(dir);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dirs
|
||||||
|
}
|
||||||
|
|
||||||
|
fn scaled_directories(&self) -> Option<Vec<&str>> {
|
||||||
|
self.get_icon_theme_section()
|
||||||
|
.and_then(|props| props.get("ScaledDirectories"))
|
||||||
|
.map(|dirs| dirs.split(',').collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_icon_theme_section(&self) -> Option<&Properties> {
|
||||||
|
self.index.section(Some("Icon Theme"))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn inherits(&self) -> Option<&str> {
|
||||||
|
self.get_icon_theme_section()
|
||||||
|
.and_then(|props| props.get("Inherits"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn directories(&self) -> Option<Vec<&str>> {
|
||||||
|
self.index
|
||||||
|
.section(Some("Icon Theme"))
|
||||||
|
.and_then(|props| props.get("Directories"))
|
||||||
|
.map(|dirs| dirs.split(',').collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_directory<'a>(&'a self, name: &'a str) -> Option<Directory> {
|
||||||
|
self.index.section(Some(name)).map(|props| {
|
||||||
|
let size = props
|
||||||
|
.get("Size")
|
||||||
|
.and_then(|size| str::parse(size).ok())
|
||||||
|
.expect("Size not found for icon");
|
||||||
|
Directory {
|
||||||
|
name,
|
||||||
|
size,
|
||||||
|
scale: props
|
||||||
|
.get("Scale")
|
||||||
|
.and_then(|scale| str::parse(scale).ok())
|
||||||
|
.unwrap_or(1),
|
||||||
|
context: props.get("Context"),
|
||||||
|
type_: props
|
||||||
|
.get("Type")
|
||||||
|
.map(DirectoryType::from)
|
||||||
|
.unwrap_or_default(),
|
||||||
|
maxsize: props
|
||||||
|
.get("MaxSize")
|
||||||
|
.and_then(|max| str::parse(max).ok())
|
||||||
|
.unwrap_or(size),
|
||||||
|
minsize: props
|
||||||
|
.get("MinSize")
|
||||||
|
.and_then(|min| str::parse(min).ok())
|
||||||
|
.unwrap_or(size),
|
||||||
|
threshold: props
|
||||||
|
.get("Threshold")
|
||||||
|
.and_then(|thrsh| str::parse(thrsh).ok())
|
||||||
|
.unwrap_or(2),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
72
src/theme/paths.rs
Normal file
72
src/theme/paths.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
use crate::theme;
|
||||||
|
use crate::theme::error::ThemeError;
|
||||||
|
use dirs::{data_dir, home_dir};
|
||||||
|
use ini::Ini;
|
||||||
|
use once_cell::sync::Lazy;
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
pub(crate) static BASE_PATHS: Lazy<Vec<PathBuf>> = Lazy::new(|| icon_theme_base_paths());
|
||||||
|
pub(crate) static FALLBACK_PATHS: Lazy<Vec<PathBuf>> = Lazy::new(|| {
|
||||||
|
vec![
|
||||||
|
data_dir()
|
||||||
|
.expect("Failed to get DATA_DIR")
|
||||||
|
.join("icons/hicolor"),
|
||||||
|
PathBuf::from("usr/share/icons/hicolor"),
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Look in $HOME/.icons (for backwards compatibility), in $XDG_DATA_DIRS/icons and in /usr/share/pixmaps (in that order).
|
||||||
|
/// Paths that are not found are filtered out.
|
||||||
|
fn icon_theme_base_paths() -> Vec<PathBuf> {
|
||||||
|
let home_icon_dir = home_dir().expect("No $HOME directory").join(".icons");
|
||||||
|
let usr_data_dir = data_dir().expect("No $XDG_DATA_DIR").join("icons");
|
||||||
|
let xdg_data_dirs_local = PathBuf::from("/usr/local/share/").join("icons");
|
||||||
|
let xdg_data_dirs = PathBuf::from("/usr/share/").join("icons");
|
||||||
|
|
||||||
|
[
|
||||||
|
home_icon_dir,
|
||||||
|
usr_data_dir,
|
||||||
|
xdg_data_dirs_local,
|
||||||
|
xdg_data_dirs,
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.filter(|p| p.exists())
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ThemePath(pub PathBuf);
|
||||||
|
|
||||||
|
impl ThemePath {
|
||||||
|
pub(super) fn index(&self) -> theme::Result<Ini> {
|
||||||
|
let index = self.0.join("index.theme");
|
||||||
|
|
||||||
|
if !index.exists() {
|
||||||
|
return Err(ThemeError::ThemeIndexNotFound(index));
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(Ini::load_from_file(index)?)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::theme::paths::icon_theme_base_paths;
|
||||||
|
use crate::theme::{get_all_themes, Theme};
|
||||||
|
use anyhow::Result;
|
||||||
|
use speculoos::prelude::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_get_theme_paths_ordered() {
|
||||||
|
let base_paths = icon_theme_base_paths();
|
||||||
|
assert_that!(base_paths).is_not_empty()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn should_read_theme_index() -> Result<()> {
|
||||||
|
let themes = get_all_themes()?;
|
||||||
|
let themes: Vec<&Theme> = themes.values().collect();
|
||||||
|
assert_that!(themes).is_not_empty();
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue