improv: use freedesktop_entry to search applications
This commit is contained in:
parent
65c1742a88
commit
2449943863
9 changed files with 146 additions and 99 deletions
4
.vscode/settings.json
vendored
4
.vscode/settings.json
vendored
|
|
@ -1,4 +1,4 @@
|
||||||
{
|
{
|
||||||
"remote.containers.dockerPath": "podman",
|
// "remote.containers.dockerPath": "podman",
|
||||||
"rust-analyzer.check.overrideCommand": ["just", "check-json"]
|
// "rust-analyzer.check.overrideCommand": ["just", "check-json"]
|
||||||
}
|
}
|
||||||
23
Cargo.lock
generated
23
Cargo.lock
generated
|
|
@ -572,15 +572,6 @@ dependencies = [
|
||||||
"crypto-common",
|
"crypto-common",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "dirs"
|
|
||||||
version = "3.0.2"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "30baa043103c9d0c2a57cf537cc2f35623889dc0d405e6c3cccfadbc81c71309"
|
|
||||||
dependencies = [
|
|
||||||
"dirs-sys 0.3.7",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
version = "4.0.0"
|
version = "4.0.0"
|
||||||
|
|
@ -786,13 +777,15 @@ dependencies = [
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "freedesktop-desktop-entry"
|
name = "freedesktop-desktop-entry"
|
||||||
version = "0.5.2"
|
version = "0.6.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c201444ddafb5506fe85265b48421664ff4617e3b7090ef99e42a0070c1aead0"
|
checksum = "4fefe79ec93a6aeaa938981fe3e11b4ed1b2f9deacc6bb631585bc48252d1bfa"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"dirs 3.0.2",
|
"dirs 5.0.1",
|
||||||
"gettext-rs",
|
"gettext-rs",
|
||||||
"memchr",
|
"memchr",
|
||||||
|
"strsim 0.11.1",
|
||||||
|
"textdistance",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"xdg",
|
"xdg",
|
||||||
]
|
]
|
||||||
|
|
@ -2350,6 +2343,12 @@ dependencies = [
|
||||||
"windows-sys 0.52.0",
|
"windows-sys 0.52.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "textdistance"
|
||||||
|
version = "1.0.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d321c8576c2b47e43953e9cce236550d4cd6af0a6ce518fe084340082ca6037b"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "thiserror"
|
name = "thiserror"
|
||||||
version = "1.0.58"
|
version = "1.0.58"
|
||||||
|
|
|
||||||
5
justfile
5
justfile
|
|
@ -2,6 +2,9 @@ ID := 'pop-launcher'
|
||||||
plugins := 'calc desktop_entries files find pop_shell pulse recent scripts terminal web cosmic_toplevel'
|
plugins := 'calc desktop_entries files find pop_shell pulse recent scripts terminal web cosmic_toplevel'
|
||||||
|
|
||||||
rootdir := ''
|
rootdir := ''
|
||||||
|
debug := '0'
|
||||||
|
|
||||||
|
target-dir := if debug == '1' { 'target/debug' } else { 'target/release' }
|
||||||
|
|
||||||
base-dir := if rootdir == '' {
|
base-dir := if rootdir == '' {
|
||||||
env_var('HOME') / '.local'
|
env_var('HOME') / '.local'
|
||||||
|
|
@ -57,7 +60,7 @@ install: install-bin install-plugins install-scripts
|
||||||
|
|
||||||
# Install pop-launcher binary
|
# Install pop-launcher binary
|
||||||
install-bin:
|
install-bin:
|
||||||
install -Dm0755 target/release/pop-launcher-bin {{bin-path}}
|
install -Dm0755 {{target-dir}}/pop-launcher-bin {{bin-path}}
|
||||||
|
|
||||||
# Install pop-launcher plugins
|
# Install pop-launcher plugins
|
||||||
install-plugins:
|
install-plugins:
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ publish = false
|
||||||
[dependencies]
|
[dependencies]
|
||||||
async-pidfd = "0.1.4"
|
async-pidfd = "0.1.4"
|
||||||
fork = "0.1.23"
|
fork = "0.1.23"
|
||||||
freedesktop-desktop-entry = "0.5.2"
|
freedesktop-desktop-entry = "0.6.0"
|
||||||
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.1"
|
new_mime_guess = "4.0.1"
|
||||||
|
|
@ -36,7 +36,9 @@ recently-used-xbel = "1.0.0"
|
||||||
|
|
||||||
# dependencies cosmic toplevel
|
# dependencies cosmic toplevel
|
||||||
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" }
|
cctk = { git = "https://github.com/pop-os/cosmic-protocols", package = "cosmic-client-toolkit" }
|
||||||
sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "3bed072", features = ["calloop"] }
|
sctk = { package = "smithay-client-toolkit", git = "https://github.com/smithay/client-toolkit", rev = "3bed072", features = [
|
||||||
|
"calloop",
|
||||||
|
] }
|
||||||
|
|
||||||
# dependencies desktop entries
|
# dependencies desktop entries
|
||||||
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
switcheroo-control = { git = "https://github.com/pop-os/dbus-settings-bindings" }
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,11 @@ mod toplevel_handler;
|
||||||
use cctk::wayland_client::Proxy;
|
use cctk::wayland_client::Proxy;
|
||||||
use cctk::{cosmic_protocols, sctk::reexports::calloop, toplevel_info::ToplevelInfo};
|
use cctk::{cosmic_protocols, sctk::reexports::calloop, toplevel_info::ToplevelInfo};
|
||||||
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
use cosmic_protocols::toplevel_info::v1::client::zcosmic_toplevel_handle_v1::ZcosmicToplevelHandleV1;
|
||||||
|
use fde::DesktopEntry;
|
||||||
use crate::send;
|
|
||||||
use freedesktop_desktop_entry as fde;
|
use freedesktop_desktop_entry as fde;
|
||||||
|
|
||||||
|
use crate::desktop_entries::utils::get_description;
|
||||||
|
use crate::send;
|
||||||
use futures::{
|
use futures::{
|
||||||
channel::mpsc,
|
channel::mpsc,
|
||||||
future::{select, Either},
|
future::{select, Either},
|
||||||
|
|
@ -16,7 +18,6 @@ use pop_launcher::{
|
||||||
Request,
|
Request,
|
||||||
};
|
};
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::{fs, path::PathBuf};
|
|
||||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||||
|
|
||||||
use self::toplevel_handler::{toplevel_handler, ToplevelAction, ToplevelEvent};
|
use self::toplevel_handler::{toplevel_handler, ToplevelAction, ToplevelEvent};
|
||||||
|
|
@ -90,7 +91,8 @@ pub async fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
struct App<W> {
|
struct App<W> {
|
||||||
desktop_entries: Vec<(fde::PathSource, PathBuf)>,
|
locales: Vec<String>,
|
||||||
|
desktop_entries: Vec<DesktopEntry<'static>>,
|
||||||
ids_to_ignore: Vec<u32>,
|
ids_to_ignore: Vec<u32>,
|
||||||
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
|
toplevels: Vec<(ZcosmicToplevelHandleV1, ToplevelInfo)>,
|
||||||
calloop_tx: calloop::channel::Sender<ToplevelAction>,
|
calloop_tx: calloop::channel::Sender<ToplevelAction>,
|
||||||
|
|
@ -103,12 +105,19 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
let (calloop_tx, calloop_rx) = calloop::channel::channel();
|
let (calloop_tx, calloop_rx) = calloop::channel::channel();
|
||||||
let _handle = std::thread::spawn(move || toplevel_handler(toplevels_tx, calloop_rx));
|
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())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
(
|
(
|
||||||
Self {
|
Self {
|
||||||
|
locales,
|
||||||
|
desktop_entries,
|
||||||
ids_to_ignore: Vec::new(),
|
ids_to_ignore: Vec::new(),
|
||||||
desktop_entries: fde::Iter::new(fde::default_paths())
|
|
||||||
.map(|path| (fde::PathSource::guess_from(&path), path))
|
|
||||||
.collect(),
|
|
||||||
toplevels: Vec::new(),
|
toplevels: Vec::new(),
|
||||||
calloop_tx,
|
calloop_tx,
|
||||||
tx,
|
tx,
|
||||||
|
|
@ -150,55 +159,57 @@ 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 item in &self.toplevels {
|
for (handle, info) in &self.toplevels {
|
||||||
let retain = query.is_empty()
|
let entry = if query.is_empty() {
|
||||||
|| contains_pattern(&item.1.app_id.to_ascii_lowercase(), &haystack)
|
fde::matching::get_best_match(
|
||||||
|| contains_pattern(&item.1.title.to_ascii_lowercase(), &haystack);
|
&[&info.app_id, &info.title],
|
||||||
|
&self.desktop_entries,
|
||||||
|
fde::matching::MatchAppIdOptions::default(),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
fde::matching::get_best_match(
|
||||||
|
&[&info.app_id, &info.title],
|
||||||
|
&self.desktop_entries,
|
||||||
|
fde::matching::MatchAppIdOptions::default(),
|
||||||
|
)
|
||||||
|
.and_then(|de| {
|
||||||
|
let score = fde::matching::get_entry_score(
|
||||||
|
&query,
|
||||||
|
de,
|
||||||
|
&self.locales,
|
||||||
|
&[&info.app_id, &info.title],
|
||||||
|
);
|
||||||
|
|
||||||
if !retain {
|
if score > 0.6 {
|
||||||
continue;
|
Some(de)
|
||||||
}
|
} else {
|
||||||
|
None
|
||||||
let mut icon_name = Cow::Borrowed("application-x-executable");
|
|
||||||
|
|
||||||
for (_, path) in &self.desktop_entries {
|
|
||||||
if let Ok(data) = fs::read_to_string(&path) {
|
|
||||||
if let Ok(entry) = fde::DesktopEntry::decode(&path, &data) {
|
|
||||||
if item.1.app_id == entry.appid
|
|
||||||
|| entry
|
|
||||||
.startup_wm_class()
|
|
||||||
.is_some_and(|class| class == item.1.app_id)
|
|
||||||
{
|
|
||||||
if let Some(icon) = entry.icon() {
|
|
||||||
icon_name = Cow::Owned(icon.to_owned());
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
};
|
||||||
|
|
||||||
send(
|
if let Some(de) = entry {
|
||||||
&mut self.tx,
|
let icon_name = if let Some(icon) = de.icon() {
|
||||||
PluginResponse::Append(PluginSearchResult {
|
Cow::Owned(icon.to_owned())
|
||||||
|
} else {
|
||||||
|
Cow::Borrowed("application-x-executable")
|
||||||
|
};
|
||||||
|
|
||||||
|
let response = PluginResponse::Append(PluginSearchResult {
|
||||||
// XXX protocol id may be re-used later
|
// XXX protocol id may be re-used later
|
||||||
id: item.0.id().protocol_id(),
|
id: handle.id().protocol_id(),
|
||||||
window: Some((0, item.0.id().clone().protocol_id())),
|
window: Some((0, handle.id().clone().protocol_id())),
|
||||||
name: item.1.app_id.clone(),
|
// XXX: why this is inversed for this plugin ????
|
||||||
description: item.1.title.clone(),
|
description: info.title.clone(),
|
||||||
|
name: get_description(de, &self.locales),
|
||||||
icon: Some(IconSource::Name(icon_name)),
|
icon: Some(IconSource::Name(icon_name)),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}),
|
});
|
||||||
)
|
|
||||||
.await;
|
send(&mut self.tx, response).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
send(&mut self.tx, PluginResponse::Finished).await;
|
send(&mut self.tx, PluginResponse::Finished).await;
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,20 @@
|
||||||
// Copyright © 2021 System76
|
// Copyright © 2021 System76
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use freedesktop_desktop_entry::{default_paths, DesktopEntry, Iter as DesktopIter, PathSource};
|
|
||||||
|
use freedesktop_desktop_entry as fde;
|
||||||
|
use freedesktop_desktop_entry::{
|
||||||
|
default_paths, get_languages_from_env, DesktopEntry, Iter as DesktopIter, PathSource,
|
||||||
|
};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use pop_launcher::*;
|
use pop_launcher::*;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::hash::{Hash, Hasher};
|
use std::hash::{Hash, Hasher};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
use tokio::io::AsyncWrite;
|
use tokio::io::AsyncWrite;
|
||||||
|
use utils::path_string;
|
||||||
|
|
||||||
|
pub(crate) mod utils;
|
||||||
|
|
||||||
#[derive(Debug, Eq)]
|
#[derive(Debug, Eq)]
|
||||||
struct Item {
|
struct Item {
|
||||||
|
|
@ -65,21 +72,16 @@ const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"];
|
||||||
|
|
||||||
struct App<W> {
|
struct App<W> {
|
||||||
entries: Vec<Item>,
|
entries: Vec<Item>,
|
||||||
locale: Option<String>,
|
locales: Vec<String>,
|
||||||
tx: W,
|
tx: W,
|
||||||
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<W: AsyncWrite + Unpin> App<W> {
|
impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
fn new(tx: W) -> Self {
|
fn new(tx: W) -> Self {
|
||||||
let lang = std::env::var("LANG").ok();
|
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
entries: Vec::new(),
|
entries: Vec::new(),
|
||||||
locale: lang
|
locales: fde::get_languages_from_env(),
|
||||||
.as_ref()
|
|
||||||
.and_then(|l| l.split('.').next())
|
|
||||||
.map(String::from),
|
|
||||||
tx,
|
tx,
|
||||||
gpus: None,
|
gpus: None,
|
||||||
}
|
}
|
||||||
|
|
@ -88,8 +90,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
async fn reload(&mut self) {
|
async fn reload(&mut self) {
|
||||||
self.entries.clear();
|
self.entries.clear();
|
||||||
|
|
||||||
let locale = self.locale.as_ref().map(String::as_ref);
|
|
||||||
|
|
||||||
let mut deduplicator = std::collections::HashSet::new();
|
let mut deduplicator = std::collections::HashSet::new();
|
||||||
|
|
||||||
let current = current_desktop();
|
let current = current_desktop();
|
||||||
|
|
@ -100,7 +100,9 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
for path in DesktopIter::new(default_paths()) {
|
for path in DesktopIter::new(default_paths()) {
|
||||||
let src = PathSource::guess_from(&path);
|
let src = PathSource::guess_from(&path);
|
||||||
if let Ok(bytes) = std::fs::read_to_string(&path) {
|
if let Ok(bytes) = std::fs::read_to_string(&path) {
|
||||||
if let Ok(entry) = DesktopEntry::decode(&path, &bytes) {
|
if let Ok(entry) =
|
||||||
|
DesktopEntry::from_str(&path, &bytes, &get_languages_from_env())
|
||||||
|
{
|
||||||
// Do not show if our desktop is defined in `NotShowIn`.
|
// Do not show if our desktop is defined in `NotShowIn`.
|
||||||
if let Some(not_show_in) = entry.desktop_entry("NotShowIn") {
|
if let Some(not_show_in) = entry.desktop_entry("NotShowIn") {
|
||||||
let current = ward::ward!(current.as_ref(), else { continue });
|
let current = ward::ward!(current.as_ref(), else { continue });
|
||||||
|
|
@ -134,7 +136,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
|
|
||||||
// Avoid showing the GNOME Shell entry entirely
|
// Avoid showing the GNOME Shell entry entirely
|
||||||
if entry
|
if entry
|
||||||
.name(None)
|
.name(&[] as &[&str])
|
||||||
.map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
|
.map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -145,21 +147,21 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some((name, exec)) = entry.name(locale).zip(entry.exec()) {
|
if let Some((name, exec)) = entry.name(&self.locales).zip(entry.exec()) {
|
||||||
if let Some(exec) = exec.split_ascii_whitespace().next() {
|
if let Some(exec) = exec.split_ascii_whitespace().next() {
|
||||||
if exec == "false" {
|
if exec == "false" {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
let item = Item {
|
let item = Item {
|
||||||
appid: entry.appid.to_owned(),
|
appid: entry.appid.to_string(),
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
description: entry
|
description: entry
|
||||||
.comment(locale)
|
.comment(&self.locales)
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.unwrap_or("")
|
.unwrap_or("")
|
||||||
.to_owned(),
|
.to_owned(),
|
||||||
keywords: entry.keywords().map(|keywords| {
|
keywords: entry.keywords(&self.locales).map(|keywords| {
|
||||||
keywords.split(';').map(String::from).collect()
|
keywords.split(';').map(String::from).collect()
|
||||||
}),
|
}),
|
||||||
icon: Some(
|
icon: Some(
|
||||||
|
|
@ -178,7 +180,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
actions
|
actions
|
||||||
.split(';')
|
.split(';')
|
||||||
.filter_map(|action| {
|
.filter_map(|action| {
|
||||||
entry.action_entry_localized(action, "Name", None)
|
entry.action_entry_localized(
|
||||||
|
action,
|
||||||
|
"Name",
|
||||||
|
&self.locales,
|
||||||
|
)
|
||||||
})
|
})
|
||||||
.map(Cow::into_owned)
|
.map(Cow::into_owned)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
|
|
@ -378,20 +384,6 @@ fn current_desktop() -> Option<String> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn path_string(source: &PathSource) -> Cow<'static, str> {
|
|
||||||
match source {
|
|
||||||
PathSource::Local | PathSource::LocalDesktop => "Local".into(),
|
|
||||||
PathSource::LocalFlatpak => "Flatpak".into(),
|
|
||||||
PathSource::LocalNix => "Nix".into(),
|
|
||||||
PathSource::Nix => "Nix (System)".into(),
|
|
||||||
PathSource::System => "System".into(),
|
|
||||||
PathSource::SystemLocal => "Local (System)".into(),
|
|
||||||
PathSource::SystemFlatpak => "Flatpak (System)".into(),
|
|
||||||
PathSource::SystemSnap => "Snap (System)".into(),
|
|
||||||
PathSource::Other(other) => Cow::Owned(other.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn try_get_gpus() -> Option<Vec<switcheroo_control::Gpu>> {
|
async fn try_get_gpus() -> Option<Vec<switcheroo_control::Gpu>> {
|
||||||
let connection = zbus::Connection::system().await.ok()?;
|
let connection = zbus::Connection::system().await.ok()?;
|
||||||
let proxy = switcheroo_control::SwitcherooControlProxy::new(&connection)
|
let proxy = switcheroo_control::SwitcherooControlProxy::new(&connection)
|
||||||
|
|
|
||||||
39
plugins/src/desktop_entries/utils.rs
Normal file
39
plugins/src/desktop_entries/utils.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
//! Reusable functions for desktop entries
|
||||||
|
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
use freedesktop_desktop_entry::{DesktopEntry, PathSource};
|
||||||
|
|
||||||
|
// todo: subscriptions with notify
|
||||||
|
|
||||||
|
pub fn path_string(source: &PathSource) -> Cow<'static, str> {
|
||||||
|
match source {
|
||||||
|
PathSource::Local | PathSource::LocalDesktop => "Local".into(),
|
||||||
|
PathSource::LocalFlatpak => "Flatpak".into(),
|
||||||
|
PathSource::LocalNix => "Nix".into(),
|
||||||
|
PathSource::Nix => "Nix (System)".into(),
|
||||||
|
PathSource::System => "System".into(),
|
||||||
|
PathSource::SystemLocal => "Local (System)".into(),
|
||||||
|
PathSource::SystemFlatpak => "Flatpak (System)".into(),
|
||||||
|
PathSource::SystemSnap => "Snap (System)".into(),
|
||||||
|
PathSource::Other(other) => Cow::Owned(other.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_description<'a>(de: &'a DesktopEntry<'a>, locales: &[String]) -> String {
|
||||||
|
let path_source = PathSource::guess_from(&de.path);
|
||||||
|
|
||||||
|
let desc_source = path_string(&path_source).to_string();
|
||||||
|
|
||||||
|
|
||||||
|
match de.comment(locales) {
|
||||||
|
Some(desc) => {
|
||||||
|
if desc.is_empty() {
|
||||||
|
desc_source
|
||||||
|
} else {
|
||||||
|
format!("{} - {}", desc_source, desc)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => desc_source,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
// Copyright © 2021 System76
|
// Copyright © 2021 System76
|
||||||
|
|
||||||
use crate::*;
|
use crate::*;
|
||||||
use freedesktop_desktop_entry as fde;
|
use freedesktop_desktop_entry::{self as fde, get_languages_from_env};
|
||||||
use futures::StreamExt;
|
use futures::StreamExt;
|
||||||
use pop_launcher::*;
|
use pop_launcher::*;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -139,7 +139,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
||||||
if let Some(name) = path.file_stem() {
|
if let Some(name) = path.file_stem() {
|
||||||
if desktop_entry == name {
|
if desktop_entry == name {
|
||||||
if let Ok(data) = fs::read_to_string(path) {
|
if let Ok(data) = fs::read_to_string(path) {
|
||||||
if let Ok(entry) = fde::DesktopEntry::decode(path, &data) {
|
if let Ok(entry) = fde::DesktopEntry::from_str(path, &data, &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());
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
// Copyright © 2021 System76
|
// Copyright © 2021 System76
|
||||||
|
|
||||||
|
use freedesktop_desktop_entry::get_languages_from_env;
|
||||||
use futures::prelude::*;
|
use futures::prelude::*;
|
||||||
use pop_launcher::*;
|
use pop_launcher::*;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
@ -123,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::decode(&path, &input).ok().and_then(|de| {
|
DesktopEntry::from_str(&path, &input, &get_languages_from_env()).ok().and_then(|de| {
|
||||||
if de.no_display()
|
if de.no_display()
|
||||||
|| de
|
|| de
|
||||||
.categories()
|
.categories()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue