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<&Path>,
13 terminal: bool,
14 default_terminal: Option<&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([
60 "--user",
61 "--scope",
62 "--collect",
63 "/usr/bin/env",
64 "bash",
65 "-c",
66 &command,
67 ]);
68 cmd
69 } else {
70 let mut cmd = Command::new("/usr/bin/env");
71 cmd.args(["bash", "-c", &command]);
72 cmd
73 }
74}
75
76fn run_command(run: &str, path: Option<&Path>) -> anyhow::Result<()> {
77 trace!("Original command: {run:?}");
78 let mut cmd = get_command(run);
79 cmd.process_group(0);
80 if let Some(path) = path {
81 cmd.current_dir(path);
82 }
83
84 debug!("Running command: {cmd:?}");
85 let out = cmd.stdout(Stdio::piped()).stderr(Stdio::piped()).spawn()?;
86 thread::spawn(move || {
87 let start = std::time::Instant::now();
88 let output = out.wait_with_output();
89 trace!("Command [{cmd:?}] finished");
90 if let Ok(output) = output {
91 if start.elapsed().as_secs() < 2
92 && (!output.stdout.is_empty() || !output.stderr.is_empty())
93 {
94 trace!("Output from [{cmd:?}]: {output:?}");
95 }
96 }
97 });
98 Ok(())
99}