Files
wayvr/dash-frontend/src/util/desktop_finder.rs
2025-11-27 23:28:10 +01:00

132 lines
2.9 KiB
Rust

use gio::prelude::{AppInfoExt, IconExt};
use gtk::traits::IconThemeExt;
#[derive(Debug, Clone)]
#[allow(dead_code)] // TODO: remove this
pub struct DesktopEntry {
pub exec_path: String,
pub exec_args: Vec<String>,
pub app_name: String,
pub icon_path: Option<String>,
pub categories: Vec<String>,
}
#[allow(dead_code)] // TODO: remove this
pub struct EntrySearchCell {
pub exec_path: String,
pub exec_args: Vec<String>,
pub app_name: String,
pub icon_name: Option<String>,
pub categories: Vec<String>,
}
const CMD_BLACKLIST: [&str; 1] = [
"lsp-plugins", // LSP Plugins collection. They clutter the application list a lot
];
const CATEGORY_TYPE_BLACKLIST: [&str; 5] = ["GTK", "Qt", "X-XFCE", "X-Bluetooth", "ConsoleOnly"];
pub fn find_entries() -> anyhow::Result<Vec<DesktopEntry>> {
let Some(icon_theme) = gtk::IconTheme::default() else {
anyhow::bail!("Failed to get current icon theme information");
};
let mut res = Vec::<DesktopEntry>::new();
let info = gio::AppInfo::all();
log::debug!("app entry count {}", info.len());
'outer: for app_entry in info {
let Some(app_entry_id) = app_entry.id() else {
log::warn!(
"failed to get desktop entry ID for application named \"{}\"",
app_entry.name()
);
continue;
};
let Some(desktop_app) = gio::DesktopAppInfo::new(&app_entry_id) else {
log::warn!(
"failed to find desktop app file from application named \"{}\"",
app_entry.name()
);
continue;
};
if desktop_app.is_nodisplay() || desktop_app.is_hidden() {
continue;
}
let Some(cmd) = desktop_app.commandline() else {
continue;
};
let name = String::from(desktop_app.name());
let exec = String::from(cmd.to_string_lossy());
for blacklisted in CMD_BLACKLIST {
if exec.contains(blacklisted) {
continue 'outer;
}
}
let (exec_path, exec_args) = match exec.split_once(" ") {
Some((left, right)) => (
String::from(left),
right
.split(" ")
.filter(|arg| !arg.starts_with('%')) // exclude arguments like "%f"
.map(String::from)
.collect(),
),
None => (exec, Vec::new()),
};
let icon_path = match desktop_app.icon() {
Some(icon) => {
if let Some(icon_str) = icon.to_string() {
if let Some(s_icon) = icon_theme.lookup_icon(&icon_str, 128, gtk::IconLookupFlags::GENERIC_FALLBACK) {
s_icon.filename().map(|p| String::from(p.to_string_lossy()))
} else {
None
}
} else {
None
}
}
None => None,
};
let categories: Vec<String> = match desktop_app.categories() {
Some(categories) => categories
.split(";")
.filter(|s| !s.is_empty())
.filter(|s| {
for b in CATEGORY_TYPE_BLACKLIST {
if *s == b {
return false;
}
}
true
})
.map(String::from)
.collect(),
None => Vec::new(),
};
let entry = DesktopEntry {
app_name: name,
categories,
exec_path,
exec_args,
icon_path,
};
res.push(entry);
}
Ok(res)
}