Skip to main content

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.