use crate::Stream; use linicon::lookup_icon; use std::fmt; use std::io::Error; use std::io::ErrorKind; use std::io::Write; use std::path::PathBuf; #[derive(Debug, Clone)] pub enum IconError { Disabled, MissingIcon(String, String), MissingTheme(String), } impl fmt::Display for IconError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match &*self { IconError::Disabled => write!(f, "The use of icons is disabled."), IconError::MissingIcon(theme, icon) => { write!(f, "\"{}\" theme does not include \"{}\" icon.", theme, icon) } IconError::MissingTheme(theme) => write!(f, "\"{}\" theme does not exist.", theme), } } } impl From<&IconError> for IconError { fn from(i: &IconError) -> Self { i.to_owned() } } pub struct Icon { /// The name of the icon. pub name: String, /// A path to an icon. pub path: PathBuf, /// The name of an icon theme. pub theme: Option, /// The base location of the `icons` directory. pub location: PathBuf, } impl Icon { pub fn new() -> Self { let base = if cfg!(target_os = "linux") { PathBuf::from("/usr/share/icons") } else if cfg!(target_os = "netbsd") { PathBuf::from("/usr/pkg/share/icons") } else { PathBuf::new() }; Icon { name: String::new(), path: PathBuf::new(), theme: None, location: base, } } pub fn get_available_themes(&self) -> Vec { let mut themes = vec![]; let mut places = vec![self.location.clone()]; if let Ok(data) = std::env::var("XDG_DATA_HOME") { places.push(PathBuf::from(data).join(".icons")); } else if let Ok(home) = std::env::var("HOME") { places.push(PathBuf::from(home).join(".icons")); } places.iter().for_each(|place| { if let Ok(entries) = place.read_dir() { for entry in entries.flatten() { let theme_path = entry.path(); if !theme_path.join("index.theme").exists() { continue; } let theme_name = theme_path.file_name(); if let Some(name) = theme_name { if let Some(str) = name.to_str() { themes.push(str.to_owned()); } } } } }); themes } pub fn print_available_themes(&self, stream: Stream) -> Result<(), Error> { let mut out = Vec::new(); writeln!(out, "Available icon themes:")?; let themes = self .get_available_themes() .into_iter() .filter(|t| !t.eq("default")); for theme in themes { writeln!(out, "> {}", theme)?; } let out = match std::str::from_utf8(&out) { Ok(s) => s, _ => return Err(Error::from(ErrorKind::InvalidData)), }; match stream { Stream::Stdout => println!("{}", out), Stream::Stderr => eprintln!("{}", out), } Ok(()) } pub fn find_icon(&mut self) -> Result { if let Some(theme) = &self.theme { let extra_search_path = &[self.location.to_str().unwrap_or_default()]; if let Ok(icon) = lookup_icon(&self.name).with_search_paths(extra_search_path) { if let Some(icon) = icon.from_theme(theme).next() { if let Ok(ico) = icon { return Ok(ico.path); } } else { return Err(IconError::MissingIcon( theme.to_owned(), self.name.to_owned(), )); } } return Err(IconError::MissingTheme(theme.to_owned())); } Err(IconError::Disabled) } }