From 8467c1b1e555ce2e52187eeed476f7af9ca826ec Mon Sep 17 00:00:00 2001 From: Kirottu Date: Sat, 8 Apr 2023 11:13:12 +0300 Subject: [PATCH] Desktop Action support for the Applications plugin --- Cargo.lock | 57 +++++++---- plugins/applications/Cargo.toml | 2 + plugins/applications/src/lib.rs | 26 ++++- plugins/applications/src/scrubber.rs | 146 +++++++++++++++++++-------- 4 files changed, 165 insertions(+), 66 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index de9c77e..8c9bb1f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -37,7 +37,7 @@ dependencies = [ "proc-macro2", "quote", "rustc_version 0.4.0", - "syn", + "syn 1.0.107", "typed-arena", ] @@ -101,6 +101,8 @@ dependencies = [ "abi_stable", "anyrun-plugin", "fuzzy-matcher", + "ron", + "serde", "sublime_fuzzy", ] @@ -113,7 +115,7 @@ dependencies = [ "core_extensions", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -352,7 +354,7 @@ dependencies = [ "proc-macro2", "quote", "scratch", - "syn", + "syn 1.0.107", ] [[package]] @@ -369,7 +371,7 @@ checksum = "3e7e2adeb6a0d4a282e581096b06e1791532b7d576dcde5ccd9382acf55db8e6" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -380,7 +382,7 @@ checksum = "3418329ca0ad70234b9735dc4ceed10af4df60eff9c8e7b06cb5e520d92c3535" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -509,7 +511,7 @@ checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -686,7 +688,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -790,7 +792,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1267,7 +1269,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -1433,7 +1435,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "version_check", ] @@ -1450,18 +1452,18 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.49" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57a8eca9f9c4ffde41714334dee777596264c7825420f521abc92b5b5deb63a5" +checksum = "2b63bdb0cd06f1f4dedf69b254734f9b45af66e4a031e42a7480257d9898b435" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.23" +version = "1.0.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "4424af4bf778aae2051a77b60283332f386554255d722233d09fbfc7e30da2fc" dependencies = [ "proc-macro2", ] @@ -1676,22 +1678,22 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +checksum = "3c04e8343c3daeec41f58990b9d77068df31209f2af111e059e9fe9646693065" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.152" +version = "1.0.159" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +checksum = "4c614d17805b093df4b147b51339e7e44bf05ef59fba1e45d83500bcfb4d8585" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.13", ] [[package]] @@ -1792,6 +1794,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "2.0.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c9da457c5285ac1f936ebd076af6dac17a61cfe7826f2076b4d015cf47bc8ec" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + [[package]] name = "system-deps" version = "6.0.3" @@ -1845,7 +1858,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", ] [[package]] @@ -2120,7 +2133,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-shared", ] @@ -2154,7 +2167,7 @@ checksum = "07bc0c051dc5f23e307b13285f9d75df86bfdf816c5721e573dec1f9b8aa193c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 1.0.107", "wasm-bindgen-backend", "wasm-bindgen-shared", ] diff --git a/plugins/applications/Cargo.toml b/plugins/applications/Cargo.toml index a50c0e1..9513059 100644 --- a/plugins/applications/Cargo.toml +++ b/plugins/applications/Cargo.toml @@ -13,3 +13,5 @@ anyrun-plugin = { path = "../../anyrun-plugin" } abi_stable = "0.11.1" sublime_fuzzy = "0.7.0" fuzzy-matcher = "0.3.7" +ron = "0.8.0" +serde = { version = "1.0.159", features = ["derive"] } diff --git a/plugins/applications/src/lib.rs b/plugins/applications/src/lib.rs index 01d3bee..46ec2ac 100644 --- a/plugins/applications/src/lib.rs +++ b/plugins/applications/src/lib.rs @@ -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 { diff --git a/plugins/applications/src/scrubber.rs b/plugins/applications/src/scrubber.rs index 66f0b28..7ecfc97 100644 --- a/plugins/applications/src/scrubber.rs +++ b/plugins/applications/src/scrubber.rs @@ -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 { + fn from_dir_entry(entry: &fs::DirEntry, config: &Config) -> Vec { 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::>(); + + let sections = lines + .split_inclusive(|line| line.starts_with('[')) + .collect::>(); + + 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::().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::().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, Box> { +pub fn scrubber(config: Config) -> Result, Box> { // Create iterator over all the files in the XDG_DATA_DIRS // XDG compliancy is cool let mut paths: Vec> = match env::var("XDG_DATA_DIRS") { @@ -68,7 +133,7 @@ pub fn scrubber() -> Result, Box // 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, Box } }; - 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()) }