1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
|
use std::{
fs::read_dir, path::PathBuf
};
use freedesktop_entry_parser as fd;
use mlua::prelude::*;
use rayon::prelude::*;
use url::Url;
use crate::lenses::{
Entry,
Lense,
Cache
};
#[derive(Default)]
pub struct Application(pub Cache);
impl Lense for Application {
const NAME: &str = "Application";
fn get_cache(&self) -> &Cache {
&self.0
}
fn set_cache(&mut self, cache: Cache) {
self.0 = cache;
}
fn query(_: &Lua, input: String) -> Result<Vec<Entry>, anyhow::Error> {
let applications_dir = "/usr/share/applications";
let entries = read_dir(applications_dir)?
.map(|result| result.map(|e| e.path()))
.collect::<Result<Vec<_>, std::io::Error>>()?;
let parsed_entries: Vec<Entry> = entries
.into_par_iter()
.filter(|path| path.extension().is_some_and(|ext| ext == "desktop"))
.filter_map(|path| {
parse_entry(path).ok().flatten()
})
.collect();
Ok(
parsed_entries
.into_iter()
.filter(|entry| entry.message.to_lowercase().contains(&input.to_lowercase()))
.collect(),
)
}
}
fn parse_entry(path: PathBuf) -> Result<Option<Entry>, ()> {
let Ok(entry) = fd::parse_entry(&path) else {
return Err(())
};
let section = entry.section("Desktop Entry");
let name = section.attr("Name").ok_or(())?.to_string();
if section.attr("Type").ok_or(())? != "Application" {
return Err(());
}
if section.attr("OnlyShowIn").is_some()
|| section.attr("Hidden").is_some()
|| section.attr("NoDisplay").is_some()
{
return Err(());
}
let exec = section.attr("Exec").ok_or(())?.to_string();
let mut new_exec = exec.clone();
for (index, _) in exec.match_indices('%') {
match exec.chars().nth(index + 1).unwrap().to_ascii_lowercase() {
'i' => {
if let Some(icon) = section.attr("Icon") {
new_exec.replace_range(index..index + 2, &format!("--icon {icon}"));
}
}
'c' => new_exec.replace_range(index..index + 2, &name),
'k' => new_exec.replace_range(index..index + 2, Url::from_file_path(&path)?.as_str()),
'f' | 'u' | 'v' | 'm' | 'd' | 'n' => new_exec.replace_range(index..index + 2, ""),
_ => continue,
}
}
Ok(Some(Entry {
message: name,
exec: Some((
new_exec,
section
.attr("Terminal")
.unwrap_or("false")
.parse()
.map_err(drop)?,
)),
}))
}
|