hyprshell_exec_lib/
run.rs1use anyhow::{Context, bail};
2use core_lib::TERMINALS;
3use std::ffi::OsString;
4use std::os::unix::prelude::CommandExt;
5use std::path::Path;
6use std::process::{Command, Stdio};
7use std::{env, thread};
8use tracing::{debug, trace};
9
10pub fn run_program(
11    run: &str,
12    path: Option<Box<Path>>,
13    terminal: bool,
14    default_terminal: &Option<Box<str>>,
15) -> anyhow::Result<()> {
16    debug!("Running: {run}");
17    if terminal {
18        if let Some(term) = default_terminal {
19            let command = format!("{term} -e {run}");
20            run_command(&command, &path).context("Failed to run command")?;
21        } else {
22            let env_path = env::var_os("PATH")
23                .unwrap_or_else(|| OsString::from("/usr/bin:/bin:/usr/local/bin"));
24            debug!(
25                "No default terminal found, searching common terminals in PATH. (Set default_terminal in config to avoid this search)"
26            );
27            trace!("PATH: {}", env_path.to_string_lossy());
28            let paths: Vec<_> = env::split_paths(&env_path).collect();
29            let mut found_terminal = false;
30            for term in TERMINALS {
31                if paths.iter().any(|p| p.join(term).exists()) {
32                    let command = format!("{term} -e {run}");
33                    if run_command(&command, &path).is_ok() {
34                        trace!("Found and launched terminal: {term}");
35                        found_terminal = true;
36                        break;
37                    }
38                }
39            }
40            if !found_terminal {
41                bail!("Failed to find a terminal to run the command");
42            }
43        }
44    } else {
45        run_command(run, &path).context("Failed to run command")?;
46    };
47    Ok(())
48}
49
50fn get_command(command: &str) -> Command {
51    let mut command = command.to_string();
53    for replacement in ["%f", "%F", "%u", "%U"] {
54        command = command.replace(replacement, "");
55    }
56    if env::var_os("INVOCATION_ID").is_some() {
58        let mut cmd = Command::new("systemd-run");
59        cmd.args(["--user", "--scope", "--collect", "sh", "-c", &command]);
60        cmd
61    } else {
62        let mut cmd = Command::new("sh");
63        cmd.args(["-c", &command]);
64        cmd
65    }
66}
67
68fn run_command(run: &str, path: &Option<Box<Path>>) -> anyhow::Result<()> {
69    trace!("Original command: {:?}", run);
70    let mut cmd = get_command(run);
71    cmd.process_group(0);
72    if let Some(path) = path {
73        cmd.current_dir(path.as_ref());
74    }
75
76    debug!("Running command: {:?}", cmd);
77    let out = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
78    thread::spawn(move || {
79        let start = std::time::Instant::now();
80        let output = out.wait_with_output();
81        trace!("Command [{cmd:?}] finished");
82        if let Ok(output) = output {
83            if start.elapsed().as_secs() < 2 && !output.stdout.is_empty()
84                || !output.stderr.is_empty()
85            {
86                trace!("Output from [{cmd:?}]: {output:?}");
87            }
88        }
89    });
90    Ok(())
91}