Skip to main content

dev_prune/
spawn.rs

1// Copyright 2026 VKrishna04
2// SPDX-License-Identifier: Apache-2.0
3
4// Building child processes that stay invisible when this process is.
5//
6// On Windows a console-subsystem child of a process that has no console — the scheduled
7// `devpw` pass, or anything launched from a GUI — gets a brand-new console window of its
8// own, visible to whoever is logged in. A background prune that spawns `git` per
9// repository would flash a black window per repository, which is exactly the behaviour
10// the windowless scheduler binary exists to prevent. `CREATE_NO_WINDOW` suppresses the
11// allocation; it is applied only when this process has no console, so interactive runs
12// spawn children exactly as they always did.
13
14use std::ffi::OsStr;
15use std::process::Command;
16
17/// A `Command` that will not flash a console window when this process has none.
18///
19/// Every child that can be spawned on Windows is built here. The launchd and systemd
20/// calls go straight to `Command::new`, which is the same thing there: the policy
21/// below is a no-op off Windows, and neither platform has a console to inherit.
22///
23/// What holds on every platform is the other half. Every child is waited on, and
24/// nothing in this program starts a process that outlives the command that started
25/// it -- there is no detached helper anywhere, which is deliberate and is checked by
26/// grepping for `.spawn()`.
27pub fn command<S: AsRef<OsStr>>(program: S) -> Command {
28    let mut cmd = Command::new(program);
29    apply_window_policy(&mut cmd);
30    cmd
31}
32
33#[cfg(windows)]
34fn apply_window_policy(cmd: &mut Command) {
35    use std::os::windows::process::CommandExt;
36    use std::sync::OnceLock;
37
38    /// Documented value of `CREATE_NO_WINDOW`.
39    const CREATE_NO_WINDOW: u32 = 0x0800_0000;
40
41    // A process gains or loses its console only through calls this crate never makes,
42    // so the answer cannot change over a run.
43    static HAS_CONSOLE: OnceLock<bool> = OnceLock::new();
44    let has_console = *HAS_CONSOLE.get_or_init(|| {
45        // SAFETY: `GetConsoleWindow` reads process state and takes no arguments.
46        !unsafe { windows_sys::Win32::System::Console::GetConsoleWindow() }.is_null()
47    });
48    if !has_console {
49        cmd.creation_flags(CREATE_NO_WINDOW);
50    }
51}
52
53#[cfg(not(windows))]
54fn apply_window_policy(_cmd: &mut Command) {}