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