From 2983ddb49e4c532ba3d45e38cfe88b9304b5613a Mon Sep 17 00:00:00 2001 From: Alexander Mohr Date: Mon, 26 May 2025 18:19:37 +0200 Subject: [PATCH] fix #67 --- worf/src/lib/config.rs | 31 ------------------ worf/src/lib/desktop.rs | 63 +++++++++++++++++++++++++++---------- worf/src/lib/modes/auto.rs | 7 ++++- worf/src/lib/modes/drun.rs | 41 ++++++++++++++++++++---- worf/src/lib/modes/emoji.rs | 6 ++-- worf/src/main.rs | 5 +-- 6 files changed, 93 insertions(+), 60 deletions(-) diff --git a/worf/src/lib/config.rs b/worf/src/lib/config.rs index f14ff23..32e355f 100644 --- a/worf/src/lib/config.rs +++ b/worf/src/lib/config.rs @@ -2,9 +2,7 @@ use crate::Error; use clap::{Parser, ValueEnum}; use serde::{Deserialize, Serialize}; use serde_json::Value; -use std::os::unix::process::CommandExt; use std::path::PathBuf; -use std::process::{Command, Stdio}; use std::str::FromStr; use std::{env, fs}; use thiserror::Error; @@ -804,35 +802,6 @@ fn merge_json(a: &mut Value, b: &Value) { } } -/// Fork into background if configured -/// # Panics -/// Panics if preexec and or setsid do not work -pub fn fork_if_configured(config: &Config) { - let fork_env_var = "WORF_PROCESS_IS_FORKED"; - if config.fork() && env::var(fork_env_var).is_err() { - let mut cmd = Command::new(env::current_exe().expect("Failed to get current executable")); - - for arg in env::args().skip(1) { - cmd.arg(arg); - } - - cmd.env(fork_env_var, "1"); - cmd.stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()); - - unsafe { - cmd.pre_exec(|| { - libc::setsid(); - Ok(()) - }); - } - - cmd.spawn().expect("Failed to fork to background"); - std::process::exit(0); - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/worf/src/lib/desktop.rs b/worf/src/lib/desktop.rs index cdb498c..0cf3043 100644 --- a/worf/src/lib/desktop.rs +++ b/worf/src/lib/desktop.rs @@ -1,4 +1,9 @@ +use freedesktop_file_parser::DesktopFile; +use notify_rust::Notification; +use rayon::prelude::*; +use regex::Regex; use std::collections::HashMap; +use std::ffi::OsStr; use std::os::unix::fs::PermissionsExt; use std::os::unix::prelude::CommandExt; use std::path::Path; @@ -6,15 +11,10 @@ use std::path::PathBuf; use std::process::{Command, Stdio}; use std::time::Instant; use std::{env, fs, io}; - -use freedesktop_file_parser::DesktopFile; -use notify_rust::Notification; -use rayon::prelude::*; -use regex::Regex; use wl_clipboard_rs::copy::{ClipboardType, MimeType, ServeRequests, Source}; use crate::Error; -use crate::config::expand_path; +use crate::config::{Config, expand_path}; /// Returns a regex with supported image extensions /// # Panics @@ -132,6 +132,23 @@ pub fn lookup_name_with_locale( .or_else(|| Some(fallback.to_owned())) } +/// Fork into background if configured +/// # Panics +/// Panics if preexec and or setsid do not work +pub fn fork_if_configured(config: &Config) { + let fork_env_var = "WORF_PROCESS_IS_FORKED"; + if config.fork() && env::var(fork_env_var).is_err() { + let mut cmd = Command::new(env::current_exe().expect("Failed to get current executable")); + + for arg in env::args().skip(1) { + cmd.arg(arg); + } + + start_forked_cmd(cmd).expect("Failed to fork to background"); + std::process::exit(0); + } +} + /// Spawn a new process and forks it away from the current worf process /// # Errors /// * No action in menu item @@ -169,18 +186,32 @@ pub fn spawn_fork(cmd: &str, working_dir: Option<&String>) -> Result<(), Error> .map(|arg| expand_path(arg)) .collect(); + start_forked(&exec, args) +} + +fn start_forked(exec: &str, args: I) -> Result<(), Error> +where + I: IntoIterator, + S: AsRef, +{ + let mut cmd = Command::new(exec); + cmd.args(args); + start_forked_cmd(cmd) +} + +fn start_forked_cmd(mut cmd: Command) -> Result<(), Error> { + cmd.stdin(Stdio::null()) + .stdout(Stdio::null()) + .stderr(Stdio::null()); + unsafe { - let _ = Command::new(exec) - .args(args) - .stdin(Stdio::null()) - .stdout(Stdio::null()) - .stderr(Stdio::null()) - .pre_exec(|| { - libc::setsid(); - Ok(()) - }) - .spawn(); + cmd.pre_exec(|| { + libc::setsid(); + Ok(()) + }); } + + cmd.spawn().map_err(|e| Error::Io(e.to_string()))?; Ok(()) } diff --git a/worf/src/lib/modes/auto.rs b/worf/src/lib/modes/auto.rs index a223bed..de8ff8c 100644 --- a/worf/src/lib/modes/auto.rs +++ b/worf/src/lib/modes/auto.rs @@ -30,7 +30,12 @@ struct AutoItemProvider { impl AutoItemProvider { fn new(config: &Config) -> Self { AutoItemProvider { - drun: DRunProvider::new(AutoRunType::DRun, config.no_actions(), config.sort_order()), + drun: DRunProvider::new( + AutoRunType::DRun, + config.no_actions(), + config.sort_order(), + config.term(), + ), file: FileItemProvider::new(AutoRunType::File, config.sort_order()), math: MathProvider::new(AutoRunType::Math), ssh: SshProvider::new(AutoRunType::Ssh, &config.sort_order()), diff --git a/worf/src/lib/modes/drun.rs b/worf/src/lib/modes/drun.rs index f420f0a..29c1f45 100644 --- a/worf/src/lib/modes/drun.rs +++ b/worf/src/lib/modes/drun.rs @@ -26,6 +26,7 @@ pub(crate) struct DRunProvider { data: T, no_actions: bool, sort_order: SortOrder, + terminal: Option, } impl ItemProvider for DRunProvider { @@ -42,7 +43,12 @@ impl ItemProvider for DRunProvider { } impl DRunProvider { - pub(crate) fn new(menu_item_data: T, no_actions: bool, sort_order: SortOrder) -> Self { + pub(crate) fn new( + menu_item_data: T, + no_actions: bool, + sort_order: SortOrder, + terminal: Option, + ) -> Self { let (cache_path, d_run_cache) = load_d_run_cache(); DRunProvider { items: None, @@ -51,6 +57,7 @@ impl DRunProvider { data: menu_item_data, no_actions, sort_order, + terminal, } } @@ -74,8 +81,8 @@ impl DRunProvider { &file.entry.name.default, )?; - let (action, working_dir) = match &file.entry.entry_type { - EntryType::Application(app) => (app.exec.clone(), app.path.clone()), + let (action, working_dir, in_terminal) = match &file.entry.entry_type { + EntryType::Application(app) => (app.exec.clone(), app.path.clone(), app.terminal.unwrap_or(false)), _ => return None, }; @@ -106,12 +113,13 @@ impl DRunProvider { let mut entry = MenuItem::new( name.clone(), icon.clone(), - action.clone(), + self.get_action(in_terminal, action, &name), Vec::new(), working_dir.clone(), sort_score, Some(self.data.clone()), ); + if !self.no_actions { for action in file.actions.values() { if let Some(action_name) = lookup_name_with_locale( @@ -127,10 +135,12 @@ impl DRunProvider { .unwrap_or("application-x-executable".to_string()); + let action = self.get_action(in_terminal, action.exec.clone(), &action_name); + entry.sub_elements.push(MenuItem::new( action_name, Some(action_icon), - action.exec.clone(), + action, Vec::new(), working_dir.clone(), 0.0, @@ -157,6 +167,25 @@ impl DRunProvider { gui::apply_sort(&mut entries, &self.sort_order); entries } + + fn get_action( + &self, + in_terminal: bool, + action: Option, + action_name: &String, + ) -> Option { + if in_terminal { + match self.terminal.as_ref() { + None => { + log::warn!("No terminal configured for terminal app {action_name}"); + None + } + Some(terminal) => action.map(|cmd| format!("{terminal} {cmd}")), + } + } else { + action + } + } } fn load_d_run_cache() -> (Option, HashMap) { @@ -188,7 +217,7 @@ pub(crate) fn update_drun_cache_and_run( /// /// Will return `Err` if it was not able to spawn the process pub fn show(config: &Config) -> Result<(), Error> { - let provider = DRunProvider::new(0, config.no_actions(), config.sort_order()); + let provider = DRunProvider::new(0, config.no_actions(), config.sort_order(), config.term()); let cache_path = provider.cache_path.clone(); let mut cache = provider.cache.clone(); diff --git a/worf/src/lib/modes/emoji.rs b/worf/src/lib/modes/emoji.rs index 2ef4e48..50a30f0 100644 --- a/worf/src/lib/modes/emoji.rs +++ b/worf/src/lib/modes/emoji.rs @@ -34,9 +34,7 @@ impl EmojiProvider { .collect::>(); gui::apply_sort(&mut menus, sort_order); - Self { - elements: menus, - } + Self { elements: menus } } } @@ -55,7 +53,7 @@ impl ItemProvider for EmojiProvider { /// /// Forwards errors from the gui. See `gui::show` for details. pub fn show(config: &Config) -> Result<(), Error> { - let provider = EmojiProvider::new( &config.sort_order(), config.emoji_hide_label()); + let provider = EmojiProvider::new(&config.sort_order(), config.emoji_hide_label()); let selection_result = gui::show(config.clone(), provider, true, None, None)?; match selection_result.menu.data { None => Err(Error::MissingAction), diff --git a/worf/src/main.rs b/worf/src/main.rs index db5e75c..66db949 100644 --- a/worf/src/main.rs +++ b/worf/src/main.rs @@ -1,7 +1,8 @@ use std::env; use anyhow::anyhow; -use worf_lib::config::{Mode, fork_if_configured}; +use worf_lib::config::Mode; +use worf_lib::desktop::fork_if_configured; use worf_lib::{Error, config, modes}; fn main() -> anyhow::Result<()> { @@ -26,7 +27,7 @@ fn main() -> anyhow::Result<()> { return Ok(()); } - fork_if_configured(&config); + fork_if_configured(&config); // may exit the program if let Some(show) = &config.show() { let result = match show {