diff --git a/Cargo.lock b/Cargo.lock
index c428600..0394d9d 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -17,6 +17,18 @@ version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
+[[package]]
+name = "ahash"
+version = "0.8.11"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "version_check",
+ "zerocopy",
+]
+
[[package]]
name = "aho-corasick"
version = "1.1.3"
@@ -26,6 +38,12 @@ dependencies = [
"memchr",
]
+[[package]]
+name = "allocator-api2"
+version = "0.2.21"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
+
[[package]]
name = "android-tzdata"
version = "0.1.1"
@@ -364,6 +382,39 @@ version = "1.9.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
+[[package]]
+name = "cached"
+version = "0.54.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9718806c4a2fe9e8a56fd736f97b340dd10ed1be8ed733ed50449f351dc33cae"
+dependencies = [
+ "ahash",
+ "cached_proc_macro",
+ "cached_proc_macro_types",
+ "hashbrown 0.14.5",
+ "once_cell",
+ "thiserror 1.0.69",
+ "web-time",
+]
+
+[[package]]
+name = "cached_proc_macro"
+version = "0.23.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "2f42a145ed2d10dce2191e1dcf30cfccfea9026660e143662ba5eec4017d5daa"
+dependencies = [
+ "darling",
+ "proc-macro2",
+ "quote",
+ "syn 2.0.90",
+]
+
+[[package]]
+name = "cached_proc_macro_types"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "ade8366b8bd5ba243f0a58f036cc0ca8a2f069cff1a2351ef1cac6b083e16fc0"
+
[[package]]
name = "calloop"
version = "0.13.0"
@@ -763,17 +814,17 @@ dependencies = [
[[package]]
name = "freedesktop-desktop-entry"
-version = "0.6.2"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "e33809936d2fa9ac78750c5c04696a7aabdb09f928454957c77a2c8247f5ff98"
+version = "0.7.5"
+source = "git+https://github.com/wiiznokes/freedesktop-desktop-entry?branch=update#3b60fdf36564af2d12fd4ec81b08601692b72086"
dependencies = [
+ "cached",
"dirs",
"gettext-rs",
"log",
"memchr",
"strsim",
"textdistance",
- "thiserror 1.0.69",
+ "thiserror 2.0.4",
"xdg",
]
@@ -979,6 +1030,16 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
+[[package]]
+name = "hashbrown"
+version = "0.14.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1"
+dependencies = [
+ "ahash",
+ "allocator-api2",
+]
+
[[package]]
name = "hashbrown"
version = "0.15.2"
@@ -1830,6 +1891,7 @@ dependencies = [
"async-trait",
"dirs",
"flume",
+ "freedesktop-desktop-entry",
"futures",
"futures_codec",
"gen-z",
diff --git a/Cargo.toml b/Cargo.toml
index ede182d..d9b48d9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -28,6 +28,7 @@ regex = "1.11.0"
ron = "0.8.1"
tokio = "1.40.0"
tokio-stream = "0.1.16"
+freedesktop-desktop-entry = "0.7.5"
[dependencies]
const_format = "0.2.33"
@@ -50,8 +51,9 @@ workspace = true
features = ["io-util"]
-# [patch.crates-io]
+[patch.crates-io]
# freedesktop-desktop-entry = { path = "../freedesktop-desktop-entry" }
+freedesktop-desktop-entry = { git = "https://github.com/wiiznokes/freedesktop-desktop-entry", branch = "update" }
[patch."https://github.com/pop-os/cosmic-protocols"]
"cosmic-client-toolkit" = { git = "https://github.com/pop-os//cosmic-protocols" }
diff --git a/README.md b/README.md
index 54fefcb..1030e9e 100644
--- a/README.md
+++ b/README.md
@@ -49,31 +49,112 @@ just plugins="calc desktop_entries files find pop_shell pulse recent scripts ter
## Plugin Config
-A plugin's metadata is defined `pop-launcher/plugins/{plugin}/plugin.ron`.
-
-```ron
-(
- name: "PluginName",
- description: "Plugin Description: Example",
- bin: (
- path: "name-of-executable-in-plugin-folder",
- ),
- icon: Name("icon-name-or-path"),
- // Optional
- query: (
- // Optional -- if we should isolate this plugin when the regex matches
- isolate: true,
- // Optional -- Plugin which searches on empty queries
- persistent: true,
- // Optional -- avoid sorting results from this plugin
- no_sort: true,
- // Optional -- pattern that a query must have to be sent to plugin
- regex: "pattern",
- // Optional -- the launcher should keep a history for this plugin
- history: true,
- )
-)
-```
+A plugin's manifest file should be installed in `{plugin_directory}`. The syntax respect the [freedesktop specification](https://specifications.freedesktop.org/desktop-entry-spec/latest/index.html). An example can be found [here](./plugins/src/calc/plugin.desktop).
+
+This are the supported fields. The ones with no default are required.
+
+
+
+ Field |
+ Description |
+ Type |
+ Default |
+ Translatable |
+
+
+ Name |
+ Plugin desciption |
+ localestring |
+ |
+ yes |
+
+
+ Description |
+ Plugin name |
+ localestring |
+ empty string |
+ yes |
+
+
+ Icon |
+ Icon name, respecting the [icon theme spec](https://freedesktop.org/wiki/Specifications/icon-theme-spec/). |
+ iconstring |
+ no icon |
+ no |
+
+
+ Exec |
+ Executable file. Should either an absolute path, or a path relative to {plugin_directory} |
+ string |
+ |
+ no |
+
+
+ Regex |
+ Activate the plugin when this regex match the query |
+ regex |
+ match all query |
+ no |
+
+
+ Isolate |
+ Isolate this plugin when the regex field matches |
+ boolean |
+ false |
+ no |
+
+
+ IsolateWith |
+ Isolate this plugin when this regex matches |
+ regex |
+ match nothing |
+ no |
+
+
+ ShowOnEmptyQuery |
+ Show plugin's result on empty queries |
+ boolean |
+ false |
+ no |
+
+
+ NoSort |
+ Avoid sorting results from this plugin |
+ boolean |
+ false |
+ no |
+
+
+ GenericQuery |
+ Default query that will activate this plugin |
+ localestring |
+ empty string |
+ no |
+
+
+ LongLived |
+ The plugin should always run in the background |
+ boolean |
+ false |
+ no |
+
+
+ History |
+ The launcher should keep a history for this plugin |
+ boolean |
+ false |
+ no |
+
+
+ Priority |
+ Result priority of the plugin |
+ Default, High or Low |
+ Default |
+ no |
+
+
+
+
## Script Directories
@@ -82,6 +163,7 @@ A plugin's metadata is defined `pop-launcher/plugins/{plugin}/plugin.ron`.
- Distribution packaging: `/usr/lib/pop-launcher/scripts`
Example script
+
#!/bin/sh
@@ -92,6 +174,7 @@ Example script
# keywords: vpn start connect
nmcli connection up "vpn-name"
+
@@ -231,7 +314,7 @@ Where `PluginSearchResult` is:
`GpuPreference` is:
```ts
-"Default" | "NonDefault"
+"Default" | "NonDefault";
```
And `IconSource` is either:
diff --git a/justfile b/justfile
index 06793e9..f9bec0a 100644
--- a/justfile
+++ b/justfile
@@ -69,7 +69,7 @@ install-plugins:
for plugin in {{plugins}}; do
dest={{plugin-dir}}${plugin}
mkdir -p ${dest}
- install -Dm0644 plugins/src/${plugin}/*.ron ${dest}
+ install -Dm0644 plugins/src/${plugin}/*.desktop ${dest}
ln -srf {{bin-path}} {{plugin-dir}}${plugin}/$(echo ${plugin} | sed 's/_/-/')
done
diff --git a/plugins/Cargo.toml b/plugins/Cargo.toml
index 41e11d9..1e54122 100644
--- a/plugins/Cargo.toml
+++ b/plugins/Cargo.toml
@@ -9,7 +9,7 @@ publish = false
[dependencies]
async-pidfd = "0.1.4"
fork = "0.2.0"
-freedesktop-desktop-entry = "0.6.2"
+freedesktop-desktop-entry.workspace = true
human_format = "1.1.0"
human-sort = "0.2.2"
new_mime_guess = "4.0.4"
diff --git a/plugins/src/calc/plugin.desktop b/plugins/src/calc/plugin.desktop
new file mode 100644
index 0000000..58a8bb1
--- /dev/null
+++ b/plugins/src/calc/plugin.desktop
@@ -0,0 +1,10 @@
+[Plugin]
+Name=Calculator with unit conversion (uses Qalculate! expressions)
+Name[fr]=Calculatrice avec conversion d'unités (utilise les Qalculate! expressions)
+Description=Syntax: = \nExample: = 10 J / (196x^2) = 4 kW/s
+Exec=calc
+Icon=x-office-spreadsheet
+Regex=^[=\\-0-9|(-0-9)].*
+GenericQuery==\s
+Priority=High
+IsolateWith="^(=).*"
\ No newline at end of file
diff --git a/plugins/src/calc/plugin.ron b/plugins/src/calc/plugin.ron
deleted file mode 100644
index 8eba8af..0000000
--- a/plugins/src/calc/plugin.ron
+++ /dev/null
@@ -1,13 +0,0 @@
-(
- name: "Calculator with unit conversion (uses Qalculate! expressions)",
- description: "Syntax: = \nExample: = 10 J / (196x^2) = 4 kW/s",
- query: (
- regex: "^[=\\-0-9|(-0-9)].*",
- help: "= ",
- priority: High,
- isolate_with: "^(=).*",
- ),
- bin: (path: "calc"),
- icon: Name("x-office-spreadsheet"),
- history: false,
-)
diff --git a/plugins/src/cosmic_toplevel/mod.rs b/plugins/src/cosmic_toplevel/mod.rs
index 38b2dfd..89007b1 100644
--- a/plugins/src/cosmic_toplevel/mod.rs
+++ b/plugins/src/cosmic_toplevel/mod.rs
@@ -21,7 +21,6 @@ use pop_launcher::{
Request,
};
use std::borrow::Cow;
-use std::iter;
use tokio::io::{AsyncWrite, AsyncWriteExt};
use self::toplevel_handler::{toplevel_handler, ToplevelAction};
@@ -73,20 +72,45 @@ pub async fn main() {
for (handle, info) in updates {
match info {
Some(info) => {
- if let Some(pos) = app.toplevels.iter().position(|t| t.0 == handle) {
+ if let Some(pos) = app.toplevels.iter().position(|t| t.handle == handle)
+ {
if info.state.contains(&State::Activated) {
app.toplevels.remove(pos);
- app.toplevels.push((handle, Box::new(info)));
+ app.toplevels.push(Box::new(TopLevel {
+ handle,
+ entry: fde::matching::find_entry_from_appid(
+ &app.desktop_entries,
+ &info.app_id,
+ )
+ .map(|e| e.to_owned())
+ .unwrap_or(
+ DesktopEntry::from_appid(info.app_id.clone())
+ .to_owned(),
+ ),
+ info,
+ }));
} else {
- app.toplevels[pos].1 = Box::new(info);
+ app.toplevels[pos].info = info;
}
} else {
- app.toplevels.push((handle, Box::new(info)));
+ app.toplevels.push(Box::new(TopLevel {
+ handle,
+ entry: fde::matching::find_entry_from_appid(
+ &app.desktop_entries,
+ &info.app_id,
+ )
+ .map(|e| e.to_owned())
+ .unwrap_or(
+ DesktopEntry::from_appid(info.app_id.clone()).to_owned(),
+ ),
+ info,
+ }));
}
}
// no info means remove
None => {
- if let Some(pos) = app.toplevels.iter().position(|t| t.0 == handle) {
+ if let Some(pos) = app.toplevels.iter().position(|t| t.handle == handle)
+ {
app.toplevels.remove(pos);
// ignore requests for this id until after the next search
app.ids_to_ignore.push(handle.id().protocol_id());
@@ -102,11 +126,18 @@ pub async fn main() {
}
}
+struct TopLevel {
+ handle: ZcosmicToplevelHandleV1,
+ info: ToplevelInfo,
+ entry: DesktopEntry,
+}
+
struct App {
locales: Vec,
- desktop_entries: Vec>,
+ desktop_entries: Vec,
ids_to_ignore: Vec,
- toplevels: Vec<(ZcosmicToplevelHandleV1, Box)>,
+ #[allow(clippy::vec_box)]
+ toplevels: Vec>,
calloop_tx: calloop::channel::Sender,
tx: W,
}
@@ -119,11 +150,9 @@ impl App {
let locales = fde::get_languages_from_env();
- let paths = fde::Iter::new(fde::default_paths());
-
- let desktop_entries = DesktopEntry::from_paths(paths, &locales)
- .filter_map(|e| e.ok())
- .collect::>();
+ let desktop_entries = fde::Iter::new(fde::default_paths())
+ .entries(Some(&locales))
+ .collect();
(
Self {
@@ -144,8 +173,8 @@ impl App {
return;
}
if let Some(handle) = self.toplevels.iter().find_map(|t| {
- if t.0.id().protocol_id() == id {
- Some(t.0.clone())
+ if t.handle.id().protocol_id() == id {
+ Some(t.handle.clone())
} else {
None
}
@@ -160,8 +189,8 @@ impl App {
return;
}
if let Some(handle) = self.toplevels.iter().find_map(|t| {
- if t.0.id().protocol_id() == id {
- Some(t.0.clone())
+ if t.handle.id().protocol_id() == id {
+ Some(t.handle.clone())
} else {
None
}
@@ -173,57 +202,30 @@ impl App {
async fn search(&mut self, query: &str) {
let query = query.to_ascii_lowercase();
- for (handle, info) in self.toplevels.iter().rev() {
- let entry = if query.is_empty() {
- fde::matching::get_best_match(
- &[&info.app_id, &info.title],
- &self.desktop_entries,
- fde::matching::MatchAppIdOptions::default(),
- )
+ for top_level in self.toplevels.iter().rev() {
+ let score = top_level.entry.match_query(&query, &self.locales, &[]);
+
+ if score < 0.8 {
+ continue;
+ }
+
+ let icon_name = if let Some(icon) = top_level.entry.icon() {
+ Cow::Owned(icon.to_owned())
} else {
- let lowercase_title = info.title.to_lowercase();
- let window_words = lowercase_title
- .split_whitespace()
- .chain(iter::once(info.app_id.as_str()))
- .chain(iter::once(info.title.as_str()))
- .collect::>();
-
- fde::matching::get_best_match(
- &window_words,
- &self.desktop_entries,
- fde::matching::MatchAppIdOptions::default(),
- )
- .and_then(|de| {
- let score =
- fde::matching::get_entry_score(&query, de, &self.locales, &window_words);
-
- if score > 0.8 {
- Some(de)
- } else {
- None
- }
- })
+ Cow::Borrowed("application-x-executable")
};
- if let Some(de) = entry {
- let icon_name = if let Some(icon) = de.icon() {
- Cow::Owned(icon.to_owned())
- } else {
- Cow::Borrowed("application-x-executable")
- };
-
- let response = PluginResponse::Append(PluginSearchResult {
- // XXX protocol id may be re-used later
- id: handle.id().protocol_id(),
- window: Some((0, handle.id().clone().protocol_id())),
- description: info.title.clone(),
- name: get_description(de, &self.locales),
- icon: Some(IconSource::Name(icon_name)),
- ..Default::default()
- });
-
- send(&mut self.tx, response).await;
- }
+ let response = PluginResponse::Append(PluginSearchResult {
+ // XXX protocol id may be re-used later
+ id: top_level.handle.id().protocol_id(),
+ window: Some((0, top_level.handle.id().clone().protocol_id())),
+ description: top_level.info.title.clone(),
+ name: get_description(&top_level.entry, &self.locales),
+ icon: Some(IconSource::Name(icon_name)),
+ ..Default::default()
+ });
+
+ send(&mut self.tx, response).await;
}
send(&mut self.tx, PluginResponse::Finished).await;
diff --git a/plugins/src/cosmic_toplevel/plugin.desktop b/plugins/src/cosmic_toplevel/plugin.desktop
new file mode 100644
index 0000000..abf10cb
--- /dev/null
+++ b/plugins/src/cosmic_toplevel/plugin.desktop
@@ -0,0 +1,8 @@
+[Plugin]
+Name=COSMIC Windows
+Description=Active windows controllable via Cosmic
+Exec=cosmic-toplevel
+Icon=focus-windows-symbolic
+Priority=High
+LongLived=true
+ShowOnEmptyQuery=true
\ No newline at end of file
diff --git a/plugins/src/cosmic_toplevel/plugin.ron b/plugins/src/cosmic_toplevel/plugin.ron
deleted file mode 100644
index 217fdc4..0000000
--- a/plugins/src/cosmic_toplevel/plugin.ron
+++ /dev/null
@@ -1,8 +0,0 @@
-(
- name: "COSMIC Windows",
- description: "Active windows controllable via Cosmic",
- query: (persistent: true, priority: High),
- bin: (path: "cosmic-toplevel"),
- icon: Name("focus-windows-symbolic"),
- long_lived: true,
-)
\ No newline at end of file
diff --git a/plugins/src/desktop_entries/mod.rs b/plugins/src/desktop_entries/mod.rs
index d98144f..955c9cd 100644
--- a/plugins/src/desktop_entries/mod.rs
+++ b/plugins/src/desktop_entries/mod.rs
@@ -43,7 +43,7 @@ const EXCLUSIONS: &[&str] = &["GNOME Shell", "Initial Setup"];
struct App {
current_desktop: Option>,
is_desktop_cosmic: bool,
- desktop_entries: Vec>,
+ desktop_entries: Vec,
locales: Vec,
tx: W,
gpus: Option>,
@@ -67,81 +67,76 @@ impl App {
let mut deduplicator = std::collections::HashSet::new();
let locales = fde::get_languages_from_env();
- let paths = fde::Iter::new(fde::default_paths());
-
- let desktop_entries = DesktopEntry::from_paths(paths, &locales)
+ self.desktop_entries = fde::Iter::new(fde::default_paths())
+ .entries(Some(&locales))
.filter_map(|de| {
- de.ok().and_then(|de| {
- // Treat Flatpak and system apps differently in the cache so they don't
- // override each other
- let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref());
- if deduplicator.contains(appid) {
- return None;
- }
- // Always cache already visited entries to allow overriding entries e.g. by
- // placing a modified copy in ~/.local/share/applications/
- deduplicator.insert(appid.to_owned());
-
- de.name(&self.locales)?;
-
- match de.exec() {
- Some(exec) => match exec.split_ascii_whitespace().next() {
- Some(exec) => {
- if exec == "false" {
- return None;
- }
- }
- None => return None,
- },
- None => return None,
- }
+ // Treat Flatpak and system apps differently in the cache so they don't
+ // override each other
+ let appid = de.flatpak().unwrap_or_else(|| de.appid.as_ref());
+ if deduplicator.contains(appid) {
+ return None;
+ }
+ // Always cache already visited entries to allow overriding entries e.g. by
+ // placing a modified copy in ~/.local/share/applications/
+ deduplicator.insert(appid.to_owned());
- // Avoid showing the GNOME Shell entry entirely
- if de
- .name(&[] as &[&str])
- .map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
- {
- return None;
- }
+ de.name(&self.locales)?;
- // 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)
- }) {
+ match de.exec() {
+ Some(exec) => match exec.split_ascii_whitespace().next() {
+ Some(exec) => {
+ if exec == "false" {
return None;
}
}
- }
+ None => return None,
+ },
+ None => return None,
+ }
- // 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;
- }
+ // Avoid showing the GNOME Shell entry entirely
+ if de
+ .name(&[] as &[&str])
+ .map_or(false, |v| EXCLUSIONS.contains(&v.as_ref()))
+ {
+ return None;
+ }
+
+ // 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;
}
}
- // Treat `OnlyShowIn` as an override otherwise do not show if `NoDisplay` is true
- // Some desktop environments set `OnlyShowIn` and `NoDisplay = true` to
- // indicate special entries
- else if de.no_display() {
- return None;
+ }
+
+ // 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;
+ }
}
+ }
+ // Treat `OnlyShowIn` as an override otherwise do not show if `NoDisplay` is true
+ // Some desktop environments set `OnlyShowIn` and `NoDisplay = true` to
+ // indicate special entries
+ else if de.no_display() {
+ return None;
+ }
- Some(de)
- })
+ Some(de)
})
- .collect::>();
-
- self.desktop_entries = desktop_entries;
+ .collect();
self.gpus = try_get_gpus().await;
}
@@ -210,32 +205,33 @@ impl App {
async fn search(&mut self, query: &str) {
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(&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 score = entry.match_query(query, &self.locales, &[]);
- send(&mut self.tx, response).await;
+ if score < 0.6 {
+ continue;
}
+ 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()
+ });
+
+ send(&mut self.tx, response).await;
}
send(&mut self.tx, PluginResponse::Finished).await;
}
- async fn gnome_context(&self, entry: &DesktopEntry<'_>) -> Vec {
+ async fn gnome_context(&self, entry: &DesktopEntry) -> Vec {
if self.gpus.is_some() {
vec![ContextOption {
id: 0,
@@ -251,7 +247,7 @@ impl App {
}
}
- async fn cosmic_context(&self, entry: &DesktopEntry<'_>) -> Vec {
+ async fn cosmic_context(&self, entry: &DesktopEntry) -> Vec {
let mut options = Vec::new();
if let Some(gpus) = self.gpus.as_ref() {
diff --git a/plugins/src/desktop_entries/plugin.desktop b/plugins/src/desktop_entries/plugin.desktop
new file mode 100644
index 0000000..c56701b
--- /dev/null
+++ b/plugins/src/desktop_entries/plugin.desktop
@@ -0,0 +1,6 @@
+[Plugin]
+Name=Desktop Entries
+Description=Query applications by their .desktop entries
+Exec=desktop-entries
+Icon=new-window-symbolic
+History=true
\ No newline at end of file
diff --git a/plugins/src/desktop_entries/plugin.ron b/plugins/src/desktop_entries/plugin.ron
deleted file mode 100644
index 18de0bf..0000000
--- a/plugins/src/desktop_entries/plugin.ron
+++ /dev/null
@@ -1,7 +0,0 @@
-(
- name: "Desktop Entries",
- description: "Query applications by their .desktop entries",
- bin: (path: "desktop-entries"),
- icon: Name("new-window-symbolic"),
- history: true,
-)
\ No newline at end of file
diff --git a/plugins/src/desktop_entries/utils.rs b/plugins/src/desktop_entries/utils.rs
index 864aced..9a28162 100644
--- a/plugins/src/desktop_entries/utils.rs
+++ b/plugins/src/desktop_entries/utils.rs
@@ -4,8 +4,6 @@ use std::borrow::Cow;
use freedesktop_desktop_entry::{DesktopEntry, PathSource};
-// todo: subscriptions with notify
-
pub fn path_string(source: &PathSource) -> Cow<'static, str> {
match source {
PathSource::Local | PathSource::LocalDesktop => "Local".into(),
@@ -20,7 +18,7 @@ pub fn path_string(source: &PathSource) -> Cow<'static, str> {
}
}
-pub fn get_description<'a>(de: &'a DesktopEntry<'a>, locales: &[String]) -> String {
+pub fn get_description(de: &DesktopEntry, locales: &[String]) -> String {
let path_source = PathSource::guess_from(&de.path);
let desc_source = path_string(&path_source).to_string();
diff --git a/plugins/src/files/plugin.desktop b/plugins/src/files/plugin.desktop
new file mode 100644
index 0000000..adfada7
--- /dev/null
+++ b/plugins/src/files/plugin.desktop
@@ -0,0 +1,10 @@
+[Plugin]
+Name=File navigation with tab autocomplete
+Name[fr]=Navigation des fichiers avec tab autocompletion
+Description=Syntax: { / | ~/ }\nExample: ~/Documents
+Exec=files
+Icon=system-file-manager
+Regex=^(/|~).*
+GenericQuery=~/
+Isolate=true
+NoSort=true
\ No newline at end of file
diff --git a/plugins/src/files/plugin.ron b/plugins/src/files/plugin.ron
deleted file mode 100644
index 30656a2..0000000
--- a/plugins/src/files/plugin.ron
+++ /dev/null
@@ -1,12 +0,0 @@
-(
- name: "File navigation with tab autocomplete",
- description: "Syntax: { / | ~/ }\nExample: ~/Documents",
- query: (
- regex: "^(/|~).*",
- help: "~/",
- isolate: true,
- no_sort: true,
- ),
- bin: (path: "files"),
- icon: Name("system-file-manager"),
-)
diff --git a/plugins/src/find/plugin.desktop b/plugins/src/find/plugin.desktop
new file mode 100644
index 0000000..0288ab4
--- /dev/null
+++ b/plugins/src/find/plugin.desktop
@@ -0,0 +1,9 @@
+[Plugin]
+Name=File search
+Name[fr]=Recherche de fichier
+Description=Syntax: find \nExample: find my-document.odt
+Exec=find
+Icon=system-file-manager
+Regex=^(find )+
+GenericQuery=find\s
+Isolate=true
\ No newline at end of file
diff --git a/plugins/src/find/plugin.ron b/plugins/src/find/plugin.ron
deleted file mode 100644
index 3791777..0000000
--- a/plugins/src/find/plugin.ron
+++ /dev/null
@@ -1,11 +0,0 @@
-(
- name: "File search",
- description: "Syntax: find \nExample: find my-document.odt",
- query: (
- regex: "^(find )+",
- help: "find ",
- isolate: true,
- ),
- bin: (path: "find"),
- icon: Name("system-file-manager"),
-)
diff --git a/plugins/src/pop_shell/mod.rs b/plugins/src/pop_shell/mod.rs
index 8946f58..38f7e5d 100644
--- a/plugins/src/pop_shell/mod.rs
+++ b/plugins/src/pop_shell/mod.rs
@@ -143,7 +143,7 @@ impl App {
if let Ok(entry) = fde::DesktopEntry::from_str(
path,
&data,
- &get_languages_from_env(),
+ Some(&get_languages_from_env()),
) {
if let Some(icon) = entry.icon() {
icon_name = Cow::Owned(icon.to_owned());
diff --git a/plugins/src/pop_shell/plugin.desktop b/plugins/src/pop_shell/plugin.desktop
new file mode 100644
index 0000000..1f5d2e9
--- /dev/null
+++ b/plugins/src/pop_shell/plugin.desktop
@@ -0,0 +1,6 @@
+[Plugin]
+Name=Pop Shell Windows
+Description=Active windows controllable via Pop Shell
+Exec=pop-shell
+Icon=focus-windows-symbolic
+ShowOnEmptyQuery=true
\ No newline at end of file
diff --git a/plugins/src/pop_shell/plugin.ron b/plugins/src/pop_shell/plugin.ron
deleted file mode 100644
index c10cf26..0000000
--- a/plugins/src/pop_shell/plugin.ron
+++ /dev/null
@@ -1,7 +0,0 @@
-(
- name: "Pop Shell Windows",
- description: "Active windows controllable via Pop Shell",
- query: (persistent: true),
- bin: (path: "pop-shell"),
- icon: Name("focus-windows-symbolic"),
-)
\ No newline at end of file
diff --git a/plugins/src/pulse/plugin.desktop b/plugins/src/pulse/plugin.desktop
new file mode 100644
index 0000000..a5b7a58
--- /dev/null
+++ b/plugins/src/pulse/plugin.desktop
@@ -0,0 +1,5 @@
+[Plugin]
+Name=PulseAudio Volume Control
+Description=Control PulseAudio devices and volume
+Exec=pulse
+Icon=multimedia-volume-control
\ No newline at end of file
diff --git a/plugins/src/pulse/plugin.ron b/plugins/src/pulse/plugin.ron
deleted file mode 100644
index 1b70c5c..0000000
--- a/plugins/src/pulse/plugin.ron
+++ /dev/null
@@ -1,6 +0,0 @@
-(
- name: "PulseAudio Volume Control",
- description: "Control PulseAudio devices and volume",
- bin: (path: "pulse"),
- icon: Name("multimedia-volume-control")
-)
\ No newline at end of file
diff --git a/plugins/src/recent/plugin.desktop b/plugins/src/recent/plugin.desktop
new file mode 100644
index 0000000..cc59e35
--- /dev/null
+++ b/plugins/src/recent/plugin.desktop
@@ -0,0 +1,9 @@
+[Plugin]
+Name=Recently-opened document search
+Name[fr]=Rechercher documents récemment ouvert
+Description=Syntax: recent \nExample: recent my-document.odt
+Exec=recent
+Regex=^(recent)\\s.*
+GenericQuery=recent\s
+Icon=system-file-manager
+Isolate=true
\ No newline at end of file
diff --git a/plugins/src/recent/plugin.ron b/plugins/src/recent/plugin.ron
deleted file mode 100644
index b0eb5af..0000000
--- a/plugins/src/recent/plugin.ron
+++ /dev/null
@@ -1,11 +0,0 @@
-(
- name: "Recently-opened document search",
- description: "Syntax: recent \nExample: recent my-document.odt",
- query: (
- regex: "^(recent)\\s.*",
- help: "recent ",
- isolate: true
- ),
- bin: (path: "recent"),
- icon: Name("system-file-manager")
-)
diff --git a/plugins/src/scripts/plugin.desktop b/plugins/src/scripts/plugin.desktop
new file mode 100644
index 0000000..c160dac
--- /dev/null
+++ b/plugins/src/scripts/plugin.desktop
@@ -0,0 +1,5 @@
+[Plugin]
+Name=Scripts
+Description=Shell scripts as launcher options
+Exec=scripts
+Icon=utilities-terminal
\ No newline at end of file
diff --git a/plugins/src/scripts/plugin.ron b/plugins/src/scripts/plugin.ron
deleted file mode 100644
index ecaba34..0000000
--- a/plugins/src/scripts/plugin.ron
+++ /dev/null
@@ -1,6 +0,0 @@
-(
- name: "Scripts",
- description: "Shell scripts as launcher options",
- bin: (path: "scripts"),
- icon: Name("utilities-terminal"),
-)
\ No newline at end of file
diff --git a/plugins/src/terminal/mod.rs b/plugins/src/terminal/mod.rs
index 9b76ab3..fc0377e 100644
--- a/plugins/src/terminal/mod.rs
+++ b/plugins/src/terminal/mod.rs
@@ -124,7 +124,7 @@ 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())
+ DesktopEntry::from_str(&path, &input, Some(&get_languages_from_env()))
.ok()
.and_then(|de| {
if de.no_display()
diff --git a/plugins/src/terminal/plugin.desktop b/plugins/src/terminal/plugin.desktop
new file mode 100644
index 0000000..2c2437b
--- /dev/null
+++ b/plugins/src/terminal/plugin.desktop
@@ -0,0 +1,10 @@
+[Plugin]
+Name=Terminal or background commands
+Name[fr]=Terminal ou commande en arrière plan
+Description=Syntax: { run | t: | : } \nExample: run sudo apt update
+Exec=terminal
+Icon=utilities-terminal
+History=true
+Isolate=true
+GenericQuery=run\s
+Regex=^(:|t:|run ).*
\ No newline at end of file
diff --git a/plugins/src/terminal/plugin.ron b/plugins/src/terminal/plugin.ron
deleted file mode 100644
index 8d03699..0000000
--- a/plugins/src/terminal/plugin.ron
+++ /dev/null
@@ -1,12 +0,0 @@
-(
- name: "Terminal or background commands",
- description: "Syntax: { run | t: | : } \nExample: run sudo apt update",
- query: (
- regex: "^(:|t:|run ).*",
- help: "run ",
- isolate: true,
- ),
- bin: (path: "terminal"),
- icon: Name("utilities-terminal"),
- history: true,
-)
diff --git a/plugins/src/web/plugin.desktop b/plugins/src/web/plugin.desktop
new file mode 100644
index 0000000..c170b84
--- /dev/null
+++ b/plugins/src/web/plugin.desktop
@@ -0,0 +1,9 @@
+[Plugin]
+Name=Web search
+Name[fr]=Recherche internet
+Description=Syntax: { ddg | google | ... } \nExample: ddg how to install Pop!_OS
+Exec=web
+Icon=system-search
+History=true
+GenericQuery=ddg\s
+Priority=High
\ No newline at end of file
diff --git a/plugins/src/web/plugin.ron b/plugins/src/web/plugin.ron
deleted file mode 100644
index 12acc61..0000000
--- a/plugins/src/web/plugin.ron
+++ /dev/null
@@ -1,8 +0,0 @@
-(
- name: "Web search",
- description: "Syntax: { ddg | google | ... } \nExample: ddg how to install Pop!_OS",
- query: (help: "ddg ", priority: High),
- bin: (path: "web"),
- icon: Name("system-search"),
- history: true,
-)
diff --git a/rust-toolchain b/rust-toolchain
deleted file mode 100644
index 2bf5ad0..0000000
--- a/rust-toolchain
+++ /dev/null
@@ -1 +0,0 @@
-stable
diff --git a/service/Cargo.toml b/service/Cargo.toml
index e79a853..01a008b 100644
--- a/service/Cargo.toml
+++ b/service/Cargo.toml
@@ -24,6 +24,7 @@ strsim = "0.11.1"
toml.workspace = true
tracing.workspace = true
flume.workspace = true
+freedesktop-desktop-entry.workspace = true
[dependencies.tokio]
workspace = true
diff --git a/service/src/lib.rs b/service/src/lib.rs
index dafbf88..ee32f73 100644
--- a/service/src/lib.rs
+++ b/service/src/lib.rs
@@ -9,10 +9,9 @@ mod recent;
pub use client::*;
pub use plugins::config;
pub use plugins::external::load;
+use plugins::help::HelpPlugin;
-use crate::plugins::{
- ExternalPlugin, HelpPlugin, Plugin, PluginConfig, PluginConnector, PluginPriority, PluginQuery,
-};
+use crate::plugins::{ExternalPlugin, Plugin, PluginConfig, PluginConnector, PluginPriority};
use crate::priority::Priority;
use crate::recent::RecentUseStorage;
use flume::{Receiver, Sender};
@@ -21,7 +20,6 @@ use pop_launcher::{
json_input_stream, plugin_paths, ContextOption, IconSource, Indice, PluginResponse,
PluginSearchResult, Request, Response, SearchResult,
};
-use regex::Regex;
use slab::Slab;
use std::{
cmp::Ordering,
@@ -142,8 +140,8 @@ impl + Unpin> Service {
futures::pin_mut!(stream);
- while let Some((exec, config, regex)) = stream.next().await {
- tracing::info!("found plugin \"{}\"", exec.display());
+ while let Some(config) = stream.next().await {
+ tracing::info!("found plugin \"{}\"", config.exec.path.display());
if self
.plugins
.iter()
@@ -153,17 +151,17 @@ impl + Unpin> Service {
continue;
}
- let name = String::from(config.name.as_ref());
+ let name = config.name.clone();
+ let exec = config.exec.clone();
- self.register_plugin(service_tx.clone(), config, regex, move |id, tx| {
- ExternalPlugin::new(id, name.clone(), exec.clone(), Vec::new(), tx)
+ self.register_plugin(service_tx.clone(), config, move |id, tx| {
+ ExternalPlugin::new(id, name.clone(), exec.clone(), tx)
});
}
self.register_plugin(
service_tx.clone(),
- plugins::help::CONFIG,
- Some(Regex::new(plugins::help::REGEX.as_ref()).expect("failed to compile help regex")),
+ plugins::help::manifest(),
HelpPlugin::new,
);
@@ -265,7 +263,6 @@ impl + Unpin> Service {
&mut self,
service_tx: Sender,
config: PluginConfig,
- regex: Option,
init: I,
) {
let entry = self.plugins.vacant_entry();
@@ -273,16 +270,8 @@ impl + Unpin> Service {
let init = std::sync::Arc::new(init);
- let isolate_with = config
- .query
- .isolate_with
- .as_ref()
- .and_then(|expr| Regex::new(expr).ok());
-
entry.insert(PluginConnector::new(
config,
- regex,
- isolate_with,
Box::new(move || {
let (request_tx, request_rx) = flume::bounded(8);
@@ -436,22 +425,22 @@ impl + Unpin> Service {
for (key, plugin) in self.plugins.iter_mut() {
// Avoid sending queries to plugins which are not matched
- if let Some(regex) = plugin.regex.as_ref() {
+ if let Some(regex) = plugin.config.regex.as_ref() {
if !regex.is_match(query) {
continue;
}
}
- if requires_persistence && !plugin.config.query.persistent {
+ if requires_persistence && !plugin.config.show_on_empty_query {
continue;
}
- if plugin.config.query.isolate {
+ if plugin.config.isolate {
isolated = Some(key);
break;
}
- if let Some(regex) = plugin.isolate_regex.as_ref() {
+ if let Some(regex) = plugin.config.isolate_with.as_ref() {
if regex.is_match(query) {
isolated = Some(key);
break;
@@ -470,7 +459,7 @@ impl + Unpin> Service {
.is_ok()
{
self.awaiting_results.insert(isolated);
- self.no_sort = plugin.config.query.no_sort;
+ self.no_sort = plugin.config.no_sort;
}
}
} else {
@@ -581,7 +570,7 @@ impl + Unpin> Service {
let get_prio = |sr: &PluginSearchResult, plg: &PluginConnector| -> Priority {
let ex = sr.cache_identifier();
Priority {
- plugin_priority: plg.config.query.priority,
+ plugin_priority: plg.config.priority,
match_score: calculate_weight(sr, query),
recent_score: ex.as_ref().map(|s| recent.get_recent(s)).unwrap_or(0.),
freq_score: ex.as_ref().map(|s| recent.get_freq(s)).unwrap_or(0.),
@@ -617,7 +606,8 @@ impl + Unpin> Service {
icon: meta.icon.clone(),
category_icon: plugins
.get(*plugin)
- .and_then(|conn| conn.config.icon.clone()),
+ .and_then(|conn| conn.config.icon.clone())
+ .map(|icon| IconSource::Name(icon.into())),
window: meta.window,
}
});
diff --git a/service/src/plugins/config.rs b/service/src/plugins/config.rs
index cd2d465..00f8ec3 100644
--- a/service/src/plugins/config.rs
+++ b/service/src/plugins/config.rs
@@ -1,86 +1,38 @@
// Copyright 2021 System76
// SPDX-License-Identifier: MPL-2.0
+use anyhow::{anyhow, bail};
+use freedesktop_desktop_entry as fde;
use regex::Regex;
-use serde::Deserialize;
-use std::{
- borrow::Cow,
- path::{Path, PathBuf},
-};
+use std::path::{Path, PathBuf};
-#[derive(Debug, Default, Deserialize, Clone)]
+#[derive(Debug, Default, Clone)]
pub struct PluginConfig {
- pub name: Cow<'static, str>,
- pub description: Cow<'static, str>,
- #[serde(
- default,
- skip_serializing_if = "Option::is_none",
- with = "::serde_with::rust::unwrap_or_skip"
- )]
- pub bin: Option,
- #[serde(
- default,
- skip_serializing_if = "Option::is_none",
- with = "::serde_with::rust::unwrap_or_skip"
- )]
- pub icon: Option,
-
- #[serde(default)]
- pub query: PluginQuery,
-
- #[serde(default)]
- pub history: bool,
-
- #[serde(default)]
- pub long_lived: bool,
-}
-
-#[derive(Debug, Default, Deserialize, Clone)]
-pub struct PluginBinary {
- path: Cow<'static, str>,
-
- #[serde(default)]
- #[allow(dead_code)]
- args: Vec>,
-}
-
-#[derive(Debug, Default, Deserialize, Clone)]
-pub struct PluginQuery {
- #[serde(
- default,
- skip_serializing_if = "Option::is_none",
- with = "::serde_with::rust::unwrap_or_skip"
- )]
- pub help: Option>,
-
- #[serde(default)]
+ pub name: String,
+ pub description: Option,
+ pub icon: Option,
+ pub exec: PluginExec,
+ pub regex: Option,
pub isolate: bool,
-
- #[serde(
- default,
- skip_serializing_if = "Option::is_none",
- with = "::serde_with::rust::unwrap_or_skip"
- )]
- pub isolate_with: Option>,
-
- #[serde(default)]
+ pub isolate_with: Option,
+ pub show_on_empty_query: bool,
pub no_sort: bool,
-
- #[serde(default)]
- pub persistent: bool,
-
- #[serde(default)]
+ pub generic_query: Option,
+ pub long_lived: bool,
+ pub history: bool,
pub priority: PluginPriority,
+}
- #[serde(
- default,
- skip_serializing_if = "Option::is_none",
- with = "::serde_with::rust::unwrap_or_skip"
- )]
- pub regex: Option>,
+#[derive(Debug, Default, Clone)]
+pub struct PluginExec {
+ pub path: PathBuf,
+ pub args: Vec,
}
-#[derive(Copy, Clone, Debug, Deserialize, Eq, PartialEq, PartialOrd, Ord)]
+#[derive(Debug, Default, Clone)]
+pub struct PluginQuery {}
+
+#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub enum PluginPriority {
High = 0,
Default = 1,
@@ -93,36 +45,109 @@ impl Default for PluginPriority {
}
}
-pub fn load(source: &Path, config_path: &Path) -> Option<(PathBuf, PluginConfig, Option)> {
- if let Ok(config_bytes) = std::fs::read_to_string(config_path) {
- let config = match ron::from_str::(&config_bytes) {
- Ok(config) => config,
- Err(why) => {
- tracing::error!("malformed config at {}: {}", config_path.display(), why);
- return None;
- }
- };
+impl PluginConfig {
+ pub fn from_path(source: &Path, config_path: &Path) -> anyhow::Result {
+ let content = std::fs::read_to_string(config_path)
+ .map_err(|e| anyhow!("error reading config at {}: {:?}", config_path.display(), e))?;
- let exec = if let Some(bin) = config.bin.as_ref() {
- if bin.path.starts_with('/') {
- PathBuf::from((*bin.path).to_owned())
- } else {
- source.join(bin.path.as_ref())
- }
- } else {
- tracing::error!(
- "bin field is missing from config at {}",
- config_path.display()
- );
- return None;
+ Self::from_str(source, config_path, &content)
+ }
+
+ pub fn from_str(source: &Path, config_path: &Path, content: &str) -> anyhow::Result {
+ let locales = fde::get_languages_from_env();
+
+ let desktop_entry = fde::DesktopEntry::from_str(config_path, content, Some(&locales))?;
+
+ let group = desktop_entry
+ .groups
+ .group("Plugin")
+ .ok_or(anyhow!("no Plugin group"))?;
+
+ let mut config = PluginConfig {
+ name: group
+ .localized_entry("Name", &locales)
+ .ok_or(anyhow!("no Name field"))?
+ .to_string(),
+ exec: {
+ let exec = group
+ .localized_entry("Exec", &locales)
+ .ok_or(anyhow!("no Exec field"))?;
+
+ let mut iter = exec.split(" ");
+
+ let mut exec = PluginExec {
+ path: PathBuf::from(iter.next().unwrap()),
+ args: iter.map(|a| a.to_string()).collect(),
+ };
+
+ if !exec.path.is_absolute() {
+ exec.path = source.join(&exec.path);
+ };
+
+ exec
+ },
+ ..Default::default()
};
- let regex = config.query.regex.as_ref().and_then(|p| Regex::new(p).ok());
+ if let Some(description) = group.entry("Description") {
+ config.description.replace(description.to_string());
+ }
- return Some((exec, config, regex));
- }
+ if let Some(icon) = group.entry("Icon") {
+ config.icon.replace(icon.to_string());
+ }
- tracing::error!("I/O error reading config at {}", config_path.display());
+ if let Some(regex) = group.entry("Regex") {
+ match Regex::new(regex) {
+ Ok(regex) => {
+ config.regex.replace(regex);
+ }
+ Err(e) => bail!("can't parse regex: {e:?}"),
+ }
+ }
+
+ if let Some(isolate) = group.entry_bool("Isolate") {
+ config.isolate = isolate;
+ }
+
+ if let Some(regex) = group.entry("IsolateWith") {
+ match Regex::new(regex) {
+ Ok(regex) => {
+ config.isolate_with.replace(regex);
+ }
+ Err(e) => bail!("can't parse isolate_with: {e:?}"),
+ }
+ }
+
+ if let Some(persistent) = group.entry_bool("ShowOnEmptyQuery") {
+ config.show_on_empty_query = persistent;
+ }
+
+ if let Some(no_sort) = group.entry_bool("NoSort") {
+ config.no_sort = no_sort;
+ }
+
+ if let Some(generic_query) = group.entry("GenericQuery") {
+ config.generic_query.replace(generic_query.to_string());
+ }
+
+ if let Some(long_lived) = group.entry_bool("LongLived") {
+ config.long_lived = long_lived;
+ }
+
+ if let Some(history) = group.entry_bool("History") {
+ config.history = history;
+ }
+
+ if let Some(priority) = group.entry("Priority") {
+ match priority {
+ "Default" => config.priority = PluginPriority::Default,
+ "High" => config.priority = PluginPriority::High,
+ "Low" => config.priority = PluginPriority::Low,
+ _ => {}
+ }
+ }
- None
+ Ok(config)
+ }
}
diff --git a/service/src/plugins/external/load.rs b/service/src/plugins/external/load.rs
index e8cf801..abb855a 100644
--- a/service/src/plugins/external/load.rs
+++ b/service/src/plugins/external/load.rs
@@ -4,21 +4,35 @@
use crate::PluginConfig;
use futures::{stream, Stream, StreamExt};
-use regex::Regex;
use std::path::PathBuf;
+use tracing::error;
/// Fetches plugins installed on the system in parallel.
///
/// Searches plugin paths from highest to least priority. User plugins will override
/// distribution plugins. Plugins are loaded in the order they are found.
-pub fn from_paths() -> impl Stream- )> {
+pub fn from_paths() -> impl Stream
- {
stream::iter(crate::plugin_paths())
.flat_map(|path| from_path(path.to_path_buf()))
.map(|(source, config)| {
- tokio::task::spawn_blocking(move || crate::plugins::config::load(&source, &config))
+ tokio::task::spawn_blocking(move || PluginConfig::from_path(&source, &config))
})
.buffered(num_cpus::get())
- .filter_map(|x| async move { x.ok().flatten() })
+ .filter_map(|x| async move {
+ match x {
+ Ok(plugin) => match plugin {
+ Ok(plugin) => Some(plugin),
+ Err(e) => {
+ error!("{e:?}");
+ None
+ }
+ },
+ Err(e) => {
+ error!("{e:?}");
+ None
+ }
+ }
+ })
}
/// Loads all plugin information found in the given path.
@@ -31,7 +45,7 @@ pub fn from_path(path: PathBuf) -> impl Stream
- {
continue;
}
- let config = source.join("plugin.ron");
+ let config = source.join("plugin.desktop");
if !config.exists() {
continue;
}
diff --git a/service/src/plugins/external/mod.rs b/service/src/plugins/external/mod.rs
index 67620c3..0f60c97 100644
--- a/service/src/plugins/external/mod.rs
+++ b/service/src/plugins/external/mod.rs
@@ -5,7 +5,6 @@ pub mod load;
use std::{
io,
- path::PathBuf,
process::Stdio,
sync::{
atomic::{AtomicBool, Ordering},
@@ -24,31 +23,25 @@ use tokio::{
};
use tracing::{event, Level};
+use super::config::PluginExec;
+
pub struct ExternalPlugin {
id: usize,
tx: Sender,
name: String,
- pub cmd: PathBuf,
- pub args: Vec,
+ exec: PluginExec,
process: Option<(JoinHandle<()>, Child, async_oneshot::Sender<()>)>,
detached: Arc,
searching: Arc,
}
impl ExternalPlugin {
- pub fn new(
- id: usize,
- name: String,
- cmd: PathBuf,
- args: Vec,
- tx: Sender,
- ) -> Self {
+ pub fn new(id: usize, name: String, exec: PluginExec, tx: Sender) -> Self {
Self {
id,
name,
tx,
- cmd,
- args,
+ exec,
process: None,
detached: Arc::default(),
searching: Arc::default(),
@@ -58,8 +51,8 @@ impl ExternalPlugin {
pub fn launch(&mut self) -> Option<&mut (JoinHandle<()>, Child, async_oneshot::Sender<()>)> {
event!(Level::DEBUG, "{}: launching plugin", self.name());
- let child = Command::new(&self.cmd)
- .args(&self.args)
+ let child = Command::new(&self.exec.path)
+ .args(&self.exec.args)
.stdout(Stdio::piped())
.stdin(Stdio::piped())
.stderr(Stdio::inherit())
diff --git a/service/src/plugins/help.rs b/service/src/plugins/help/mod.rs
similarity index 79%
rename from service/src/plugins/help.rs
rename to service/src/plugins/help/mod.rs
index 1dbf1bc..06e4442 100644
--- a/service/src/plugins/help.rs
+++ b/service/src/plugins/help/mod.rs
@@ -1,31 +1,23 @@
// Copyright 2021 System76
// SPDX-License-Identifier: MPL-2.0
+use std::path::Path;
+
use crate::*;
use flume::Sender;
use pop_launcher::*;
use slab::Slab;
-use std::borrow::Cow;
-
-pub const REGEX: Cow<'static, str> = Cow::Borrowed("^(\\?).*");
-
-pub const CONFIG: PluginConfig = PluginConfig {
- name: Cow::Borrowed("Help"),
- description: Cow::Borrowed("Show available plugin prefixes"),
- bin: None,
- query: PluginQuery {
- help: None,
- isolate: true,
- isolate_with: None,
- no_sort: true,
- persistent: false,
- priority: PluginPriority::Default,
- regex: None,
- },
- icon: Some(IconSource::Name(Cow::Borrowed("system-help-symbolic"))),
- history: false,
- long_lived: false,
-};
+
+pub fn manifest() -> PluginConfig {
+ PluginConfig::from_str(
+ &Path::new(""),
+ // dummy value
+ &Path::new("a.b.desktop"),
+ include_str!("plugin.desktop"),
+ )
+ .unwrap()
+}
+
pub struct HelpPlugin {
pub id: usize,
pub details: Slab,
diff --git a/service/src/plugins/help/plugin.desktop b/service/src/plugins/help/plugin.desktop
new file mode 100644
index 0000000..fe48538
--- /dev/null
+++ b/service/src/plugins/help/plugin.desktop
@@ -0,0 +1,8 @@
+[Plugin]
+Name=Help
+Description=Show available plugin prefixes
+Exec=
+Isolate=true
+NoSort=true
+Icon=system-help-symbolic
+Regex=^(\\?).*
\ No newline at end of file
diff --git a/service/src/plugins/mod.rs b/service/src/plugins/mod.rs
index 24c1078..56ab032 100644
--- a/service/src/plugins/mod.rs
+++ b/service/src/plugins/mod.rs
@@ -5,14 +5,12 @@ pub mod config;
pub(crate) mod external;
pub mod help;
-pub use self::config::{PluginConfig, PluginPriority, PluginQuery};
+pub use self::config::{PluginConfig, PluginPriority};
pub use self::external::ExternalPlugin;
-pub use self::help::HelpPlugin;
use crate::{Indice, PluginHelp, Request};
use async_trait::async_trait;
use flume::{Receiver, Sender};
-use regex::Regex;
#[async_trait]
pub trait Plugin
@@ -81,44 +79,25 @@ pub struct PluginConnector {
/// this plugin to spawn as a background service
pub init: Box Sender>,
- pub isolate_regex: Option,
-
- /// A compiled regular expression that a query must match
- /// for the launcher service to justify spawning and sending
- /// queries to this plugin
- pub regex: Option,
-
/// The sender of the spawned background service that will be
/// forwarded to the launncher service
pub sender: Option>,
}
impl PluginConnector {
- pub fn new(
- config: PluginConfig,
- regex: Option,
- isolate_regex: Option,
- init: Box Sender + Send>,
- ) -> Self {
+ pub fn new(config: PluginConfig, init: Box Sender + Send>) -> Self {
Self {
config,
init,
- isolate_regex,
- regex,
sender: None,
}
}
pub fn details(&self) -> PluginHelp {
PluginHelp {
- name: self.config.name.as_ref().to_owned(),
- description: self.config.description.as_ref().to_owned(),
- help: self
- .config
- .query
- .help
- .as_ref()
- .map(|x| x.as_ref().to_owned()),
+ name: self.config.name.to_string(),
+ description: self.config.description.clone().unwrap_or_default(),
+ help: self.config.generic_query.clone(),
}
}
diff --git a/toolkit/examples/plugin.desktop b/toolkit/examples/plugin.desktop
new file mode 100644
index 0000000..dd06131
--- /dev/null
+++ b/toolkit/examples/plugin.desktop
@@ -0,0 +1,9 @@
+[Plugin]
+Name=Find man pages
+Description=Syntax: { whatis }\nExample: whatis git
+Exec=man-pages-plugin
+Icon=org.gnome.Documents-symbolic
+NoSort=true
+Isolate=true
+GenericQuery=whatis\s
+Regex=^(whatis ).+
\ No newline at end of file
diff --git a/toolkit/examples/plugin.ron b/toolkit/examples/plugin.ron
deleted file mode 100644
index 5a73a37..0000000
--- a/toolkit/examples/plugin.ron
+++ /dev/null
@@ -1,12 +0,0 @@
-(
- name: "Find man pages",
- description: "Syntax: { whatis }\nExample: whatis git",
- query: (
- regex: "^(whatis ).+",
- help: "whatis",
- isolate: true,
- no_sort: true,
- ),
- bin: (path: "man-pages-plugin"),
- icon: Name("org.gnome.Documents-symbolic"),
-)