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