mirror of
https://github.com/imgurbot12/rmenu.git
synced 2025-01-12 15:26:16 +01:00
feat: upgraded freedesktop parser
This commit is contained in:
parent
129ac75287
commit
370ebfa5ec
3 changed files with 48 additions and 45 deletions
|
@ -7,7 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.9", features = ["derive"] }
|
||||
freedesktop-desktop-entry = "0.5.2"
|
||||
freedesktop-desktop-entry = "0.6.2"
|
||||
itertools = "0.13.0"
|
||||
once_cell = "1.19.0"
|
||||
rayon = "1.10.0"
|
||||
|
|
|
@ -51,23 +51,23 @@ fn theme_inis(cfgdir: &PathBuf) -> Vec<String> {
|
|||
}
|
||||
|
||||
/// Parse FreeDesktop Theme-Name from Index File
|
||||
fn get_theme_name(path: &PathBuf) -> Option<String> {
|
||||
fn get_theme_name(path: &PathBuf, locales: &[&str]) -> Option<String> {
|
||||
let content = read_to_string(path).ok()?;
|
||||
let config = DesktopEntry::decode(&path, &content).ok()?;
|
||||
let config = DesktopEntry::from_str(&path, &content, locales).ok()?;
|
||||
config
|
||||
.groups
|
||||
.get(INDEX_MAIN)
|
||||
.and_then(|g| g.get(INDEX_NAME))
|
||||
.map(|key| key.0.to_owned())
|
||||
.map(|key| key.0.to_string())
|
||||
}
|
||||
|
||||
/// Determine XDG Icon Theme based on Preexisting Configuration Files
|
||||
pub fn active_themes(cfgdir: &PathBuf, icondirs: &Vec<PathBuf>) -> Vec<String> {
|
||||
pub fn active_themes(cfgdir: &PathBuf, icondirs: &Vec<PathBuf>, locales: &[&str]) -> Vec<String> {
|
||||
let mut themes: Vec<String> = icondirs
|
||||
.iter()
|
||||
.map(|d| d.join(DEFAULT_INDEX))
|
||||
.filter(|p| p.exists())
|
||||
.filter_map(|p| get_theme_name(&p))
|
||||
.filter_map(|p| get_theme_name(&p, locales))
|
||||
.collect();
|
||||
themes.extend(theme_inis(cfgdir));
|
||||
let default = DEFAULT_THEME.to_string();
|
||||
|
@ -82,7 +82,7 @@ pub enum ThemeError {
|
|||
#[error("Failed to Read Index")]
|
||||
FileError(#[from] std::io::Error),
|
||||
#[error("Failed to Parse Index")]
|
||||
IndexError(#[from] freedesktop_desktop_entry::DecodeError),
|
||||
IndexError(String),
|
||||
#[error("No Such Group")]
|
||||
NoSuchGroup(&'static str),
|
||||
#[error("No Such Key")]
|
||||
|
@ -138,11 +138,11 @@ fn sort_dirs(dirs: &mut Vec<PathPriority>) -> Vec<PathBuf> {
|
|||
}
|
||||
|
||||
/// Parse Theme Index and Sort Directories based on Size Preference
|
||||
fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
|
||||
fn parse_index(spec: &ThemeSpec, locales: &[&str]) -> Result<ThemeInfo, ThemeError> {
|
||||
// parse file content
|
||||
let index = spec.root.join(INDEX_FILE);
|
||||
let content = read_to_string(&index)?;
|
||||
let config = DesktopEntry::decode(&index, &content)?;
|
||||
let config = DesktopEntry::from_path(index, locales)
|
||||
.map_err(|e| ThemeError::IndexError(e.to_string()))?;
|
||||
let main = config
|
||||
.groups
|
||||
.get(INDEX_MAIN)
|
||||
|
@ -151,7 +151,8 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
|
|||
let name = main
|
||||
.get(INDEX_NAME)
|
||||
.ok_or_else(|| ThemeError::NoSuchKey(INDEX_NAME))?
|
||||
.0;
|
||||
.0
|
||||
.to_string();
|
||||
// check if name in supported themes
|
||||
let index = spec
|
||||
.themes
|
||||
|
@ -179,7 +180,7 @@ fn parse_index(spec: &ThemeSpec) -> Result<ThemeInfo, ThemeError> {
|
|||
.collect();
|
||||
Ok(ThemeInfo {
|
||||
priority: index,
|
||||
name: name.to_owned(),
|
||||
name,
|
||||
paths: sort_dirs(&mut directories),
|
||||
})
|
||||
}
|
||||
|
@ -238,15 +239,15 @@ impl IconSpec {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn standard(cfg: &PathBuf, sizes: Vec<usize>) -> Self {
|
||||
pub fn standard(cfg: &PathBuf, sizes: Vec<usize>, locales: &[&str]) -> Self {
|
||||
let icon_paths = crate::data_dirs("icons");
|
||||
let themes = active_themes(cfg, &icon_paths);
|
||||
let themes = active_themes(cfg, &icon_paths, locales);
|
||||
Self::new(icon_paths, themes, sizes)
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse and Collect a list of Directories to Find Icons in Order of Preference
|
||||
fn parse_themes(icons: IconSpec) -> Vec<PathBuf> {
|
||||
fn parse_themes(icons: IconSpec, locales: &[&str]) -> Vec<PathBuf> {
|
||||
// retrieve supported theme information
|
||||
let mut infos: Vec<ThemeInfo> = icons
|
||||
.paths
|
||||
|
@ -258,7 +259,7 @@ fn parse_themes(icons: IconSpec) -> Vec<PathBuf> {
|
|||
// parse or guess index themes
|
||||
.filter_map(|icondir| {
|
||||
let spec = ThemeSpec::new(&icondir, &icons.themes, &icons.sizes);
|
||||
parse_index(&spec)
|
||||
parse_index(&spec, locales)
|
||||
.map(|r| Ok(r))
|
||||
.unwrap_or_else(|_| guess_index(&spec))
|
||||
.ok()
|
||||
|
@ -284,20 +285,24 @@ fn is_icon(fname: &str) -> bool {
|
|||
}
|
||||
|
||||
/// Collect Unique Icon Map based on Preffered Paths
|
||||
pub fn collect_icons(spec: IconSpec) -> IconMap {
|
||||
pub fn collect_icons(spec: IconSpec, locales: &[&str]) -> IconMap {
|
||||
let mut map = HashMap::new();
|
||||
for path in parse_themes(spec).into_iter() {
|
||||
for path in parse_themes(spec, locales).into_iter() {
|
||||
let icons = WalkDir::new(path)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_type().is_file());
|
||||
for icon in icons {
|
||||
let Some(fname) = icon.file_name().to_str() else { continue };
|
||||
let Some(fname) = icon.file_name().to_str() else {
|
||||
continue;
|
||||
};
|
||||
if !is_icon(&fname) {
|
||||
continue;
|
||||
}
|
||||
let Some((name, _)) = fname.rsplit_once(".") else { continue };
|
||||
let Some((name, _)) = fname.rsplit_once(".") else {
|
||||
continue;
|
||||
};
|
||||
map.entry(name.to_owned())
|
||||
.or_insert_with(|| icon.path().to_owned());
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
use std::fs::read_to_string;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::Parser;
|
||||
|
@ -50,28 +49,25 @@ fn fix_exec(exec: &str) -> String {
|
|||
}
|
||||
|
||||
/// Parse XDG Desktop Entry into RMenu Entry
|
||||
fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> {
|
||||
let bytes = read_to_string(path).ok()?;
|
||||
let entry = DesktopEntry::decode(&path, &bytes).ok()?;
|
||||
|
||||
// no-display entries should not be shown
|
||||
fn parse_desktop(path: PathBuf, locales: &[&str]) -> Option<Entry> {
|
||||
let entry = DesktopEntry::from_path(path, locales).ok()?;
|
||||
// hide `NoDisplay` entries
|
||||
if entry.no_display() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// if an entry only is shown on a specific desktop, check whether desktop is set and matches,
|
||||
// otherwise return None
|
||||
let de = std::env::var(XDG_CURRENT_DESKTOP_ENV);
|
||||
if entry
|
||||
.only_show_in()
|
||||
.is_some_and(|e| !(de.is_ok() && de.unwrap() == e.to_string()))
|
||||
{
|
||||
return None;
|
||||
// hide entries restricted by `OnlyShowIn`
|
||||
if let Ok(de) = std::env::var(XDG_CURRENT_DESKTOP_ENV) {
|
||||
if entry
|
||||
.only_show_in()
|
||||
.is_some_and(|only| only.contains(&de.as_str()))
|
||||
{
|
||||
return None;
|
||||
};
|
||||
}
|
||||
|
||||
let name = entry.name(locale)?.to_string();
|
||||
// parse desktop entry into rmenu entry
|
||||
let name = entry.name(locales)?.to_string();
|
||||
let icon = entry.icon().map(|i| i.to_string());
|
||||
let comment = entry.comment(locale).map(|s| s.to_string());
|
||||
let comment = entry.comment(locales).map(|s| s.to_string());
|
||||
let terminal = entry.terminal();
|
||||
let mut actions = match entry.exec() {
|
||||
Some(exec) => vec![Action {
|
||||
|
@ -84,12 +80,11 @@ fn parse_desktop(path: &PathBuf, locale: Option<&str>) -> Option<Entry> {
|
|||
actions.extend(
|
||||
entry
|
||||
.actions()
|
||||
.unwrap_or("")
|
||||
.split(";")
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.filter(|a| a.len() > 0)
|
||||
.filter_map(|a| {
|
||||
let name = entry.action_name(a, locale)?;
|
||||
let name = entry.action_name(a, locales)?;
|
||||
let exec = entry.action_exec(a)?;
|
||||
Some(Action {
|
||||
name: name.to_string(),
|
||||
|
@ -124,17 +119,20 @@ struct Cli {
|
|||
/// Only Allow Unique Desktop Entries
|
||||
#[clap(short, long)]
|
||||
non_unique: bool,
|
||||
/// Locale Override
|
||||
#[clap(short, long, default_value = "en")]
|
||||
locale: String,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let cli = Cli::parse();
|
||||
let locale = Some("en");
|
||||
let locales = &[cli.locale.as_str()];
|
||||
let sizes = vec![64, 32, 96, 22, 128];
|
||||
|
||||
// collect icons
|
||||
let cfg = config_dir();
|
||||
let spec = icons::IconSpec::standard(&cfg, sizes);
|
||||
let icons = icons::collect_icons(spec);
|
||||
let spec = icons::IconSpec::standard(&cfg, sizes, locales);
|
||||
let icons = icons::collect_icons(spec, locales);
|
||||
|
||||
// collect applications
|
||||
let app_paths = data_dirs("applications");
|
||||
|
@ -147,7 +145,7 @@ fn main() {
|
|||
.and_then(|n| n.to_str())
|
||||
.map(|s| s.to_string()),
|
||||
})
|
||||
.filter_map(|f| parse_desktop(&f, locale))
|
||||
.filter_map(|f| parse_desktop(f, locales))
|
||||
.map(|mut e| {
|
||||
e.icon = e.icon.and_then(|s| assign_icon(s, &icons));
|
||||
e
|
||||
|
|
Loading…
Reference in a new issue