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,
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<Desktop, Box<dyn Error>> {
fn parse(f: &Path) -> Result<Desktop, Box<dyn Error>> {
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<H: Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
pub fn load_desktop_cache() -> Result<Vec<Desktop>, Box<dyn Error>> {
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<Desktop> {
let mut seen: HashSet<String> = 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")