diff --git a/examples/hexagon_launcher.rs b/examples/hexagon_launcher.rs index 318c68e..71720cf 100644 --- a/examples/hexagon_launcher.rs +++ b/examples/hexagon_launcher.rs @@ -34,7 +34,7 @@ const HEX_DIRECTION_VECTORS: [Hex; 6] = [ Hex { q: 1, r: -1, s: 0 }, Hex { q: 0, r: -1, s: 1 }, Hex { q: -1, r: 0, s: 1 }, - Hex { q: -1, r: 1, s: 0 }, + Hex{ q: -1, r: 1, s: 0 }, Hex { q: 0, r: 1, s: -1 }, ]; @@ -162,7 +162,7 @@ impl App { ) -> Option { let position = position.into(); let protostar = - ProtoStar::create_from_desktop_file(parent, position, desktop_file.clone()).ok()?; + ProtoStar::create_from_desktop_file(parent, desktop_file.clone()).ok()?; Some(App { _desktop_file: desktop_file, protostar, diff --git a/examples/sirius.rs b/examples/sirius.rs index 8fc0acc..542c1ef 100644 --- a/examples/sirius.rs +++ b/examples/sirius.rs @@ -34,8 +34,8 @@ struct Star { } impl Star { - fn new(parent: &Spatial, name: Option<&str>,path: &str) -> Option { - let cli = ProtoStar::new_raw(parent, Vec3::default(), name, None, path.to_string()).unwrap(); + fn new(parent: &Spatial, _name: Option<&str>,path: &str) -> Option { + let cli = ProtoStar::new_raw(parent, None, path.to_string()).unwrap(); Some(Star { cli, }) @@ -60,9 +60,9 @@ struct Sirius { impl Sirius { fn new(client: &Client) -> Result { let client_list: Vec<(Option<&str>, &str)> = Vec::from([ - (Some("Magnetar"), "$HOME/repos/stardust/telescope/repos/magnetar/target/release/magnetar"), - (Some("Atmosphere"), "$HOME/repos/stardust/telescope/repos/atmosphere/target/release/atmosphere"), - (Some("Manifold"), "$HOME/repos/stardust/telescope/repos/manifold/target/release/manifold"), + (Some("Magnetar"), "/home/bc/repos/stardust/telescope/repos/magnetar/target/release/magnetar"), + (Some("Atmosphere"), "/home/bc/repos/stardust/telescope/repos/atmosphere/target/release/atmosphere"), + (Some("Manifold"), "/home/bc/repos/stardust/telescope/repos/manifold/target/release/manifold"), ]); let root = Spatial::create(client.get_root(), Transform::default(), false).unwrap(); diff --git a/src/xdg.rs b/src/xdg.rs index 9390232..bd18044 100644 --- a/src/xdg.rs +++ b/src/xdg.rs @@ -1,52 +1,40 @@ -use cached::proc_macro::cached; use color_eyre::eyre::Result; -use linicon; -use regex::Regex; use resvg::render; use resvg::tiny_skia::{Pixmap, Transform}; use resvg::usvg::{FitTo, Tree}; use std::ffi::OsString; use std::fs::create_dir_all; use std::io::{BufRead, BufReader, ErrorKind}; -use std::os::unix::fs::symlink; use std::path::{Path, PathBuf}; use std::str::FromStr; use std::{env, fs}; use walkdir::WalkDir; -fn get_data_dirs() -> Vec { - let xdg_data_dirs_str = std::env::var("XDG_DATA_DIRS").unwrap_or_default(); - let xdg_data_dirs = xdg_data_dirs_str - .split(":") - .filter_map(|dir| PathBuf::from_str(dir).ok()); - - let data_home = dirs::home_dir() - .unwrap_or(PathBuf::from_str("/usr/share/").expect( - "No XDG_DATA_DIR set, no HOME directory found and no /usr/share direcotry found", - )) - .join(".local") - .join("share"); - - xdg_data_dirs - .chain([data_home].into_iter()) - .filter(|dir| dir.exists() && dir.is_dir()) - .collect() -} - -fn get_app_dirs() -> Vec { - get_data_dirs() - .into_iter() - .map(|dir| dir.join("applications")) - .filter(|dir| dir.exists() && dir.is_dir()) - .collect() -} +const ICON_SIZES: &[&str] = &["128x128", "scalable", "256x256", "64x64", "32x32"]; pub fn get_desktop_files() -> Vec { + // Get the XDG data directories + let xdg_data_dirs = + std::env::var("XDG_DATA_DIRS").unwrap_or("/usr/local/share:/usr/share".to_string()); + + // Append the applications directory to each data directory + let app_dirs = xdg_data_dirs + .split(":") + .map(|dir| Path::new(dir).join("applications")); + + // Get the user's local applications directory + let local_app_dir = dirs::home_dir() + .unwrap() + .join(".local") + .join("share") + .join("applications"); + let desktop_extension = OsString::from_str("desktop").unwrap(); + // Get the list of directories to search - let app_dirs = get_app_dirs(); app_dirs - .into_iter() + .chain(Some(local_app_dir)) + .filter(|dir| dir.exists() && dir.is_dir()) .flat_map(|dir| { // Follow symlinks and recursively search directories WalkDir::new(dir) @@ -87,10 +75,6 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { let mut command = None; let mut categories = Vec::new(); let mut icon = None; - let mut no_display = false; - let mut desktop_entry_found = false; - - let re = Regex::new(r"^\[([^\]]*)\]$").unwrap(); // Loop through each line of the file for line in reader.lines() { @@ -104,14 +88,6 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { continue; } - if let Some(captures) = re.captures(&line) { - let entry = captures.get(1).unwrap(); - desktop_entry_found = entry.as_str().contains("Desktop Entry"); - } - - if !desktop_entry_found { - continue; - } // Split the line into a key-value pair by looking for the first "=" character let parts = line.split_once('='); let (key, value) = match parts { @@ -131,12 +107,6 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { .collect() } "Icon" => icon = Some(value.to_string()), - "NoDisplay" => { - no_display = match value { - "true" => true, - _ => false, - } - } _ => (), // Ignore unknown keys } } @@ -148,7 +118,6 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { command, categories, icon, - no_display, }) } @@ -180,81 +149,76 @@ pub struct DesktopFile { pub command: Option, pub categories: Vec, pub icon: Option, - pub no_display: bool, } impl DesktopFile { - pub fn get_raw_icons(&self) -> Vec { + pub fn get_raw_icons(&self) -> Vec { // Get the name of the icon from the DesktopFile struct let Some(icon_name) = self.icon.as_ref() else { return Vec::new(); }; let test_icon_path = self.path.join(Path::new(icon_name)); if test_icon_path.exists() { - if let Some(icon) = Icon::from_path(test_icon_path, 128) { - return vec![icon]; - } + return RawIconType::from_path(test_icon_path) + .map(|i| vec![i]) + .unwrap_or_default(); } - let cache_icon_path = get_image_cache_dir().join(icon_name).canonicalize(); - if cache_icon_path.is_ok() { - if let Some(icon) = Icon::from_path(cache_icon_path.unwrap(), 128) { - return vec![icon]; - } - } + let Ok(icon_name) = OsString::from_str(icon_name) else { return Vec::new(); }; - let mut icons_iter = linicon::lookup_icon(icon_name) - .use_fallback_themes(false) - .peekable(); + // Get the current icon theme from the XDG_ICON_THEME environment variable, or use "hicolor" as the default theme if the variable is not defined + let icon_theme = env::var_os("XDG_ICON_THEME").unwrap_or("hicolor".into()); - if icons_iter.peek().is_none() { - //dbg!("No icons found in current theme"); - icons_iter = linicon::lookup_icon(icon_name).peekable(); - } + // Get the XDG_DATA_HOME and XDG_DATA_DIRS environment variables, and split the XDG_DATA_DIRS variable into a list of directories + let Some(xdg_data_dirs) = env::var_os("XDG_DATA_DIRS") else { return Vec::new(); }; + let Ok(binding) = xdg_data_dirs.into_string() else { return Vec::new(); }; + let xdg_data_dirs = binding.split(":").map(Path::new); - let sized_png: Vec = icons_iter - .filter_map(|i| i.ok()) - .filter(|i| i.icon_type != linicon::IconType::XMP) //TODO: support XMP - .map(|i| Icon::from_path(i.path, i.max_size - 2).unwrap()) - .collect(); - sized_png + // Concatenate the XDG_DATA_HOME and XDG_DATA_DIRS directories with the default path for icon themes + xdg_data_dirs // XDG_DATA_DIRS directories + .flat_map(|dir| { + let icons_path = dir.join("icons").join(&icon_theme); + ICON_SIZES + .iter() + .map(|path| icons_path.join(path).join("apps")) + .collect::>() + }) + .filter_map(|dir| { + let dir = fs::read_dir(dir).ok()?; + Some( + dir.filter_map(|e| e.ok()) + .map(|file| file.path()) + .filter(|file| file.file_stem() == Some(&icon_name)), + ) + }) + .flatten() + .filter_map(RawIconType::from_path) + .collect() } } -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct Icon { - pub icon_type: IconType, - pub path: PathBuf, - pub size: u16, +#[derive(Debug, PartialEq, Eq)] +pub enum RawIconType { + Png(PathBuf), + Svg(PathBuf), + Gltf(PathBuf), } - -#[derive(Debug, PartialEq, Eq, Clone)] -pub enum IconType { - Png, - Svg, - Gltf, -} -impl Icon { - pub fn from_path(path: PathBuf, size: u16) -> Option { - let icon_type = match path.extension().and_then(|ext| ext.to_str()) { - Some("png") => IconType::Png, - Some("svg") => IconType::Svg, - Some("glb") | Some("gltf") => IconType::Gltf, - _ => return None, - }; - return Some(Icon { - icon_type, - path, - size, - }); +impl RawIconType { + pub fn from_path(path: PathBuf) -> Option { + match path.extension().and_then(|ext| ext.to_str()) { + Some("png") => Some(RawIconType::Png(path)), + Some("svg") => Some(RawIconType::Svg(path)), + Some("glb") | Some("gltf") => Some(RawIconType::Gltf(path)), + _ => None, + } } - pub fn cached_process(self, size: u16) -> Result { - let new_path = - get_image_cache_dir().join(self.path.with_extension("").file_name().unwrap()); - if !new_path.exists() { - _ = symlink(self.path.clone(), new_path); - } - match self.icon_type { - IconType::Svg => Ok(Icon::from_path(get_png_from_svg(self.path, size)?, size).unwrap()), - _ => Ok(self), + pub fn process(self, size: u32) -> Result { + match self { + RawIconType::Png(path) => Ok(Icon::Png(path)), + RawIconType::Svg(path) => { + let png_path = path.with_extension("png"); + render_svg_to_png(path, &png_path, size)?; + Ok(Icon::Png(png_path)) + } + RawIconType::Gltf(path) => Ok(Icon::Gltf(path)), } } } @@ -268,7 +232,6 @@ fn test_get_icon_path() { command: None, categories: vec![], icon: Some("krita".into()), - no_display: false, }; // Call the get_icon_path() function with a size argument and store the result @@ -276,49 +239,37 @@ fn test_get_icon_path() { dbg!(&icon_paths); // Assert that the get_icon_path() function returns the expected result - assert!(icon_paths.contains( - &Icon::from_path( - PathBuf::from("/usr/share/icons/hicolor/32x32/apps/krita.png"), - 32 - ) - .unwrap() - )); + assert!(icon_paths.contains(&RawIconType::Png(PathBuf::from( + "/usr/share/icons/hicolor/16x16/apps/krita.png" + )))); } -#[cached] -pub fn get_image_cache_dir() -> PathBuf { - let cache_dir; - if let Ok(xdg_cache_home) = std::env::var("XDG_CACHE_HOME") { - cache_dir = - PathBuf::from_str(&xdg_cache_home).unwrap_or(dirs::home_dir().unwrap().join(".cache")) - } else { - cache_dir = dirs::home_dir().unwrap().join(".cache"); - } - let image_cache_dir = cache_dir.join("protostar_icon_cache"); - create_dir_all(&image_cache_dir).expect("Could not create image cache directory"); - return image_cache_dir; +#[derive(Debug, PartialEq, Eq)] +pub enum Icon { + Png(PathBuf), + Gltf(PathBuf), } -pub fn get_png_from_svg(svg_path: impl AsRef, size: u16) -> Result { +pub fn render_svg_to_png( + cache_dir: impl AsRef, + svg_path: impl AsRef, + size: u32, +) -> Result { let svg_path = fs::canonicalize(svg_path)?; - let svg_data = fs::read(svg_path.as_path())?; - let tree = Tree::from_data(svg_data.as_slice(), &resvg::usvg::Options::default()) - .map_err(|_| ErrorKind::InvalidData)?; - - let png_path = get_image_cache_dir().join(format!( - "{}-{}.png", - svg_path.file_name().unwrap().to_str().unwrap(), - svg_data.len() - )); - - if png_path.exists() { - return Ok(png_path); - } - - let mut pixmap = Pixmap::new(size.into(), size.into()).unwrap(); + let tree = Tree::from_data( + fs::read(svg_path.as_path())?.as_slice(), + &resvg::usvg::Options::default(), + ) + .map_err(|_| ErrorKind::InvalidData)?; + create_dir_all(cache_dir.as_ref())?; + let png_path = cache_dir + .as_ref() + .join(svg_path.file_name().unwrap()) + .with_extension("png"); + let mut pixmap = Pixmap::new(size, size).unwrap(); render( &tree, - FitTo::Width(size.into()), + FitTo::Width(size), Transform::identity(), pixmap.as_mut(), ); @@ -342,7 +293,7 @@ fn test_render_svg_to_png() { fs::write(&svg_path, test_svg_data).unwrap(); // Call the function with the test input and output paths and a size of 200 - let png_path = get_png_from_svg(&svg_path, 200).unwrap(); + let png_path = render_svg_to_png(".", &svg_path, 200).unwrap(); dbg!(&png_path); // Check that the output file exists