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::{Level, debug, span};
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) -> BTreeSet<Box<str>> {
66    let mut names = BTreeSet::new();
67    let mut dirs_to_visit = vec![dir.to_path_buf()];
68
69    while let Some(current_dir) = dirs_to_visit.pop() {
70        if current_dir.is_dir() {
71            if let Ok(entries) = read_dir(&current_dir) {
72                for entry in entries.flatten() {
73                    let path = entry.path();
74                    if path.is_dir() {
75                        dirs_to_visit.push(path);
76                    } else if let Some(name_osstr) = path.file_stem() {
77                        // Avoid allocation unless needed
78                        let name = name_osstr.to_string_lossy();
79                        if !name.is_empty() && !names.contains(&*name) {
80                            names.insert(name.into_owned().into_boxed_str());
81                        }
82                    }
83                }
84            }
85        }
86    }
87    names
88}