Skip to main content

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