diff --git a/Cargo.lock b/Cargo.lock index 1c51260..621b480 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -17,6 +17,18 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -26,6 +38,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "android-tzdata" version = "0.1.1" @@ -414,6 +432,39 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b" +[[package]] +name = "cached" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0839c297f8783316fcca9d90344424e968395413f0662a5481f79c6648bbc14" +dependencies = [ + "ahash", + "cached_proc_macro", + "cached_proc_macro_types", + "hashbrown 0.14.5", + "once_cell", + "thiserror 2.0.4", + "web-time", +] + +[[package]] +name = "cached_proc_macro" +version = "0.24.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "673992d934f0711b68ebb3e1b79cdc4be31634b37c98f26867ced0438ca5c603" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.90", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0" + [[package]] name = "calloop" version = "0.13.0" @@ -859,17 +910,16 @@ dependencies = [ [[package]] name = "freedesktop-desktop-entry" -version = "0.6.2" +version = "0.7.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e33809936d2fa9ac78750c5c04696a7aabdb09f928454957c77a2c8247f5ff98" +checksum = "7e5e2bd2e383df08a8439c2e096be16d5355aa00f976b295cf8e077ea5953d5d" dependencies = [ - "dirs", + "cached", "gettext-rs", "log", "memchr", "strsim", - "textdistance", - "thiserror 1.0.69", + "thiserror 2.0.4", "xdg", ] @@ -1075,6 +1125,16 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" +dependencies = [ + "ahash", + "allocator-api2", +] + [[package]] name = "hashbrown" version = "0.15.2" @@ -1485,7 +1545,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" dependencies = [ "cfg-if", - "windows-targets 0.48.5", + "windows-targets 0.52.6", ] [[package]] @@ -2650,12 +2710,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "textdistance" -version = "1.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa672c55ab69f787dbc9126cc387dbe57fdd595f585e4524cf89018fa44ab819" - [[package]] name = "thiserror" version = "1.0.69" diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml index 41e11d9..d76170c 100644 --- a/plugins/Cargo.toml +++ b/plugins/Cargo.toml @@ -9,7 +9,7 @@ publish = false [dependencies] async-pidfd = "0.1.4" fork = "0.2.0" -freedesktop-desktop-entry = "0.6.2" +freedesktop-desktop-entry = "0.7.9" human_format = "1.1.0" human-sort = "0.2.2" new_mime_guess = "4.0.4" diff --git a/plugins/src/cosmic_toplevel/mod.rs b/plugins/src/cosmic_toplevel/mod.rs index da38a6d..67c22da 100644 --- a/plugins/src/cosmic_toplevel/mod.rs +++ b/plugins/src/cosmic_toplevel/mod.rs @@ -4,7 +4,7 @@ use cctk::cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v use cctk::wayland_client::Proxy; use cctk::{sctk::reexports::calloop, toplevel_info::ToplevelInfo}; use fde::DesktopEntry; -use freedesktop_desktop_entry as fde; +use freedesktop_desktop_entry::{self as fde, default_paths, Iter}; use toplevel_handler::ToplevelUpdate; use tracing::{debug, error, info, warn}; @@ -21,7 +21,6 @@ use pop_launcher::{ }; use std::borrow::Cow; use std::iter; -use std::time::Instant; use tokio::io::{AsyncWrite, AsyncWriteExt}; use self::toplevel_handler::{toplevel_handler, ToplevelAction}; @@ -80,12 +79,12 @@ pub async fn main() { { if info.state.contains(&State::Activated) { app.toplevels.remove(pos); - app.toplevels.push(Box::new(info)); + app.toplevels.push(info); } else { - app.toplevels[pos] = Box::new(info); + app.toplevels[pos] = info; } } else { - app.toplevels.push(Box::new(info)); + app.toplevels.push(info); } } ToplevelUpdate::Remove(foreign_toplevel) => { @@ -111,9 +110,9 @@ pub async fn main() { struct App { locales: Vec, - desktop_entries: Vec>, + desktop_entries: Vec, ids_to_ignore: Vec, - toplevels: Vec>, + toplevels: Vec, calloop_tx: calloop::channel::Sender, tx: W, } @@ -125,11 +124,8 @@ impl App { let _handle = std::thread::spawn(move || toplevel_handler(toplevels_tx, calloop_rx)); let locales = fde::get_languages_from_env(); - - let paths = fde::Iter::new(fde::default_paths()); - - let desktop_entries = DesktopEntry::from_paths(paths, &locales) - .filter_map(|e| e.ok()) + let desktop_entries = Iter::new(default_paths()) + .entries(Some(&locales)) .collect::>(); ( @@ -180,59 +176,26 @@ impl App { async fn search(&mut self, query: &str) { let query = query.to_ascii_lowercase(); - for info in self.toplevels.iter().rev() { - let entry = if query.is_empty() { - fde::matching::get_best_match( - &[&info.app_id, &info.title], - &self.desktop_entries, - fde::matching::MatchAppIdOptions::default(), - ) - } else { - let lowercase_title = info.title.to_lowercase(); - let window_words = lowercase_title - .split_whitespace() - .chain(iter::once(info.app_id.as_str())) - .chain(iter::once(info.title.as_str())) - .collect::>(); - - // if there's an exact appid match, use that instead - let exact_appid_match = self - .desktop_entries + for de in self.desktop_entries.iter() { + let ret = match query.is_empty() { + true => self + .toplevels .iter() - .find(|de| de.appid == info.app_id); - if exact_appid_match.is_some() - && fde::matching::get_entry_score( - &query, - exact_appid_match.unwrap(), - &self.locales, - &window_words, - ) > 0.8 - { - exact_appid_match - } else { - fde::matching::get_best_match( - &window_words, - &self.desktop_entries, - fde::matching::MatchAppIdOptions::default(), - ) - .and_then(|de| { - let score = fde::matching::get_entry_score( - &query, - de, - &self.locales, - &window_words, - ); - - if score > 0.8 { - Some(de) - } else { - None - } - }) - } + .find(|info| de.match_query(&info.title, &self.locales, &[]) > 0.8), + + false => self.toplevels.iter().find(|info| { + let lowercase_title = info.title.to_lowercase(); + let window_words = lowercase_title + .split_whitespace() + .chain(iter::once(info.app_id.as_str())) + .chain(iter::once(info.title.as_str())) + .collect::>(); + de.appid == info.app_id + || de.match_query(&info.title, &self.locales, &window_words) > 0.8 + }), }; - if let Some(de) = entry { + if let Some(toplevel) = ret { let icon_name = if let Some(icon) = de.icon() { Cow::Owned(icon.to_owned()) } else { @@ -241,9 +204,9 @@ impl App { let response = PluginResponse::Append(PluginSearchResult { // XXX protocol id may be re-used later - id: info.foreign_toplevel.id().protocol_id(), - window: Some((0, info.foreign_toplevel.id().protocol_id())), - description: info.title.clone(), + id: toplevel.foreign_toplevel.id().protocol_id(), + window: Some((0, toplevel.foreign_toplevel.id().protocol_id())), + description: toplevel.title.clone(), name: get_description(de, &self.locales), icon: Some(IconSource::Name(icon_name)), ..Default::default() diff --git a/plugins/src/desktop_entries/mod.rs b/plugins/src/desktop_entries/mod.rs index b5bbaa3..2875455 100644 --- a/plugins/src/desktop_entries/mod.rs +++ b/plugins/src/desktop_entries/mod.rs @@ -43,7 +43,7 @@ const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"]; struct App { current_desktop: Option>, is_desktop_cosmic: bool, - desktop_entries: Vec>, + desktop_entries: Vec, locales: Vec, tx: W, gpus: Option>, @@ -69,83 +69,81 @@ impl App { let paths = fde::Iter::new(fde::default_paths()); - let desktop_entries = DesktopEntry::from_paths(paths, &locales) - .filter_map(|de| { - de.ok().and_then(|de| { - // Treat Flatpak and system apps differently in the cache so they don't - // override each other - let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref()); - if deduplicator.contains(appid) { - return None; - } - - de.name(&self.locales)?; - - match de.exec() { - Some(exec) => match exec.split_ascii_whitespace().next() { - Some(exec) => { - if exec == "false" { - return None; + self.desktop_entries = paths + .filter_map(|path| { + DesktopEntry::from_path(path, Some(&locales)) + .ok() + .and_then(|de| { + let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref()); + if deduplicator.contains(appid) { + return None; + } + + de.name(&self.locales)?; + + match de.exec() { + Some(exec) => match exec.split_ascii_whitespace().next() { + Some(exec) => { + if exec == "false" { + return None; + } } - } + None => return None, + }, None => return None, - }, - None => return None, - } - - // Avoid showing the GNOME Shell entry entirely - if de - .name(&[] as &[&str]) - .map_or(false, |v| EXCLUSIONS.contains(&v.as_ref())) - { - return None; - } - - // Do not show if our desktop is defined in `NotShowIn`. - if let Some(not_show_in) = de.not_show_in() { - if let Some(current_desktop) = &self.current_desktop { - if not_show_in.iter().any(|not_show| { - current_desktop - .iter() - .any(|desktop| ¬_show.to_ascii_lowercase() == desktop) - }) { - return None; + } + + // Avoid showing the GNOME Shell entry entirely + if de + .name(&[] as &[&str]) + .is_some_and(|v| EXCLUSIONS.contains(&v.as_ref())) + { + return None; + } + + // Do not show if our desktop is defined in `NotShowIn`. + if let Some(not_show_in) = de.not_show_in() { + if let Some(current_desktop) = &self.current_desktop { + if not_show_in.iter().any(|not_show| { + current_desktop + .iter() + .any(|desktop| ¬_show.to_ascii_lowercase() == desktop) + }) { + return None; + } } } - } - - // Do not show if our desktop is not defined in `OnlyShowIn`. - if let Some(only_show_in) = de.only_show_in() { - if let Some(current_desktop) = &self.current_desktop { - if !only_show_in.iter().any(|show_in| { - current_desktop - .iter() - .any(|desktop| &show_in.to_ascii_lowercase() == desktop) - }) { - return None; + + // Do not show if our desktop is not defined in `OnlyShowIn`. + if let Some(only_show_in) = de.only_show_in() { + if let Some(current_desktop) = &self.current_desktop { + if !only_show_in.iter().any(|show_in| { + current_desktop + .iter() + .any(|desktop| &show_in.to_ascii_lowercase() == desktop) + }) { + return None; + } } } - } - // Treat `OnlyShowIn` as an override otherwise do not show if `NoDisplay` is true - // Some desktop environments set `OnlyShowIn` and `NoDisplay = true` to - // indicate special entries - else if de.no_display() { - return None; - } - - // Always cache already visited entries to allow overriding entries e.g. by - // placing a modified copy in ~/.local/share/applications/ - // - // We only do this when we can add an entry to our list, otherwise we risk - // ignoring user overrides or valid applications due to shell URL handlers - deduplicator.insert(appid.to_owned()); - - Some(de) - }) - }) - .collect::>(); + // Treat `OnlyShowIn` as an override otherwise do not show if `NoDisplay` is true + // Some desktop environments set `OnlyShowIn` and `NoDisplay = true` to + // indicate special entries + else if de.no_display() { + return None; + } - self.desktop_entries = desktop_entries; + // Always cache already visited entries to allow overriding entries e.g. by + // placing a modified copy in ~/.local/share/applications/ + // + // We only do this when we can add an entry to our list, otherwise we risk + // ignoring user overrides or valid applications due to shell URL handlers + deduplicator.insert(appid.to_owned()); + + Some(de) + }) + }) + .collect(); self.gpus = try_get_gpus().await; } @@ -214,9 +212,7 @@ impl App { async fn search(&mut self, query: &str) { for (id, entry) in self.desktop_entries.iter().enumerate() { - let score = fde::matching::get_entry_score(query, entry, &self.locales, &[]); - - if score > 0.6 { + if entry.match_query(query, &self.locales, &[]) > 0.6 { let response = PluginResponse::Append(PluginSearchResult { id: id as u32, name: entry.name(&self.locales).unwrap_or_default().to_string(), @@ -239,7 +235,7 @@ impl App { send(&mut self.tx, PluginResponse::Finished).await; } - async fn gnome_context(&self, entry: &DesktopEntry<'_>) -> Vec { + async fn gnome_context(&self, entry: &DesktopEntry) -> Vec { if self.gpus.is_some() { vec![ContextOption { id: 0, @@ -255,7 +251,7 @@ impl App { } } - async fn cosmic_context(&self, entry: &DesktopEntry<'_>) -> Vec { + async fn cosmic_context(&self, entry: &DesktopEntry) -> Vec { let mut options = Vec::new(); if let Some(gpus) = self.gpus.as_ref() { diff --git a/plugins/src/desktop_entries/utils.rs b/plugins/src/desktop_entries/utils.rs index 864aced..43afa27 100644 --- a/plugins/src/desktop_entries/utils.rs +++ b/plugins/src/desktop_entries/utils.rs @@ -20,7 +20,7 @@ pub fn path_string(source: &PathSource) -> Cow<'static, str> { } } -pub fn get_description<'a>(de: &'a DesktopEntry<'a>, locales: &[String]) -> String { +pub fn get_description(de: &DesktopEntry, locales: &[String]) -> String { let path_source = PathSource::guess_from(&de.path); let desc_source = path_string(&path_source).to_string(); diff --git a/plugins/src/pop_shell/mod.rs b/plugins/src/pop_shell/mod.rs index 8946f58..38f7e5d 100644 --- a/plugins/src/pop_shell/mod.rs +++ b/plugins/src/pop_shell/mod.rs @@ -143,7 +143,7 @@ impl App { if let Ok(entry) = fde::DesktopEntry::from_str( path, &data, - &get_languages_from_env(), + Some(&get_languages_from_env()), ) { if let Some(icon) = entry.icon() { icon_name = Cow::Owned(icon.to_owned()); diff --git a/plugins/src/scripts/mod.rs b/plugins/src/scripts/mod.rs index e4468bd..38ffbd5 100644 --- a/plugins/src/scripts/mod.rs +++ b/plugins/src/scripts/mod.rs @@ -188,8 +188,6 @@ async fn load_from(path: &Path, paths: &mut VecDeque, tx: Sender