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]
|
[package]
|
||||||
name = "pop-launcher"
|
name = "pop-launcher"
|
||||||
version = "1.2.4"
|
version = "1.2.5"
|
||||||
license = "MPL-2.0"
|
license = "MPL-2.0"
|
||||||
authors = ["Michael Aaron Murphy <michael@mmurphy.dev>"]
|
authors = ["Michael Aaron Murphy <michael@mmurphy.dev>"]
|
||||||
description = "Library for writing plugins and frontends for pop-launcher"
|
description = "Library for writing plugins and frontends for pop-launcher"
|
||||||
|
|
@ -15,22 +15,22 @@ resolver = "2"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
anyhow = "1.0.90"
|
anyhow = "1.0.98"
|
||||||
serde = { version = "1.0.210", features = ["derive"] }
|
serde = { version = "1.0.219", features = ["derive"] }
|
||||||
serde_json = "1.0.129"
|
serde_json = "1.0.140"
|
||||||
tracing = "0.1.40"
|
tracing = "0.1.40"
|
||||||
dirs = "5.0.1"
|
dirs = "6.0.0"
|
||||||
serde_with = "3.11.0"
|
serde_with = "3.12.0"
|
||||||
futures = "0.3.31"
|
futures = "0.3.31"
|
||||||
flume = "0.11.0"
|
flume = "0.11.0"
|
||||||
toml = "0.8.19"
|
toml = "0.8.22"
|
||||||
regex = "1.11.0"
|
regex = "1.11.0"
|
||||||
ron = "0.8.1"
|
ron = "0.9.0"
|
||||||
tokio = "1.40.0"
|
tokio = "1.44.2"
|
||||||
tokio-stream = "0.1.16"
|
tokio-stream = "0.1.17"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
const_format = "0.2.33"
|
const_format = "0.2.34"
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
futures.workspace = true
|
futures.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
|
@ -49,7 +49,6 @@ features = ["io-std", "io-util"]
|
||||||
workspace = true
|
workspace = true
|
||||||
features = ["io-util"]
|
features = ["io-util"]
|
||||||
|
|
||||||
|
|
||||||
# [patch.crates-io]
|
# [patch.crates-io]
|
||||||
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }
|
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-pidfd = "0.1.4"
|
async-pidfd = "0.1.4"
|
||||||
fork = "0.2.0"
|
fork = "0.2.0"
|
||||||
freedesktop-desktop-entry = "0.6.2"
|
freedesktop-desktop-entry = "0.7.11"
|
||||||
human_format = "1.1.0"
|
human_format = "1.1.0"
|
||||||
human-sort = "0.2.2"
|
human-sort = "0.2.2"
|
||||||
new_mime_guess = "4.0.4"
|
new_mime_guess = "4.0.4"
|
||||||
|
|
|
||||||
|
|
@ -20,8 +20,6 @@ use pop_launcher::{
|
||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::iter;
|
|
||||||
use std::time::Instant;
|
|
||||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
use self::toplevel_handler::{toplevel_handler, ToplevelAction};
|
use self::toplevel_handler::{toplevel_handler, ToplevelAction};
|
||||||
|
|
@ -111,7 +109,7 @@ pub async fn main() {
|
||||||
|
|
||||||
struct App<W> {
|
struct App<W> {
|
||||||
locales: Vec<String>,
|
locales: Vec<String>,
|
||||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
desktop_entries: Vec<DesktopEntry>,
|
||||||
ids_to_ignore: Vec<u32>,
|
ids_to_ignore: Vec<u32>,
|
||||||
toplevels: Vec<Box<ToplevelInfo>>,
|
toplevels: Vec<Box<ToplevelInfo>>,
|
||||||
calloop_tx: calloop::channel::Sender<ToplevelAction>,
|
calloop_tx: calloop::channel::Sender<ToplevelAction>,
|
||||||
|
|
@ -126,10 +124,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
|
|
||||||
let locales = fde::get_languages_from_env();
|
let locales = fde::get_languages_from_env();
|
||||||
|
|
||||||
let paths = fde::Iter::new(fde::default_paths());
|
let desktop_entries = fde::Iter::new(fde::default_paths())
|
||||||
|
.map(|path| DesktopEntry::from_path(path, Some(&locales)))
|
||||||
let desktop_entries = DesktopEntry::from_paths(paths, &locales)
|
.filter_map(Result::ok)
|
||||||
.filter_map(|e| e.ok())
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(
|
(
|
||||||
|
|
@ -178,62 +175,30 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search(&mut self, query: &str) {
|
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 query = query.to_ascii_lowercase();
|
||||||
|
let haystack = query.split_ascii_whitespace().collect::<Vec<&str>>();
|
||||||
|
|
||||||
for info in self.toplevels.iter().rev() {
|
for info in &self.toplevels {
|
||||||
let entry = if query.is_empty() {
|
let retain = query.is_empty()
|
||||||
fde::matching::get_best_match(
|
|| contains_pattern(&info.app_id, &haystack)
|
||||||
&[&info.app_id, &info.title],
|
|| contains_pattern(&info.title, &haystack);
|
||||||
&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::<Vec<_>>();
|
|
||||||
|
|
||||||
// if there's an exact appid match, use that instead
|
if !retain {
|
||||||
let exact_appid_match = self
|
continue;
|
||||||
.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
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(de) = entry {
|
let appid = fde::unicase::Ascii::new(info.app_id.as_str());
|
||||||
let icon_name = if let Some(icon) = de.icon() {
|
|
||||||
|
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())
|
Cow::Owned(icon.to_owned())
|
||||||
} else {
|
} else {
|
||||||
Cow::Borrowed("application-x-executable")
|
Cow::Borrowed("application-x-executable")
|
||||||
|
|
@ -244,16 +209,19 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
id: info.foreign_toplevel.id().protocol_id(),
|
id: info.foreign_toplevel.id().protocol_id(),
|
||||||
window: Some((0, info.foreign_toplevel.id().protocol_id())),
|
window: Some((0, info.foreign_toplevel.id().protocol_id())),
|
||||||
description: info.title.clone(),
|
description: info.title.clone(),
|
||||||
name: get_description(de, &self.locales),
|
name: get_description(&entry, &self.locales),
|
||||||
icon: Some(IconSource::Name(icon_name)),
|
icon: Some(IconSource::Name(icon_name)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
send(&mut self.tx, response).await;
|
send(
|
||||||
}
|
&mut self.tx,
|
||||||
|
response,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
send(&mut self.tx, PluginResponse::Finished).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> {
|
struct App<W> {
|
||||||
current_desktop: Option<Vec<String>>,
|
current_desktop: Option<Vec<String>>,
|
||||||
is_desktop_cosmic: bool,
|
is_desktop_cosmic: bool,
|
||||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
desktop_entries: Vec<DesktopEntry>,
|
||||||
locales: Vec<String>,
|
locales: Vec<String>,
|
||||||
tx: W,
|
tx: W,
|
||||||
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
||||||
|
|
@ -69,9 +69,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
|
|
||||||
let paths = fde::Iter::new(fde::default_paths());
|
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| {
|
.filter_map(|de| {
|
||||||
de.ok().and_then(|de| {
|
|
||||||
// Treat Flatpak and system apps differently in the cache so they don't
|
// Treat Flatpak and system apps differently in the cache so they don't
|
||||||
// override each other
|
// override each other
|
||||||
let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref());
|
let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref());
|
||||||
|
|
@ -142,7 +142,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
|
|
||||||
Some(de)
|
Some(de)
|
||||||
})
|
})
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
self.desktop_entries = desktop_entries;
|
self.desktop_entries = desktop_entries;
|
||||||
|
|
@ -213,10 +212,39 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn search(&mut self, query: &str) {
|
async fn search(&mut self, query: &str) {
|
||||||
for (id, entry) in self.desktop_entries.iter().enumerate() {
|
let query = query.to_ascii_lowercase();
|
||||||
let score = fde::matching::get_entry_score(query, entry, &self.locales, &[]);
|
|
||||||
|
|
||||||
if score > 0.6 {
|
let &mut Self {
|
||||||
|
ref desktop_entries,
|
||||||
|
ref locales,
|
||||||
|
ref mut tx,
|
||||||
|
..
|
||||||
|
} = self;
|
||||||
|
|
||||||
|
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 {
|
let response = PluginResponse::Append(PluginSearchResult {
|
||||||
id: id as u32,
|
id: id as u32,
|
||||||
name: entry.name(&self.locales).unwrap_or_default().to_string(),
|
name: entry.name(&self.locales).unwrap_or_default().to_string(),
|
||||||
|
|
@ -232,14 +260,18 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
..Default::default()
|
..Default::default()
|
||||||
});
|
});
|
||||||
|
|
||||||
send(&mut self.tx, response).await;
|
send(tx, response).await;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send(&mut self.tx, PluginResponse::Finished).await;
|
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() {
|
if self.gpus.is_some() {
|
||||||
vec![ContextOption {
|
vec![ContextOption {
|
||||||
id: 0,
|
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();
|
let mut options = Vec::new();
|
||||||
|
|
||||||
if let Some(gpus) = self.gpus.as_ref() {
|
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 path_source = PathSource::guess_from(&de.path);
|
||||||
|
|
||||||
let desc_source = path_string(&path_source).to_string();
|
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(
|
if let Ok(entry) = fde::DesktopEntry::from_str(
|
||||||
path,
|
path,
|
||||||
&data,
|
&data,
|
||||||
&get_languages_from_env(),
|
Some(&get_languages_from_env()),
|
||||||
) {
|
) {
|
||||||
if let Some(icon) = entry.icon() {
|
if let Some(icon) = entry.icon() {
|
||||||
icon_name = Cow::Owned(icon.to_owned());
|
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())
|
freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
||||||
.filter_map(|path| {
|
.filter_map(|path| {
|
||||||
std::fs::read_to_string(&path).ok().and_then(|input| {
|
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()
|
.ok()
|
||||||
.and_then(|de| {
|
.and_then(|de| {
|
||||||
if de.no_display()
|
if de.no_display()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue