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 <noreply@anthropic.com>
This commit is contained in:
Bendik Aagaard Lynghaug
2026-10-03 09:15:53 +00:00
co-authored by Claude Fable 5.1
parent 2bed8b7fac
commit 01af00b5ea
+20 -5
View File
@@ -3,7 +3,9 @@ use std::{
collections::HashSet, collections::HashSet,
error::Error, error::Error,
fs::{read_to_string, File}, fs::{read_to_string, File},
hash::{Hash, Hasher},
io::{Error as io_error, ErrorKind}, io::{Error as io_error, ErrorKind},
path::Path,
}; };
use serde::{self, Deserialize, Serialize}; use serde::{self, Deserialize, Serialize};
@@ -12,7 +14,7 @@ use walkdir::WalkDir;
use crate::utils::{inish, xdg}; use crate::utils::{inish, xdg};
#[derive(Serialize, Deserialize, Clone, Debug, Eq, Hash)] #[derive(Serialize, Deserialize, Clone, Debug, Eq)]
#[serde(rename_all = "snake_case")] #[serde(rename_all = "snake_case")]
pub struct Desktop { pub struct Desktop {
pub entry_type: String, pub entry_type: String,
@@ -26,7 +28,7 @@ pub struct Desktop {
} }
impl Desktop { impl Desktop {
fn parse(f: &str) -> Result<Desktop, Box<dyn Error>> { fn parse(f: &Path) -> Result<Desktop, Box<dyn Error>> {
let s = read_to_string(f)?; let s = read_to_string(f)?;
let config = inish::parse(&s)?; let config = inish::parse(&s)?;
match config.get("Desktop Entry") { 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<H: Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
pub fn load_desktop_cache() -> Result<Vec<Desktop>, Box<dyn Error>> { pub fn load_desktop_cache() -> Result<Vec<Desktop>, Box<dyn Error>> {
match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) { match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) {
Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()), Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()),
@@ -95,9 +104,15 @@ pub fn load_desktop_files() -> Vec<Desktop> {
let mut seen: HashSet<String> = HashSet::new(); let mut seen: HashSet<String> = HashSet::new();
for dir in dirs { for dir in dirs {
for entry in WalkDir::new(format!("{}/applications", dir)) { for entry in WalkDir::new(format!("{}/applications", dir)).into_iter().flatten() {
if let Ok(entry) = entry { // Entries are often symlinks, so only rule out directories.
if let Ok(d) = Desktop::parse(entry.path().to_str().unwrap()) { 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 if d.hidden
|| d.no_display || d.no_display
|| (d.entry_type != "Application" && d.entry_type != "Link") || (d.entry_type != "Application" && d.entry_type != "Link")