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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -34,6 +34,7 @@ pub enum ToplevelEvent {
Update(ZcosmicToplevelHandleV1, ToplevelInfo),
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct Toplevel {
pub name: String,

View file

@ -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;
// 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;
if de.name(&self.locales).is_none() {
return None;
}
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;
// 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| &not_show.to_ascii_lowercase() == desktop)
}) {
return None;
}
}
}
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 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;
}
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,
)
}
} else {
if de.no_display() {
return None;
}
}
deduplicator.insert(de.appid.to_string());
Some(de)
})
.map(Cow::into_owned)
.collect::<Vec<_>>()
})
.unwrap_or_default(),
};
.collect::<Vec<_>>();
deduplicator.insert(item);
}
}
}
}
}
self.entries.extend(deduplicator);
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 {
} 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
}
};
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();
let &mut Self {
ref entries,
ref mut tx,
..
} = self;
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);
for (id, entry) in self.desktop_entries.iter().enumerate() {
let score = fde::matching::get_entry_score(query, entry, &self.locales, &[]);
if score > 0.6 {
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()),
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()
});
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)

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();
match de.comment(locales) {
Some(desc) => {
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> {
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")

View file

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

View file

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

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

View file

@ -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,11 +124,13 @@ 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| {
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"))
.map(|c| c.iter().all(|c| *c != "TerminalEmulator"))
.unwrap_or(true)
|| de.exec().is_none()
{

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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