background app entry finder
This commit is contained in:
@@ -130,6 +130,9 @@ impl Frontend {
|
|||||||
|
|
||||||
let timestep = Timestep::new(60.0);
|
let timestep = Timestep::new(60.0);
|
||||||
|
|
||||||
|
let mut desktop_finder = DesktopFinder::new();
|
||||||
|
desktop_finder.refresh();
|
||||||
|
|
||||||
let mut frontend = Self {
|
let mut frontend = Self {
|
||||||
layout,
|
layout,
|
||||||
state,
|
state,
|
||||||
@@ -149,7 +152,7 @@ impl Frontend {
|
|||||||
window_audio_settings: WguiWindow::default(),
|
window_audio_settings: WguiWindow::default(),
|
||||||
view_audio_settings: None,
|
view_audio_settings: None,
|
||||||
executor: Rc::new(smol::LocalExecutor::new()),
|
executor: Rc::new(smol::LocalExecutor::new()),
|
||||||
desktop_finder: DesktopFinder::new(),
|
desktop_finder,
|
||||||
};
|
};
|
||||||
|
|
||||||
// init some things first
|
// init some things first
|
||||||
|
|||||||
@@ -116,7 +116,7 @@ impl TabApps {
|
|||||||
extra: Default::default(),
|
extra: Default::default(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let entries = frontend.desktop_finder.find_entries()?;
|
let entries = frontend.desktop_finder.find_entries();
|
||||||
|
|
||||||
let frontend_tasks = frontend.tasks.clone();
|
let frontend_tasks = frontend.tasks.clone();
|
||||||
let globals = frontend.layout.state.globals.clone();
|
let globals = frontend.layout.state.globals.clone();
|
||||||
|
|||||||
@@ -1,8 +1,15 @@
|
|||||||
use std::{collections::HashMap, ffi::OsStr, rc::Rc};
|
use std::{collections::HashMap, ffi::OsStr, rc::Rc, sync::Arc, thread::JoinHandle, time::Instant};
|
||||||
|
|
||||||
use freedesktop::{xdg_data_dirs, xdg_data_home, ApplicationEntry};
|
use freedesktop::{xdg_data_dirs, xdg_data_home, ApplicationEntry};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
struct DesktopEntryOwned {
|
||||||
|
exec_path: String,
|
||||||
|
exec_args: String,
|
||||||
|
app_name: String,
|
||||||
|
icon_path: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
pub struct DesktopEntry {
|
pub struct DesktopEntry {
|
||||||
pub exec_path: Rc<str>,
|
pub exec_path: Rc<str>,
|
||||||
@@ -11,6 +18,17 @@ pub struct DesktopEntry {
|
|||||||
pub icon_path: Option<Rc<str>>,
|
pub icon_path: Option<Rc<str>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<DesktopEntryOwned> for DesktopEntry {
|
||||||
|
fn from(value: DesktopEntryOwned) -> Self {
|
||||||
|
Self {
|
||||||
|
exec_path: value.exec_path.into(),
|
||||||
|
exec_args: value.exec_args.into(),
|
||||||
|
app_name: value.app_name.into(),
|
||||||
|
icon_path: value.icon_path.map(|x| x.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const CMD_BLACKLIST: [&str; 1] = [
|
const CMD_BLACKLIST: [&str; 1] = [
|
||||||
"lsp-plugins", // LSP Plugins collection. They clutter the application list a lot
|
"lsp-plugins", // LSP Plugins collection. They clutter the application list a lot
|
||||||
];
|
];
|
||||||
@@ -18,9 +36,10 @@ const CMD_BLACKLIST: [&str; 1] = [
|
|||||||
const CATEGORY_TYPE_BLACKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"];
|
const CATEGORY_TYPE_BLACKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"];
|
||||||
|
|
||||||
pub struct DesktopFinder {
|
pub struct DesktopFinder {
|
||||||
size_preferences: Vec<&'static OsStr>,
|
size_preferences: Arc<[&'static OsStr]>,
|
||||||
icon_cache: HashMap<String, Rc<str>>,
|
icon_folders: Arc<[String]>,
|
||||||
icon_folders: Vec<String>,
|
entry_cache: Vec<DesktopEntry>,
|
||||||
|
bg_task: Option<JoinHandle<Vec<DesktopEntryOwned>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DesktopFinder {
|
impl DesktopFinder {
|
||||||
@@ -48,60 +67,25 @@ impl DesktopFinder {
|
|||||||
icon_folders.push(data_dir.to_string_lossy().to_string());
|
icon_folders.push(data_dir.to_string_lossy().to_string());
|
||||||
}
|
}
|
||||||
|
|
||||||
let size_preferences = ["scalable", "128x128", "96x96", "72x72", "64x64", "48x48", "32x32"]
|
let icon_folders: Arc<[String]> = icon_folders.into_iter().collect();
|
||||||
|
|
||||||
|
let size_preferences: Arc<[&'static OsStr]> = ["scalable", "128x128", "96x96", "72x72", "64x64", "48x48", "32x32"]
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(OsStr::new)
|
.map(OsStr::new)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
icon_folders,
|
|
||||||
icon_cache: HashMap::new(),
|
|
||||||
size_preferences,
|
size_preferences,
|
||||||
|
icon_folders,
|
||||||
|
entry_cache: Vec::new(),
|
||||||
|
bg_task: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_icon(&mut self, icon_name: &str) -> Option<Rc<str>> {
|
fn build_cache(icon_folders: Arc<[String]>, size_preferences: Arc<[&'static OsStr]>) -> Vec<DesktopEntryOwned> {
|
||||||
if let Some(icon_path) = self.icon_cache.get(icon_name) {
|
let start = Instant::now();
|
||||||
return Some(icon_path.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
for folder in &self.icon_folders {
|
|
||||||
let pattern = format!("{}/*/apps/*/{}.*", folder, icon_name);
|
|
||||||
|
|
||||||
let mut entries: Vec<_> = glob::glob(&pattern)
|
|
||||||
.expect("Bad glob pattern!")
|
|
||||||
.filter_map(Result::ok)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
log::warn!("Looking for '{pattern}' resulted in {} entries.", entries.len());
|
|
||||||
|
|
||||||
// sort by SIZE_PREFERENCES
|
|
||||||
entries.sort_by_key(|path| {
|
|
||||||
path
|
|
||||||
.components()
|
|
||||||
.rev()
|
|
||||||
.nth(1) // ← <THEME>/apps/<*SIZE*>/filename.ext
|
|
||||||
.map(|c| c.as_os_str())
|
|
||||||
.and_then(|size| {
|
|
||||||
log::warn!("looking for {size:?} in size preferences.");
|
|
||||||
self.size_preferences.iter().position(|&p| p == size)
|
|
||||||
})
|
|
||||||
.unwrap_or(usize::MAX)
|
|
||||||
});
|
|
||||||
|
|
||||||
if let Some(first) = entries.into_iter().next() {
|
|
||||||
let rc: Rc<str> = first.to_string_lossy().into();
|
|
||||||
log::warn!("Found icon for {icon_name} at {rc}");
|
|
||||||
self.icon_cache.insert(icon_name.to_string(), rc.clone());
|
|
||||||
return Some(rc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn find_entries(&mut self) -> anyhow::Result<Vec<DesktopEntry>> {
|
|
||||||
let mut res = Vec::<DesktopEntry>::new();
|
|
||||||
|
|
||||||
|
let mut res = Vec::<DesktopEntryOwned>::new();
|
||||||
'app_entries: for app_entry in ApplicationEntry::all() {
|
'app_entries: for app_entry in ApplicationEntry::all() {
|
||||||
let Some(app_entry_id) = app_entry.id() else {
|
let Some(app_entry_id) = app_entry.id() else {
|
||||||
log::warn!(
|
log::warn!(
|
||||||
@@ -111,7 +95,7 @@ impl DesktopFinder {
|
|||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
|
|
||||||
let Some(name) = app_entry.name() else {
|
let Some(app_name) = app_entry.name() else {
|
||||||
log::warn!("No Name on desktop entry {}", app_entry_id);
|
log::warn!("No Name on desktop entry {}", app_entry_id);
|
||||||
continue;
|
continue;
|
||||||
};
|
};
|
||||||
@@ -136,7 +120,9 @@ impl DesktopFinder {
|
|||||||
None => (exec.into(), "".into()),
|
None => (exec.into(), "".into()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let icon_path = app_entry.icon().and_then(|icon_name| self.find_icon(&icon_name));
|
let icon_path = app_entry
|
||||||
|
.icon()
|
||||||
|
.and_then(|icon_name| Self::find_icon(&icon_folders, &size_preferences, &icon_name));
|
||||||
|
|
||||||
for cat in app_entry.categories().unwrap_or_default() {
|
for cat in app_entry.categories().unwrap_or_default() {
|
||||||
if CATEGORY_TYPE_BLACKLIST.contains(&cat.as_str()) {
|
if CATEGORY_TYPE_BLACKLIST.contains(&cat.as_str()) {
|
||||||
@@ -144,8 +130,8 @@ impl DesktopFinder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let entry = DesktopEntry {
|
let entry = DesktopEntryOwned {
|
||||||
app_name: name.into(),
|
app_name,
|
||||||
exec_path,
|
exec_path,
|
||||||
exec_args,
|
exec_args,
|
||||||
icon_path,
|
icon_path,
|
||||||
@@ -153,7 +139,64 @@ impl DesktopFinder {
|
|||||||
|
|
||||||
res.push(entry);
|
res.push(entry);
|
||||||
}
|
}
|
||||||
|
log::error!("App entry cache rebuild took {:?}", start.elapsed());
|
||||||
|
|
||||||
Ok(res)
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_icon(icon_folders: &[String], size_preferences: &[&'static OsStr], icon_name: &str) -> Option<String> {
|
||||||
|
for folder in icon_folders {
|
||||||
|
let pattern = format!("{}/hicolor/*/apps/{}.*", folder, icon_name);
|
||||||
|
|
||||||
|
let mut entries: Vec<_> = glob::glob(&pattern)
|
||||||
|
.expect("Bad glob pattern!")
|
||||||
|
.filter_map(Result::ok)
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// sort by SIZE_PREFERENCES
|
||||||
|
entries.sort_by_key(|path| {
|
||||||
|
path
|
||||||
|
.components()
|
||||||
|
.rev()
|
||||||
|
.nth(2) // ← hicolor/<*SIZE*>/apps/filename.ext
|
||||||
|
.map(|c| c.as_os_str())
|
||||||
|
.and_then(|size| size_preferences.iter().position(|&p| p == size))
|
||||||
|
.unwrap_or(usize::MAX)
|
||||||
|
});
|
||||||
|
|
||||||
|
if let Some(first) = entries.into_iter().next() {
|
||||||
|
return Some(first.to_string_lossy().into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
fn wait_for_entries(&mut self) {
|
||||||
|
let Some(bg_task) = self.bg_task.take() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let Ok(entries) = bg_task.join() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
self.entry_cache.clear();
|
||||||
|
for entry in entries {
|
||||||
|
self.entry_cache.push(entry.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find_entries(&mut self) -> Vec<DesktopEntry> {
|
||||||
|
self.wait_for_entries();
|
||||||
|
self.entry_cache.clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn refresh(&mut self) {
|
||||||
|
let bg_task = std::thread::spawn({
|
||||||
|
let icon_folders = self.icon_folders.clone();
|
||||||
|
let size_preferences = self.size_preferences.clone();
|
||||||
|
move || Self::build_cache(icon_folders, size_preferences)
|
||||||
|
});
|
||||||
|
self.bg_task = Some(bg_task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user