diff --git a/Cargo.lock b/Cargo.lock index e1d5ff5..30fbbd2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1427,7 +1427,6 @@ dependencies = [ "anyhow", "async-native-tls", "chrono", - "freedesktop", "glam", "glob", "http-body-util", @@ -1435,13 +1434,16 @@ dependencies = [ "keyvalues-parser", "log", "rust-embed", + "rust-ini", "serde", "serde_json", "smol", "smol-hyper", + "walkdir", "wayvr-ipc", "wgui", "wlx-common", + "xdg 3.0.0", ] [[package]] @@ -1511,27 +1513,6 @@ dependencies = [ "crypto-common", ] -[[package]] -name = "dirs" -version = "6.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" -dependencies = [ - "dirs-sys", -] - -[[package]] -name = "dirs-sys" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" -dependencies = [ - "libc", - "option-ext", - "redox_users", - "windows-sys 0.61.2", -] - [[package]] name = "dispatch" version = "0.2.0" @@ -1992,33 +1973,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "freedesktop" -version = "0.0.3" -source = "git+https://github.com/galister/freedesktop.git?rev=8dd020d#8dd020df81770872065a48b922413471db5606f0" -dependencies = [ - "freedesktop-apps", - "freedesktop-core", -] - -[[package]] -name = "freedesktop-apps" -version = "0.0.3" -source = "git+https://github.com/galister/freedesktop.git?rev=8dd020d#8dd020df81770872065a48b922413471db5606f0" -dependencies = [ - "freedesktop-core", - "libc", - "regex", -] - -[[package]] -name = "freedesktop-core" -version = "0.0.3" -source = "git+https://github.com/galister/freedesktop.git?rev=8dd020d#8dd020df81770872065a48b922413471db5606f0" -dependencies = [ - "dirs", -] - [[package]] name = "futures" version = "0.3.31" @@ -2928,7 +2882,7 @@ dependencies = [ "semver", "serde", "serde_json", - "xdg", + "xdg 2.5.2", ] [[package]] @@ -3780,12 +3734,6 @@ dependencies = [ "mint", ] -[[package]] -name = "option-ext" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" - [[package]] name = "orbclient" version = "0.3.49" @@ -4448,17 +4396,6 @@ dependencies = [ "bitflags 2.10.0", ] -[[package]] -name = "redox_users" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" -dependencies = [ - "getrandom 0.2.16", - "libredox", - "thiserror 2.0.17", -] - [[package]] name = "regex" version = "1.12.2" @@ -6872,7 +6809,6 @@ version = "0.1.0" dependencies = [ "anyhow", "chrono", - "freedesktop", "glam", "idmap", "idmap-derive", @@ -6880,6 +6816,7 @@ dependencies = [ "serde", "smol", "wayvr-ipc", + "xdg 3.0.0", ] [[package]] @@ -6895,7 +6832,6 @@ dependencies = [ "config", "dash-frontend", "dbus", - "freedesktop", "futures", "glam", "idmap", @@ -6939,6 +6875,7 @@ dependencies = [ "wlx-capture", "wlx-common", "xcb", + "xdg 3.0.0", "xkbcommon 0.9.0", ] @@ -7004,6 +6941,12 @@ version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213b7324336b53d2414b2db8537e56544d981803139155afa84f76eeebb7a546" +[[package]] +name = "xdg" +version = "3.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fb433233f2df9344722454bc7e96465c9d03bff9d77c248f9e7523fe79585b5" + [[package]] name = "xkbcommon" version = "0.8.0" diff --git a/Cargo.toml b/Cargo.toml index 3d1212d..58e66af 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,8 +26,7 @@ resolver = "3" anyhow = "1.0.100" glam = { version = "0.30.9", features = ["mint", "serde"] } clap = { version = "4.5.53", features = ["derive"] } -freedesktop = { git = "https://github.com/galister/freedesktop.git", rev = "8dd020d", default-features = false, features = ["core"] } -# freedesktop = { version = "0.0.3", default-features = false, features = ["core"] } +xdg = "3.0.0" idmap = "0.2.2" idmap-derive = "0.2.22" log = "0.4.29" diff --git a/dash-frontend/Cargo.toml b/dash-frontend/Cargo.toml index 8f2e389..b337217 100644 --- a/dash-frontend/Cargo.toml +++ b/dash-frontend/Cargo.toml @@ -9,9 +9,9 @@ wgui = { path = "../wgui/" } wlx-common = { path = "../wlx-common" } anyhow.workspace = true -freedesktop = { workspace = true, features = ["apps"] } glam = { workspace = true, features = ["mint", "serde"] } log.workspace = true +xdg.workspace = true rust-embed.workspace = true serde = { workspace = true, features = ["rc"] } serde_json.workspace = true @@ -24,3 +24,5 @@ http-body-util = "0.1.3" async-native-tls = "0.5.0" smol-hyper = "0.1.1" glob = "0.3.3" +walkdir = "2.5.0" +rust-ini = "0.21.3" diff --git a/dash-frontend/src/util/desktop_finder.rs b/dash-frontend/src/util/desktop_finder.rs index 2887472..2a20341 100644 --- a/dash-frontend/src/util/desktop_finder.rs +++ b/dash-frontend/src/util/desktop_finder.rs @@ -1,7 +1,11 @@ -use std::{collections::HashMap, ffi::OsStr, rc::Rc, sync::Arc, thread::JoinHandle, time::Instant}; +use std::{ + collections::HashSet, ffi::OsStr, fmt::Debug, fs::exists, path::Path, rc::Rc, sync::Arc, thread::JoinHandle, + time::Instant, +}; -use freedesktop::{xdg_data_dirs, xdg_data_home, ApplicationEntry}; +use ini::Ini; use serde::{Deserialize, Serialize}; +use walkdir::WalkDir; struct DesktopEntryOwned { exec_path: String, @@ -29,123 +33,206 @@ impl From for DesktopEntry { } } -const CMD_BLACKLIST: [&str; 1] = [ +const CMD_BLOCKLIST: [&str; 3] = [ "lsp-plugins", // LSP Plugins collection. They clutter the application list a lot + "vrmonitor", + "vrurlhandler", ]; -const CATEGORY_TYPE_BLACKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"]; +const CATEGORY_TYPE_BLOCKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"]; + +struct DesktopFinderParams { + size_preferences: Vec<&'static OsStr>, + icon_folders: Vec, + app_folders: Vec, +} pub struct DesktopFinder { - size_preferences: Arc<[&'static OsStr]>, - icon_folders: Arc<[String]>, + params: Arc, entry_cache: Vec, bg_task: Option>>, } impl DesktopFinder { pub fn new() -> Self { - let data_home = xdg_data_home(); + let xdg = xdg::BaseDirectories::new(); - let mut icon_folders = vec![ - // XDG_DATA_HOME takes priority - { - let mut data_home_flatpak = data_home.clone(); - data_home_flatpak.push("flatpak/exports/share/icons"); - data_home_flatpak.to_string_lossy().to_string() - }, - { - let mut data_home = data_home.clone(); - data_home.push("icons"); - data_home.to_string_lossy().to_string() - }, - "/var/lib/flatpak/exports/share/icons".into(), - ]; + let mut app_folders = vec![]; + let mut icon_folders = vec![]; - let data_dirs = xdg_data_dirs(); - for mut data_dir in data_dirs { - data_dir.push("icons"); - icon_folders.push(data_dir.to_string_lossy().to_string()); + if let Some(data_home) = xdg.get_data_home() { + app_folders.push(data_home.join("applications").to_string_lossy().to_string()); + app_folders.push( + data_home + .join("flatpak/exports/share/applications") + .to_string_lossy() + .to_string(), + ); + icon_folders.push(data_home.join("icons").to_string_lossy().to_string()); + icon_folders.push( + data_home + .join("flatpak/exports/share/icons") + .to_string_lossy() + .to_string(), + ); } - let icon_folders: Arc<[String]> = icon_folders.into_iter().collect(); + app_folders.push("/var/lib/flatpak/exports/share/applications".into()); + icon_folders.push("/var/lib/flatpak/exports/share/icons".into()); - let size_preferences: Arc<[&'static OsStr]> = ["scalable", "128x128", "96x96", "72x72", "64x64", "48x48", "32x32"] + // /usr/share and such + for data_dir in xdg.get_data_dirs() { + app_folders.push(data_dir.join("applications").to_string_lossy().to_string()); + icon_folders.push(data_dir.join("icons").to_string_lossy().to_string()); + } + + let size_preferences: Vec<&'static OsStr> = ["scalable", "128x128", "96x96", "72x72", "64x64", "48x48", "32x32"] .into_iter() .map(OsStr::new) .collect(); Self { - size_preferences, - icon_folders, + params: Arc::new(DesktopFinderParams { + app_folders, + icon_folders, + size_preferences, + }), entry_cache: Vec::new(), bg_task: None, } } - fn build_cache(icon_folders: Arc<[String]>, size_preferences: Arc<[&'static OsStr]>) -> Vec { + fn build_cache(params: Arc) -> Vec { let start = Instant::now(); - let mut res = Vec::::new(); - 'app_entries: for app_entry in ApplicationEntry::all() { - let Some(app_entry_id) = app_entry.id() else { - log::warn!( - "No desktop entry id for application \"{}\"", - app_entry.name().as_deref().unwrap_or("") - ); - continue; - }; + let mut known_files = HashSet::new(); + let mut entries = Vec::::new(); - let Some(app_name) = app_entry.name() else { - log::warn!("No Name on desktop entry {}", app_entry_id); - continue; - }; + for path in ¶ms.app_folders { + log::debug!("Searching desktop entries in path {}", path); - let Some(exec) = app_entry.exec() else { - log::warn!("No Exec on desktop entry {}", app_entry_id); - continue; - }; + 'entries: for entry in WalkDir::new(path) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| !e.file_type().is_dir()) + { + let Some(extension) = Path::new(entry.file_name()).extension() else { + continue; + }; - if app_entry.no_display() || app_entry.is_hidden() || app_entry.terminal() { - continue; - } - - for blacklisted in CMD_BLACKLIST { - if exec.contains(blacklisted) { - continue 'app_entries; + if extension != "desktop" { + continue; // ignore, go on } - } - let (exec_path, exec_args) = match exec.split_once(" ") { - Some((left, right)) => (left.into(), right.into()), - None => (exec.into(), "".into()), - }; + let file_name = entry.file_name().to_string_lossy(); - let icon_path = app_entry - .icon() - .and_then(|icon_name| Self::find_icon(&icon_folders, &size_preferences, &icon_name)); - - for cat in app_entry.categories().unwrap_or_default() { - if CATEGORY_TYPE_BLACKLIST.contains(&cat.as_str()) { - continue 'app_entries; + if known_files.contains(file_name.as_ref()) { + // as per xdg spec, user entries of the same filename will override system entries + continue; } + + let file_path = format!("{}/{}", path, file_name); + + let ini = match Ini::load_from_file(&file_path) { + Ok(ini) => ini, + Err(e) => { + log::debug!("Failed to read INI for .desktop file {}: {:?}, skipping", file_path, e); + continue; + } + }; + + let Some(section) = ini.section(Some("Desktop Entry")) else { + log::debug!("Failed to get [Desktop Entry] section for file {}, skipping", file_path); + continue; + }; + + if section.contains_key("OnlyShowIn") { + continue; // probably XFCE, KDE, GNOME or other DE-specific stuff + } + + if let Some(x) = section.get("Terminal") + && x == "true" + { + continue; + } + + if let Some(x) = section.get("NoDisplay") + && x.eq_ignore_ascii_case("true") + { + continue; // This application is hidden + } + + if let Some(x) = section.get("Hidden") + && x.eq_ignore_ascii_case("true") + { + continue; // This application is hidden + } + + let Some(exec) = section.get("Exec") else { + log::debug!("Failed to get desktop entry Exec for file {}, skipping", file_path); + continue; + }; + + for entry in &CMD_BLOCKLIST { + if exec.contains(entry) { + continue 'entries; + } + } + + let (exec_path, exec_args) = match exec.split_once(" ") { + Some((left, right)) => ( + left, + right + .split(" ") + .filter(|arg| !arg.starts_with('%')) // exclude arguments like "%f" + .map(String::from) + .collect(), + ), + None => (exec, Vec::new()), + }; + + let Some(app_name) = section.get("Name") else { + log::debug!( + "Failed to get desktop entry application name for file {}, skipping", + file_path + ); + continue; + }; + + let icon_path = section + .get("Icon") + .and_then(|icon_name| Self::find_icon(¶ms, &icon_name)); + + if let Some(categories) = section.get("Categories") { + for cat in categories.split(";") { + if CATEGORY_TYPE_BLOCKLIST.contains(&cat) { + continue 'entries; + } + } + } + + known_files.insert(file_name.to_string()); + + entries.push(DesktopEntryOwned { + app_name: String::from(app_name), + exec_path: String::from(exec_path), + exec_args: exec_args.join(" "), + icon_path, + }); } - - let entry = DesktopEntryOwned { - app_name, - exec_path, - exec_args, - icon_path, - }; - - res.push(entry); } - log::debug!("App entry cache rebuild took {:?}", start.elapsed()); - res + log::debug!("Desktop entry & icon scan took {:?}", start.elapsed()); + + entries } - fn find_icon(icon_folders: &[String], size_preferences: &[&'static OsStr], icon_name: &str) -> Option { - for folder in icon_folders { + fn find_icon(params: &DesktopFinderParams, icon_name: &str) -> Option { + if icon_name.starts_with("/") && exists(icon_name).unwrap_or(false) { + return Some(icon_name.to_string()); + } + + for folder in ¶ms.icon_folders { let pattern = format!("{}/hicolor/*/apps/{}.*", folder, icon_name); let mut entries: Vec<_> = glob::glob(&pattern) @@ -160,7 +247,7 @@ impl DesktopFinder { .rev() .nth(2) // ← hicolor/<*SIZE*>/apps/filename.ext .map(|c| c.as_os_str()) - .and_then(|size| size_preferences.iter().position(|&p| p == size)) + .and_then(|size| params.size_preferences.iter().position(|&p| p == size)) .unwrap_or(usize::MAX) }); @@ -193,9 +280,8 @@ impl DesktopFinder { pub fn refresh(&mut self) { let bg_task = std::thread::spawn({ - let icon_folders = self.icon_folders.clone(); - let size_preferences = self.size_preferences.clone(); - move || Self::build_cache(icon_folders, size_preferences) + let params = self.params.clone(); + move || Self::build_cache(params) }); self.bg_task = Some(bg_task); } diff --git a/wlx-common/Cargo.toml b/wlx-common/Cargo.toml index c02c7bd..5f66b0d 100644 --- a/wlx-common/Cargo.toml +++ b/wlx-common/Cargo.toml @@ -7,13 +7,13 @@ edition = "2024" [dependencies] wayvr-ipc = { path = "../wayvr-ipc", default-features = false } -freedesktop.workspace = true anyhow.workspace = true glam.workspace = true idmap = { workspace = true, features = ["serde"] } idmap-derive.workspace = true log.workspace = true serde = { workspace = true, features = ["rc"] } +xdg.workspace = true chrono = "0.4.42" smol = "2.0.2" diff --git a/wlx-common/src/cache_dir.rs b/wlx-common/src/cache_dir.rs index edaa237..73b2713 100644 --- a/wlx-common/src/cache_dir.rs +++ b/wlx-common/src/cache_dir.rs @@ -1,12 +1,16 @@ use std::{path::PathBuf, sync::LazyLock}; -use freedesktop::xdg_cache_home; +const FALLBACK_CACHE_PATH: &str = "/tmp/wayvr_cache"; static CACHE_ROOT_PATH: LazyLock = LazyLock::new(|| { - // Panics if neither $XDG_CACHE_HOME nor $HOME is set - let mut dir = xdg_cache_home(); - dir.push("wayvr"); - dir + +if let Some(mut dir) = xdg::BaseDirectories::new().get_cache_home() { + dir.push("wayvr"); + return dir; + } + //Return fallback cache path + log::error!("Err: Failed to find cache path, using {FALLBACK_CACHE_PATH}"); + PathBuf::from(FALLBACK_CACHE_PATH) // Panics if neither $XDG_CACHE_HOME nor $HOME is set }); fn get_cache_root() -> PathBuf { diff --git a/wlx-overlay-s/Cargo.toml b/wlx-overlay-s/Cargo.toml index 8218a29..eb70858 100644 --- a/wlx-overlay-s/Cargo.toml +++ b/wlx-overlay-s/Cargo.toml @@ -28,7 +28,6 @@ wlx-common = { path = "../wlx-common" } anyhow.workspace = true clap.workspace = true -freedesktop.workspace = true glam = { workspace = true, features = ["mint", "serde"] } idmap = { workspace = true, features = ["serde"] } idmap-derive. workspace = true @@ -40,6 +39,7 @@ serde = { workspace = true, features = ["rc"] } serde_json.workspace = true vulkano.workspace = true vulkano-shaders.workspace = true +xdg.workspace = true ash = "^0.38.0" # must match vulkano bytes = { version = "1.11.0" } diff --git a/wlx-overlay-s/src/config_io.rs b/wlx-overlay-s/src/config_io.rs index d246878..9ee03f4 100644 --- a/wlx-overlay-s/src/config_io.rs +++ b/wlx-overlay-s/src/config_io.rs @@ -1,4 +1,3 @@ -use freedesktop::xdg_config_home; use log::error; use std::{path::PathBuf, sync::LazyLock}; @@ -11,10 +10,13 @@ pub enum ConfigRoot { const FALLBACK_CONFIG_PATH: &str = "/tmp/wlxoverlay"; static CONFIG_ROOT_PATH: LazyLock = LazyLock::new(|| { - // Panics if $XDG_CONFIG_HOME and $HOME are both unset. - let mut dir = xdg_config_home(); - dir.push("wlxoverlay"); - return dir; + if let Some(mut dir) = xdg::BaseDirectories::new().get_config_home() { + dir.push("wlxoverlay"); + return dir; + } + //Return fallback config path + error!("Err: Failed to find config path, using {FALLBACK_CONFIG_PATH}"); + PathBuf::from(FALLBACK_CONFIG_PATH) }); pub fn get_config_root() -> PathBuf {