From ff0aeccf44329a633762233bee04c3fc38116942 Mon Sep 17 00:00:00 2001 From: Cheong Lau <234708519+Cheong-Lau@users.noreply.github.com> Date: Wed, 5 Nov 2025 14:54:24 +1000 Subject: [PATCH] chore(deps): update `freedesktop-entry-parser` --- Cargo.lock | 8 +-- Cargo.toml | 2 +- src/app.rs | 41 +++++++------- src/mime_app.rs | 135 ++++++++++++++++++++------------------------- src/tab.rs | 20 ++++--- src/thumbnailer.rs | 23 ++++---- 6 files changed, 112 insertions(+), 117 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index e07e45d6..f014b856 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2536,12 +2536,12 @@ dependencies = [ [[package]] name = "freedesktop_entry_parser" -version = "1.3.0" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db9c27b72f19a99a895f8ca89e2d26e4ef31013376e56fdafef697627306c3e4" +checksum = "fc6d3a3635983a889f065aa9ce760384713f23a9b4a04f696f86c39a5d7a6a5a" dependencies = [ - "nom 7.1.3", - "thiserror 1.0.69", + "indexmap 2.12.0", + "nom 8.0.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 336f0375..21beb484 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-c cosmic-mime-apps = { git = "https://github.com/pop-os/cosmic-mime-apps.git", optional = true } dirs = "6.0.0" env_logger = "0.11" -freedesktop_entry_parser = "1.3" +freedesktop_entry_parser = "2.0" futures = "0.3.31" gio = { version = "0.21", optional = true } glib = { version = "0.21", optional = true } diff --git a/src/app.rs b/src/app.rs index 44d58802..6c77f532 100644 --- a/src/app.rs +++ b/src/app.rs @@ -838,29 +838,28 @@ impl App { fn launch_desktop_entries(paths: &[impl AsRef]) { for path in paths.iter().map(AsRef::as_ref) { match freedesktop_entry_parser::parse_entry(path) { - Ok(entry) => match entry.section("Desktop Entry").attr("Exec") { - Some(exec) => match mime_app::exec_to_command(exec, &[] as &[&str; 0]) { - Some(commands) => { - for mut command in commands { - if let Err(err) = spawn_detached(&mut command) { - log::warn!("failed to execute {}: {}", path.display(), err); - } - } - } - None => { - log::warn!( - "failed to parse {}: invalid Desktop Entry/Exec", - path.display() - ); + Ok(entry) => { + let Some(exec_attr) = entry.get("Desktop Entry", "Exec") else { + log::warn!("failed to parse {}: missing Desktop Entry", path.display()); + continue; + }; + + let Some(exec) = exec_attr.first() else { + log::warn!("failed to parse {}: missing Exec", path.display()); + continue; + }; + + let Some(commands) = mime_app::exec_to_command(exec, &[] as &[&str]) else { + log::warn!("failed to parse {}: invalid Exec", path.display()); + continue; + }; + + for mut command in commands { + if let Err(err) = spawn_detached(&mut command) { + log::warn!("failed to execute {}: {}", path.display(), err); } - }, - None => { - log::warn!( - "failed to parse {}: missing Desktop Entry/Exec", - path.display() - ); } - }, + } Err(err) => { log::warn!("failed to parse {}: {}", path.display(), err); } diff --git a/src/mime_app.rs b/src/mime_app.rs index 1ad8643b..c92699af 100644 --- a/src/mime_app.rs +++ b/src/mime_app.rs @@ -4,6 +4,7 @@ #[cfg(feature = "desktop")] use cosmic::desktop; use cosmic::widget; +use freedesktop_entry_parser::Section; pub use mime_guess::Mime; use rustc_hash::FxHashMap; use std::{ @@ -48,35 +49,29 @@ pub fn exec_to_command( let field_code_pos = args_vec .iter() .position(|arg| EXEC_HANDLERS.contains(&arg.as_str())); - let args_handler = field_code_pos.and_then(|i| args_vec.get(i)); - // msrv - // .inspect(|handler| log::trace!("Found paths handler: {handler} for exec: {exec}")); + let args_handler = field_code_pos + .and_then(|i| args_vec.get(i)) + .inspect(|&handler| log::trace!("Found paths handler: {handler} for exec: {exec}")); // Number of args before the field code. // This won't be an off by one err below because take is not zero indexed. let field_code_pos = field_code_pos.unwrap_or_default(); let mut processes = match args_handler.map(String::as_str) { Some("%f") => { - let mut processes = Vec::with_capacity(path_opt.len()); - - for path in path_opt.iter().map(AsRef::as_ref) { - // TODO: %f and %F need to handle non-file URLs (see spec) - if from_file_or_dir(path).is_none() { - log::warn!("Desktop file expects a file path instead of a URL: {path:?}"); - } - - // Passing multiple paths to %f should open an instance per path - let mut process = process::Command::new(program); - process.args( - args_vec - .iter() - .map(AsRef::as_ref) - .take(field_code_pos) - .chain(std::iter::once(path)), - ); - processes.push(process); - } + path_opt + .iter() + .map(|path| { + let path = path.as_ref(); + // TODO: %f and %F need to handle non-file URLs (see spec) + if from_file_or_dir(path).is_none() { + log::warn!("Desktop file expects a file path instead of a URL: {path:?}"); + } - processes + // Passing multiple paths to %f should open an instance per path + let mut process = process::Command::new(program); + process.args(args_vec.iter().take(field_code_pos)).arg(path); + process + }) + .collect() } Some("%F") => { // TODO: %f and %F need to handle non-file URLs (see spec) @@ -90,13 +85,9 @@ pub fn exec_to_command( // Launch one instance with all args let mut process = process::Command::new(program); - process.args( - args_vec - .iter() - .map(OsStr::new) - .take(field_code_pos) - .chain(path_opt.iter().map(AsRef::as_ref)), - ); + process + .args(args_vec.iter().take(field_code_pos)) + .args(path_opt); vec![process] } @@ -104,25 +95,15 @@ pub fn exec_to_command( .iter() .map(|path| { let mut process = process::Command::new(program); - process.args( - args_vec - .iter() - .map(OsStr::new) - .take(field_code_pos) - .chain(std::iter::once(path.as_ref())), - ); + process.args(args_vec.iter().take(field_code_pos)).arg(path); process }) .collect(), Some("%U") => { let mut process = process::Command::new(program); - process.args( - args_vec - .iter() - .map(OsStr::new) - .take(field_code_pos) - .chain(path_opt.iter().map(AsRef::as_ref)), - ); + process + .args(args_vec.iter().take(field_code_pos)) + .args(path_opt); vec![process] } Some(invalid) => unreachable!("All valid variants were checked; got: {invalid}"), @@ -315,38 +296,42 @@ impl MimeAppCache { } }; - for attr in entry - .section("Added Associations") - .attrs() - .chain(entry.section("Default Applications").attrs()) + let added_iter = entry.section("Added Associations").map(Section::attrs); + let default_iter = entry.section("Default Applications").map(Section::attrs); + // TODO: replace with Option::into_flat_iter when stable and within MSRV + // https://github.com/rust-lang/rust/issues/148441 + for (attr_key, attrs) in added_iter + .into_iter() + .flatten() + .chain(default_iter.into_iter().flatten()) { - if let Ok(mime) = attr.name.parse::() { - if let Some(filenames) = attr.value { - for filename in filenames.split_terminator(';') { - log::trace!("add {mime}={filename}"); - let apps = self - .cache - .entry(mime.clone()) - .or_insert_with(|| Vec::with_capacity(1)); - if !apps.iter().any(|x| filename_eq(&x.path, filename)) { - if let Some(app) = - all_apps.iter().find(|&x| filename_eq(&x.path, filename)) - { - apps.push(MimeApp::from(app)); - } else { - log::info!( - "failed to add association for {mime:?}: application {filename:?} not found" - ); - } + if let Ok(mime) = attr_key.key.parse::() + && let Some(filenames) = attrs.first() + { + let mime_str = mime.as_ref(); + for filename in filenames.split_terminator(';') { + log::trace!("add {mime_str}={filename}"); + let apps = self.cache.entry(mime.clone()).or_default(); + if !apps.iter().any(|x| filename_eq(&x.path, filename)) { + if let Some(app) = + all_apps.iter().find(|&x| filename_eq(&x.path, filename)) + { + apps.push(MimeApp::from(app)); + } else { + log::info!( + "failed to add association for {mime_str}: application {filename} not found" + ); } } } } } - for attr in entry.section("Removed Associations").attrs() { - if let Ok(mime) = attr.name.parse::() { - if let Some(filenames) = attr.value { + if let Some(removed_associations) = entry.section("Removed Associations") { + for (attr_key, attrs) in removed_associations.attrs() { + if let Ok(mime) = attr_key.key.parse::() + && let Some(filenames) = attrs.first() + { for filename in filenames.split_terminator(';') { log::trace!("remove {mime}={filename}"); if let Some(apps) = self.cache.get_mut(&mime) { @@ -357,14 +342,16 @@ impl MimeAppCache { } } - for attr in entry.section("Default Applications").attrs() { - if let Ok(mime) = attr.name.parse::() { - if let Some(filenames) = attr.value { + if let Some(default_applications) = entry.section("Default Applications") { + for (attr_key, attrs) in default_applications.attrs() { + if let Ok(mime) = attr_key.key.parse::() + && let Some(filenames) = attrs.first() + { for filename in filenames.split_terminator(';') { log::trace!("default {mime}={filename}"); if let Some(apps) = self.cache.get_mut(&mime) { let mut found = false; - for app in apps.iter_mut() { + for app in apps { if filename_eq(&app.path, filename) { app.is_default = true; found = true; @@ -376,7 +363,7 @@ impl MimeAppCache { break; } log::debug!( - "failed to set default for {mime:?}: application {filename:?} not found" + "failed to set default for {mime}: application {filename} not found" ); } } diff --git a/src/tab.rs b/src/tab.rs index b643ba23..bbe4a3ff 100644 --- a/src/tab.rs +++ b/src/tab.rs @@ -608,9 +608,10 @@ fn get_desktop_file_display_name(path: &Path) -> Option { }; entry - .section("Desktop Entry") + .section("Desktop Entry")? .attr("Name") - .map(str::to_string) + .first() + .cloned() } fn get_desktop_file_icon(path: &Path) -> Option { @@ -623,9 +624,10 @@ fn get_desktop_file_icon(path: &Path) -> Option { }; entry - .section("Desktop Entry") + .section("Desktop Entry")? .attr("Icon") - .map(str::to_string) + .first() + .cloned() } pub fn parse_desktop_file(path: &Path) -> (Option, Option) { @@ -636,10 +638,14 @@ pub fn parse_desktop_file(path: &Path) -> (Option, Option) { return (None, None); } }; - let section = entry.section("Desktop Entry"); + + let Some(section) = entry.section("Desktop Entry") else { + return (None, None); + }; + ( - section.attr("Name").map(str::to_string), - section.attr("Icon").map(str::to_string), + section.attr("Name").first().cloned(), + section.attr("Icon").first().cloned(), ) } diff --git a/src/thumbnailer.rs b/src/thumbnailer.rs index 8a22ba9b..b49c77e9 100644 --- a/src/thumbnailer.rs +++ b/src/thumbnailer.rs @@ -123,13 +123,21 @@ impl ThumbnailerCache { } }; + let Some(section) = entry.section("Thumbnailer Entry") else { + log::warn!( + "missing Thumbnailer Entry section for thumbnailer {}", + path.display() + ); + continue; + }; + //TODO: use TryExec? - let section = entry.section("Thumbnailer Entry"); - let Some(exec) = section.attr("Exec") else { + let Some(exec) = section.attr("Exec").first() else { log::warn!("missing Exec attribute for thumbnailer {}", path.display()); continue; }; - let Some(mime_types) = section.attr("MimeType") else { + + let Some(mime_types) = section.attr("MimeType").first() else { log::warn!( "missing MimeType attribute for thumbnailer {}", path.display() @@ -140,13 +148,8 @@ impl ThumbnailerCache { for mime_type in mime_types.split_terminator(';') { if let Ok(mime) = mime_type.parse::() { log::trace!("thumbnailer {}={}", mime, path.display()); - let apps = self - .cache - .entry(mime) - .or_insert_with(|| Vec::with_capacity(1)); - apps.push(Thumbnailer { - exec: exec.to_string(), - }); + let apps = self.cache.entry(mime).or_default(); + apps.push(Thumbnailer { exec: exec.clone() }); } } }