This commit is contained in:
Alexander Mohr 2025-05-26 18:19:37 +02:00
parent f1aceb18eb
commit 2983ddb49e
6 changed files with 93 additions and 60 deletions

View file

@ -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::*;

View file

@ -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<I, S>(exec: &str, args: I) -> Result<(), Error>
where
I: IntoIterator<Item = S>,
S: AsRef<OsStr>,
{
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(())
}

View file

@ -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()),

View file

@ -26,6 +26,7 @@ pub(crate) struct DRunProvider<T: Clone> {
data: T,
no_actions: bool,
sort_order: SortOrder,
terminal: Option<String>,
}
impl<T: Clone + Send + Sync> ItemProvider<T> for DRunProvider<T> {
@ -42,7 +43,12 @@ impl<T: Clone + Send + Sync> ItemProvider<T> for DRunProvider<T> {
}
impl<T: Clone + Send + Sync> DRunProvider<T> {
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<String>,
) -> Self {
let (cache_path, d_run_cache) = load_d_run_cache();
DRunProvider {
items: None,
@ -51,6 +57,7 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
data: menu_item_data,
no_actions,
sort_order,
terminal,
}
}
@ -74,8 +81,8 @@ impl<T: Clone + Send + Sync> DRunProvider<T> {
&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<T: Clone + Send + Sync> DRunProvider<T> {
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<T: Clone + Send + Sync> DRunProvider<T> {
.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<T: Clone + Send + Sync> DRunProvider<T> {
gui::apply_sort(&mut entries, &self.sort_order);
entries
}
fn get_action(
&self,
in_terminal: bool,
action: Option<String>,
action_name: &String,
) -> Option<String> {
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<PathBuf>, HashMap<String, i64>) {
@ -188,7 +217,7 @@ pub(crate) fn update_drun_cache_and_run<T: Clone>(
///
/// 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();

View file

@ -34,9 +34,7 @@ impl EmojiProvider {
.collect::<Vec<_>>();
gui::apply_sort(&mut menus, sort_order);
Self {
elements: menus,
}
Self { elements: menus }
}
}
@ -55,7 +53,7 @@ impl ItemProvider<String> 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),

View file

@ -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 {