Skip to main content

rightkit_child_process/
no_window.rs

1//! Hide the console window of a child you build yourself (`std::process::Command`, and
2//! `tokio::process::Command` with the `tokio` feature), without going through
3//! [`OwnedCommand`](crate::OwnedCommand).
4
5/// The Windows `CREATE_NO_WINDOW` creation flag, exposed on every platform so a caller that
6/// composes its own flags can OR it in.
7pub const CREATE_NO_WINDOW: u32 = 0x0800_0000;
8
9/// The Windows `CREATE_BREAKAWAY_FROM_JOB` creation flag, exposed on every platform so a caller
10/// that composes its own flags (for example a deliberately detached replacement process) can OR
11/// it in. It asks the new process to leave the creator's job object; the launch fails with
12/// access denied when that job forbids breakaway.
13pub const CREATE_BREAKAWAY_FROM_JOB: u32 = 0x0100_0000;
14
15/// A `std::process::Command` for `program` that already has the no-window flag set on Windows,
16/// returned by value so it chains: `command_std("git").args(["status"]).output()`. Same effect as
17/// `Command::new(program)` followed by [`NoWindow::no_window`]; on Unix the child also inherits
18/// no descriptor above stderr (see `SanitizeDescriptors`; pass
19/// descriptors with a later `sanitize_descriptors(&[fd])`). The async variant is
20/// `tokio_support::command` (feature `tokio`).
21pub fn command_std(program: impl AsRef<std::ffi::OsStr>) -> std::process::Command {
22    let mut command = std::process::Command::new(program);
23    command.no_window();
24    // Unix: the child inherits no descriptor above stderr (Orthic-Labs/rightkit#835).
25    #[cfg(unix)]
26    crate::SanitizeDescriptors::sanitize_descriptors(&mut command, &[]);
27    command
28}
29
30/// Suppresses the child's console window on Windows. A no-op elsewhere.
31///
32/// On Windows this calls `creation_flags(CREATE_NO_WINDOW)`, which *replaces* any creation
33/// flags set earlier on the same command (that is how `creation_flags` works for both std and
34/// tokio). Call it first and OR any further flags yourself with [`CREATE_NO_WINDOW`], or call
35/// `creation_flags(CREATE_NO_WINDOW | other)` directly.
36pub trait NoWindow {
37    /// Sets the no-window creation flag. Returns `self` for chaining.
38    fn no_window(&mut self) -> &mut Self;
39}
40
41impl NoWindow for std::process::Command {
42    fn no_window(&mut self) -> &mut Self {
43        #[cfg(windows)]
44        {
45            use std::os::windows::process::CommandExt;
46            self.creation_flags(CREATE_NO_WINDOW);
47        }
48        self
49    }
50}
51
52#[cfg(test)]
53mod tests {
54    use super::*;
55
56    #[test]
57    fn the_flag_value_is_the_windows_constant() {
58        assert_eq!(CREATE_NO_WINDOW, 0x0800_0000);
59    }
60
61    #[test]
62    fn breakaway_flag_is_the_windows_constant() {
63        assert_eq!(CREATE_BREAKAWAY_FROM_JOB, 0x0100_0000);
64    }
65
66    #[test]
67    fn command_std_returns_a_ready_command_by_value() {
68        let command = command_std(if cfg!(windows) { "cmd" } else { "true" });
69        assert_eq!(
70            command.get_program(),
71            if cfg!(windows) { "cmd" } else { "true" }
72        );
73    }
74
75    #[test]
76    fn a_std_command_chains_and_still_runs() {
77        let mut command = if cfg!(windows) {
78            let mut c = std::process::Command::new("cmd");
79            c.args(["/C", "exit 0"]);
80            c
81        } else {
82            std::process::Command::new("true")
83        };
84        let status = command.no_window().status().expect("run");
85        assert!(status.success());
86    }
87}