improv: desktop entries update
This commit is contained in:
parent
ab7a71a057
commit
172532056b
25 changed files with 559 additions and 663 deletions
|
|
@ -3,34 +3,33 @@ name = "pop-launcher-plugins"
|
|||
version = "1.2.3"
|
||||
license = "GPL-3.0-only"
|
||||
authors = ["Michael Aaron Murphy <mmstick@pm.me>"]
|
||||
edition = "2018"
|
||||
edition.workspace = true
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
async-pidfd = "0.1.4"
|
||||
fork = "0.1.23"
|
||||
freedesktop-desktop-entry = "0.6.0"
|
||||
freedesktop-desktop-entry = "0.6.2"
|
||||
human_format = "1.1.0"
|
||||
human-sort = "0.2.2"
|
||||
new_mime_guess = "4.0.1"
|
||||
pop-launcher = { path = "../" }
|
||||
regex = "1.10.4"
|
||||
ron = "0.8.1"
|
||||
serde = "1.0.198"
|
||||
serde_json = "1.0.116"
|
||||
regex.workspace = true
|
||||
ron.workspace = true
|
||||
serde.workspace = true
|
||||
serde_json.workspace = true
|
||||
slab = "0.4.9"
|
||||
strsim = "0.11.1"
|
||||
tracing = "0.1.40"
|
||||
tracing.workspace = true
|
||||
urlencoding = "2.1.3"
|
||||
zbus = "3.15.2"
|
||||
zvariant = "3.15.2"
|
||||
ward = "2.1.0"
|
||||
zbus = "4.2.2"
|
||||
zvariant = "4.1.1"
|
||||
url = "2.5.0"
|
||||
sysfs-class = "0.1.3"
|
||||
anyhow = "1.0.82"
|
||||
flume = "0.11.0"
|
||||
dirs = "5.0.1"
|
||||
futures = "0.3.30"
|
||||
anyhow.workspace = true
|
||||
flume.workspace = true
|
||||
dirs.workspace = true
|
||||
futures.workspace = true
|
||||
bytes = "1.6.0"
|
||||
recently-used-xbel = "1.0.0"
|
||||
|
||||
|
|
@ -49,5 +48,5 @@ default-features = false
|
|||
features = ["rustls-tls"]
|
||||
|
||||
[dependencies.tokio]
|
||||
version = "1.37.0"
|
||||
workspace = true
|
||||
features = ["fs", "io-std", "macros", "process", "rt"]
|
||||
|
|
|
|||
|
|
@ -116,13 +116,13 @@ impl App {
|
|||
async fn qcalc(regex: &mut Regex, expression: &str, decimal_comma: bool) -> Option<String> {
|
||||
let mut command = Command::new("qalc");
|
||||
|
||||
command.args(&["-u8"]);
|
||||
command.args(&["-set", "maxdeci 9"]);
|
||||
command.args(["-u8"]);
|
||||
command.args(["-set", "maxdeci 9"]);
|
||||
|
||||
if decimal_comma {
|
||||
command.args(&["-set", "decimal comma on"]);
|
||||
command.args(["-set", "decimal comma on"]);
|
||||
} else {
|
||||
command.args(&["-set", "decimal comma off"]);
|
||||
command.args(["-set", "decimal comma off"]);
|
||||
}
|
||||
|
||||
let spawn = command
|
||||
|
|
|
|||
|
|
@ -213,7 +213,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush();
|
||||
let _ = self.tx.flush().await;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ pub enum ToplevelEvent {
|
|||
Update(ZcosmicToplevelHandleV1, ToplevelInfo),
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Toplevel {
|
||||
pub name: String,
|
||||
|
|
|
|||
|
|
@ -4,45 +4,15 @@
|
|||
use crate::*;
|
||||
|
||||
use freedesktop_desktop_entry as fde;
|
||||
use freedesktop_desktop_entry::{
|
||||
default_paths, get_languages_from_env, DesktopEntry, Iter as DesktopIter, PathSource,
|
||||
};
|
||||
use freedesktop_desktop_entry::DesktopEntry;
|
||||
use futures::StreamExt;
|
||||
use pop_launcher::*;
|
||||
use std::borrow::Cow;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::path::PathBuf;
|
||||
use tokio::io::AsyncWrite;
|
||||
use utils::path_string;
|
||||
use utils::get_description;
|
||||
|
||||
pub(crate) mod utils;
|
||||
|
||||
#[derive(Debug, Eq)]
|
||||
struct Item {
|
||||
appid: String,
|
||||
description: String,
|
||||
exec: String,
|
||||
icon: Option<String>,
|
||||
keywords: Option<Vec<String>>,
|
||||
name: String,
|
||||
path: PathBuf,
|
||||
prefers_non_default_gpu: bool,
|
||||
src: PathSource,
|
||||
actions: Vec<String>,
|
||||
}
|
||||
|
||||
impl Hash for Item {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.appid.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Item {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.appid == other.appid
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn main() {
|
||||
let mut app = App::new(async_stdout());
|
||||
app.reload().await;
|
||||
|
|
@ -71,7 +41,9 @@ pub async fn main() {
|
|||
const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"];
|
||||
|
||||
struct App<W> {
|
||||
entries: Vec<Item>,
|
||||
current_desktop: Option<Vec<String>>,
|
||||
is_desktop_cosmic: bool,
|
||||
desktop_entries: Vec<DesktopEntry<'static>>,
|
||||
locales: Vec<String>,
|
||||
tx: W,
|
||||
gpus: Option<Vec<switcheroo_control::Gpu>>,
|
||||
|
|
@ -79,8 +51,14 @@ struct App<W> {
|
|||
|
||||
impl<W: AsyncWrite + Unpin> App<W> {
|
||||
fn new(tx: W) -> Self {
|
||||
let current_desktop = fde::current_desktop();
|
||||
Self {
|
||||
entries: Vec::new(),
|
||||
current_desktop: fde::current_desktop(),
|
||||
is_desktop_cosmic: current_desktop
|
||||
.unwrap_or_default()
|
||||
.iter()
|
||||
.any(|e| e == "cosmic"),
|
||||
desktop_entries: Vec::new(),
|
||||
locales: fde::get_languages_from_env(),
|
||||
tx,
|
||||
gpus: None,
|
||||
|
|
@ -88,127 +66,89 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn reload(&mut self) {
|
||||
self.entries.clear();
|
||||
self.desktop_entries.clear();
|
||||
|
||||
let mut deduplicator = std::collections::HashSet::new();
|
||||
let locales = fde::get_languages_from_env();
|
||||
|
||||
let current = current_desktop();
|
||||
let current = current
|
||||
.as_ref()
|
||||
.map(|x| x.split(':').collect::<Vec<&str>>());
|
||||
let paths = fde::Iter::new(fde::default_paths());
|
||||
|
||||
for path in DesktopIter::new(default_paths()) {
|
||||
let src = PathSource::guess_from(&path);
|
||||
if let Ok(bytes) = std::fs::read_to_string(&path) {
|
||||
if let Ok(entry) =
|
||||
DesktopEntry::from_str(&path, &bytes, &get_languages_from_env())
|
||||
{
|
||||
// Do not show if our desktop is defined in `NotShowIn`.
|
||||
if let Some(not_show_in) = entry.desktop_entry("NotShowIn") {
|
||||
let current = ward::ward!(current.as_ref(), else { continue });
|
||||
|
||||
let matched = not_show_in
|
||||
.to_ascii_lowercase()
|
||||
.split(';')
|
||||
.any(|desktop| current.iter().any(|c| *c == desktop));
|
||||
|
||||
if matched {
|
||||
continue;
|
||||
}
|
||||
let desktop_entries = DesktopEntry::from_paths(paths, &locales)
|
||||
.filter_map(|de| {
|
||||
de.ok().and_then(|de| {
|
||||
if deduplicator.contains(de.appid.as_ref()) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Track this condition so that we can override `NoDisplay` if this is true.
|
||||
let mut only_show_in = false;
|
||||
if de.name(&self.locales).is_none() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Do not show if our desktop is not defined in `OnlyShowIn`.
|
||||
if let Some(desktops) = entry.only_show_in() {
|
||||
let current = ward::ward!(current.as_ref(), else { continue });
|
||||
|
||||
only_show_in = desktops
|
||||
.to_ascii_lowercase()
|
||||
.split(';')
|
||||
.any(|desktop| current.iter().any(|c| *c == desktop));
|
||||
|
||||
if !only_show_in {
|
||||
continue;
|
||||
}
|
||||
match de.exec() {
|
||||
Some(exec) => match exec.split_ascii_whitespace().next() {
|
||||
Some(exec) => {
|
||||
if exec == "false" {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
None => return None,
|
||||
},
|
||||
None => return None,
|
||||
}
|
||||
|
||||
// Avoid showing the GNOME Shell entry entirely
|
||||
if entry
|
||||
if de
|
||||
.name(&[] as &[&str])
|
||||
.map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
|
||||
{
|
||||
continue;
|
||||
return None;
|
||||
}
|
||||
|
||||
// And also avoid showing anything that's set as `NoDisplay`
|
||||
if !only_show_in && entry.no_display() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if let Some((name, exec)) = entry.name(&self.locales).zip(entry.exec()) {
|
||||
if let Some(exec) = exec.split_ascii_whitespace().next() {
|
||||
if exec == "false" {
|
||||
continue;
|
||||
// Do not show if our desktop is defined in `NotShowIn`.
|
||||
if let Some(not_show_in) = de.not_show_in() {
|
||||
if let Some(current_desktop) = &self.current_desktop {
|
||||
if not_show_in.iter().any(|not_show| {
|
||||
current_desktop
|
||||
.iter()
|
||||
.any(|desktop| ¬_show.to_ascii_lowercase() == desktop)
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
|
||||
let item = Item {
|
||||
appid: entry.appid.to_string(),
|
||||
name: name.to_string(),
|
||||
description: entry
|
||||
.comment(&self.locales)
|
||||
.as_deref()
|
||||
.unwrap_or("")
|
||||
.to_owned(),
|
||||
keywords: entry.keywords(&self.locales).map(|keywords| {
|
||||
keywords.split(';').map(String::from).collect()
|
||||
}),
|
||||
icon: Some(
|
||||
entry
|
||||
.icon()
|
||||
.map(|x| x.to_owned())
|
||||
.unwrap_or_else(|| "application-x-executable".to_string()),
|
||||
),
|
||||
exec: exec.to_owned(),
|
||||
path: path.clone(),
|
||||
prefers_non_default_gpu: entry.prefers_non_default_gpu(),
|
||||
src,
|
||||
actions: entry
|
||||
.actions()
|
||||
.map(|actions| {
|
||||
actions
|
||||
.split(';')
|
||||
.filter_map(|action| {
|
||||
entry.action_entry_localized(
|
||||
action,
|
||||
"Name",
|
||||
&self.locales,
|
||||
)
|
||||
})
|
||||
.map(Cow::into_owned)
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.unwrap_or_default(),
|
||||
};
|
||||
|
||||
deduplicator.insert(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.entries.extend(deduplicator);
|
||||
// Do not show if our desktop is not defined in `OnlyShowIn`.
|
||||
if let Some(only_show_in) = de.only_show_in() {
|
||||
if let Some(current_desktop) = &self.current_desktop {
|
||||
if !only_show_in.iter().any(|show_in| {
|
||||
current_desktop
|
||||
.iter()
|
||||
.any(|desktop| &show_in.to_ascii_lowercase() == desktop)
|
||||
}) {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if de.no_display() {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
deduplicator.insert(de.appid.to_string());
|
||||
Some(de)
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
self.desktop_entries = desktop_entries;
|
||||
|
||||
self.gpus = try_get_gpus().await;
|
||||
}
|
||||
|
||||
async fn activate(&mut self, id: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let response = PluginResponse::DesktopEntry {
|
||||
path: entry.path.clone(),
|
||||
gpu_preference: if entry.prefers_non_default_gpu {
|
||||
path: entry.path.to_path_buf(),
|
||||
gpu_preference: if entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
|
|
@ -221,33 +161,29 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn activate_context(&mut self, id: u32, context: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
let is_cosmic = matches!(current_desktop().as_deref(), Some("cosmic"));
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let gpu_len = self.gpus.as_ref().map(Vec::len).unwrap_or(0) as u32;
|
||||
|
||||
let gpu_preference = if is_cosmic {
|
||||
let gpu_preference = if self.is_desktop_cosmic {
|
||||
if context < gpu_len {
|
||||
GpuPreference::SpecificIdx(context)
|
||||
} else {
|
||||
if entry.prefers_non_default_gpu {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if !entry.prefers_non_default_gpu {
|
||||
} else if entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
}
|
||||
} else if !entry.prefers_non_default_gpu() {
|
||||
GpuPreference::NonDefault
|
||||
} else {
|
||||
GpuPreference::Default
|
||||
};
|
||||
|
||||
let response = PluginResponse::DesktopEntry {
|
||||
path: entry.path.clone(),
|
||||
path: entry.path.to_path_buf(),
|
||||
gpu_preference,
|
||||
action_name: (is_cosmic && context >= gpu_len)
|
||||
.then(|| entry.actions[(context - gpu_len) as usize].clone()),
|
||||
action_name: (self.is_desktop_cosmic && context >= gpu_len).then(|| {
|
||||
entry.actions().unwrap_or_default()[(context - gpu_len) as usize].to_string()
|
||||
}),
|
||||
};
|
||||
|
||||
send(&mut self.tx, response).await;
|
||||
|
|
@ -255,10 +191,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn context(&mut self, id: u32) {
|
||||
if let Some(entry) = self.entries.get(id as usize) {
|
||||
let options = match current_desktop().as_deref() {
|
||||
Some("cosmic") => self.cosmic_context(entry).await,
|
||||
_ => self.gnome_context(entry).await,
|
||||
if let Some(entry) = self.desktop_entries.get(id as usize) {
|
||||
let options = if self.is_desktop_cosmic {
|
||||
self.cosmic_context(entry).await
|
||||
} else {
|
||||
self.gnome_context(entry).await
|
||||
};
|
||||
|
||||
if !options.is_empty() {
|
||||
|
|
@ -270,65 +207,37 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
async fn search(&mut self, query: &str) {
|
||||
let query = query.to_ascii_lowercase();
|
||||
for (id, entry) in self.desktop_entries.iter().enumerate() {
|
||||
let score = fde::matching::get_entry_score(query, entry, &self.locales, &[]);
|
||||
|
||||
let &mut Self {
|
||||
ref entries,
|
||||
ref mut tx,
|
||||
..
|
||||
} = self;
|
||||
if score > 0.6 {
|
||||
let response = PluginResponse::Append(PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: entry.name(&self.locales).unwrap_or_default().to_string(),
|
||||
description: get_description(entry, &self.locales),
|
||||
keywords: entry
|
||||
.keywords(&self.locales)
|
||||
.map(|v| v.iter().map(|e| e.to_string()).collect()),
|
||||
icon: entry
|
||||
.icon()
|
||||
.map(|e| Cow::Owned(e.to_string()))
|
||||
.map(IconSource::Name),
|
||||
exec: entry.exec().map(|e| e.to_string()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let mut items = Vec::with_capacity(16);
|
||||
|
||||
for (id, entry) in entries.iter().enumerate() {
|
||||
items.extend(entry.name.split_ascii_whitespace());
|
||||
|
||||
if let Some(keywords) = entry.keywords.as_ref() {
|
||||
items.extend(keywords.iter().map(String::as_str));
|
||||
}
|
||||
|
||||
items.push(entry.exec.as_str());
|
||||
|
||||
for search_interest in items.drain(..) {
|
||||
let search_interest = search_interest.to_ascii_lowercase();
|
||||
let append = search_interest.starts_with(&*query)
|
||||
|| query
|
||||
.split_ascii_whitespace()
|
||||
.any(|query| search_interest.contains(&*query))
|
||||
|| strsim::jaro_winkler(&*query, &*search_interest) > 0.6;
|
||||
|
||||
if append {
|
||||
let desc_source = path_string(&entry.src);
|
||||
|
||||
let response = PluginResponse::Append(PluginSearchResult {
|
||||
id: id as u32,
|
||||
name: entry.name.clone(),
|
||||
description: if entry.description.is_empty() {
|
||||
desc_source.to_string()
|
||||
} else {
|
||||
format!("{} - {}", desc_source, entry.description)
|
||||
},
|
||||
keywords: entry.keywords.clone(),
|
||||
icon: entry.icon.clone().map(Cow::Owned).map(IconSource::Name),
|
||||
exec: Some(entry.exec.clone()),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
send(tx, response).await;
|
||||
|
||||
break;
|
||||
}
|
||||
send(&mut self.tx, response).await;
|
||||
}
|
||||
}
|
||||
|
||||
send(tx, PluginResponse::Finished).await;
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
}
|
||||
|
||||
async fn gnome_context(&self, entry: &Item) -> Vec<ContextOption> {
|
||||
async fn gnome_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
if self.gpus.is_some() {
|
||||
vec![ContextOption {
|
||||
id: 0,
|
||||
name: (if entry.prefers_non_default_gpu {
|
||||
name: (if entry.prefers_non_default_gpu() {
|
||||
"Launch Using Integrated Graphics Card"
|
||||
} else {
|
||||
"Launch Using Discrete Graphics Card"
|
||||
|
|
@ -340,11 +249,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
}
|
||||
|
||||
async fn cosmic_context(&self, entry: &Item) -> Vec<ContextOption> {
|
||||
async fn cosmic_context(&self, entry: &DesktopEntry<'_>) -> Vec<ContextOption> {
|
||||
let mut options = Vec::new();
|
||||
|
||||
if let Some(gpus) = self.gpus.as_ref() {
|
||||
let default_idx = if entry.prefers_non_default_gpu {
|
||||
let default_idx = if entry.prefers_non_default_gpu() {
|
||||
gpus.iter().position(|gpu| !gpu.default).unwrap_or(0)
|
||||
} else {
|
||||
gpus.iter().position(|gpu| gpu.default).unwrap_or(0)
|
||||
|
|
@ -355,17 +264,17 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
name: format!(
|
||||
"Launch using {}{}",
|
||||
gpu.name,
|
||||
(i == default_idx).then_some(" (default)").unwrap_or("")
|
||||
if i == default_idx { " (default)" } else { "" }
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let options_offset = self.gpus.as_ref().map(|gpus| gpus.len()).unwrap_or(0);
|
||||
for (i, action) in entry.actions.iter().enumerate() {
|
||||
for (i, action) in entry.actions().unwrap_or_default().iter().enumerate() {
|
||||
options.push(ContextOption {
|
||||
id: (i + options_offset) as u32,
|
||||
name: action.clone(),
|
||||
name: action.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -373,17 +282,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
}
|
||||
|
||||
fn current_desktop() -> Option<String> {
|
||||
std::env::var("XDG_CURRENT_DESKTOP").ok().map(|x| {
|
||||
let x = x.to_ascii_lowercase();
|
||||
if x == "unity" {
|
||||
"gnome".to_owned()
|
||||
} else {
|
||||
x
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async fn try_get_gpus() -> Option<Vec<switcheroo_control::Gpu>> {
|
||||
let connection = zbus::Connection::system().await.ok()?;
|
||||
let proxy = switcheroo_control::SwitcherooControlProxy::new(&connection)
|
||||
|
|
|
|||
|
|
@ -25,7 +25,6 @@ pub fn get_description<'a>(de: &'a DesktopEntry<'a>, locales: &[String]) -> Stri
|
|||
|
||||
let desc_source = path_string(&path_source).to_string();
|
||||
|
||||
|
||||
match de.comment(locales) {
|
||||
Some(desc) => {
|
||||
if desc.is_empty() {
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ pub async fn or<T>(future1: impl Future<Output = T>, future2: impl Future<Output
|
|||
pub fn mime_from_path(path: &Path) -> Cow<'static, str> {
|
||||
if path.is_dir() {
|
||||
Cow::Borrowed("inode/directory")
|
||||
} else if let Some(guess) = new_mime_guess::from_path(&path).first() {
|
||||
} else if let Some(guess) = new_mime_guess::from_path(path).first() {
|
||||
Cow::Owned(guess.essence_str().to_owned())
|
||||
} else {
|
||||
Cow::Borrowed("text/plain")
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use freedesktop_desktop_entry::{self as fde, get_languages_from_env};
|
|||
use futures::StreamExt;
|
||||
use pop_launcher::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{convert::TryFrom, fs, path::PathBuf, sync::Arc};
|
||||
use std::{convert::TryFrom, fs, path::PathBuf};
|
||||
use tokio::io::{AsyncWrite, AsyncWriteExt};
|
||||
use zbus::Connection;
|
||||
use zvariant::{Signature, Type};
|
||||
|
|
@ -36,7 +36,7 @@ pub async fn main() {
|
|||
Ok(conn) => conn,
|
||||
Err(_) => {
|
||||
let mut out = async_stdout();
|
||||
let _ = crate::send(&mut out, PluginResponse::Deactivate);
|
||||
let _ = crate::send(&mut out, PluginResponse::Deactivate).await;
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
|
@ -86,7 +86,7 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
&mut self,
|
||||
method: &str,
|
||||
args: &A,
|
||||
) -> zbus::Result<Arc<zbus::Message>> {
|
||||
) -> zbus::Result<zbus::Message> {
|
||||
self.connection
|
||||
.call_method(Some(DEST), PATH, Some(DEST), method, args)
|
||||
.await
|
||||
|
|
@ -95,7 +95,8 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
async fn reload(&mut self) {
|
||||
if let Ok(message) = self.call_method("WindowList", &()).await {
|
||||
self.entries = message
|
||||
.body::<Vec<Item>>()
|
||||
.body()
|
||||
.deserialize()
|
||||
.expect("pop-shell returned invalid WindowList response");
|
||||
}
|
||||
}
|
||||
|
|
@ -139,7 +140,11 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
if let Some(name) = path.file_stem() {
|
||||
if desktop_entry == name {
|
||||
if let Ok(data) = fs::read_to_string(path) {
|
||||
if let Ok(entry) = fde::DesktopEntry::from_str(path, &data, &get_languages_from_env()) {
|
||||
if let Ok(entry) = fde::DesktopEntry::from_str(
|
||||
path,
|
||||
&data,
|
||||
&get_languages_from_env(),
|
||||
) {
|
||||
if let Some(icon) = entry.icon() {
|
||||
icon_name = Cow::Owned(icon.to_owned());
|
||||
}
|
||||
|
|
@ -167,6 +172,6 @@ impl<W: AsyncWrite + Unpin> App<W> {
|
|||
}
|
||||
|
||||
send(&mut self.tx, PluginResponse::Finished).await;
|
||||
let _ = self.tx.flush();
|
||||
let _ = self.tx.flush().await;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -138,7 +138,7 @@ fn pactl_sinks() -> flume::Receiver<String> {
|
|||
tokio::spawn(async move {
|
||||
let child = tokio::process::Command::new("pactl")
|
||||
.env("LANG", "C")
|
||||
.args(&["list", "sinks"])
|
||||
.args(["list", "sinks"])
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.spawn();
|
||||
|
||||
|
|
|
|||
|
|
@ -221,9 +221,9 @@ async fn load_from(path: &Path, paths: &mut VecDeque<PathBuf>, tx: Sender<Script
|
|||
}
|
||||
|
||||
if let Some(stripped) = line.strip_prefix("name:") {
|
||||
info.name = stripped.trim_start().to_owned();
|
||||
stripped.trim_start().clone_into(&mut info.name);
|
||||
} else if let Some(stripped) = line.strip_prefix("description:") {
|
||||
info.description = stripped.trim_start().to_owned();
|
||||
stripped.trim_start().clone_into(&mut info.description);
|
||||
} else if let Some(stripped) = line.strip_prefix("icon:") {
|
||||
info.icon = Some(stripped.trim_start().to_owned());
|
||||
} else if let Some(stripped) = line.strip_prefix("keywords:") {
|
||||
|
|
|
|||
|
|
@ -61,11 +61,11 @@ impl App {
|
|||
|
||||
if self.shell_only {
|
||||
cmd = Command::new("sh");
|
||||
cmd.args(&["-c", &exe]);
|
||||
cmd.args(["-c", &exe]);
|
||||
} else {
|
||||
let (terminal, arg) = detect_terminal();
|
||||
cmd = Command::new(terminal);
|
||||
cmd.args(&[
|
||||
cmd.args([
|
||||
arg,
|
||||
"sh",
|
||||
"-c",
|
||||
|
|
@ -124,19 +124,21 @@ fn detect_terminal() -> (PathBuf, &'static str) {
|
|||
freedesktop_desktop_entry::Iter::new(freedesktop_desktop_entry::default_paths())
|
||||
.filter_map(|path| {
|
||||
std::fs::read_to_string(&path).ok().and_then(|input| {
|
||||
DesktopEntry::from_str(&path, &input, &get_languages_from_env()).ok().and_then(|de| {
|
||||
if de.no_display()
|
||||
|| de
|
||||
.categories()
|
||||
.map(|c| c.split_terminator(';').all(|c| c != "TerminalEmulator"))
|
||||
.unwrap_or(true)
|
||||
|| de.exec().is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
DesktopEntry::from_str(&path, &input, &get_languages_from_env())
|
||||
.ok()
|
||||
.and_then(|de| {
|
||||
if de.no_display()
|
||||
|| de
|
||||
.categories()
|
||||
.map(|c| c.iter().all(|c| *c != "TerminalEmulator"))
|
||||
.unwrap_or(true)
|
||||
|| de.exec().is_none()
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
Some((de.id().to_owned(), de.exec().unwrap().to_owned()))
|
||||
})
|
||||
Some((de.id().to_owned(), de.exec().unwrap().to_owned()))
|
||||
})
|
||||
})
|
||||
})
|
||||
.collect();
|
||||
|
|
|
|||
|
|
@ -151,17 +151,13 @@ impl App {
|
|||
// Generate List of Icon sources in order of priority
|
||||
let mut icon_sources = vec![
|
||||
// First use the defined icon source, if it is defined
|
||||
Some(icon_source)
|
||||
.filter(|s| !s.is_empty())
|
||||
.map(|url| fetch(url)),
|
||||
Some(icon_source).filter(|s| !s.is_empty()).map(fetch),
|
||||
];
|
||||
|
||||
if let Some(domain) = domain {
|
||||
icon_sources.append(&mut vec![
|
||||
// Searches for the favicon if it's not defined at the root of the domain.
|
||||
favicon_from_page(&domain, client)
|
||||
.await
|
||||
.map(|url| fetch(url)),
|
||||
favicon_from_page(&domain, client).await.map(fetch),
|
||||
// If not found, fetch from root domain.
|
||||
Some(fetch(["https://", &domain, "/favicon.ico"].concat())),
|
||||
// If all else fails, try Google.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue