hyprshell_exec_lib/
run.rs

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