improv: desktop entries update

This commit is contained in:
wiiznokes 2024-07-13 01:13:17 +02:00 • committed by GitHub
parent ab7a71a057
commit 172532056b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
25 changed files with 559 additions and 663 deletions

658
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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" }

View file

@ -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"]

View file

@ -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");
} }
} }

View file

@ -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"]

View file

@ -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

View file

@ -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;
} }
} }

View file

@ -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,

View file

@ -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| &not_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)

View file

@ -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() {

View file

@ -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")

View file

@ -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;
} }
} }

View file

@ -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();

View file

@ -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:") {

View file

@ -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();

View file

@ -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.

View file

@ -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"]

View file

@ -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;

View file

@ -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));
} }

View file

@ -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());
} }

View file

@ -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;

View file

@ -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),

View file

@ -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,
} }

View file

@ -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

View file

@ -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);