azalea_shell/service/
search.rs1use 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 Search(String),
52
53 SearchApplication(String),
55
56 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}