pop_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    SpecificIdx(u32),
58}
59
60#[derive(Debug, Clone, Deserialize, Serialize)]
61pub enum IconSource {
62    // Locate by name or path.
63    Name(Cow<'static, str>),
64    // Icon is a mime type.
65    Mime(Cow<'static, str>),
66}
67
68/// Sent from a plugin to the launcher service.
69#[derive(Debug, Deserialize, Serialize, Clone)]
70pub enum PluginResponse {
71    /// Append a new search item to the launcher.
72    Append(PluginSearchResult),
73    /// Clear all results in the launcher list.
74    Clear,
75    /// Close the launcher.
76    Close,
77    // Additional options for launching a certain item.
78    Context {
79        id: Indice,
80        options: Vec<ContextOption>,
81    },
82    /// Instruct the launcher service to deactivate this plugin.
83    Deactivate,
84    // Notifies that a .desktop entry should be launched by the frontend.
85    DesktopEntry {
86        path: PathBuf,
87        gpu_preference: GpuPreference,
88        #[serde(skip_serializing_if = "Option::is_none", default)]
89        action_name: Option<String>,
90    },
91    /// Update the text in the launcher.
92    Fill(String),
93    /// Indicates that a plugin is finished with its queries.
94    Finished,
95}
96
97/// Search information from a plugin to be sorted and filtered by the launcher service.
98#[derive(Debug, Default, Deserialize, Serialize, Clone)]
99pub struct PluginSearchResult {
100    /// Numeric identifier tracked by the plugin.
101    pub id: Indice,
102    /// The name / title.
103    pub name: String,
104    /// The description / subtitle.
105    pub description: String,
106    /// Extra words to match when sorting and filtering.
107    pub keywords: Option<Vec<String>>,
108    /// Icon to display in the frontend.
109    pub icon: Option<IconSource>,
110    /// Command that is executed by this result, used for sorting and filtering.
111    pub exec: Option<String>,
112    /// Designates that this search item refers to a window.
113    pub window: Option<(Generation, Indice)>,
114}
115
116impl PluginSearchResult {
117    #[must_use]
118    #[inline]
119    pub fn cache_identifier(&self) -> Option<String> {
120        // the exec field may clash in multiple search results as the arguments
121        // are cut from the string
122        // self.exec.to_owned().unwrap_or_else(|| self.name.to_owned())
123        self.exec.as_ref().map(|_| self.name.clone())
124    }
125}
126
127// Sent to the input pipe of the launcher service, and disseminated to its plugins.
128#[derive(Debug, Deserialize, Serialize, Clone)]
129pub enum Request {
130    /// Activate on the selected item.
131    Activate(Indice),
132    /// Activate a context item on an item.
133    ActivateContext { id: Indice, context: Indice },
134    /// Perform a tab completion from the selected item.
135    Complete(Indice),
136    /// Request for any context options this result may have.
137    Context(Indice),
138    /// Request to end the service.
139    Exit,
140    /// The frontend was closed and the service should release resources
141    /// and prepare for the next query
142    Close,
143    /// Requests to cancel any active searches.
144    Interrupt,
145    /// Request to close the selected item.
146    Quit(Indice),
147    /// Perform a search in our database.
148    Search(String),
149}
150
151/// Sent from the launcher service to a frontend.
152#[derive(Debug, Deserialize, Serialize, Clone)]
153pub enum Response {
154    // An operation was performed and the frontend may choose to exit its process.
155    Close,
156    // Additional options for launching a certain item
157    Context {
158        id: Indice,
159        options: Vec<ContextOption>,
160    },
161    // Notifies that a .desktop entry should be launched by the frontend.
162    DesktopEntry {
163        path: PathBuf,
164        gpu_preference: GpuPreference,
165        action_name: Option<String>,
166    },
167    // The frontend should clear its search results and display a new list.
168    Update(Vec<SearchResult>),
169    // An item was selected that resulted in a need to autofill the launcher.
170    Fill(String),
171}
172
173/// Serialized response to launcher frontend about a search result.
174#[derive(Debug, Serialize, Deserialize, Clone)]
175pub struct SearchResult {
176    /// Numeric identifier tracked by the plugin.
177    pub id: Indice,
178    /// The name / title.
179    pub name: String,
180    /// The description / subtitle.
181    pub description: String,
182
183    #[serde(
184        default,
185        skip_serializing_if = "Option::is_none",
186        with = "::serde_with::rust::unwrap_or_skip"
187    )]
188    /// Icon to display in the frontend for this item
189    pub icon: Option<IconSource>,
190
191    #[serde(
192        default,
193        skip_serializing_if = "Option::is_none",
194        with = "::serde_with::rust::unwrap_or_skip"
195    )]
196    /// Icon to display in the frontend for this plugin
197    pub category_icon: Option<IconSource>,
198
199    #[serde(
200        default,
201        skip_serializing_if = "Option::is_none",
202        with = "::serde_with::rust::unwrap_or_skip"
203    )]
204    /// Designates that this search item refers to a window.
205    pub window: Option<(Generation, Indice)>,
206}