hyprshell_core_lib/
theme_icon_cache.rs

1// https://github.com/H3rmt/hyprshell/discussions/137#discussioncomment-12078216
2use std::collections::BTreeSet;
3use std::env;
4use std::fs::read_dir;
5use std::path::{Path, PathBuf};
6use std::sync::{Mutex, MutexGuard, OnceLock};
7use std::time::Instant;
8use tracing::{debug, span, Level};
9
10fn get_icon_map() -> &'static Mutex<BTreeSet<Box<str>>> {
11    static MAP_LOCK: OnceLock<Mutex<BTreeSet<Box<str>>>> = OnceLock::new();
12    MAP_LOCK.get_or_init(|| Mutex::new(BTreeSet::new()))
13}
14
15pub fn init_icon_map(icon_names: Vec<String>, search_path: Vec<PathBuf>, threads: bool) {
16    let _span = span!(Level::TRACE, "init_icon_map").entered();
17    let mut map = get_icon_map().lock().expect("Failed to lock icon map");
18    let instant = Instant::now();
19
20    debug!("found {} icons from theme", icon_names.len());
21    for icon in icon_names {
22        map.insert(icon.into_boxed_str());
23    }
24    drop(map);
25
26    // gtk4 only reports 500 icons for candy-theme, scan through the filesystem
27    if env::var_os("HYPRSHELL_NO_ALL_ICONS").is_none() {
28        for path in search_path {
29            if path.exists() {
30                if threads {
31                    std::thread::spawn(move || {
32                        let paths = collect_unique_files_recursive(&path);
33                        debug!(
34                            "found {} icons from filesystem in {path:?} paths (using threads)",
35                            paths.len()
36                        );
37                        let mut map2 = get_icon_map().lock().expect("Failed to lock icon map");
38                        map2.extend(paths);
39                        drop(map2)
40                    });
41                } else {
42                    let paths = collect_unique_files_recursive(&path);
43                    debug!(
44                        "found {} icons from filesystem in {path:?} paths",
45                        paths.len()
46                    );
47                    let mut map2 = get_icon_map().lock().expect("Failed to lock icon map");
48                    map2.extend(paths);
49                    drop(map2)
50                }
51            }
52        }
53    }
54    debug!("icon map filled in {:?}", instant.elapsed());
55}
56
57pub fn get_all_icons<'a>() -> MutexGuard<'a, BTreeSet<Box<str>>> {
58    get_icon_map().lock().expect("Failed to lock icon map")
59}
60
61pub fn theme_has_icon_name(name: &str) -> bool {
62    let map = get_icon_map().lock().expect("Failed to lock icon map");
63    map.contains(&Box::from(name))
64}
65fn collect_unique_files_recursive(dir: &Path) -> Vec<Box<str>> {
66    let mut files = Vec::with_capacity(5000);
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                            let boxed_name = name.into_owned().into_boxed_str();
82                            files.push(boxed_name.clone());
83                            names.insert(boxed_name);
84                        }
85                    }
86                }
87            }
88        }
89    }
90
91    files
92}