From dae8108cb1b5c7f676fd7ebc4f26e2f86ceeaf49 Mon Sep 17 00:00:00 2001 From: Michael Aaron Murphy Date: Tue, 17 Aug 2021 15:15:23 +0200 Subject: [PATCH] feat: Support context options --- README.md | 48 ++++++++++++++++-- plugins/src/desktop_entries/mod.rs | 60 +++++++++++++++++++++-- plugins/src/pop_shell/mod.rs | 2 +- service/src/lib.rs | 75 +++++++++++++++++++++++------ service/src/plugins/external/mod.rs | 16 ++++-- service/src/plugins/help.rs | 4 ++ service/src/plugins/mod.rs | 16 ++++-- src/lib.rs | 36 +++++++++++++- 8 files changed, 220 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 722c1f4..ef72b7d 100644 --- a/README.md +++ b/README.md @@ -38,8 +38,12 @@ If you are writing a frontend, you are sending these events to the pop-launcher pub enum Request { /// Activate on the selected item Activate(Indice), + /// Activate a context item on an item. + ActivateContext { id: Indice, context: Indice }, /// Perform a tab completion from the selected item Complete(Indice), + /// Request for any context options this result may have. + Context(Indice), /// Request to end the service Exit, /// Requests to cancel any active searches @@ -54,7 +58,9 @@ pub enum Request { #### JSON Equivalent - `{ "Activate": number }` +- `{ "ActivateContext": { "id": number, "context": id }}` - `{ "Complete": number }` +- `{ "Context": number }` - `"Exit"` - `"Interrupt"` - `{ "Quit": number }` @@ -72,8 +78,16 @@ pub enum PluginResponse { Clear, /// Close the launcher Close, - // Notifies that a .desktop entry should be launched by the frontend - DesktopEntry(PathBuf), + // Additional options for launching a certain item + Context { + id: Indice, + options: Vec, + }, + // Notifies that a .desktop entry should be launched by the frontend. + DesktopEntry { + path: PathBuf, + gpu_preference: GpuPreference, + }, /// Update the text in the launcher Fill(String), /// Indicoates that a plugin is finished with its queries @@ -86,7 +100,8 @@ pub enum PluginResponse { - `{ "Append": PluginSearchResult }`, - `"Clear"`, - `"Close"`, -- `{ "DesktopEntry": string }` +- `{ "Context": { "id": number, "options": Array }}` +- `{ "DesktopEntry": { "path": string, "gpu_preference": GpuPreference }}` - `{ "Fill": string }` - `"Finished"` @@ -104,6 +119,21 @@ Where `PluginSearchResult` is: } ``` +`ContextOption` is: + +```ts +{ + id: number, + name: string +} +``` + +`GpuPreference` is: + +```ts +"Default" | "NonDefault" +``` + And `IconSource` is either: - `{ "Name": string }`, where the name is a system icon, or an icon referred to by path @@ -117,8 +147,16 @@ Those implementing frontends should listen for these events: pub enum Response { // An operation was performed and the frontend may choose to exit its process. Close, - // Notifies that a .desktop entry should be launched by the frontend - DesktopEntry(PathBuf), + // Additional options for launching a certain item + Context { + id: Indice, + options: Vec, + }, + // Notifies that a .desktop entry should be launched by the frontend. + DesktopEntry { + path: PathBuf, + gpu_preference: GpuPreference, + }, // The frontend should clear its search results and display a new list Update(Vec), // An item was selected that resulted in a need to autofill the launcher diff --git a/plugins/src/desktop_entries/mod.rs b/plugins/src/desktop_entries/mod.rs index c047621..e3910ec 100644 --- a/plugins/src/desktop_entries/mod.rs +++ b/plugins/src/desktop_entries/mod.rs @@ -34,7 +34,7 @@ impl PartialEq for Item { } pub async fn main() { - let mut app = DesktopEntryPlugin::new(async_stdout()); + let mut app = App::new(async_stdout()); app.reload().await; let mut requests = json_input_stream(async_stdin()); @@ -45,6 +45,10 @@ pub async fn main() { tracing::debug!("received request: {:?}", request); match request { Request::Activate(id) => app.activate(id).await, + Request::ActivateContext { id, context } => { + app.activate_context(id, context).await + } + Request::Context(id) => app.context(id).await, Request::Search(query) => app.search(&query).await, Request::Exit => break, _ => (), @@ -58,13 +62,13 @@ pub async fn main() { } } -struct DesktopEntryPlugin { +struct App { entries: Vec, locale: Option, tx: W, } -impl DesktopEntryPlugin { +impl App { fn new(tx: W) -> Self { let lang = std::env::var("LANG").ok(); @@ -138,9 +142,55 @@ impl DesktopEntryPlugin { } async fn activate(&mut self, id: u32) { - tracing::debug!("activate {} from {:?}", id, self.entries); if let Some(entry) = self.entries.get(id as usize) { - let response = PluginResponse::DesktopEntry(entry.path.clone()); + let response = PluginResponse::DesktopEntry { + path: entry.path.clone(), + gpu_preference: if entry.prefers_non_default_gpu { + GpuPreference::NonDefault + } else { + GpuPreference::Default + }, + }; + + send(&mut self.tx, response).await; + } + } + + async fn activate_context(&mut self, id: u32, context: u32) { + if let Some(entry) = self.entries.get(id as usize) { + let response = match context { + 0 => PluginResponse::DesktopEntry { + path: entry.path.clone(), + gpu_preference: if !entry.prefers_non_default_gpu { + GpuPreference::NonDefault + } else { + GpuPreference::Default + }, + }, + _ => return, + }; + + send(&mut self.tx, response).await; + } + } + + async fn context(&mut self, id: u32) { + if let Some(entry) = self.entries.get(id as usize) { + let option = ContextOption { + id: 0, + name: (if entry.prefers_non_default_gpu { + "Launch Using Integrated Graphics Card" + } else { + "Launch Using Discrete Graphics Card" + }) + .to_owned(), + }; + + let response = PluginResponse::Context { + id, + options: vec![option], + }; + send(&mut self.tx, response).await; } } diff --git a/plugins/src/pop_shell/mod.rs b/plugins/src/pop_shell/mod.rs index 61d0bdb..10e3692 100644 --- a/plugins/src/pop_shell/mod.rs +++ b/plugins/src/pop_shell/mod.rs @@ -36,10 +36,10 @@ pub async fn main() { match request { Ok(request) => match request { Request::Activate(id) => app.activate(id).await, - Request::Complete(_) | Request::Interrupt => (), Request::Quit(_id) => (), Request::Search(query) => app.search(&query).await, Request::Exit => break, + _ => (), }, Err(why) => { tracing::error!("malformed JSON request: {}", why); diff --git a/service/src/lib.rs b/service/src/lib.rs index de58d2e..49464a2 100644 --- a/service/src/lib.rs +++ b/service/src/lib.rs @@ -8,7 +8,7 @@ use postage::prelude::*; use regex::Regex; use slab::Slab; use std::{ - collections::HashSet, + collections::{HashMap, HashSet}, io::{self, Write}, }; @@ -34,6 +34,7 @@ pub async fn main() { pub struct Service { active_search: Vec<(PluginKey, PluginSearchResult)>, + associated_list: HashMap, awaiting_results: HashSet, last_query: String, output: O, @@ -46,6 +47,7 @@ impl Service { pub fn new(output: O) -> Self { Self { active_search: Vec::new(), + associated_list: HashMap::new(), awaiting_results: HashSet::new(), last_query: String::new(), output, @@ -105,7 +107,11 @@ impl Service { Request::Search(query) => self.search(query).await, Request::Interrupt => self.interrupt().await, Request::Activate(id) => self.activate(id).await, + Request::ActivateContext { id, context } => { + self.activate_context(id, context).await + } Request::Complete(id) => self.complete(id).await, + Request::Context(id) => self.context(id).await, Request::Quit(id) => self.quit(id).await, // When requested to exit, the service will forward that @@ -125,10 +131,17 @@ impl Service { PluginResponse::Append(item) => self.append(plugin, item), PluginResponse::Clear => self.clear(), PluginResponse::Close => self.close(), + PluginResponse::Context { id, options } => self.context_response(id, options), PluginResponse::Fill(text) => self.fill(text), PluginResponse::Finished => self.finished(plugin).await, - PluginResponse::DesktopEntry(path) => { - self.respond(&Response::DesktopEntry(path)); + PluginResponse::DesktopEntry { + path, + gpu_preference, + } => { + self.respond(&Response::DesktopEntry { + path, + gpu_preference, + }); } }, @@ -185,12 +198,24 @@ impl Service { )); } - async fn activate(&mut self, id: u32) { + async fn activate(&mut self, id: Indice) { if let Some((plugin, meta)) = self.search_result(id as usize) { let _ = plugin.sender_exec().send(Request::Activate(meta.id)).await; } } + async fn activate_context(&mut self, id: Indice, context: Indice) { + if let Some((plugin, meta)) = self.search_result(id as usize) { + let _ = plugin + .sender_exec() + .send(Request::ActivateContext { + id: meta.id, + context, + }) + .await; + } + } + fn append(&mut self, plugin: PluginKey, append: PluginSearchResult) { self.active_search.push((plugin, append)); } @@ -203,12 +228,25 @@ impl Service { self.respond(&Response::Close); } - async fn complete(&mut self, id: u32) { + fn context_response(&mut self, id: Indice, options: Vec) { + if let Some(id) = self.associated_list.get(&id) { + let id = *id; + self.respond(&Response::Context { id, options }); + } + } + + async fn complete(&mut self, id: Indice) { if let Some((plugin, meta)) = self.search_result(id as usize) { let _ = plugin.sender_exec().send(Request::Complete(meta.id)).await; } } + async fn context(&mut self, id: Indice) { + if let Some((plugin, meta)) = self.search_result(id as usize) { + let _ = plugin.sender_exec().send(Request::Context(meta.id)).await; + } + } + fn fill(&mut self, text: String) { self.respond(&Response::Fill(text)); } @@ -222,7 +260,7 @@ impl Service { } let search_list = self.sort(); - self.respond(&Response::Update(search_list)) + self.respond(&Response::Update(search_list)); } } @@ -234,7 +272,7 @@ impl Service { } } - async fn quit(&mut self, id: u32) { + async fn quit(&mut self, id: Indice) { if let Some((plugin, meta)) = self.search_result(id as usize) { let _ = plugin.sender_exec().send(Request::Quit(meta.id)).await; } @@ -342,6 +380,7 @@ impl Service { fn sort(&mut self) -> Vec { let &mut Self { ref mut active_search, + ref mut associated_list, ref mut no_sort, ref last_query, ref plugins, @@ -423,21 +462,25 @@ impl Service { let mut windows = Vec::with_capacity(take); let mut non_windows = Vec::with_capacity(take); + associated_list.clear(); let search_results = active_search .iter() .take(take) .enumerate() - .map(|(id, (plugin, meta))| SearchResult { - id: id as u32, - name: meta.name.clone(), - description: meta.description.clone(), - icon: meta.icon.clone(), - category_icon: plugins - .get(*plugin) - .and_then(|conn| conn.config.icon.clone()), - window: meta.window, + .map(|(id, (plugin, meta))| { + associated_list.insert(meta.id, id as u32); + SearchResult { + id: id as u32, + name: meta.name.clone(), + description: meta.description.clone(), + icon: meta.icon.clone(), + category_icon: plugins + .get(*plugin) + .and_then(|conn| conn.config.icon.clone()), + window: meta.window, + } }); for result in search_results { diff --git a/service/src/plugins/external/mod.rs b/service/src/plugins/external/mod.rs index 715756c..aa46cd1 100644 --- a/service/src/plugins/external/mod.rs +++ b/service/src/plugins/external/mod.rs @@ -9,7 +9,7 @@ use std::{ }, }; -use crate::{Event, Plugin, PluginResponse, Request}; +use crate::{Event, Indice, Plugin, PluginResponse, Request}; use async_oneshot::oneshot; use futures_lite::{AsyncWriteExt, FutureExt, StreamExt}; use postage::mpsc::Sender; @@ -168,14 +168,22 @@ impl ExternalPlugin { #[async_trait::async_trait] impl Plugin for ExternalPlugin { - async fn activate(&mut self, id: u32) { + async fn activate(&mut self, id: Indice) { let _ = self.query(&Request::Activate(id)).await; } - async fn complete(&mut self, id: u32) { + async fn activate_context(&mut self, id: Indice, context: Indice) { + let _ = self.query(&Request::ActivateContext { id, context }).await; + } + + async fn complete(&mut self, id: Indice) { let _ = self.query(&Request::Complete(id)).await; } + async fn context(&mut self, id: Indice) { + let _ = self.query(&Request::Context(id)).await; + } + fn exit(&mut self) { if let Some((_, _, mut trigger)) = self.process.take() { let _ = trigger.send(()); @@ -200,7 +208,7 @@ impl Plugin for ExternalPlugin { .await; } } - async fn quit(&mut self, id: u32) { + async fn quit(&mut self, id: Indice) { let _ = self.query(&Request::Quit(id)).await; } } diff --git a/service/src/plugins/help.rs b/service/src/plugins/help.rs index 0736e2d..737ef4b 100644 --- a/service/src/plugins/help.rs +++ b/service/src/plugins/help.rs @@ -58,10 +58,14 @@ impl Plugin for HelpPlugin { } } + async fn activate_context(&mut self, _: u32, _: u32) {} + async fn complete(&mut self, id: u32) { self.activate(id).await } + async fn context(&mut self, _: u32) {} + fn exit(&mut self) {} async fn interrupt(&mut self) {} diff --git a/service/src/plugins/mod.rs b/service/src/plugins/mod.rs index caf3f6f..43d06d9 100644 --- a/service/src/plugins/mod.rs +++ b/service/src/plugins/mod.rs @@ -6,7 +6,7 @@ pub use self::config::{PluginBinary, PluginConfig, PluginQuery}; pub use self::external::ExternalPlugin; pub use self::help::HelpPlugin; -use crate::{PluginHelp, Request}; +use crate::{Indice, PluginHelp, Request}; use async_trait::async_trait; use postage::mpsc::{Receiver, Sender}; use postage::prelude::*; @@ -18,9 +18,13 @@ where Self: Sized + Send, { /// Activate the selected ID from this plugin - async fn activate(&mut self, id: u32); + async fn activate(&mut self, id: Indice); - async fn complete(&mut self, id: u32); + async fn activate_context(&mut self, id: Indice, context: Indice); + + async fn complete(&mut self, id: Indice); + + async fn context(&mut self, id: Indice); fn exit(&mut self); @@ -30,7 +34,7 @@ where async fn search(&mut self, query: &str); - async fn quit(&mut self, id: u32); + async fn quit(&mut self, id: Indice); async fn run(&mut self, mut rx: Receiver) { while let Some(request) = rx.recv().await { @@ -44,7 +48,11 @@ where Request::Search(query) => self.search(&query).await, Request::Interrupt => self.interrupt().await, Request::Activate(id) => self.activate(id).await, + Request::ActivateContext { id, context } => { + self.activate_context(id, context).await + } Request::Complete(id) => self.complete(id).await, + Request::Context(id) => self.context(id).await, Request::Quit(id) => self.quit(id).await, Request::Exit => { self.exit(); diff --git a/src/lib.rs b/src/lib.rs index 71b1b63..d0e829e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -41,6 +41,18 @@ pub type Generation = u32; /// u32 value defining the indice of a slot. pub type Indice = u32; +#[derive(Debug, Deserialize, Serialize)] +pub struct ContextOption { + pub id: Indice, + pub name: String, +} + +#[derive(Debug, Deserialize, Serialize)] +pub enum GpuPreference { + Default, + NonDefault, +} + #[derive(Debug, Clone, Deserialize, Serialize)] pub enum IconSource { // Locate by name or path. @@ -60,8 +72,16 @@ pub enum PluginResponse { Clear, /// Close the launcher. Close, + // Additional options for launching a certain item + Context { + id: Indice, + options: Vec, + }, // Notifies that a .desktop entry should be launched by the frontend. - DesktopEntry(PathBuf), + DesktopEntry { + path: PathBuf, + gpu_preference: GpuPreference, + }, /// Update the text in the launcher. Fill(String), /// Indicoates that a plugin is finished with its queries. @@ -92,8 +112,12 @@ pub struct PluginSearchResult { pub enum Request { /// Activate on the selected item. Activate(Indice), + /// Activate a context item on an item. + ActivateContext { id: Indice, context: Indice }, /// Perform a tab completion from the selected item. Complete(Indice), + /// Request for any context options this result may have. + Context(Indice), /// Request to end the service. Exit, /// Requests to cancel any active searches. @@ -109,8 +133,16 @@ pub enum Request { pub enum Response { // An operation was performed and the frontend may choose to exit its process. Close, + // Additional options for launching a certain item + Context { + id: Indice, + options: Vec, + }, // Notifies that a .desktop entry should be launched by the frontend. - DesktopEntry(PathBuf), + DesktopEntry { + path: PathBuf, + gpu_preference: GpuPreference, + }, // The frontend should clear its search results and display a new list. Update(Vec), // An item was selected that resulted in a need to autofill the launcher.