mirror of
https://github.com/imgurbot12/rmenu.git
synced 2025-02-12 05:05:06 +01:00
feat: impl terminal/normal run launch and better icon lookups
This commit is contained in:
parent
8a219e6044
commit
2894bb257d
7 changed files with 94 additions and 24 deletions
|
@ -7,6 +7,7 @@ edition = "2021"
|
|||
|
||||
[dependencies]
|
||||
freedesktop-desktop-entry = "0.5.0"
|
||||
regex = "1.9.1"
|
||||
rmenu-plugin = { version = "0.0.0", path = "../rmenu-plugin" }
|
||||
rust-ini = { version = "0.19.0", features = ["unicase"] }
|
||||
serde_json = "1.0.103"
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
use std::collections::{HashMap, HashSet};
|
||||
use std::fs::FileType;
|
||||
use std::path::PathBuf;
|
||||
use std::{fs::read_to_string, path::Path};
|
||||
|
||||
use freedesktop_desktop_entry::DesktopEntry;
|
||||
use ini::Ini;
|
||||
use rmenu_plugin::{Action, Entry};
|
||||
use regex::Regex;
|
||||
use rmenu_plugin::{Action, Entry, Method};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
static XDG_DATA_ENV: &'static str = "XDG_DATA_DIRS";
|
||||
|
@ -167,17 +167,23 @@ fn data_dirs(dir: &str) -> Vec<PathBuf> {
|
|||
.collect()
|
||||
}
|
||||
|
||||
#[inline(always)]
|
||||
fn fix_exec(exec: &str, ematch: &Regex) -> String {
|
||||
ematch.replace_all(exec, "").trim().to_string()
|
||||
}
|
||||
|
||||
/// Parse XDG Desktop Entry into RMenu Entry
|
||||
fn parse_desktop(path: &Path, locale: Option<&str>) -> Option<Entry> {
|
||||
fn parse_desktop(path: &Path, locale: Option<&str>, ematch: &Regex) -> Option<Entry> {
|
||||
let bytes = read_to_string(path).ok()?;
|
||||
let entry = DesktopEntry::decode(&path, &bytes).ok()?;
|
||||
let name = entry.name(locale)?.to_string();
|
||||
let icon = entry.icon().map(|s| s.to_string());
|
||||
let comment = entry.comment(locale).map(|s| s.to_string());
|
||||
let terminal = entry.terminal();
|
||||
let mut actions = match entry.exec() {
|
||||
Some(exec) => vec![Action {
|
||||
name: "main".to_string(),
|
||||
exec: exec.to_string(),
|
||||
exec: Method::new(fix_exec(exec, ematch), terminal),
|
||||
comment: None,
|
||||
}],
|
||||
None => vec![],
|
||||
|
@ -194,7 +200,7 @@ fn parse_desktop(path: &Path, locale: Option<&str>) -> Option<Entry> {
|
|||
let exec = entry.action_exec(a)?;
|
||||
Some(Action {
|
||||
name: name.to_string(),
|
||||
exec: exec.to_string(),
|
||||
exec: Method::new(fix_exec(exec, ematch), terminal),
|
||||
comment: None,
|
||||
})
|
||||
}),
|
||||
|
@ -208,14 +214,14 @@ fn parse_desktop(path: &Path, locale: Option<&str>) -> Option<Entry> {
|
|||
}
|
||||
|
||||
/// Iterate Path and Parse All `.desktop` files into Entries
|
||||
fn find_desktops(path: PathBuf, locale: Option<&str>) -> Vec<Entry> {
|
||||
fn find_desktops(path: PathBuf, locale: Option<&str>, ematch: &Regex) -> Vec<Entry> {
|
||||
WalkDir::new(path)
|
||||
.follow_links(true)
|
||||
.into_iter()
|
||||
.filter_map(|e| e.ok())
|
||||
.filter(|e| e.file_name().to_string_lossy().ends_with(".desktop"))
|
||||
.filter(|e| e.file_type().is_file())
|
||||
.filter_map(|e| parse_desktop(e.path(), locale))
|
||||
.filter_map(|e| parse_desktop(e.path(), locale, ematch))
|
||||
.collect()
|
||||
}
|
||||
|
||||
|
@ -236,6 +242,8 @@ fn assign_icons(icons: &Vec<IconGroup>, mut e: Entry) -> Entry {
|
|||
fn main() {
|
||||
let locale = Some("en");
|
||||
let sizes = (32, 64, 128);
|
||||
// build regex desktop formatter args
|
||||
let ematch = regex::Regex::new(r"%\w").expect("Failed Regex Compile");
|
||||
// build a collection of icons for configured themes
|
||||
let cfgdir = config_dir();
|
||||
let themes = find_theme(&cfgdir);
|
||||
|
@ -252,11 +260,16 @@ fn main() {
|
|||
.collect();
|
||||
// add extra icons found in base folders
|
||||
icons.extend(icon_paths.iter().map(|p| find_icon_extras(p)));
|
||||
// retrieve desktop applications and assign icons before printing results
|
||||
data_dirs("applications")
|
||||
// retrieve desktop applications and sort alphabetically
|
||||
let mut applications: Vec<Entry> = data_dirs("applications")
|
||||
.into_iter()
|
||||
.map(|p| find_desktops(p, locale))
|
||||
.map(|p| find_desktops(p, locale, &ematch))
|
||||
.flatten()
|
||||
.collect();
|
||||
applications.sort_by_cached_key(|e| e.name.to_owned());
|
||||
// assign icons and print results
|
||||
applications
|
||||
.into_iter()
|
||||
.map(|e| assign_icons(&icons, e))
|
||||
.filter_map(|e| serde_json::to_string(&e).ok())
|
||||
.map(|s| println!("{}", s))
|
||||
|
|
|
@ -1,15 +1,25 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase")]
|
||||
pub enum Method {
|
||||
Terminal,
|
||||
Desktop,
|
||||
Terminal(String),
|
||||
Run(String),
|
||||
}
|
||||
|
||||
impl Method {
|
||||
pub fn new(exec: String, terminal: bool) -> Self {
|
||||
match terminal {
|
||||
true => Self::Terminal(exec),
|
||||
false => Self::Run(exec),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Serialize, Deserialize)]
|
||||
pub struct Action {
|
||||
pub name: String,
|
||||
pub exec: String,
|
||||
pub exec: Method,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
|
@ -17,7 +27,7 @@ impl Action {
|
|||
pub fn new(exec: &str) -> Self {
|
||||
Self {
|
||||
name: "main".to_string(),
|
||||
exec: exec.to_string(),
|
||||
exec: Method::Run(exec.to_string()),
|
||||
comment: None,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,4 +25,6 @@ serde_json = "1.0.103"
|
|||
serde_yaml = "0.9.24"
|
||||
shell-words = "1.1.0"
|
||||
shellexpand = "3.1.0"
|
||||
strfmt = "0.2.4"
|
||||
thiserror = "1.0.43"
|
||||
which = "4.4.0"
|
||||
|
|
|
@ -142,6 +142,7 @@ pub struct Config {
|
|||
pub plugins: BTreeMap<String, Vec<String>>,
|
||||
pub keybinds: KeyConfig,
|
||||
pub window: WindowConfig,
|
||||
pub terminal: Option<String>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
|
@ -155,6 +156,7 @@ impl Default for Config {
|
|||
plugins: Default::default(),
|
||||
keybinds: Default::default(),
|
||||
window: Default::default(),
|
||||
terminal: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,14 +1,56 @@
|
|||
//! Execution Implementation for Entry Actions
|
||||
use std::os::unix::process::CommandExt;
|
||||
use std::process::Command;
|
||||
use std::{collections::HashMap, os::unix::process::CommandExt};
|
||||
|
||||
use rmenu_plugin::Action;
|
||||
use rmenu_plugin::{Action, Method};
|
||||
use shell_words::split;
|
||||
use strfmt::strfmt;
|
||||
use which::which;
|
||||
|
||||
pub fn execute(action: &Action) {
|
||||
log::info!("executing: {} {:?}", action.name, action.exec);
|
||||
let args = match shell_words::split(&action.exec) {
|
||||
/// Find Best Terminal To Execute
|
||||
fn find_terminal() -> String {
|
||||
vec![
|
||||
("alacritty", "-e {cmd}"),
|
||||
("kitty", "{cmd}"),
|
||||
("gnome-terminal", "-x {cmd}"),
|
||||
("foot", "-e {cmd}"),
|
||||
("xterm", "-C {cmd}"),
|
||||
]
|
||||
.into_iter()
|
||||
.map(|(t, v)| (which(t), v))
|
||||
.filter(|(c, _)| c.is_ok())
|
||||
.map(|(c, v)| (c.unwrap(), v))
|
||||
.map(|(p, v)| {
|
||||
(
|
||||
p.to_str()
|
||||
.expect("Failed to Parse Terminal Path")
|
||||
.to_owned(),
|
||||
v,
|
||||
)
|
||||
})
|
||||
.find_map(|(p, v)| Some(format!("{p} {v}")))
|
||||
.expect("Failed to Find Terminal Executable!")
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn parse_args(exec: &str) -> Vec<String> {
|
||||
match split(exec) {
|
||||
Ok(args) => args,
|
||||
Err(err) => panic!("{:?} invalid command {err}", action.exec),
|
||||
Err(err) => panic!("{:?} invalid command {err}", exec),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn execute(action: &Action, term: Option<String>) {
|
||||
log::info!("executing: {} {:?}", action.name, action.exec);
|
||||
let args = match &action.exec {
|
||||
Method::Run(exec) => parse_args(&exec),
|
||||
Method::Terminal(exec) => {
|
||||
let mut args = HashMap::new();
|
||||
let terminal = term.unwrap_or_else(find_terminal);
|
||||
args.insert("cmd".to_string(), exec.to_owned());
|
||||
let command = strfmt(&terminal, &args).expect("Failed String Format");
|
||||
parse_args(&command)
|
||||
}
|
||||
};
|
||||
let err = Command::new(&args[0]).args(&args[1..]).exec();
|
||||
panic!("Command Error: {err:?}");
|
||||
|
|
|
@ -119,16 +119,16 @@ impl<'a> AppState<'a> {
|
|||
/// Execute the Current Action
|
||||
pub fn execute(&self) {
|
||||
let (pos, subpos) = self.position();
|
||||
println!("double click {pos} {subpos}");
|
||||
log::debug!("execute {pos} {subpos}");
|
||||
let Some(result) = self.results.get(pos) else {
|
||||
return;
|
||||
};
|
||||
println!("result: {result:?}");
|
||||
log::debug!("result: {result:?}");
|
||||
let Some(action) = result.actions.get(subpos) else {
|
||||
return;
|
||||
};
|
||||
println!("action: {action:?}");
|
||||
execute(action);
|
||||
log::debug!("action: {action:?}");
|
||||
execute(action, self.app.config.terminal.clone());
|
||||
}
|
||||
|
||||
/// Set Current Key/Action for Later Evaluation
|
||||
|
|
Loading…
Reference in a new issue