Desktop Action support for the Applications plugin
This commit is contained in:
@@ -2,7 +2,13 @@ use abi_stable::std_types::{ROption, RString, RVec};
|
||||
use anyrun_plugin::{anyrun_interface::HandleResult, *};
|
||||
use fuzzy_matcher::FuzzyMatcher;
|
||||
use scrubber::DesktopEntry;
|
||||
use std::process::Command;
|
||||
use serde::Deserialize;
|
||||
use std::{fs, process::Command};
|
||||
|
||||
#[derive(Deserialize, Default)]
|
||||
pub struct Config {
|
||||
desktop_actions: bool,
|
||||
}
|
||||
|
||||
mod scrubber;
|
||||
|
||||
@@ -25,8 +31,22 @@ pub fn handler(selection: Match, entries: &mut Vec<(DesktopEntry, u64)>) -> Hand
|
||||
HandleResult::Close
|
||||
}
|
||||
|
||||
pub fn init(_config_dir: RString) -> Vec<(DesktopEntry, u64)> {
|
||||
scrubber::scrubber().expect("Failed to load desktop entries!")
|
||||
pub fn init(config_dir: RString) -> Vec<(DesktopEntry, u64)> {
|
||||
let config: Config = match fs::read_to_string(format!("{}/applications.ron", config_dir)) {
|
||||
Ok(content) => ron::from_str(&content).unwrap_or_else(|why| {
|
||||
eprintln!("Error parsing applications plugin config: {}", why);
|
||||
Config::default()
|
||||
}),
|
||||
Err(why) => {
|
||||
eprintln!("Error reading applications plugin config: {}", why);
|
||||
Config::default()
|
||||
}
|
||||
};
|
||||
|
||||
scrubber::scrubber(config).unwrap_or_else(|why| {
|
||||
eprintln!("Failed to load desktop entries: {}", why);
|
||||
Vec::new()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn get_matches(input: RString, entries: &mut Vec<(DesktopEntry, u64)>) -> RVec<Match> {
|
||||
|
@@ -1,5 +1,7 @@
|
||||
use std::{collections::HashMap, env, ffi::OsStr, fs, io};
|
||||
|
||||
use crate::Config;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DesktopEntry {
|
||||
pub exec: String,
|
||||
@@ -12,55 +14,118 @@ const FIELD_CODE_LIST: &[&str] = &[
|
||||
];
|
||||
|
||||
impl DesktopEntry {
|
||||
fn from_dir_entry(entry: &fs::DirEntry) -> Option<Self> {
|
||||
fn from_dir_entry(entry: &fs::DirEntry, config: &Config) -> Vec<Self> {
|
||||
if entry.path().extension() == Some(OsStr::new("desktop")) {
|
||||
let content = match fs::read_to_string(entry.path()) {
|
||||
Ok(content) => content,
|
||||
Err(_) => return None,
|
||||
Err(_) => return Vec::new(),
|
||||
};
|
||||
|
||||
let mut map = HashMap::new();
|
||||
for line in content.lines() {
|
||||
if line.starts_with("[") && line != "[Desktop Entry]" {
|
||||
break;
|
||||
let lines = content.lines().collect::<Vec<_>>();
|
||||
|
||||
let sections = lines
|
||||
.split_inclusive(|line| line.starts_with('['))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut line = None;
|
||||
let mut new_sections = Vec::new();
|
||||
|
||||
for section in sections.iter() {
|
||||
if let Some(line) = line {
|
||||
let mut section = section.to_vec();
|
||||
section.insert(0, line);
|
||||
section.pop();
|
||||
new_sections.push(section);
|
||||
}
|
||||
let (key, val) = match line.split_once("=") {
|
||||
Some(keyval) => keyval,
|
||||
None => continue,
|
||||
};
|
||||
map.insert(key, val);
|
||||
line = Some(section.last().unwrap_or(&""));
|
||||
}
|
||||
|
||||
if map.get("Type")? == &"Application"
|
||||
&& match map.get("NoDisplay") {
|
||||
Some(no_display) => !no_display.parse::<bool>().unwrap_or(true),
|
||||
None => true,
|
||||
}
|
||||
{
|
||||
Some(DesktopEntry {
|
||||
exec: {
|
||||
let mut exec = map.get("Exec")?.to_string();
|
||||
for field_code in FIELD_CODE_LIST {
|
||||
exec = exec.replace(field_code, "");
|
||||
let mut ret = Vec::new();
|
||||
|
||||
let entry = match new_sections.iter().find_map(|section| {
|
||||
if section[0].starts_with("[Desktop Entry]") {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for line in section.iter().skip(1) {
|
||||
if let Some((key, val)) = line.split_once('=') {
|
||||
map.insert(key, val);
|
||||
}
|
||||
exec
|
||||
},
|
||||
name: map.get("Name")?.to_string(),
|
||||
icon: map
|
||||
.get("Icon")
|
||||
.unwrap_or(&"application-x-executable")
|
||||
.to_string(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
||||
if map.get("Type")? == &"Application"
|
||||
&& match map.get("NoDisplay") {
|
||||
Some(no_display) => !no_display.parse::<bool>().unwrap_or(true),
|
||||
None => true,
|
||||
}
|
||||
{
|
||||
Some(DesktopEntry {
|
||||
exec: {
|
||||
let mut exec = map.get("Exec")?.to_string();
|
||||
|
||||
for field_code in FIELD_CODE_LIST {
|
||||
exec = exec.replace(field_code, "");
|
||||
}
|
||||
exec
|
||||
},
|
||||
name: map.get("Name")?.to_string(),
|
||||
icon: map
|
||||
.get("Icon")
|
||||
.unwrap_or(&"application-x-executable")
|
||||
.to_string(),
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}) {
|
||||
Some(entry) => entry,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
|
||||
if config.desktop_actions {
|
||||
for section in new_sections {
|
||||
let mut map = HashMap::new();
|
||||
|
||||
for line in section.iter().skip(1) {
|
||||
if let Some((key, val)) = line.split_once('=') {
|
||||
map.insert(key, val);
|
||||
}
|
||||
}
|
||||
|
||||
if section[0].starts_with("[Desktop Action") {
|
||||
ret.push(DesktopEntry {
|
||||
exec: match map.get("Exec") {
|
||||
Some(exec) => {
|
||||
let mut exec = exec.to_string();
|
||||
|
||||
for field_code in FIELD_CODE_LIST {
|
||||
exec = exec.replace(field_code, "");
|
||||
}
|
||||
exec
|
||||
}
|
||||
None => continue,
|
||||
},
|
||||
name: match map.get("Name") {
|
||||
Some(name) => format!("{}: {}", entry.name, name),
|
||||
None => continue,
|
||||
},
|
||||
icon: entry.icon.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ret.push(entry);
|
||||
ret
|
||||
} else {
|
||||
None
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scrubber() -> Result<Vec<(DesktopEntry, u64)>, Box<dyn std::error::Error>> {
|
||||
pub fn scrubber(config: Config) -> Result<Vec<(DesktopEntry, u64)>, Box<dyn std::error::Error>> {
|
||||
// Create iterator over all the files in the XDG_DATA_DIRS
|
||||
// XDG compliancy is cool
|
||||
let mut paths: Vec<Result<fs::DirEntry, io::Error>> = match env::var("XDG_DATA_DIRS") {
|
||||
@@ -68,7 +133,7 @@ pub fn scrubber() -> Result<Vec<(DesktopEntry, u64)>, Box<dyn std::error::Error>
|
||||
// The vec for all the DirEntry objects
|
||||
let mut paths = Vec::new();
|
||||
// Parse the XDG_DATA_DIRS variable and list files of all the paths
|
||||
for dir in data_dirs.split(":") {
|
||||
for dir in data_dirs.split(':') {
|
||||
match fs::read_dir(format!("{}/applications/", dir)) {
|
||||
Ok(dir) => {
|
||||
paths.extend(dir);
|
||||
@@ -102,20 +167,19 @@ pub fn scrubber() -> Result<Vec<(DesktopEntry, u64)>, Box<dyn std::error::Error>
|
||||
}
|
||||
};
|
||||
|
||||
paths.extend(fs::read_dir(&user_path)?);
|
||||
|
||||
// Keeping track of the entries
|
||||
let mut id = 0;
|
||||
paths.extend(fs::read_dir(user_path)?);
|
||||
|
||||
Ok(paths
|
||||
.iter()
|
||||
.filter_map(|entry| {
|
||||
id += 1;
|
||||
let entry = match entry {
|
||||
Ok(entry) => entry,
|
||||
Err(_why) => return None,
|
||||
};
|
||||
DesktopEntry::from_dir_entry(entry).map(|val| (val, id))
|
||||
Some(DesktopEntry::from_dir_entry(entry, &config))
|
||||
})
|
||||
.flatten()
|
||||
.enumerate()
|
||||
.map(|(i, val)| (val, i as u64))
|
||||
.collect())
|
||||
}
|
||||
|
Reference in New Issue
Block a user