Files
wldash/src/utils/desktop.rs
T

133 lines
4.0 KiB
Rust
Raw Normal View History

2023-02-02 23:52:59 +01:00
use std::{
cmp::Ordering,
collections::HashSet,
error::Error,
2023-04-04 01:53:02 +02:00
fs::{read_to_string, File},
hash::{Hash, Hasher},
2023-02-02 23:52:59 +01:00
io::{Error as io_error, ErrorKind},
path::Path,
2023-02-02 23:52:59 +01:00
};
2023-04-04 01:53:02 +02:00
use serde::{self, Deserialize, Serialize};
use simd_json;
2023-01-11 00:28:54 +01:00
use walkdir::WalkDir;
2023-04-04 01:53:02 +02:00
use crate::utils::{inish, xdg};
2023-02-02 23:52:59 +01:00
#[derive(Serialize, Deserialize, Clone, Debug, Eq)]
2023-04-04 01:53:02 +02:00
#[serde(rename_all = "snake_case")]
2023-01-11 00:28:54 +01:00
pub struct Desktop {
pub entry_type: String,
pub name: String,
pub no_display: bool,
pub hidden: bool,
pub exec: Option<String>,
pub url: Option<String>,
pub term: bool,
pub keywords: Vec<String>,
}
impl Desktop {
fn parse(f: &Path) -> Result<Desktop, Box<dyn Error>> {
2023-02-02 23:52:59 +01:00
let s = read_to_string(f)?;
let config = inish::parse(&s)?;
match config.get("Desktop Entry") {
Some(section) => Ok(Desktop {
entry_type: section.get("Type").unwrap_or(&"").to_string(),
name: section.get("Name").unwrap_or(&"").to_string(),
term: section.get("Terminal").unwrap_or(&"") == &"true",
no_display: section.get("NoDisplay").unwrap_or(&"") == &"true",
hidden: section.get("Hidden").unwrap_or(&"") == &"true",
exec: section.get("Exec").map(|x| x.to_string()),
url: section.get("URL").map(|x| x.to_string()),
keywords: section
2023-01-11 00:28:54 +01:00
.get("Keywords")
.map(|x| {
x.split(';')
.map(|y| y.trim().to_string())
.filter(|z| z != "")
.collect()
})
.unwrap_or_else(|| vec![]),
}),
None => Err(Box::new(io_error::new(
ErrorKind::NotFound,
"no desktop entry in file",
))),
}
}
}
impl Ord for Desktop {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Desktop {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl PartialEq for Desktop {
fn eq(&self, other: &Self) -> bool {
self.name == other.name
}
}
// Must agree with PartialEq: equal entries (same name) must hash equally.
impl Hash for Desktop {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state)
}
}
2023-04-04 01:53:02 +02:00
pub fn load_desktop_cache() -> Result<Vec<Desktop>, Box<dyn Error>> {
match File::open(format!("{}/wldash/desktop.json", xdg::cache_folder())) {
Ok(f) => simd_json::from_reader(f).map_err(|e| e.into()),
2025-01-12 23:10:21 +01:00
Err(e) => Err(e.into()),
2023-04-04 01:53:02 +02:00
}
}
pub fn write_desktop_cache(v: &Vec<Desktop>) -> Result<(), Box<dyn Error>> {
match File::create(format!("{}/wldash/desktop.json", xdg::cache_folder())) {
Ok(f) => simd_json::to_writer(f, v).map_err(|e| e.into()),
2025-01-12 23:10:21 +01:00
Err(e) => Err(e.into()),
2023-04-04 01:53:02 +02:00
}
}
2023-01-11 00:28:54 +01:00
pub fn load_desktop_files() -> Vec<Desktop> {
2023-04-04 01:53:02 +02:00
let dirs = xdg::data_folders();
2023-02-02 23:52:59 +01:00
let mut desktop: Vec<Desktop> = Vec::new();
let mut seen: HashSet<String> = HashSet::new();
for dir in dirs {
for entry in WalkDir::new(format!("{}/applications", dir)).into_iter().flatten() {
// Entries are often symlinks, so only rule out directories.
if entry.file_type().is_dir()
|| entry.path().extension().and_then(|e| e.to_str()) != Some("desktop")
{
continue;
}
{
if let Ok(d) = Desktop::parse(entry.path()) {
2023-02-02 23:52:59 +01:00
if d.hidden
|| d.no_display
|| (d.entry_type != "Application" && d.entry_type != "Link")
{
continue;
}
if !seen.contains(&d.name) {
seen.insert(d.name.to_string());
desktop.push(d);
}
}
}
}
}
desktop
2023-01-11 00:28:54 +01:00
}