1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
// SPDX-License-Identifier: GPL-3.0-only
// Copyright © 2021 System76

mod codec;
pub mod config;

pub use self::codec::*;

use const_format::concatcp;
use serde::{Deserialize, Serialize};
use std::{
    borrow::Cow,
    path::{Path, PathBuf},
};

pub const LOCAL: &str = "~/.local/share/pop-launcher";
pub const LOCAL_PLUGINS: &str = concatcp!(LOCAL, "/plugins");

pub const SYSTEM: &str = "/etc/pop-launcher";
pub const SYSTEM_PLUGINS: &str = concatcp!(SYSTEM, "/plugins");

pub const DISTRIBUTION: &str = "/usr/lib/pop-launcher";
pub const DISTRIBUTION_PLUGINS: &str = concatcp!(DISTRIBUTION, "/plugins");

pub const PLUGIN_PATHS: &[&str] = &[LOCAL_PLUGINS, SYSTEM_PLUGINS, DISTRIBUTION_PLUGINS];

pub fn plugin_paths() -> impl Iterator<Item = Cow<'static, Path>> {
    PLUGIN_PATHS.iter().map(|path| {
        #[allow(deprecated)]
        if let Some(path) = path.strip_prefix("~/") {
            let path = std::env::home_dir()
                .expect("user does not have home dir")
                .join(path);
            Cow::Owned(path)
        } else {
            Cow::Borrowed(Path::new(path))
        }
    })
}

/// u32 value defining the generation of an indice.
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.
    Name(Cow<'static, str>),
    // Icon is a mime type.
    Mime(Cow<'static, str>),
}

/// Sent from a plugin to the launcher service.
#[derive(Debug, Deserialize, Serialize)]
pub enum PluginResponse {
    /// Append a new search item to the launcher.
    Append(PluginSearchResult),
    /// Clear all results in the launcher list.
    Clear,
    /// Close the launcher.
    Close,
    // Additional options for launching a certain item.
    Context {
        id: Indice,
        options: Vec<ContextOption>,
    },
    /// Instruct the launcher service to deactivate this plugin.
    Deactivate,
    // 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.
    Finished,
}

/// Search information from a plugin to be sorted and filtered by the launcher service.
#[derive(Debug, Default, Deserialize, Serialize)]
pub struct PluginSearchResult {
    /// Numeric identifier tracked by the plugin.
    pub id: Indice,
    /// The name / title.
    pub name: String,
    /// The description / subtitle.
    pub description: String,
    /// Extra words to match when sorting and filtering.
    pub keywords: Option<Vec<String>>,
    /// Icon to display in the frontend.
    pub icon: Option<IconSource>,
    /// Command that is executed by this result, used for sorting and filtering.
    pub exec: Option<String>,
    /// Designates that this search item refers to a window.
    pub window: Option<(Generation, Indice)>,
}

// Sent to the input pipe of the launcher service, and disseminated to its plugins.
#[derive(Debug, Deserialize, Serialize)]
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.
    Interrupt,
    /// Request to close the selected item.
    Quit(Indice),
    /// Perform a search in our database.
    Search(String),
}

/// Sent from the launcher service to a frontend.
#[derive(Debug, Deserialize, Serialize)]
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<ContextOption>,
    },
    // 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<SearchResult>),
    // An item was selected that resulted in a need to autofill the launcher.
    Fill(String),
}

/// Serialized response to launcher frontend about a search result.
#[derive(Debug, Serialize, Deserialize)]
pub struct SearchResult {
    /// Numeric identifier tracked by the plugin.
    pub id: Indice,
    /// The name / title.
    pub name: String,
    /// The description / subtitle.
    pub description: String,

    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        with = "::serde_with::rust::unwrap_or_skip"
    )]
    /// Icon to display in the frontend for this item
    pub icon: Option<IconSource>,

    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        with = "::serde_with::rust::unwrap_or_skip"
    )]
    /// Icon to display in the frontend for this plugin
    pub category_icon: Option<IconSource>,

    #[serde(
        default,
        skip_serializing_if = "Option::is_none",
        with = "::serde_with::rust::unwrap_or_skip"
    )]
    /// Designates that this search item refers to a window.
    pub window: Option<(Generation, Indice)>,
}