improv: desktop entries update
This commit is contained in:
parent
ab7a71a057
commit
172532056b
25 changed files with 559 additions and 663 deletions
658
Cargo.lock
generated
658
Cargo.lock
generated
File diff suppressed because it is too large
Load diff
40
Cargo.toml
40
Cargo.toml
|
|
@ -5,27 +5,51 @@ license = "MPL-2.0"
|
|||
authors = ["Michael Aaron Murphy <mmstick@pm.me>"]
|
||||
description = "Library for writing plugins and frontends for pop-launcher"
|
||||
repository = "https://github.com/pop-os/launcher"
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
|
||||
[workspace]
|
||||
members = ["bin", "plugins", "service", "toolkit"]
|
||||
resolver = "2"
|
||||
|
||||
[workspace.package]
|
||||
edition = "2021"
|
||||
|
||||
[workspace.dependencies]
|
||||
anyhow = "1.0.82"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
tracing = "0.1.40"
|
||||
dirs = "5.0.1"
|
||||
serde_with = "3.7.0"
|
||||
futures = "0.3.30"
|
||||
flume = "0.11.0"
|
||||
toml = "0.8.12"
|
||||
regex = "1.10.4"
|
||||
ron = "0.8.1"
|
||||
tokio = "1.37.0"
|
||||
tokio-stream = "0.1.15"
|
||||
|
||||
[dependencies]
|
||||
const_format = "0.2.32"
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.30"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
serde_with = "3.7.0"
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with.workspace = true
|
||||
|
||||
[profile.release]
|
||||
lto = "fat"
|
||||
panic = "abort"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.37.0"
|
||||
workspace = true
|
||||
features = ["io-std", "io-util"]
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
version = "0.1.15"
|
||||
workspace = true
|
||||
features = ["io-util"]
|
||||
|
||||
|
||||
|
||||
# [patch.crates-io]
|
||||
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
[package]
|
||||
name = "pop-launcher-bin"
|
||||
version = "1.2.3"
|
||||
edition = "2018"
|
||||
license = "GPL-3.0-only"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
pop-launcher-toolkit = { path = "../toolkit" }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std", "fmt", "env-filter"] }
|
||||
dirs = "5.0.1"
|
||||
tracing.workspace = true
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std", "fmt", "env-filter", "chrono"] }
|
||||
dirs.workspace = true
|
||||
mimalloc = "0.1.39"
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.37.0"
|
||||
workspace = true
|
||||
features = ["rt"]
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ use pop_launcher_toolkit::plugins;
|
|||
use pop_launcher_toolkit::service;
|
||||
|
||||
use mimalloc::MiMalloc;
|
||||
use tracing_subscriber::layer::SubscriberExt;
|
||||
use tracing_subscriber::EnvFilter;
|
||||
|
||||
#[global_allocator]
|
||||
static GLOBAL: MiMalloc = MiMalloc;
|
||||
|
|
@ -37,7 +39,10 @@ async fn main() {
|
|||
}
|
||||
}
|
||||
|
||||
// todo: support journald once this issue is resolved: https://github.com/tokio-rs/tracing/issues/2348
|
||||
fn init_logging(cmd: &str) {
|
||||
use tracing_subscriber::{fmt, Registry};
|
||||
|
||||
let logdir = match dirs::state_dir() {
|
||||
Some(dir) => dir.join("pop-launcher/"),
|
||||
None => dirs::home_dir()
|
||||
|
|
@ -49,15 +54,27 @@ fn init_logging(cmd: &str) {
|
|||
|
||||
let logfile = std::fs::OpenOptions::new()
|
||||
.create(true)
|
||||
.truncate(true)
|
||||
.write(true)
|
||||
.append(true)
|
||||
.open(logdir.join([cmd, ".log"].concat().as_str()).as_path());
|
||||
|
||||
if let Ok(file) = logfile {
|
||||
use tracing_subscriber::{fmt, EnvFilter};
|
||||
fmt()
|
||||
.with_env_filter(EnvFilter::from_default_env())
|
||||
.with_writer(file)
|
||||
.init();
|
||||
if let Ok(meta) = file.metadata() {
|
||||
if meta.len() > 1000 {
|
||||
let _ = file.set_len(0);
|
||||
}
|
||||
}
|
||||
|
||||
let filter_layer = EnvFilter::try_from_default_env()
|
||||
.or_else(|_| EnvFilter::try_new("warn"))
|
||||
.unwrap();
|
||||
|
||||
let fmt_layer = fmt::layer()
|
||||
.with_target(false)
|
||||
.with_timer(fmt::time::ChronoLocal::new("%T".into()))
|
||||
.with_writer(file);
|
||||
|
||||
let subscriber = Registry::default().with(filter_layer).with(fmt_layer);
|
||||
|
||||
tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,34 +3,33 @@ name = "pop-launcher-plugins"
|
|||
version = "1.2.3"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["Michael Aaron Murphy <mmstick@pm.me>"]
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
async-pidfd = "0.1.4"
|
||||
fork = "0.1.23"
|
||||
freedesktop-desktop-entry = "0.6.0"
|
||||
freedesktop-desktop-entry = "0.6.2"
|
||||
human_format = "1.1.0"
|
||||
human-sort = "0.2.2"
|
||||
new_mime_guess = "4.0.1"
|
||||
pop-launcher = { path = "../" }
|
||||
regex = "1.10.4"
|
||||
ron = "0.8.1"
|
||||
serde = "1.0.198"
|
||||
serde_json = "1.0.116"
|
||||
regex.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
slab = "0.4.9"
|
||||
strsim = "0.11.1"
|
||||
tracing = "0.1.40"
|
||||
tracing.workspace = true
|
||||
urlencoding = "2.1.3"
|
||||
zbus = "3.15.2"
|
||||
zvariant = "3.15.2"
|
||||
ward = "2.1.0"
|
||||
zbus = "4.2.2"
|
||||
zvariant = "4.1.1"
|
||||
url = "2.5.0"
|
||||
sysfs-class = "0.1.3"
|
||||
anyhow = "1.0.82"
|
||||
flume = "0.11.0"
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.30"
|
||||
anyhow.workspace = true
|
||||
flume.workspace = true
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
bytes = "1.6.0"
|
||||
recently-used-xbel = "1.0.0"
|
||||
|
||||
|
|
@ -49,5 +48,5 @@ default-features = false
|
|||
features = ["rustls-tls"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.37.0"
|
||||
workspace = true
|
||||
features = ["fs", "io-std", "macros", "process", "rt"]
|
||||
|
|
|
|||
|
|
@ -116,13 +116,13 @@ impl App {
|
|||
async fn qcalc(regex: &mut Regex, expression: &str, decimal_comma: bool) -> Option<String> {
|
||||
let mut command = Command::new("qalc");
|
||||
|
||||
command.args(&["-u8"]);
|
||||
command.args(&["-set", "maxdeci 9"]);
|
||||
command.args(["-u8"]);
|
||||
command.args(["-set", "maxdeci 9"]);
|
||||
|
||||
if decimal_comma {
|
||||
command.args(&["-set", "decimal comma on"]);
|
||||
command.args(["-set", "decimal comma on"]);
|
||||
} else {
|
||||
command.args(&["-set", "decimal comma off"]);
|
||||
command.args(["-set", "decimal comma off"]);
|
||||
}
|
||||
|
||||
let spawn = command
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush();
|
||||
let _ = self.tx.flush().await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub enum ToplevelEvent {
|
|||
Update(ZcosmicToplevelHandleV1, ToplevelInfo),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Toplevel {
|
||||
pub name: String,
|
||||
|
|
|
|||
|
|
@ -4,45 +4,15 @@
|
|||
use crate::*;
|
||||
|
||||
use freedesktop_desktop_entry as fde;
|
||||
use freedesktop_desktop_entry::{
|
||||
default_paths, get_languages_from_env, DesktopEntry, Iter as DesktopIter, PathSource,
|
||||
};
|
||||
use freedesktop_desktop_entry::DesktopEntry;
|
||||
use futures::StreamExt;
|
||||
use pop_launcher::*;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use tokio::io::AsyncWrite;
|
||||
use utils::path_string;
|
||||
use utils::get_description;
|
||||
|
||||
pub(crate) mod utils;
|
||||
|
||||
#[derive(Debug, Eq)]
|
||||
struct Item {
|
||||
appid: String,
|
||||
description: String,
|
||||
exec: String,
|
||||
icon: Option<String>,
|
||||
keywords: Option<Vec<String>>,
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
prefers_non_default_gpu: bool,
|
||||
src: PathSource,
|
||||
actions: Vec<String>,
|
||||
}
|
||||
|
||||
impl Hash for Item {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.appid.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Item {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.appid == other.appid
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn main() {
|
||||
let mut app = App::new(async_stdout());
|
||||
app.reload().await;
|
||||
|
|
@ -71,7 +41,9 @@ pub async fn main() {
|
|||
const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"];
|
||||
|
||||
struct App<W> {
|
||||
entries: Vec<Item>,
|
||||
current_desktop: Option<Vec<String>>,
|
||||
is_desktop_cosmic: bool,
|
||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
||||
locales: Vec<String>,
|
||||
tx: W,
|
||||
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
||||
|
|
@ -79,8 +51,14 @@ struct App<W> {
|
|||
|
||||
impl<W: AsyncWrite + Unpin> App<W> {
|
||||
fn new(tx: W) -> Self {
|
||||
let current_desktop = fde::current_desktop();
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
current_desktop: fde::current_desktop(),
|
||||
is_desktop_cosmic: current_desktop
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|e| e == "cosmic"),
|
||||
desktop_entries: Vec::new(),
|
||||
locales: fde::get_languages_from_env(),
|
||||
tx,
|
||||
gpus: None,
|
||||
|
|
@ -88,127 +66,89 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn reload(&mut self) {
|
||||
self.entries.clear();
|
||||
self.desktop_entries.clear();
|
||||
|
||||
let mut deduplicator = std::collections::HashSet::new();
|
||||
let locales = fde::get_languages_from_env();
|
||||
|
||||
let current = current_desktop();
|
||||
let current = current
|
||||
.as_ref()
|
||||
.map(|x| x.split(':').collect::<Vec<&str>>());
|
||||
let paths = fde::Iter::new(fde::default_paths());
|
||||
|
||||
for path in DesktopIter::new(default_paths()) {
|
||||
let src = PathSource::guess_from(&path);
|
||||
if let Ok(bytes) = std::fs::read_to_string(&path) {
|
||||
if let Ok(entry) =
|
||||
DesktopEntry::from_str(&path, &bytes, &get_languages_from_env())
|
||||
{
|
||||
// Do not show if our desktop is defined in `NotShowIn`.
|
||||
if let Some(not_show_in) = entry.desktop_entry("NotShowIn") {
|
||||
let current = ward::ward!(current.as_ref(), else { continue });
|
||||
|
||||
let matched = not_show_in
|
||||
.to_ascii_lowercase()
|
||||
.split(';')
|
||||
.any(|desktop| current.iter().any(|c| *c == desktop));
|
||||
|
||||
if matched {
|
||||
continue;
|
||||
}
|
||||
let desktop_entries = DesktopEntry::from_paths(paths, &locales)
|
||||
.filter_map(|de| {
|
||||
de.ok().and_then(|de| {
|
||||
if deduplicator.contains(de.appid.as_ref()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Track this condition so that we can override `NoDisplay` if this is true.
|
||||
let mut only_show_in = false;
|
||||
if de.name(&self.locales).is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Do not show if our desktop is not defined in `OnlyShowIn`.
|
||||
if let Some(desktops) = entry.only_show_in() {
|
||||
let current = ward::ward!(current.as_ref(), else { continue });
|
||||
|
||||
only_show_in = desktops
|
||||
.to_ascii_lowercase()
|
||||
.split(';')
|
||||
.any(|desktop| current.iter().any(|c| *c == desktop));
|
||||
|
||||
if !only_show_in {
|
||||
continue;
|
||||
}
|
||||
match de.exec() {
|
||||
Some(exec) => match exec.split_ascii_whitespace().next() {
|
||||
Some(exec) => {
|
||||
if exec == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
None => return None,
|
||||
}
|
||||
|
||||
// Avoid showing the GNOME Shell entry entirely
|
||||
if entry
|
||||
if de
|
||||
.name(&[] as &[&str])
|
||||
.map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
|
||||
{
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
|
||||
// And also avoid showing anything that's set as `NoDisplay`
|
||||
if !only_show_in && entry.no_display() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((name, exec)) = entry.name(&self.locales).zip(entry.exec()) {
|
||||
if let Some(exec) = exec.split_ascii_whitespace().next() {
|
||||
if exec == "false" {
|
||||
continue;
|
||||
// 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;
|
||||
}
|
||||
|
||||
let item = Item {
|
||||
appid: entry.appid.to_string(),
|
||||
name: name.to_string(),
|
||||
description: entry
|
||||
.comment(&self.locales)
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
keywords: entry.keywords(&self.locales).map(|keywords| {
|
||||
keywords.split(';').map(String::from).collect()
|
||||
}),
|
||||
icon: Some(
|
||||
entry
|
||||
.icon()
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or_else(|| "application-x-executable".to_string()),
|
||||
),
|
||||
exec: exec.to_owned(),
|
||||
path: path.clone(),
|
||||
prefers_non_default_gpu: entry.prefers_non_default_gpu(),
|
||||
src,
|
||||
actions: entry
|
||||
.actions()
|
||||
.map(|actions| {
|
||||
actions
|
||||
.split(';')
|
||||
.filter_map(|action| {
|
||||
entry.action_entry_localized(
|
||||
action,
|
||||
"Name",
|
||||
&self.locales,
|
||||
)
|
||||
})
|
||||
.map(Cow::into_owned)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
deduplicator.insert(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.entries.extend(deduplicator);
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if de.no_display() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
deduplicator.insert(de.appid.to_string());
|
||||
Some(de)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.desktop_entries = desktop_entries;
|
||||
|
||||
self.gpus = try_get_gpus().await;
|
||||
}
|
||||
|
||||
async fn activate(&mut self, id: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let response = PluginResponse::DesktopEntry {
|
||||
path: entry.path.clone(),
|
||||
gpu_preference: if entry.prefers_non_default_gpu {
|
||||
path: entry.path.to_path_buf(),
|
||||
gpu_preference: if entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
|
|
@ -221,33 +161,29 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn activate_context(&mut self, id: u32, context: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
let is_cosmic = matches!(current_desktop().as_deref(), Some("cosmic"));
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let gpu_len = self.gpus.as_ref().map(Vec::len).unwrap_or(0) as u32;
|
||||
|
||||
let gpu_preference = if is_cosmic {
|
||||
let gpu_preference = if self.is_desktop_cosmic {
|
||||
if context < gpu_len {
|
||||
GpuPreference::SpecificIdx(context)
|
||||
} else {
|
||||
if entry.prefers_non_default_gpu {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !entry.prefers_non_default_gpu {
|
||||
} else if entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
}
|
||||
} else if !entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
};
|
||||
|
||||
let response = PluginResponse::DesktopEntry {
|
||||
path: entry.path.clone(),
|
||||
path: entry.path.to_path_buf(),
|
||||
gpu_preference,
|
||||
action_name: (is_cosmic && context >= gpu_len)
|
||||
.then(|| entry.actions[(context - gpu_len) as usize].clone()),
|
||||
action_name: (self.is_desktop_cosmic && context >= gpu_len).then(|| {
|
||||
entry.actions().unwrap_or_default()[(context - gpu_len) as usize].to_string()
|
||||
}),
|
||||
};
|
||||
|
||||
send(&mut self.tx, response).await;
|
||||
|
|
@ -255,10 +191,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn context(&mut self, id: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
let options = match current_desktop().as_deref() {
|
||||
Some("cosmic") => self.cosmic_context(entry).await,
|
||||
_ => self.gnome_context(entry).await,
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let options = if self.is_desktop_cosmic {
|
||||
self.cosmic_context(entry).await
|
||||
} else {
|
||||
self.gnome_context(entry).await
|
||||
};
|
||||
|
||||
if !options.is_empty() {
|
||||
|
|
@ -270,65 +207,37 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn search(&mut self, query: &str) {
|
||||
let query = query.to_ascii_lowercase();
|
||||
for (id, entry) in self.desktop_entries.iter().enumerate() {
|
||||
let score = fde::matching::get_entry_score(query, entry, &self.locales, &[]);
|
||||
|
||||
let &mut Self {
|
||||
ref entries,
|
||||
ref mut tx,
|
||||
..
|
||||
} = self;
|
||||
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 items = Vec::with_capacity(16);
|
||||
|
||||
for (id, entry) in entries.iter().enumerate() {
|
||||
items.extend(entry.name.split_ascii_whitespace());
|
||||
|
||||
if let Some(keywords) = entry.keywords.as_ref() {
|
||||
items.extend(keywords.iter().map(String::as_str));
|
||||
}
|
||||
|
||||
items.push(entry.exec.as_str());
|
||||
|
||||
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 desc_source = path_string(&entry.src);
|
||||
|
||||
let response = PluginResponse::Append(PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: entry.name.clone(),
|
||||
description: if entry.description.is_empty() {
|
||||
desc_source.to_string()
|
||||
} else {
|
||||
format!("{} - {}", desc_source, entry.description)
|
||||
},
|
||||
keywords: entry.keywords.clone(),
|
||||
icon: entry.icon.clone().map(Cow::Owned).map(IconSource::Name),
|
||||
exec: Some(entry.exec.clone()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
send(tx, response).await;
|
||||
|
||||
break;
|
||||
}
|
||||
send(&mut self.tx, response).await;
|
||||
}
|
||||
}
|
||||
|
||||
send(tx, PluginResponse::Finished).await;
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
}
|
||||
|
||||
async fn gnome_context(&self, entry: &Item) -> Vec<ContextOption> {
|
||||
async fn gnome_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
if self.gpus.is_some() {
|
||||
vec![ContextOption {
|
||||
id: 0,
|
||||
name: (if entry.prefers_non_default_gpu {
|
||||
name: (if entry.prefers_non_default_gpu() {
|
||||
"Launch Using Integrated Graphics Card"
|
||||
} else {
|
||||
"Launch Using Discrete Graphics Card"
|
||||
|
|
@ -340,11 +249,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn cosmic_context(&self, entry: &Item) -> Vec<ContextOption> {
|
||||
async fn cosmic_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
let mut options = Vec::new();
|
||||
|
||||
if let Some(gpus) = self.gpus.as_ref() {
|
||||
let default_idx = if entry.prefers_non_default_gpu {
|
||||
let default_idx = if entry.prefers_non_default_gpu() {
|
||||
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
|
||||
} else {
|
||||
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
|
||||
|
|
@ -355,17 +264,17 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
name: format!(
|
||||
"Launch using {}{}",
|
||||
gpu.name,
|
||||
(i == default_idx).then_some(" (default)").unwrap_or("")
|
||||
if i == default_idx { " (default)" } else { "" }
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let options_offset = self.gpus.as_ref().map(|gpus| gpus.len()).unwrap_or(0);
|
||||
for (i, action) in entry.actions.iter().enumerate() {
|
||||
for (i, action) in entry.actions().unwrap_or_default().iter().enumerate() {
|
||||
options.push(ContextOption {
|
||||
id: (i + options_offset) as u32,
|
||||
name: action.clone(),
|
||||
name: action.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -373,17 +282,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
}
|
||||
|
||||
fn current_desktop() -> Option<String> {
|
||||
std::env::var("XDG_CURRENT_DESKTOP").ok().map(|x| {
|
||||
let x = x.to_ascii_lowercase();
|
||||
if x == "unity" {
|
||||
"gnome".to_owned()
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_get_gpus() -> Option<Vec<switcheroo_control::Gpu>> {
|
||||
let connection = zbus::Connection::system().await.ok()?;
|
||||
let proxy = switcheroo_control::SwitcherooControlProxy::new(&connection)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ pub fn get_description<'a>(de: &'a DesktopEntry<'a>, locales: &[String]) -> Stri
|
|||
|
||||
let desc_source = path_string(&path_source).to_string();
|
||||
|
||||
|
||||
match de.comment(locales) {
|
||||
Some(desc) => {
|
||||
if desc.is_empty() {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub async fn or<T>(future1: impl Future<Output = T>, future2: impl Future<Output
|
|||
pub fn mime_from_path(path: &Path) -> Cow<'static, str> {
|
||||
if path.is_dir() {
|
||||
Cow::Borrowed("inode/directory")
|
||||
} else if let Some(guess) = new_mime_guess::from_path(&path).first() {
|
||||
} else if let Some(guess) = new_mime_guess::from_path(path).first() {
|
||||
Cow::Owned(guess.essence_str().to_owned())
|
||||
} else {
|
||||
Cow::Borrowed("text/plain")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use freedesktop_desktop_entry::{self as fde, get_languages_from_env};
|
|||
use futures::StreamExt;
|
||||
use pop_launcher::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, fs, path::PathBuf, sync::Arc};
|
||||
use std::{convert::TryFrom, fs, path::PathBuf};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
use zbus::Connection;
|
||||
use zvariant::{Signature, Type};
|
||||
|
|
@ -36,7 +36,7 @@ pub async fn main() {
|
|||
Ok(conn) => conn,
|
||||
Err(_) => {
|
||||
let mut out = async_stdout();
|
||||
let _ = crate::send(&mut out, PluginResponse::Deactivate);
|
||||
let _ = crate::send(&mut out, PluginResponse::Deactivate).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -86,7 +86,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
&mut self,
|
||||
method: &str,
|
||||
args: &A,
|
||||
) -> zbus::Result<Arc<zbus::Message>> {
|
||||
) -> zbus::Result<zbus::Message> {
|
||||
self.connection
|
||||
.call_method(Some(DEST), PATH, Some(DEST), method, args)
|
||||
.await
|
||||
|
|
@ -95,7 +95,8 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
async fn reload(&mut self) {
|
||||
if let Ok(message) = self.call_method("WindowList", &()).await {
|
||||
self.entries = message
|
||||
.body::<Vec<Item>>()
|
||||
.body()
|
||||
.deserialize()
|
||||
.expect("pop-shell returned invalid WindowList response");
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +140,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
if let Some(name) = path.file_stem() {
|
||||
if desktop_entry == name {
|
||||
if let Ok(data) = fs::read_to_string(path) {
|
||||
if let Ok(entry) = fde::DesktopEntry::from_str(path, &data, &get_languages_from_env()) {
|
||||
if let Ok(entry) = fde::DesktopEntry::from_str(
|
||||
path,
|
||||
&data,
|
||||
&get_languages_from_env(),
|
||||
) {
|
||||
if let Some(icon) = entry.icon() {
|
||||
icon_name = Cow::Owned(icon.to_owned());
|
||||
}
|
||||
|
|
@ -167,6 +172,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush();
|
||||
let _ = self.tx.flush().await;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ fn pactl_sinks() -> flume::Receiver<String> {
|
|||
tokio::spawn(async move {
|
||||
let child = tokio::process::Command::new("pactl")
|
||||
.env("LANG", "C")
|
||||
.args(&["list", "sinks"])
|
||||
.args(["list", "sinks"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn();
|
||||
|
||||
|
|
|
|||
|
|
@ -221,9 +221,9 @@ async fn load_from(path: &Path, paths: &mut VecDeque<PathBuf>, tx: Sender<Script
|
|||
}
|
||||
|
||||
if let Some(stripped) = line.strip_prefix("name:") {
|
||||
info.name = stripped.trim_start().to_owned();
|
||||
stripped.trim_start().clone_into(&mut info.name);
|
||||
} else if let Some(stripped) = line.strip_prefix("description:") {
|
||||
info.description = stripped.trim_start().to_owned();
|
||||
stripped.trim_start().clone_into(&mut info.description);
|
||||
} else if let Some(stripped) = line.strip_prefix("icon:") {
|
||||
info.icon = Some(stripped.trim_start().to_owned());
|
||||
} else if let Some(stripped) = line.strip_prefix("keywords:") {
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ impl App {
|
|||
|
||||
if self.shell_only {
|
||||
cmd = Command::new("sh");
|
||||
cmd.args(&["-c", &exe]);
|
||||
cmd.args(["-c", &exe]);
|
||||
} else {
|
||||
let (terminal, arg) = detect_terminal();
|
||||
cmd = Command::new(terminal);
|
||||
cmd.args(&[
|
||||
cmd.args([
|
||||
arg,
|
||||
"sh",
|
||||
"-c",
|
||||
|
|
@ -124,19 +124,21 @@ 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()).ok().and_then(|de| {
|
||||
if de.no_display()
|
||||
|| de
|
||||
.categories()
|
||||
.map(|c| c.split_terminator(';').all(|c| c != "TerminalEmulator"))
|
||||
.unwrap_or(true)
|
||||
|| de.exec().is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
DesktopEntry::from_str(&path, &input, &get_languages_from_env())
|
||||
.ok()
|
||||
.and_then(|de| {
|
||||
if de.no_display()
|
||||
|| de
|
||||
.categories()
|
||||
.map(|c| c.iter().all(|c| *c != "TerminalEmulator"))
|
||||
.unwrap_or(true)
|
||||
|| de.exec().is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((de.id().to_owned(), de.exec().unwrap().to_owned()))
|
||||
})
|
||||
Some((de.id().to_owned(), de.exec().unwrap().to_owned()))
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -151,17 +151,13 @@ impl App {
|
|||
// Generate List of Icon sources in order of priority
|
||||
let mut icon_sources = vec![
|
||||
// First use the defined icon source, if it is defined
|
||||
Some(icon_source)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|url| fetch(url)),
|
||||
Some(icon_source).filter(|s| !s.is_empty()).map(fetch),
|
||||
];
|
||||
|
||||
if let Some(domain) = domain {
|
||||
icon_sources.append(&mut vec![
|
||||
// Searches for the favicon if it's not defined at the root of the domain.
|
||||
favicon_from_page(&domain, client)
|
||||
.await
|
||||
.map(|url| fetch(url)),
|
||||
favicon_from_page(&domain, client).await.map(fetch),
|
||||
// If not found, fetch from root domain.
|
||||
Some(fetch(["https://", &domain, "/favicon.ico"].concat())),
|
||||
// If all else fails, try Google.
|
||||
|
|
|
|||
|
|
@ -2,34 +2,33 @@
|
|||
name = "pop-launcher-service"
|
||||
version= "1.2.3"
|
||||
license = "MPL-2.0"
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.82"
|
||||
anyhow.workspace = true
|
||||
async-oneshot = "0.5.9"
|
||||
async-trait = "0.1.80"
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.30"
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
futures_codec = "0.4.1"
|
||||
gen-z = "0.1.0"
|
||||
num_cpus = "1.16.0"
|
||||
pop-launcher = { path = "../" }
|
||||
regex = "1.10.4"
|
||||
ron = "0.8.1"
|
||||
serde = { version = "1.0.198", features = ["derive"] }
|
||||
serde_json = "1.0.116"
|
||||
regex.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
serde_with = "3.7.0"
|
||||
slab = "0.4.9"
|
||||
strsim = "0.11.1"
|
||||
toml = "0.8.12"
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", default-features = false, features = ["std", "fmt", "env-filter"] }
|
||||
flume = "0.11.0"
|
||||
toml.workspace = true
|
||||
tracing.workspace = true
|
||||
flume.workspace = true
|
||||
|
||||
[dependencies.tokio]
|
||||
version= "1.37.0"
|
||||
workspace = true
|
||||
features = ["io-std", "process", "rt"]
|
||||
|
||||
[dependencies.tokio-stream]
|
||||
version= "0.1.15"
|
||||
workspace = true
|
||||
features = ["io-util"]
|
||||
|
|
|
|||
|
|
@ -225,7 +225,6 @@ impl<O: futures::Sink<Response> + Unpin> Service<O> {
|
|||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
// Report the plugin as finished and remove it from future polling
|
||||
PluginResponse::Deactivate => {
|
||||
self.finished(plugin).await;
|
||||
|
|
|
|||
|
|
@ -91,7 +91,7 @@ impl Default for PluginPriority {
|
|||
}
|
||||
|
||||
pub fn load(source: &Path, config_path: &Path) -> Option<(PathBuf, PluginConfig, Option<Regex>)> {
|
||||
if let Ok(config_bytes) = std::fs::read_to_string(&config_path) {
|
||||
if let Ok(config_bytes) = std::fs::read_to_string(config_path) {
|
||||
let config = match ron::from_str::<PluginConfig>(&config_bytes) {
|
||||
Ok(config) => config,
|
||||
Err(why) => {
|
||||
|
|
@ -114,11 +114,7 @@ pub fn load(source: &Path, config_path: &Path) -> Option<(PathBuf, PluginConfig,
|
|||
return None;
|
||||
};
|
||||
|
||||
let regex = config
|
||||
.query
|
||||
.regex
|
||||
.as_ref()
|
||||
.and_then(|p| Regex::new(&*p).ok());
|
||||
let regex = config.query.regex.as_ref().and_then(|p| Regex::new(p).ok());
|
||||
|
||||
return Some((exec, config, regex));
|
||||
}
|
||||
|
|
|
|||
7
service/src/plugins/external/mod.rs
vendored
7
service/src/plugins/external/mod.rs
vendored
|
|
@ -110,10 +110,9 @@ impl ExternalPlugin {
|
|||
futures::pin_mut!(responder);
|
||||
futures::pin_mut!(trip);
|
||||
|
||||
let _ = futures::future::select(responder, trip)
|
||||
futures::future::select(responder, trip)
|
||||
.await
|
||||
.factor_first()
|
||||
.0;
|
||||
.factor_first();
|
||||
|
||||
// Ensure that a task that was searching sends a finished signal if it dies.
|
||||
if searching.swap(false, Ordering::SeqCst) {
|
||||
|
|
@ -161,7 +160,7 @@ impl ExternalPlugin {
|
|||
if let Some(stdin) = child.stdin.as_mut() {
|
||||
if let Ok(mut serialized) = serde_json::to_vec(event) {
|
||||
serialized.push(b'\n');
|
||||
let _ = stdin.write_all(&serialized).await?;
|
||||
stdin.write_all(&serialized).await?;
|
||||
tracing::debug!("{}: sent message to external process", self.name());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ pub mod config;
|
|||
pub(crate) mod external;
|
||||
pub mod help;
|
||||
|
||||
pub use external::load;
|
||||
|
||||
pub use self::config::{PluginConfig, PluginPriority, PluginQuery};
|
||||
pub use self::external::ExternalPlugin;
|
||||
pub use self::help::HelpPlugin;
|
||||
|
|
|
|||
|
|
@ -48,7 +48,9 @@ impl PartialEq for Priority {
|
|||
impl Eq for Priority {}
|
||||
|
||||
impl PartialOrd for Priority {
|
||||
#[allow(clippy::non_canonical_partial_ord_impl)]
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
// todo: what is going on here ?
|
||||
(
|
||||
other.plugin_priority,
|
||||
self.compute_value(other),
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ pub enum PluginResponse {
|
|||
},
|
||||
/// Update the text in the launcher.
|
||||
Fill(String),
|
||||
/// Indicoates that a plugin is finished with its queries.
|
||||
/// Indicates that a plugin is finished with its queries.
|
||||
Finished,
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
[package]
|
||||
name = "pop-launcher-toolkit"
|
||||
version = "1.2.3"
|
||||
edition = "2021"
|
||||
description = "A wrapper around pop-launcher, pop-launcher-service and pop-launcher-plugins types for writing plugins and frontends for pop-launcher."
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ fn open_man_page(arg: &str) -> io::Result<()> {
|
|||
let (terminal, targ) = detect_terminal();
|
||||
|
||||
if let Ok(Fork::Child) = daemon(true, false) {
|
||||
Command::new(terminal).args(&[targ, "man", arg]).exec();
|
||||
Command::new(terminal).args([targ, "man", arg]).exec();
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue