onagre_launcher/
lib.rs

1// Copyright 2021 System76 <info@system76.com>
2// SPDX-License-Identifier: MPL-2.0
3
4mod codec;
5pub mod config;
6
7pub use self::codec::*;
8
9use const_format::concatcp;
10use serde::{Deserialize, Serialize};
11use std::{
12    borrow::Cow,
13    path::{Path, PathBuf},
14};
15
16pub const LOCAL: &str = "~/.local/share/pop-launcher";
17pub const LOCAL_PLUGINS: &str = concatcp!(LOCAL, "/plugins");
18
19pub const SYSTEM: &str = "/etc/pop-launcher";
20pub const SYSTEM_PLUGINS: &str = concatcp!(SYSTEM, "/plugins");
21
22pub const DISTRIBUTION: &str = "/usr/lib/pop-launcher";
23pub const DISTRIBUTION_PLUGINS: &str = concatcp!(DISTRIBUTION, "/plugins");
24
25pub const PLUGIN_PATHS: &[&str] = &[LOCAL_PLUGINS, SYSTEM_PLUGINS, DISTRIBUTION_PLUGINS];
26
27pub fn plugin_paths() -> impl Iterator<Item = Cow<'static, Path>> {
28    PLUGIN_PATHS.iter().map(|path| {
29        #[allow(deprecated)]
30        if let Some(path) = path.strip_prefix("~/") {
31            let path = dirs::home_dir()
32                .expect("user does not have home dir")
33                .join(path);
34            Cow::Owned(path)
35        } else {
36            Cow::Borrowed(Path::new(path))
37        }
38    })
39}
40
41/// u32 value defining the generation of an indice.
42pub type Generation = u32;
43
44/// u32 value defining the indice of a slot.
45pub type Indice = u32;
46
47#[derive(Debug, Deserialize, Serialize, Clone)]
48pub struct ContextOption {
49    pub id: Indice,
50    pub name: String,
51}
52
53#[derive(Debug, Deserialize, Serialize, Clone, Copy)]
54pub enum GpuPreference {
55    Default,
56    NonDefault,
57}
58
59#[derive(Debug, Clone, Deserialize, Serialize)]
60pub enum IconSource {
61    // Locate by name or path.
62    Name(Cow<'static, str>),
63    // Icon is a mime type.
64    Mime(Cow<'static, str>),
65}
66
67/// Sent from a plugin to the launcher service.
68#[derive(Debug, Deserialize, Serialize, Clone)]
69pub enum PluginResponse {
70    /// Append a new search item to the launcher.
71    Append(PluginSearchResult),
72    /// Clear all results in the launcher list.
73    Clear,
74    /// Close the launcher.
75    Close,
76    // Additional options for launching a certain item.
77    Context {
78        id: Indice,
79        options: Vec<ContextOption>,
80    },
81    /// Instruct the launcher service to deactivate this plugin.
82    Deactivate,
83    // Notifies that a .desktop entry should be launched by the frontend.
84    DesktopEntry {
85        path: PathBuf,
86        gpu_preference: GpuPreference,
87    },
88    /// Update the text in the launcher.
89    Fill(String),
90    /// Indicoates that a plugin is finished with its queries.
91    Finished,
92}
93
94/// Search information from a plugin to be sorted and filtered by the launcher service.
95#[derive(Debug, Default, Deserialize, Serialize, Clone)]
96pub struct PluginSearchResult {
97    /// Numeric identifier tracked by the plugin.
98    pub id: Indice,
99    /// The name / title.
100    pub name: String,
101    /// The description / subtitle.
102    pub description: String,
103    /// Extra words to match when sorting and filtering.
104    pub keywords: Option<Vec<String>>,
105    /// Icon to display in the frontend.
106    pub icon: Option<IconSource>,
107    /// Command that is executed by this result, used for sorting and filtering.
108    pub exec: Option<String>,
109    /// Designates that this search item refers to a window.
110    pub window: Option<(Generation, Indice)>,
111}
112
113impl PluginSearchResult {
114    #[must_use]
115    #[inline]
116    pub fn cache_identifier(&self) -> Option<String> {
117        // the exec field may clash in multiple search results as the arguments
118        // are cut from the string
119        // self.exec.to_owned().unwrap_or_else(|| self.name.to_owned())
120        self.exec.as_ref().map(|_| self.name.clone())
121    }
122}
123
124// Sent to the input pipe of the launcher service, and disseminated to its plugins.
125#[derive(Debug, Deserialize, Serialize, Clone)]
126pub enum Request {
127    /// Activate on the selected item.
128    Activate(Indice),
129    /// Activate a context item on an item.
130    ActivateContext { id: Indice, context: Indice },
131    /// Perform a tab completion from the selected item.
132    Complete(Indice),
133    /// Request for any context options this result may have.
134    Context(Indice),
135    /// Request to end the service.
136    Exit,
137    /// Requests to cancel any active searches.
138    Interrupt,
139    /// Request to close the selected item.
140    Quit(Indice),
141    /// Perform a search in our database.
142    Search(String),
143}
144
145/// Sent from the launcher service to a frontend.
146#[derive(Debug, Deserialize, Serialize, Clone)]
147pub enum Response {
148    // An operation was performed and the frontend may choose to exit its process.
149    Close,
150    // Additional options for launching a certain item
151    Context {
152        id: Indice,
153        options: Vec<ContextOption>,
154    },
155    // Notifies that a .desktop entry should be launched by the frontend.
156    DesktopEntry {
157        path: PathBuf,
158        gpu_preference: GpuPreference,
159    },
160    // The frontend should clear its search results and display a new list.
161    Update(Vec<SearchResult>),
162    // An item was selected that resulted in a need to autofill the launcher.
163    Fill(String),
164}
165
166/// Serialized response to launcher frontend about a search result.
167#[derive(Debug, Serialize, Deserialize, Clone)]
168pub struct SearchResult {
169    /// Numeric identifier tracked by the plugin.
170    pub id: Indice,
171    /// The name / title.
172    pub name: String,
173    /// The description / subtitle.
174    pub description: String,
175
176    #[serde(
177        default,
178        skip_serializing_if = "Option::is_none",
179        with = "::serde_with::rust::unwrap_or_skip"
180    )]
181    /// Icon to display in the frontend for this item
182    pub icon: Option<IconSource>,
183
184    #[serde(
185        default,
186        skip_serializing_if = "Option::is_none",
187        with = "::serde_with::rust::unwrap_or_skip"
188    )]
189    /// Icon to display in the frontend for this plugin
190    pub category_icon: Option<IconSource>,
191
192    #[serde(
193        default,
194        skip_serializing_if = "Option::is_none",
195        with = "::serde_with::rust::unwrap_or_skip"
196    )]
197    /// Designates that this search item refers to a window.
198    pub window: Option<(Generation, Indice)>,
199}