hyprshell_core_lib/default/
mod.rs

1use crate::ini_owned::IniFileOwned;
2use crate::{collect_desktop_files, collect_mime_files};
3use anyhow::bail;
4use std::collections::BTreeSet;
5use std::fs::{DirEntry, read_dir, read_to_string};
6use std::path::{Path, PathBuf};
7use std::sync::{OnceLock, RwLock, RwLockReadGuard};
8use std::{env, thread};
9use tracing::{debug, debug_span, trace, warn};
10
11fn get_desktop_files_from_cache() -> &'static RwLock<Vec<(DirEntry, IniFileOwned)>> {
12    static MAP_LOCK: OnceLock<RwLock<Vec<(DirEntry, IniFileOwned)>>> = OnceLock::new();
13    MAP_LOCK.get_or_init(|| RwLock::new(Vec::default()))
14}
15fn get_mime_files_from_cache() -> &'static RwLock<Vec<(DirEntry, IniFileOwned)>> {
16    static MAP_LOCK: OnceLock<RwLock<Vec<(DirEntry, IniFileOwned)>>> = OnceLock::new();
17    MAP_LOCK.get_or_init(|| RwLock::new(Vec::default()))
18}
19fn get_icons_from_cache() -> &'static RwLock<BTreeSet<Box<str>>> {
20    static MAP_LOCK: OnceLock<RwLock<BTreeSet<Box<str>>>> = OnceLock::new();
21    MAP_LOCK.get_or_init(|| RwLock::new(BTreeSet::default()))
22}
23
24pub fn get_all_desktop_files<'a>()
25-> anyhow::Result<RwLockReadGuard<'a, Vec<(DirEntry, IniFileOwned)>>> {
26    get_desktop_files_from_cache()
27        .read()
28        .map_err(|_| anyhow::anyhow!("Failed to lock desktop files mutex"))
29}
30
31pub fn get_all_mime_files<'a>() -> anyhow::Result<RwLockReadGuard<'a, Vec<(DirEntry, IniFileOwned)>>>
32{
33    get_mime_files_from_cache()
34        .read()
35        .map_err(|_| anyhow::anyhow!("Failed to lock desktop files mutex"))
36}
37
38pub fn get_all_icons<'a>() -> anyhow::Result<RwLockReadGuard<'a, BTreeSet<Box<str>>>> {
39    get_icons_from_cache()
40        .read()
41        .map_err(|_| anyhow::anyhow!("Failed to lock icon map"))
42}
43
44#[must_use]
45pub fn theme_has_icon_name(name: &str) -> bool {
46    get_icons_from_cache()
47        .read()
48        .map(|map| map.contains(&Box::from(name)))
49        .unwrap_or(false)
50}
51
52pub fn get_default_desktop_file<F, R>(mime: &str, r#fn: F) -> Option<R>
53where
54    F: FnOnce(&(DirEntry, IniFileOwned)) -> Option<R>,
55{
56    let mime_apps = get_mime_files_from_cache().read().ok()?;
57    let desktop_files = get_desktop_files_from_cache().read().ok()?;
58
59    for (_, ini) in mime_apps.iter() {
60        if let Some(ini) = ini
61            .get_section("Default Applications")
62            .and_then(|section| section.get_first(mime))
63            .or_else(|| {
64                ini.get_section("Added Associations")
65                    .and_then(|section| section.get_first(mime))
66            })
67            .and_then(|default| {
68                desktop_files
69                    .iter()
70                    .find(|(entry, _)| entry.file_name() == *default)
71            })
72        {
73            return r#fn(ini);
74        }
75    }
76    drop((mime_apps, desktop_files));
77    None
78}
79
80/// Reloads desktop files and mime files from the system.
81///
82/// Stores them in global data mutexes.
83pub fn reload_default_files() -> anyhow::Result<()> {
84    let _span = tracing::span!(tracing::Level::TRACE, "reload_files").entered();
85    let mut desktop_files_data = vec![];
86    let mut mime_files_data = vec![];
87    for file in collect_desktop_files() {
88        let Ok(content) = read_to_string(file.path()) else {
89            warn!("Failed to read desktop file: {}", file.path().display());
90            continue;
91        };
92        let ini = IniFileOwned::from_str(&content);
93        desktop_files_data.push((file, ini));
94    }
95    trace!("Collected all desktop files");
96
97    for file in collect_mime_files() {
98        let Ok(content) = read_to_string(file.path()) else {
99            warn!("Failed to read desktop file: {}", file.path().display());
100            continue;
101        };
102        let ini = IniFileOwned::from_str(&content);
103        mime_files_data.push((file, ini));
104    }
105    trace!("Collected all mime files");
106
107    let mut desktop_files = get_desktop_files_from_cache()
108        .write()
109        .map_err(|_| anyhow::anyhow!("Failed to lock desktop files global data mutex"))?;
110    *desktop_files = desktop_files_data;
111    drop(desktop_files);
112    let mut mime_files = get_mime_files_from_cache()
113        .write()
114        .map_err(|_| anyhow::anyhow!("Failed to lock mime files global data mutex"))?;
115    *mime_files = mime_files_data;
116    drop(mime_files);
117    Ok(())
118}
119
120pub fn reload_available_icons(
121    icon_names: Vec<String>,
122    search_path: Vec<PathBuf>,
123    in_background: bool,
124) -> anyhow::Result<()> {
125    let span = debug_span!("reload_icons");
126    let _span = span.enter();
127
128    let Ok(mut map) = get_icons_from_cache().write() else {
129        bail!("Failed to lock global data mutex");
130    };
131    debug!("found {} icons from theme", icon_names.len());
132    map.clear();
133    for icon in icon_names {
134        map.insert(icon.into_boxed_str());
135    }
136    drop(map);
137
138    if env::var_os("HYPRSHELL_NO_ALL_ICONS").is_none() {
139        for path in search_path {
140            let span_2 = span.clone();
141            if path.exists() {
142                if in_background {
143                    thread::spawn(move || {
144                        let _span = span_2.entered();
145                        let paths = collect_unique_filenames_recursive(&path);
146                        debug!(
147                            "found {} icons from filesystem in {path:?} paths (in background)",
148                            paths.len()
149                        );
150                        let Ok(mut map) = get_icons_from_cache().write() else {
151                            warn!("Failed to lock global data mutex");
152                            return;
153                        };
154                        map.extend(paths);
155                        drop(map);
156                    });
157                } else {
158                    let paths = collect_unique_filenames_recursive(&path);
159                    debug!(
160                        "found {} icons from filesystem in {path:?} paths",
161                        paths.len()
162                    );
163                    let Ok(mut map) = get_icons_from_cache().write() else {
164                        bail!("Failed to lock global data mutex");
165                    };
166                    map.extend(paths);
167                    drop(map);
168                }
169            }
170        }
171    }
172    trace!("icon map filled");
173    Ok(())
174}
175
176fn collect_unique_filenames_recursive(dir: &Path) -> BTreeSet<Box<str>> {
177    let mut names = BTreeSet::new();
178    let mut dirs_to_visit = vec![dir.to_path_buf()];
179
180    while let Some(current_dir) = dirs_to_visit.pop() {
181        if current_dir.is_dir() {
182            if let Ok(entries) = read_dir(&current_dir) {
183                for entry in entries.flatten() {
184                    let path = entry.path();
185                    if path.is_dir() {
186                        dirs_to_visit.push(path);
187                    } else if let Some(name_osstr) = path.file_stem() {
188                        // Avoid allocation unless needed
189                        let name = name_osstr.to_string_lossy();
190                        if !name.is_empty() && !names.contains(&*name) {
191                            names.insert(name.into_owned().into_boxed_str());
192                        }
193                    }
194                }
195            }
196        }
197    }
198    names
199}