From 931a3ffccc9033d35c74bdbfcc39dc8eab01fcca Mon Sep 17 00:00:00 2001 From: Alessandro Astone Date: Fri, 8 May 2026 15:29:04 +0200 Subject: [PATCH] Parse dekstop files using Gio.DesktopAppInfo Drop the custom parsing of desktop files in favour of the implementation in GLib. This gives us the benefit of supporting localizations on distros with langpacks like Ubuntu (albeit through a downstream patch in GLib). Assisted-by: GitHub Copilot (GPT-5.4) --- src/utils/app.rs | 113 ++++++++++++----------------------------------- 1 file changed, 28 insertions(+), 85 deletions(-) diff --git a/src/utils/app.rs b/src/utils/app.rs index d97663c3..97e2ecde 100644 --- a/src/utils/app.rs +++ b/src/utils/app.rs @@ -7,8 +7,9 @@ use std::{ use anyhow::{Context, Result, bail}; use gtk::{ - gio::{File, FileIcon, Icon, ThemedIcon}, + gio::{DesktopAppInfo, Icon, ThemedIcon}, glib::GString, + prelude::AppInfoExt, }; use lazy_regex::{Lazy, Regex, lazy_regex}; use log::{debug, info, trace}; @@ -18,7 +19,7 @@ use process_data::{ pci_slot::PciSlot, }; -use crate::{i18n::i18n, utils::read_parsed}; +use crate::i18n::i18n; use futures::future::{FutureExt, LocalBoxFuture, Shared}; @@ -127,45 +128,6 @@ static KNOWN_EXECUTABLE_NAME_EXCEPTIONS: LazyLock> = LazyLock::new(|| { - let envs = ["LC_MESSAGES", "LANGUAGE", "LANG", "LC_ALL"]; - let mut return_vec: Vec = Vec::new(); - - for env in &envs { - if let Ok(locales) = std::env::var(env) { - // split because LANGUAGE may contain multiple languages - for locale in locales.split(':') { - let locale = locale.to_string(); - - if !return_vec.contains(&locale) { - return_vec.push(locale.clone()); - } - - if let Some(no_character_encoding) = locale.split_once('.') { - let no_character_encoding = no_character_encoding.0.to_string(); - if !return_vec.contains(&no_character_encoding) { - return_vec.push(no_character_encoding); - } - } - - if let Some(no_country_code) = locale.split_once('_') { - let no_country_code = no_country_code.0.to_string(); - if !return_vec.contains(&no_country_code) { - return_vec.push(no_country_code); - } - } - } - } - } - - debug!( - "Using the following locales for app names and descriptions: {:?}", - &return_vec - ); - - return_vec -}); - /// Future that resolves to an optional app ID once deferred association completes. type DeferredAppFuture = Shared>>; @@ -250,16 +212,17 @@ impl App { pub fn from_desktop_file>(file_path: P) -> Result { let file_path = file_path.as_ref(); - let ini = ini::Ini::load_from_str(&read_parsed::(file_path)?)?; - - let desktop_entry = ini - .section(Some("Desktop Entry")) - .context("no desktop entry section")?; + let desktop_entry = DesktopAppInfo::from_filename(file_path).with_context(|| { + format!( + "unable to load desktop app info from {}", + file_path.to_string_lossy() + ) + })?; let id = desktop_entry - .get("X-Flatpak") // is there a X-Flatpak section? - .or_else(|| desktop_entry.get("X-AppImage-Identifier")) // or maybe a X-AppImageIdentifier - .map(str::to_string) + .string("X-Flatpak") // is there a X-Flatpak section? + .or_else(|| desktop_entry.string("X-AppImage-Identifier")) // or maybe a X-AppImageIdentifier + .map(String::from) .or_else(|| { // if not, presume that the ID is in the file name Some(file_path.file_stem()?.to_string_lossy().to_string()) @@ -273,10 +236,13 @@ impl App { } let exec = desktop_entry - .get("X-ExecLocation") // appimaged adds this entry that points to the original AppImage path - .or(desktop_entry.get("Exec")); - let is_flatpak = exec.is_some_and(|exec| exec.starts_with("/usr/bin/flatpak run")); + .string("X-ExecLocation") // appimaged adds this entry that points to the original AppImage path + .or_else(|| desktop_entry.string("Exec")); + let is_flatpak = exec + .as_deref() + .is_some_and(|exec| exec.starts_with("/usr/bin/flatpak run")); let commandline = exec + .as_deref() .and_then(|exec| { RE_ENV_FILTER .captures(exec) @@ -308,44 +274,21 @@ impl App { } } - let icon = if let Some(desktop_icon) = desktop_entry.get("Icon") { - if Path::new(&format_path(desktop_icon)).exists() { - FileIcon::new(&File::for_path(desktop_icon)).into() - } else { - ThemedIcon::new(desktop_icon).into() - } - } else { - ThemedIcon::new("generic-process").into() - }; - - let mut display_name_opt = None; - let mut description_opt = None; - - for locale in MESSAGE_LOCALES.iter() { - if let Some(name) = desktop_entry.get(format!("Name[{locale}]")) { - display_name_opt = Some(name); - break; - } - } - - for locale in MESSAGE_LOCALES.iter() { - if let Some(comment) = desktop_entry.get(format!("Comment[{locale}]")) { - description_opt = Some(comment); - break; - } - } + let icon = desktop_entry + .icon() + .unwrap_or_else(|| ThemedIcon::new("generic-process").into()); - let display_name = display_name_opt - .or_else(|| desktop_entry.get("Name")) + // like desktop_entry.name(), but with custom fallback instead of "Unnamed" + let display_name = desktop_entry + .locale_string("Name") + .as_deref() .unwrap_or(&id) .to_string(); - let description = description_opt - .or_else(|| desktop_entry.get("Comment")) - .map(str::to_string); + let description = desktop_entry.description().map(String::from); - let is_snap = desktop_entry.get("X-SnapInstanceName").is_some(); - let is_appimage = desktop_entry.get("X-AppImage-Identifier").is_some(); + let is_snap = desktop_entry.has_key("X-SnapInstanceName"); + let is_appimage = desktop_entry.has_key("X-AppImage-Identifier"); let containerization = if is_flatpak { debug!(