hyprshell_core_lib/default/
mod.rs1use crate::ini_owned::IniFileOwned;
2use crate::util::{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 .is_ok_and(|map| map.contains(&Box::from(name)))
49}
50
51pub fn get_default_desktop_file<F, R>(mime: &str, r#fn: F) -> Option<R>
52where
53 F: FnOnce(&(DirEntry, IniFileOwned)) -> Option<R>,
54{
55 let mime_apps = get_mime_files_from_cache().read().ok()?;
56 let desktop_files = get_desktop_files_from_cache().read().ok()?;
57
58 for (_, ini) in mime_apps.iter() {
59 if let Some(ini) = ini
60 .get_section("Default Applications")
61 .and_then(|section| section.get_first(mime))
62 .or_else(|| {
63 ini.get_section("Added Associations")
64 .and_then(|section| section.get_first(mime))
65 })
66 .and_then(|default| {
67 desktop_files
68 .iter()
69 .find(|(entry, _)| entry.file_name() == *default)
70 })
71 {
72 return r#fn(ini);
73 }
74 }
75 drop((mime_apps, desktop_files));
76 None
77}
78
79pub fn reload_default_files() -> anyhow::Result<()> {
83 let _span = tracing::span!(tracing::Level::TRACE, "reload_files").entered();
84 let mut desktop_files_data = vec![];
85 let mut mime_files_data = vec![];
86 for file in collect_desktop_files() {
87 let Ok(content) = read_to_string(file.path()) else {
88 warn!("Failed to read desktop file: {}", file.path().display());
89 continue;
90 };
91 let ini = IniFileOwned::from_str(&content);
92 desktop_files_data.push((file, ini));
93 }
94 trace!("Collected all desktop files");
95
96 for file in collect_mime_files() {
97 let Ok(content) = read_to_string(file.path()) else {
98 warn!("Failed to read desktop file: {}", file.path().display());
99 continue;
100 };
101 let ini = IniFileOwned::from_str(&content);
102 mime_files_data.push((file, ini));
103 }
104 trace!("Collected all mime files");
105
106 let mut desktop_files = get_desktop_files_from_cache()
107 .write()
108 .map_err(|_| anyhow::anyhow!("Failed to lock desktop files global data mutex"))?;
109 *desktop_files = desktop_files_data;
110 drop(desktop_files);
111 let mut mime_files = get_mime_files_from_cache()
112 .write()
113 .map_err(|_| anyhow::anyhow!("Failed to lock mime files global data mutex"))?;
114 *mime_files = mime_files_data;
115 drop(mime_files);
116 Ok(())
117}
118
119pub fn reload_available_icons(
120 icon_names: Vec<String>,
121 search_path: Vec<PathBuf>,
122 in_background: bool,
123) -> anyhow::Result<()> {
124 let span = debug_span!("reload_icons");
125 let _span = span.enter();
126
127 let Ok(mut map) = get_icons_from_cache().write() else {
128 bail!("Failed to lock global data mutex");
129 };
130 debug!("found {} icons from theme", icon_names.len());
131 map.clear();
132 for icon in icon_names {
133 map.insert(icon.into_boxed_str());
134 }
135 drop(map);
136
137 if env::var_os("HYPRSHELL_NO_ALL_ICONS").is_none() {
138 for path in search_path {
139 let span_2 = span.clone();
140 if path.exists() {
141 if in_background {
142 thread::spawn(move || {
143 let _span = span_2.entered();
144 let paths = collect_unique_filenames_recursive(&path);
145 debug!(
146 "found {} icons from filesystem in {path:?} paths (in background)",
147 paths.len()
148 );
149 let Ok(mut map) = get_icons_from_cache().write() else {
150 warn!("Failed to lock global data mutex");
151 return;
152 };
153 map.extend(paths);
154 drop(map);
155 });
156 } else {
157 let paths = collect_unique_filenames_recursive(&path);
158 debug!(
159 "found {} icons from filesystem in {path:?} paths",
160 paths.len()
161 );
162 let Ok(mut map) = get_icons_from_cache().write() else {
163 bail!("Failed to lock global data mutex");
164 };
165 map.extend(paths);
166 drop(map);
167 }
168 }
169 }
170 }
171 trace!("icon map filled");
172 Ok(())
173}
174
175fn collect_unique_filenames_recursive(dir: &Path) -> BTreeSet<Box<str>> {
176 let mut names = BTreeSet::new();
177 let mut dirs_to_visit = vec![dir.to_path_buf()];
178
179 while let Some(current_dir) = dirs_to_visit.pop() {
180 if current_dir.is_dir()
181 && let Ok(entries) = read_dir(¤t_dir)
182 {
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 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 names
198}