azalea_shell/service/
search.rs

1use std::{collections::HashMap, path::PathBuf};
2
3use gtk::{
4    gio::{self, prelude::*},
5    glib,
6};
7use tokio::sync::broadcast;
8
9#[derive(Default, azalea_derive::StaticServiceManager)]
10
11pub struct Service {
12    applications: HashMap<AppId, AppInfo>,
13}
14
15pub type AppId = String;
16
17#[derive(Clone, Debug)]
18pub struct AppInfo {
19    pub id: AppId,
20    pub name: String,
21    pub icon: Option<glib::Variant>,
22    pub display_name: String,
23    pub executable: PathBuf,
24    pub command: PathBuf,
25}
26
27impl From<&gio::AppInfo> for AppInfo {
28    fn from(value: &gio::AppInfo) -> Self {
29        Self {
30            id: value
31                .id()
32                .map(|id| id.to_string())
33                .unwrap_or(value.name().to_string()),
34            name: value.name().to_string(),
35            icon: value.icon().and_then(|icon| icon.serialize()),
36            display_name: value.display_name().to_string(),
37            executable: value.executable(),
38            command: value.commandline().unwrap_or(value.executable()),
39        }
40    }
41}
42
43#[derive(Default, Clone)]
44pub struct Init {}
45
46#[derive(Clone, Debug)]
47pub enum Input {
48    LaunchApplication(AppId),
49
50    /// Generic search
51    Search(String),
52
53    /// Search only for applications
54    SearchApplication(String),
55
56    /// Get all applications in case you want to search "locally"
57    GetAllApplications(flume::Sender<Vec<AppInfo>>),
58}
59
60#[derive(Clone, Debug)]
61pub enum Output {
62    Applications(Vec<AppInfo>),
63}
64
65impl azalea_service::Service for Service {
66    type Init = Init;
67    type Input = Input;
68    type Event = ();
69    type Output = Output;
70    const DISABLE_EVENTS: bool = true;
71
72    async fn new(
73        _init: Self::Init,
74        _: flume::Sender<Self::Input>,
75        _: broadcast::Sender<Self::Output>,
76    ) -> Self {
77        Self {
78            applications: gio::AppInfo::all()
79                .into_iter()
80                .map(|app| {
81                    let app = AppInfo::from(&app);
82                    (app.id.clone(), app)
83                })
84                .collect(),
85        }
86    }
87
88    async fn message(
89        &mut self,
90        input: Self::Input,
91        output_sender: &broadcast::Sender<Self::Output>,
92    ) {
93        match input {
94            Input::Search(term) | Input::SearchApplication(term) => {
95                drop(
96                    output_sender.send(Output::Applications(
97                        self.applications
98                            .values()
99                            .filter(|app| app.name.starts_with(&term))
100                            .map(|app| app.to_owned())
101                            .collect(),
102                    )),
103                );
104            }
105            Input::GetAllApplications(sender) => {
106                drop(
107                    sender.send(
108                        self.applications
109                            .values()
110                            .map(|app| app.to_owned())
111                            .collect(),
112                    ),
113                );
114            }
115            Input::LaunchApplication(app_id) => {
116                let Some(app) = self.applications.get(&app_id) else {
117                    azalea_log::warning!("Application not found: {app_id}");
118                    return;
119                };
120                match std::process::Command::new(&app.executable)
121                    .stdin(std::process::Stdio::null())
122                    .stdout(std::process::Stdio::null())
123                    .stderr(std::process::Stdio::null())
124                    .spawn()
125                {
126                    Ok(_) => azalea_log::debug!("Launched application: {:?}", app.executable),
127                    Err(e) => azalea_log::warning!(
128                        "Failed to launch application {:?}: {e}",
129                        app.executable
130                    ),
131                }
132            }
133        }
134    }
135}