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