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. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FieldDescriptionTypeDefaultTranslatable
NamePlugin desciptionlocalestringyes
DescriptionPlugin namelocalestringempty stringyes
IconIcon name, respecting the [icon theme spec](https://freedesktop.org/wiki/Specifications/icon-theme-spec/).iconstringno iconno
ExecExecutable file. Should either an absolute path, or a path relative to {plugin_directory}stringno
RegexActivate the plugin when this regex match the queryregexmatch all queryno
IsolateIsolate this plugin when the regex field matchesbooleanfalseno
IsolateWithIsolate this plugin when this regex matchesregexmatch nothingno
ShowOnEmptyQueryShow plugin's result on empty queriesbooleanfalseno
NoSortAvoid sorting results from this pluginbooleanfalseno
GenericQueryDefault query that will activate this pluginlocalestringempty stringno
LongLivedThe plugin should always run in the backgroundbooleanfalseno
HistoryThe launcher should keep a history for this pluginbooleanfalseno
PriorityResult priority of the pluginDefault, High or LowDefaultno
+ + ## 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"), -)