From 01af00b5ea83626334d771edd6f1c92c03416dd5 Mon Sep 17 00:00:00 2001 From: Bendik Aagaard Lynghaug Date: Sat, 3 Oct 2026 09:15:53 +0000 Subject: [PATCH] desktop: hash entries the same way they compare, only parse *.desktop Desktop derived Hash over every field but PartialEq compared only the name, violating the Hash/Eq contract the launcher's HashMap relies on (clippy denies this by default, so `cargo clippy` failed to build). Also skip directories and files without a .desktop extension (mimeinfo cache, .directory files, ...) and stop unwrapping non-UTF-8 paths. Co-Authored-By: Claude Fable 5.1 --- src/utils/desktop.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/utils/desktop.rs b/src/utils/desktop.rs index 5074760..5d887b0 100644 --- a/src/utils/desktop.rs +++ b/src/utils/desktop.rs @@ -3,7 +3,9 @@ use std::{ collections::HashSet, error::Error, fs::{read_to_string, File}, + hash::{Hash, Hasher}, io::{Error as io_error, ErrorKind}, + path::Path, }; use serde::{self, Deserialize, Serialize}; @@ -12,7 +14,7 @@ use walkdir::WalkDir; use crate::utils::{inish, xdg}; -#[derive(Serialize, Deserialize, Clone, Debug, Eq, Hash)] +#[derive(Serialize, Deserialize, Clone, Debug, Eq)] #[serde(rename_all = "snake_case")] pub struct Desktop { pub entry_type: String, @@ -26,7 +28,7 @@ pub struct Desktop { } impl Desktop { - fn parse(f: &str) -> Result> { + fn parse(f: &Path) -> Result> { let s = read_to_string(f)?; let config = inish::parse(&s)?; match config.get("Desktop Entry") { @@ -74,6 +76,13 @@ impl PartialEq for Desktop { } } +// Must agree with PartialEq: equal entries (same name) must hash equally. +impl Hash for Desktop { + fn hash(&self, state: &mut H) { + self.name.hash(state) + } +} + pub fn load_desktop_cache() -> Result, Box> { match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) { Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()), @@ -95,9 +104,15 @@ pub fn load_desktop_files() -> Vec { let mut seen: HashSet = HashSet::new(); for dir in dirs { - for entry in WalkDir::new(format!("{}/applications", dir)) { - if let Ok(entry) = entry { - if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) { + for entry in WalkDir::new(format!("{}/applications", dir)).into_iter().flatten() { + // Entries are often symlinks, so only rule out directories. + if entry.file_type().is_dir() + || entry.path().extension().and_then(|e| e.to_str()) != Some("desktop") + { + continue; + } + { + if let Ok(d) = Desktop::parse(entry.path()) { if d.hidden || d.no_display || (d.entry_type != "Application" && d.entry_type != "Link")