diff --git a/Cargo.lock b/Cargo.lock index 65d80c8..09bb87d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -684,15 +684,6 @@ dependencies = [ "ttf-parser", ] -[[package]] -name = "fork" -version = "0.1.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9788ce090af4bf8d6e8f43d3f7d12305c787456387bd2d88856fcda3aa1f0dca" -dependencies = [ - "libc", -] - [[package]] name = "freedesktop_entry_parser" version = "1.3.0" @@ -1506,7 +1497,6 @@ dependencies = [ "directories", "dirs", "ez-pixmap", - "fork", "glam", "image", "lazy_static", diff --git a/Cargo.toml b/Cargo.toml index eb37e63..c09c708 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,6 @@ color-eyre = "0.6.2" directories = "4.0.1" dirs = "4.0.0" ez-pixmap = "0.2.2" -fork = "0.1.20" glam = { version = "0.22.0", features = ["mint"] } image = "0.24.5" lazy_static = "1.4.0" diff --git a/examples/app_grid.rs b/examples/app_grid.rs index 6a8907a..5d8354c 100644 --- a/examples/app_grid.rs +++ b/examples/app_grid.rs @@ -8,7 +8,9 @@ use protostar::{ }; use stardust_xr_molecules::fusion::{ client::{Client, FrameInfo, RootHandler}, - spatial::Spatial, drawable::{Text, TextStyle, Bounds, TextFit, Alignment}, core::values::Transform, + core::values::Transform, + drawable::{Alignment, Bounds, Text, TextFit, TextStyle}, + spatial::Spatial, }; const APP_LIMIT: usize = 300; @@ -39,7 +41,6 @@ struct AppGrid { //style: TextStyle, } impl AppGrid { - fn new(client: &Client) -> Self { let apps = get_desktop_files() .into_iter() @@ -96,15 +97,15 @@ impl App { }; let protostar = ProtoStar::create_from_desktop_file(parent, desktop_file.clone()).ok()?; let text = Text::create( - protostar.content_parent(), - Transform::from_position_rotation( - [0.0, 0.0, APP_SIZE / 2.0], - Quat::from_rotation_y(3.14), - ), - desktop_file.name.as_deref().unwrap_or("Unknown"), - style, - ) - .unwrap(); + protostar.content_parent(), + Transform::from_position_rotation( + [0.0, 0.0, APP_SIZE / 2.0], + Quat::from_rotation_y(3.14), + ), + desktop_file.name.as_deref().unwrap_or("Unknown"), + style, + ) + .unwrap(); protostar .content_parent() .set_position(None, position) diff --git a/examples/hexagon_launcher.rs b/examples/hexagon_launcher.rs index 85cc8f3..8a83eef 100644 --- a/examples/hexagon_launcher.rs +++ b/examples/hexagon_launcher.rs @@ -8,7 +8,9 @@ use protostar::{ }; use stardust_xr_molecules::fusion::{ client::{Client, FrameInfo, RootHandler}, - spatial::Spatial, drawable::{Text, TextStyle, Bounds, TextFit, Alignment}, core::values::Transform, + core::values::Transform, + drawable::{Alignment, Bounds, Text, TextFit, TextStyle}, + spatial::Spatial, }; use tween::TweenTime; @@ -20,32 +22,37 @@ struct Hex { s: isize, } -const HEX_CENTER: Hex = Hex{q:0,r:0,s:0}; +const HEX_CENTER: Hex = Hex { q: 0, r: 0, s: 0 }; const HEX_DIRECTION_VECTORS: [Hex; 6] = [ - Hex{q:1, r:0, s:-1}, 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:0, r:1, s:-1}, + Hex { q: 1, r: 0, s: -1 }, + 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: 0, r: 1, s: -1 }, ]; impl Hex { - fn new(q:isize, r:isize, s:isize) -> Self{ - Hex{q:q, r:r, s:s} + fn new(q: isize, r: isize, s: isize) -> Self { + Hex { q: q, r: r, s: s } } - fn get_coords(&self) -> [f32; 3]{ - let x = 3.0/2.0 * APP_SIZE/2.0 * (-self.q-self.s).to_f32(); - let y = 3.0_f32.sqrt() * APP_SIZE/2.0 * ( (-self.q-self.s).to_f32()/2.0 + self.s.to_f32()); - [x,y,0.0] + fn get_coords(&self) -> [f32; 3] { + let x = 3.0 / 2.0 * APP_SIZE / 2.0 * (-self.q - self.s).to_f32(); + let y = + 3.0_f32.sqrt() * APP_SIZE / 2.0 * ((-self.q - self.s).to_f32() / 2.0 + self.s.to_f32()); + [x, y, 0.0] } - fn add(self, vec:&Hex) -> Self{ + fn add(self, vec: &Hex) -> Self { Hex::new(self.q + vec.q, self.r + vec.r, self.s + vec.s) } - fn neighbor(self, direction:usize) -> Self{ + fn neighbor(self, direction: usize) -> Self { self.add(&HEX_DIRECTION_VECTORS[direction]) } - fn scale(self, factor:isize) -> Self { + fn scale(self, factor: isize) -> Self { Hex::new(self.q * factor, self.r * factor, self.s * factor) } } @@ -73,7 +80,6 @@ struct AppHexGrid { apps: Vec, } impl AppHexGrid { - fn new(client: &Client) -> Self { let mut desktop_files: Vec = get_desktop_files() .into_iter() @@ -84,19 +90,30 @@ impl AppHexGrid { desktop_files.sort_by_key(|d| d.clone().name.unwrap()); let mut apps = Vec::new(); - let mut radius = 1; + let mut radius = 1; while !desktop_files.is_empty() { let mut hex = HEX_CENTER.add(&HEX_DIRECTION_VECTORS[4].clone().scale(radius)); - for i in 0..6{ - if desktop_files.is_empty() {break}; - for _ in 0..radius{ - if desktop_files.is_empty() {break}; - apps.push(App::new(client.get_root(),hex.get_coords(),desktop_files.pop().unwrap()).unwrap()); - hex = hex.neighbor(i); - } + for i in 0..6 { + if desktop_files.is_empty() { + break; + }; + for _ in 0..radius { + if desktop_files.is_empty() { + break; + }; + apps.push( + App::new( + client.get_root(), + hex.get_coords(), + desktop_files.pop().unwrap(), + ) + .unwrap(), + ); + hex = hex.neighbor(i); + } } radius += 1; - } + } AppHexGrid { apps } } } @@ -120,7 +137,7 @@ impl App { desktop_file: DesktopFile, ) -> Option { let position = position.into(); - let style= TextStyle { + let style = TextStyle { character_height: APP_SIZE * 0.1, bounds: Some(Bounds { bounds: [APP_SIZE; 2].into(), @@ -132,15 +149,12 @@ impl App { }; let protostar = ProtoStar::create_from_desktop_file(parent, desktop_file.clone()).ok()?; let text = Text::create( - protostar.content_parent(), - Transform::from_position_rotation( - [0.0, 0.0, 0.004], - Quat::from_rotation_y(3.14), - ), - desktop_file.name.as_deref().unwrap_or("Unknown"), - style, - ) - .unwrap(); + protostar.content_parent(), + Transform::from_position_rotation([0.0, 0.0, 0.004], Quat::from_rotation_y(3.14)), + desktop_file.name.as_deref().unwrap_or("Unknown"), + style, + ) + .unwrap(); protostar .content_parent() .set_position(None, position) diff --git a/src/protostar.rs b/src/protostar.rs index eb62e45..21f1a46 100644 --- a/src/protostar.rs +++ b/src/protostar.rs @@ -2,9 +2,7 @@ use crate::xdg::{DesktopFile, Icon, IconType}; use color_eyre::eyre::{eyre, Result}; use glam::Quat; use mint::Vector3; -use fork::{daemon, Fork, setsid}; -use std::process::{Command,Stdio}; -use std::os::unix::process::CommandExt; +use nix::unistd::setsid; use stardust_xr_molecules::{ fusion::{ client::{Client, FrameInfo, RootHandler}, @@ -17,16 +15,18 @@ use stardust_xr_molecules::{ }, GrabData, Grabbable, }; -use std::{f32::consts::PI, ffi::CStr, sync::Arc}; +use std::os::unix::process::CommandExt; +use std::process::{Command, Stdio}; +use std::{f32::consts::PI, sync::Arc}; use tween::{QuartInOut, Tweener}; -use ustr::ustr; -use nix::unistd::fork; fn model_from_icon(parent: &Spatial, icon: &Icon) -> Result { - return match &icon.icon_type { IconType::Png => { - let t = Transform::from_rotation_scale(Quat::from_rotation_x(PI/2.0)*Quat::from_rotation_y(PI),[0.03,0.03,0.03]); + let t = Transform::from_rotation_scale( + Quat::from_rotation_x(PI / 2.0) * Quat::from_rotation_y(PI), + [0.03, 0.03, 0.03], + ); let model = Model::create( parent, @@ -36,7 +36,7 @@ fn model_from_icon(parent: &Spatial, icon: &Icon) -> Result { model.set_material_parameter( 1, "color", - MaterialParameter::Color([0.0,1.0,1.0,1.0]), + MaterialParameter::Color([0.0, 1.0, 1.0, 1.0]), )?; model.set_material_parameter( 0, @@ -74,19 +74,16 @@ impl ProtoStar { IconType::Gltf => true, _ => false, }) - .or( - raw_icons - .into_iter() - .max_by_key(|i| i.size) - ); + .or(raw_icons.into_iter().max_by_key(|i| i.size)); - match icon{ + match icon { Some(i) => { icon = match i.cached_process(128) { Ok(i) => Some(i), _ => None, - }}, - None => {}, + } + } + None => {} } Self::new_raw( @@ -109,9 +106,7 @@ impl ProtoStar { parent, Transform::default(), &field, - GrabData { - max_distance: 0.01, - }, + GrabData { max_distance: 0.01 }, )?; field.set_spatial_parent(grabbable.content_parent())?; let icon = icon @@ -119,7 +114,10 @@ impl ProtoStar { .unwrap_or_else(|| { Ok(Model::create( grabbable.content_parent(), - Transform::from_rotation_scale(Quat::from_xyzw(0.0,0.707,0.707,0.0),[0.03,0.03,0.03]), + Transform::from_rotation_scale( + Quat::from_xyzw(0.0, 0.707, 0.707, 0.0), + [0.03, 0.03, 0.03], + ), &ResourceID::new_namespaced("protostar", "hexagon/hexagon"), )?) })?; @@ -147,15 +145,15 @@ impl RootHandler for ProtoStar { self.icon .set_scale(None, Vector3::from([scale; 3])) .unwrap(); - } - if let Some(icon_grow) = &mut self.icon_shrink { - if !icon_grow.is_finished(){ - let scale = icon_grow.move_by(info.delta); - self.icon - .set_scale(None, Vector3::from([scale; 3])) - .unwrap(); } - } + if let Some(icon_grow) = &mut self.icon_shrink { + if !icon_grow.is_finished() { + let scale = icon_grow.move_by(info.delta); + self.icon + .set_scale(None, Vector3::from([scale; 3])) + .unwrap(); + } + } } else if self.grabbable.grab_action().actor_stopped() { let startup_settings = StartupSettings::create(&self.field.client().unwrap()).unwrap(); self.icon @@ -179,15 +177,15 @@ impl RootHandler for ProtoStar { std::env::set_var("STARDUST_STARTUP_TOKEN", future.await.unwrap()); unsafe { Command::new(executable) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .pre_exec(|| { - setsid(); - Ok(()) - }) - .spawn() - .expect("Failed to start child process") + .stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .pre_exec(|| { + setsid(); + Ok(()) + }) + .spawn() + .expect("Failed to start child process") } }); self.icon_grow = Some(Tweener::quart_in_out(0.00, 0.03, 0.25)); //TODO make the scale a parameter diff --git a/src/xdg.rs b/src/xdg.rs index ce7c2ac..f9bd6af 100644 --- a/src/xdg.rs +++ b/src/xdg.rs @@ -1,29 +1,29 @@ +use cached::proc_macro::cached; use color_eyre::eyre::Result; +use linicon; 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::os::unix::fs::{symlink}; +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; -use linicon; -use cached::proc_macro::cached; fn get_data_dirs() -> Vec { - let xdg_data_dirs_str = std::env::var("XDG_DATA_DIRS") - .unwrap_or_default(); + let xdg_data_dirs_str = std::env::var("XDG_DATA_DIRS").unwrap_or_default(); - let xdg_data_dirs = xdg_data_dirs_str + 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")) + .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"); @@ -33,7 +33,7 @@ fn get_data_dirs() -> Vec { .collect() } -fn get_app_dirs() -> Vec{ +fn get_app_dirs() -> Vec { get_data_dirs() .into_iter() .map(|dir| dir.join("applications")) @@ -107,7 +107,7 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { Some((key, value)) => (key, value), None => continue, }; - + // Parse the key-value pair based on the key match key { "Name" => name = Some(value.to_string()), @@ -120,10 +120,12 @@ pub fn parse_desktop_file(path: PathBuf) -> Result { .collect() } "Icon" => icon = Some(value.to_string()), - "NoDisplay" => no_display = match value{ - "true" => true, - _ => false - }, + "NoDisplay" => { + no_display = match value { + "true" => true, + _ => false, + } + } _ => (), // Ignore unknown keys } } @@ -175,27 +177,29 @@ impl DesktopFile { 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] + if let Some(icon) = Icon::from_path(test_icon_path, 128) { + return vec![icon]; } } - + let cache_icon_path = get_image_cache_dir().join(icon_name).canonicalize(); if cache_icon_path.is_ok() { - return vec![Icon::from_path(cache_icon_path.unwrap(), 128).unwrap()] + return vec![Icon::from_path(cache_icon_path.unwrap(), 128).unwrap()]; } - let mut icons_iter= linicon::lookup_icon(icon_name).use_fallback_themes(false).peekable(); - - if icons_iter.peek().is_none(){ + let mut icons_iter = linicon::lookup_icon(icon_name) + .use_fallback_themes(false) + .peekable(); + + if icons_iter.peek().is_none() { //dbg!("No icons found in current theme"); - icons_iter= linicon::lookup_icon(icon_name).peekable(); + icons_iter = linicon::lookup_icon(icon_name).peekable(); } - let sized_png : Vec = icons_iter + 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()) + .map(|i| Icon::from_path(i.path, i.max_size - 2).unwrap()) .collect(); sized_png } @@ -215,23 +219,29 @@ pub enum IconType { Gltf, } impl Icon { - pub fn from_path(path: PathBuf, size: u16) -> Option{ + pub fn from_path(path: PathBuf, size: u16) -> Option { let icon_type = match path.extension().and_then(|ext| ext.to_str()) { Some("png") => Some(IconType::Png), Some("svg") => Some(IconType::Svg), Some("glb") | Some("gltf") => Some(IconType::Gltf), - _ => {return None}, - }.unwrap(); - return Some(Icon{icon_type,path,size}) + _ => return None, + } + .unwrap(); + return Some(Icon { + icon_type, + path, + size, + }); } 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(){ + 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()), + IconType::Svg => Ok(Icon::from_path(get_png_from_svg(self.path, size)?, size).unwrap()), _ => Ok(self), } } @@ -254,39 +264,43 @@ 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( + &Icon::from_path( + PathBuf::from("/usr/share/icons/hicolor/32x32/apps/krita.png"), + 32 + ) + .unwrap() + )); } #[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") - ) + 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 + return image_cache_dir; } - -pub fn get_png_from_svg(svg_path: impl AsRef, size: u16,) -> Result { +pub fn get_png_from_svg(svg_path: impl AsRef, size: u16) -> 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())); + 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) + return Ok(png_path); } let mut pixmap = Pixmap::new(size.into(), size.into()).unwrap();