spell_framework/vault.rs
1//! `vault` contains the necessary utilities/APTs for various common tasks required
2//! when creating a custom shell. This includes apps, pipewire, PAM, Mpris etc
3//! among other things.
4//!
5//! <div class="warning">
6//! For now, this module doesn't contain much utilities. As, more common methods
7//! are added, docs will expand to include examples and panic cases.
8//! </div>
9//!
10//! The whole module is divided into structs representing these utilities and
11//! their corresponding traits (if it applies). The [`Services`] is used to bind
12//! and initialise the traits. Why do most utilities have a trait counterpart?
13//!
14//! Traits are made so as to represent actions when occured from the other server/
15//! application side. For example, An [`AppSelector`] is used and initialised for
16//! usage by your shell but [`new_app_added`](AppHandler::new_app_added) was called
17//! when the coming of a new desktop entry is to be notified. Some utilities like
18//! `AppSelector` are more user intensive and less trait intensive(i.e. there are not
19//! many cases when server will ping, hence not much methods in traits). On the
20//! other hand implementations like that of notifications (via [`NotificationHandler`])
21//! are majorly trait intensive. Then, utilities like audio handling (via [`PipeWireHandler`] and
22//! [`AudioManager`]) have equal chances of being accessed from anywhere.
23//!
24//! As a general tip, the best way to implement traits is to stores weak reference to
25//! your widget windows on slint side in structs and then implement these traits on it.
26use crate::vault::application::desktop_entry_extracter;
27use rust_fuzzy_search::fuzzy_search_best_n;
28use std::{
29 env,
30 ffi::OsStr,
31 path::{Component, Path, PathBuf},
32 sync::Arc,
33 thread,
34};
35mod application;
36
37pub trait NotificationHandler: Send + Sync {}
38pub trait AppHandler: Send + Sync {
39 fn new_app_added(&mut self, app_data: AppData);
40}
41pub trait MprisHandler: Send + Sync {}
42pub trait PipeWireHandler: Send + Sync {}
43
44#[derive(Default, Clone)]
45pub struct Services {
46 notification: Option<Arc<dyn NotificationHandler>>,
47 app: Option<Arc<dyn AppHandler>>,
48 mpris: Option<Arc<dyn MprisHandler>>,
49 pipewire: Option<Arc<dyn PipeWireHandler>>,
50}
51
52impl Services {
53 pub fn add_notification_handle(
54 &mut self,
55 noti_handler: Arc<dyn NotificationHandler>,
56 ) -> &mut Self {
57 self.notification = Some(noti_handler);
58 self
59 }
60
61 pub fn add_app_handle(&mut self, app_handler: Arc<dyn AppHandler>) -> &mut Self {
62 self.app = Some(app_handler);
63 self
64 }
65
66 pub fn add_mpris_handle(&mut self, mpris_handler: Arc<dyn MprisHandler>) -> &mut Self {
67 self.mpris = Some(mpris_handler);
68 self
69 }
70
71 pub fn add_pipewire_handle(&mut self, pipewire_handler: Arc<dyn PipeWireHandler>) -> &mut Self {
72 self.pipewire = Some(pipewire_handler);
73 self
74 }
75
76 pub fn generate_serices(&mut self) {
77 if let Some(app) = &self.app {
78 let app_clone = app.clone();
79 thread::spawn(move || {
80 check_for_new_apps(app_clone);
81 });
82 }
83 }
84}
85
86fn check_for_new_apps(_app: Arc<dyn AppHandler>) {
87 todo!()
88}
89
90pub struct AudioManager;
91// TODO needs to fix the below mentioned bugs.
92/// AppSelector stores the data for each application with possible actions. Known bugs
93/// include failing to open flatpak apps in certain cases and failing to find icons
94/// of apps in certain cases both of which will be fixed in coming releases.
95#[derive(Debug, Clone)]
96pub struct AppSelector {
97 /// Storing [`AppData`] in a vector.
98 pub app_list: Vec<AppData>,
99}
100
101impl Default for AppSelector {
102 fn default() -> Self {
103 let data_dirs: String =
104 env::var("XDG_DATA_DIRS").expect("XDG_DATA_DIRS couldn't be fetched");
105 let mut app_line_data: Vec<AppData> = Vec::new();
106 let mut data_dirs_vec = data_dirs.split(':').collect::<Vec<_>>();
107 // Adding some other directories.
108 data_dirs_vec.push("/home/ramayen/.local/share/");
109 for dir in data_dirs_vec.iter() {
110 // To check if the directory mentioned in var actually exists.
111 if Path::new(dir).is_dir() {
112 for inner_dir in Path::new(dir)
113 .read_dir()
114 .expect("Couldn't read the directory")
115 .flatten()
116 {
117 // if let Ok(inner_dir_present) = inner_dir {
118 if *inner_dir
119 .path()
120 .components()
121 .collect::<Vec<_>>()
122 .last()
123 .unwrap()
124 == Component::Normal(OsStr::new("applications"))
125 {
126 let app_dir: PathBuf = inner_dir.path();
127 for entry_or_dir in
128 app_dir.read_dir().expect("Couldn't read app dir").flatten()
129 {
130 if entry_or_dir.path().is_dir() {
131 println!("Encountered a directory");
132 } else if entry_or_dir.path().extension() == Some(OsStr::new("desktop"))
133 {
134 let new_data: Vec<Option<AppData>> =
135 desktop_entry_extracter(entry_or_dir.path());
136 let filtered_data: Vec<AppData> = new_data
137 .iter()
138 .filter_map(|val| val.to_owned())
139 .collect::<Vec<AppData>>();
140 app_line_data.extend(filtered_data);
141 // if let Some(data) = new_data {
142 // // if !app_line_data.iter().any(|pushed_data| {
143 // // pushed_data.desktop_file_id == data.desktop_file_id
144 // // }) {
145 // app_line_data.push(data);
146 // //}
147 // }
148 } else if entry_or_dir.path().is_symlink() {
149 println!("GOt the symlink");
150 } else {
151 // println!("Found something else");
152 }
153 }
154 }
155 }
156 }
157 }
158
159 AppSelector {
160 app_list: app_line_data,
161 }
162 }
163}
164
165impl AppSelector {
166 /// Returns an iterator over primary enteries of applications.
167 pub fn get_primary(&self) -> impl Iterator<Item = &AppData> {
168 self.app_list.iter().filter(|val| val.is_primary)
169 }
170
171 /// Returns an iterator of all enteries of all applications.
172 pub fn get_all(&self) -> impl Iterator<Item = &AppData> {
173 self.app_list.iter()
174 }
175
176 /// Returns an iterator over the most relevent result of applications' primary enteries
177 /// for a given string query. `size` determines the number of enteries to
178 /// yield.
179 pub fn query_primary(&self, query_val: &str, size: usize) -> Vec<&AppData> {
180 let query_val = query_val.to_lowercase();
181 let query_list = self
182 .app_list
183 .iter()
184 .filter(|val| val.is_primary)
185 .map(|val| val.name.to_lowercase())
186 .collect::<Vec<String>>();
187 let query_list: Vec<&str> = query_list.iter().map(|v| v.as_str()).collect();
188 let best_match_names: Vec<&str> =
189 fuzzy_search_best_n(query_val.as_str(), &query_list, size)
190 .iter()
191 .map(|val| val.0)
192 .collect();
193 best_match_names
194 .iter()
195 .map(|app_name| {
196 self.app_list
197 .iter()
198 .find(|val| val.name.to_lowercase().as_str() == *app_name)
199 .unwrap()
200 })
201 .collect::<Vec<&AppData>>()
202 }
203
204 /// Returns an iterator over the most relevent result of all applications' enteries
205 /// for a given string query. `size` determines the number of enteries to
206 /// yield.
207 pub fn query_all(&self, query_val: &str, size: usize) -> Vec<&AppData> {
208 let query_val = query_val.to_lowercase();
209 let query_list = self
210 .app_list
211 .iter()
212 .map(|val| val.name.to_lowercase())
213 .collect::<Vec<String>>();
214 let query_list: Vec<&str> = query_list.iter().map(|v| v.as_ref()).collect();
215 let best_match_names: Vec<&str> =
216 fuzzy_search_best_n(query_val.as_str(), &query_list, size)
217 .iter()
218 .map(|val| val.0)
219 .collect();
220
221 best_match_names
222 .iter()
223 .map(|app_name| {
224 self.app_list
225 .iter()
226 .find(|val| val.name.to_lowercase().as_str() == *app_name)
227 .unwrap()
228 })
229 .collect::<Vec<&AppData>>()
230 }
231}
232
233// TODO add representation for GenericName and comments for better searching
234/// Stores the relevent data for an application. Used internally by [`AppSelector`].
235#[derive(Debug, Clone)]
236pub struct AppData {
237 /// Unique ID of an application desktop file according to
238 /// [spec](https://specifications.freedesktop.org/desktop-entry-spec/latest/file-naming.html#desktop-file-id).
239 pub desktop_file_id: String,
240 /// Determines if the entry is primary or an action of an application.
241 pub is_primary: bool,
242 /// Image path of the application if could be fetched.
243 pub image_path: Option<String>,
244 /// Name of application
245 pub name: String,
246 /// Execute command which runs in an spaned thread when an application is asked to run.
247 pub exec_comm: Option<String>,
248}
249
250// TODO have to replace fuzzy search with a custom implementation to avoid dependency.
251// There needs to be performance improvements in AppSelector's default implementation
252// TODO add an example section in this module with pseudocode for trait implementations.