hyprshell_core_lib/
theme_icon_cache.rs

1// https://github.com/H3rmt/hyprshell/discussions/137#discussioncomment-12078216
2
3use std::collections::BTreeSet;
4use std::env;
5use std::fs::read_dir;
6use std::path::{Path, PathBuf};
7use std::sync::{Mutex, MutexGuard, OnceLock};
8use std::time::Instant;
9use tracing::{Level, debug, span};
10
11fn get_icon_map() -> &'static Mutex<BTreeSet<Box<str>>> {
12    static MAP_LOCK: OnceLock<Mutex<BTreeSet<Box<str>>>> = OnceLock::new();
13    MAP_LOCK.get_or_init(|| Mutex::new(BTreeSet::new()))
14}
15
16pub fn init_icon_map(icon_names: Vec<String>, search_path: Vec<PathBuf>, in_background: bool) {
17    let _span = span!(Level::TRACE, "init_icon_map").entered();
18    let mut map = get_icon_map().lock().expect("Failed to lock icon map");
19    let instant = Instant::now();
20
21    debug!("found {} icons from theme", icon_names.len());
22    for icon in icon_names {
23        map.insert(icon.into_boxed_str());
24    }
25    drop(map);
26
27    // gtk4 only reports 500 icons for candy-theme, scan through the filesystem
28    if env::var_os("HYPRSHELL_NO_ALL_ICONS").is_none() {
29        for path in search_path {
30            if path.exists() {
31                if in_background {
32                    std::thread::spawn(move || {
33                        let paths = collect_unique_files_recursive(&path);
34                        debug!(
35                            "found {} icons from filesystem in {path:?} paths (in background)",
36                            paths.len()
37                        );
38                        let mut map2 = get_icon_map().lock().expect("Failed to lock icon map");
39                        map2.extend(paths);
40                        drop(map2)
41                    });
42                } else {
43                    let paths = collect_unique_files_recursive(&path);
44                    debug!(
45                        "found {} icons from filesystem in {path:?} paths",
46                        paths.len()
47                    );
48                    let mut map2 = get_icon_map().lock().expect("Failed to lock icon map");
49                    map2.extend(paths);
50                    drop(map2)
51                }
52            }
53        }
54    }
55    debug!("icon map filled in {:?}", instant.elapsed());
56}
57
58pub fn get_all_icons<'a>() -> MutexGuard<'a, BTreeSet<Box<str>>> {
59    get_icon_map().lock().expect("Failed to lock icon map")
60}
61
62pub fn theme_has_icon_name(name: &str) -> bool {
63    let map = get_icon_map().lock().expect("Failed to lock icon map");
64    map.contains(&Box::from(name))
65}
66fn collect_unique_files_recursive(dir: &Path) -> BTreeSet<Box<str>> {
67    let mut names = BTreeSet::new();
68    let mut dirs_to_visit = vec![dir.to_path_buf()];
69
70    while let Some(current_dir) = dirs_to_visit.pop() {
71        if current_dir.is_dir() {
72            if let Ok(entries) = read_dir(&current_dir) {
73                for entry in entries.flatten() {
74                    let path = entry.path();
75                    if path.is_dir() {
76                        dirs_to_visit.push(path);
77                    } else if let Some(name_osstr) = path.file_stem() {
78                        // Avoid allocation unless needed
79                        let name = name_osstr.to_string_lossy();
80                        if !name.is_empty() && !names.contains(&*name) {
81                            names.insert(name.into_owned().into_boxed_str());
82                        }
83                    }
84                }
85            }
86        }
87    }
88    names
89}