fix: update freedesktop-desktop-entry
This commit is contained in:
parent
58a8f2db64
commit
8d9da92dba
8 changed files with 833 additions and 610 deletions
1102
Cargo.lock
generated
1102
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
23
Cargo.toml
23
Cargo.toml
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "pop-launcher"
|
||||
version = "1.2.4"
|
||||
version = "1.2.5"
|
||||
license = "MPL-2.0"
|
||||
authors = ["Michael Aaron Murphy <michael@mmurphy.dev>"]
|
||||
description = "Library for writing plugins and frontends for pop-launcher"
|
||||
|
|
@ -15,22 +15,22 @@ resolver = "2"
|
|||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.90"
|
||||
serde = { version = "1.0.210", features = ["derive"] }
|
||||
serde_json = "1.0.129"
|
||||
anyhow = "1.0.98"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.140"
|
||||
tracing = "0.1.40"
|
||||
dirs = "5.0.1"
|
||||
serde_with = "3.11.0"
|
||||
dirs = "6.0.0"
|
||||
serde_with = "3.12.0"
|
||||
futures = "0.3.31"
|
||||
flume = "0.11.0"
|
||||
toml = "0.8.19"
|
||||
toml = "0.8.22"
|
||||
regex = "1.11.0"
|
||||
ron = "0.8.1"
|
||||
tokio = "1.40.0"
|
||||
tokio-stream = "0.1.16"
|
||||
ron = "0.9.0"
|
||||
tokio = "1.44.2"
|
||||
tokio-stream = "0.1.17"
|
||||
|
||||
[dependencies]
|
||||
const_format = "0.2.33"
|
||||
const_format = "0.2.34"
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
serde.workspace = true
|
||||
|
|
@ -49,7 +49,6 @@ features = ["io-std", "io-util"]
|
|||
workspace = true
|
||||
features = ["io-util"]
|
||||
|
||||
|
||||
# [patch.crates-io]
|
||||
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }
|
||||
|
||||
|
|
|
|||
|
|
@ -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.11"
|
||||
human_format = "1.1.0"
|
||||
human-sort = "0.2.2"
|
||||
new_mime_guess = "4.0.4"
|
||||
|
|
|
|||
|
|
@ -20,8 +20,6 @@ use pop_launcher::{
|
|||
Request,
|
||||
};
|
||||
use std::borrow::Cow;
|
||||
use std::iter;
|
||||
use std::time::Instant;
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
|
||||
use self::toplevel_handler::{toplevel_handler, ToplevelAction};
|
||||
|
|
@ -111,7 +109,7 @@ pub async fn main() {
|
|||
|
||||
struct App<W> {
|
||||
locales: Vec<String>,
|
||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
||||
desktop_entries: Vec<DesktopEntry>,
|
||||
ids_to_ignore: Vec<u32>,
|
||||
toplevels: Vec<Box<ToplevelInfo>>,
|
||||
calloop_tx: calloop::channel::Sender<ToplevelAction>,
|
||||
|
|
@ -126,10 +124,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
|
||||
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 = fde::Iter::new(fde::default_paths())
|
||||
.map(|path| DesktopEntry::from_path(path, Some(&locales)))
|
||||
.filter_map(Result::ok)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
(
|
||||
|
|
@ -178,82 +175,53 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn search(&mut self, query: &str) {
|
||||
fn contains_pattern(needle: &str, haystack: &[&str]) -> bool {
|
||||
let needle = needle.to_ascii_lowercase();
|
||||
haystack.iter().all(|h| needle.contains(h))
|
||||
}
|
||||
|
||||
let query = query.to_ascii_lowercase();
|
||||
let haystack = query.split_ascii_whitespace().collect::<Vec<&str>>();
|
||||
|
||||
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(),
|
||||
)
|
||||
for info in &self.toplevels {
|
||||
let retain = query.is_empty()
|
||||
|| contains_pattern(&info.app_id, &haystack)
|
||||
|| contains_pattern(&info.title, &haystack);
|
||||
|
||||
if !retain {
|
||||
continue;
|
||||
}
|
||||
|
||||
let appid = fde::unicase::Ascii::new(info.app_id.as_str());
|
||||
|
||||
let entry = fde::find_app_by_id(&self.desktop_entries, appid)
|
||||
.map(ToOwned::to_owned)
|
||||
.unwrap_or_else(|| fde::DesktopEntry::from_appid(appid.to_string()).to_owned());
|
||||
|
||||
let icon_name = if let Some(icon) = entry.icon() {
|
||||
Cow::Owned(icon.to_owned())
|
||||
} 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::<Vec<_>>();
|
||||
|
||||
// if there's an exact appid match, use that instead
|
||||
let exact_appid_match = self
|
||||
.desktop_entries
|
||||
.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
|
||||
}
|
||||
})
|
||||
}
|
||||
Cow::Borrowed("application-x-executable")
|
||||
};
|
||||
|
||||
if let Some(de) = entry {
|
||||
let icon_name = if let Some(icon) = de.icon() {
|
||||
Cow::Owned(icon.to_owned())
|
||||
} else {
|
||||
Cow::Borrowed("application-x-executable")
|
||||
};
|
||||
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(),
|
||||
name: get_description(&entry, &self.locales),
|
||||
icon: Some(IconSource::Name(icon_name)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
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(),
|
||||
name: get_description(de, &self.locales),
|
||||
icon: Some(IconSource::Name(icon_name)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
send(&mut self.tx, response).await;
|
||||
}
|
||||
send(
|
||||
&mut self.tx,
|
||||
response,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush().await;
|
||||
let _ = self.tx.flush();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,7 +43,7 @@ const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"];
|
|||
struct App<W> {
|
||||
current_desktop: Option<Vec<String>>,
|
||||
is_desktop_cosmic: bool,
|
||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
||||
desktop_entries: Vec<DesktopEntry>,
|
||||
locales: Vec<String>,
|
||||
tx: W,
|
||||
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
||||
|
|
@ -69,79 +69,78 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
|
||||
let paths = fde::Iter::new(fde::default_paths());
|
||||
|
||||
let desktop_entries = DesktopEntry::from_paths(paths, &locales)
|
||||
let desktop_entries = paths
|
||||
.flat_map(|path| DesktopEntry::from_path(path, Some(&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;
|
||||
}
|
||||
// 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)?;
|
||||
de.name(&self.locales)?;
|
||||
|
||||
match de.exec() {
|
||||
Some(exec) => match exec.split_ascii_whitespace().next() {
|
||||
Some(exec) => {
|
||||
if exec == "false" {
|
||||
return None;
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
// 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());
|
||||
// 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)
|
||||
})
|
||||
Some(de)
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
|
|
@ -213,33 +212,66 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
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, &[]);
|
||||
let query = query.to_ascii_lowercase();
|
||||
|
||||
if score > 0.6 {
|
||||
let response = PluginResponse::Append(PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: entry.name(&self.locales).unwrap_or_default().to_string(),
|
||||
description: get_description(entry, &self.locales),
|
||||
keywords: entry
|
||||
.keywords(&self.locales)
|
||||
.map(|v| v.iter().map(|e| e.to_string()).collect()),
|
||||
icon: entry
|
||||
.icon()
|
||||
.map(|e| Cow::Owned(e.to_string()))
|
||||
.map(IconSource::Name),
|
||||
exec: entry.exec().map(|e| e.to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
let &mut Self {
|
||||
ref desktop_entries,
|
||||
ref locales,
|
||||
ref mut tx,
|
||||
..
|
||||
} = self;
|
||||
|
||||
send(&mut self.tx, response).await;
|
||||
let mut items = Vec::with_capacity(16);
|
||||
|
||||
for (id, entry) in desktop_entries.iter().enumerate() {
|
||||
let name = entry.name(locales).unwrap_or_default();
|
||||
let keywords = entry.keywords(locales);
|
||||
items.extend(name.split_ascii_whitespace().map(ToOwned::to_owned));
|
||||
|
||||
if let Some(keywords) = keywords.as_ref() {
|
||||
items.extend(keywords.iter().map(|x| String::from(x.as_ref())));
|
||||
}
|
||||
|
||||
if let Some(exec) = entry.exec() {
|
||||
items.push(exec.to_owned());
|
||||
}
|
||||
|
||||
for search_interest in items.drain(..) {
|
||||
let search_interest = search_interest.to_ascii_lowercase();
|
||||
let append = search_interest.starts_with(&*query)
|
||||
|| query
|
||||
.split_ascii_whitespace()
|
||||
.any(|query| search_interest.contains(&*query))
|
||||
|| strsim::jaro_winkler(&*query, &*search_interest) > 0.6;
|
||||
|
||||
if append {
|
||||
let response = PluginResponse::Append(PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: entry.name(&self.locales).unwrap_or_default().to_string(),
|
||||
description: get_description(entry, &self.locales),
|
||||
keywords: entry
|
||||
.keywords(&self.locales)
|
||||
.map(|v| v.iter().map(|e| e.to_string()).collect()),
|
||||
icon: entry
|
||||
.icon()
|
||||
.map(|e| Cow::Owned(e.to_string()))
|
||||
.map(IconSource::Name),
|
||||
exec: entry.exec().map(|e| e.to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
send(tx, response).await;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush();
|
||||
}
|
||||
|
||||
async fn gnome_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
async fn gnome_context(&self, entry: &DesktopEntry) -> Vec<ContextOption> {
|
||||
if self.gpus.is_some() {
|
||||
vec![ContextOption {
|
||||
id: 0,
|
||||
|
|
@ -255,7 +287,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn cosmic_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
async fn cosmic_context(&self, entry: &DesktopEntry) -> Vec<ContextOption> {
|
||||
let mut options = Vec::new();
|
||||
|
||||
if let Some(gpus) = self.gpus.as_ref() {
|
||||
|
|
|
|||
|
|
@ -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<'a>(de: &'a DesktopEntry, locales: &[String]) -> String {
|
||||
let path_source = PathSource::guess_from(&de.path);
|
||||
|
||||
let desc_source = path_string(&path_source).to_string();
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
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());
|
||||
|
|
|
|||
|
|
@ -124,7 +124,7 @@ fn detect_terminal() -> (PathBuf, &'static str) {
|
|||
freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
||||
.filter_map(|path| {
|
||||
std::fs::read_to_string(&path).ok().and_then(|input| {
|
||||
DesktopEntry::from_str(&path, &input, &get_languages_from_env())
|
||||
DesktopEntry::from_str(&path, &input, Some(&get_languages_from_env()))
|
||||
.ok()
|
||||
.and_then(|de| {
|
||||
if de.no_display()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue