hyprshell_core_lib/default/
mod.rs1use 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
78pub 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(¤t_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 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}