mirror of
https://github.com/l0ng-ai/tty7.git
synced 2026-09-22 08:02:24 +00:00
Merge pull request #129 from l0ng-ai/fix/windows-git-probe-console-flash
fix(windows): stop console windows flashing on every git status probe
This commit is contained in:
@@ -762,10 +762,9 @@ fn enable_codex_hooks_feature() -> Result<(), String> {
|
||||
.chain(home_dir().map(|h| h.join(".local/bin/codex")))
|
||||
.find(|p| p.exists());
|
||||
let program = candidates.unwrap_or_else(|| PathBuf::from("codex"));
|
||||
match std::process::Command::new(&program)
|
||||
.args(["features", "enable", "hooks"])
|
||||
.output()
|
||||
{
|
||||
let mut cmd = std::process::Command::new(&program);
|
||||
cmd.args(["features", "enable", "hooks"]);
|
||||
match crate::core::proc::hide_console(&mut cmd).output() {
|
||||
Ok(out) if out.status.success() => Ok(()),
|
||||
Ok(out) => Err(format!(
|
||||
"codex exited with {}: {}",
|
||||
|
||||
@@ -18,6 +18,7 @@ pub mod config;
|
||||
#[allow(dead_code)]
|
||||
pub mod keychain;
|
||||
pub mod osc;
|
||||
pub mod proc;
|
||||
pub mod session;
|
||||
pub mod shells;
|
||||
pub mod ssh_config;
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
//! One place for the Windows subprocess flag every helper shell-out needs.
|
||||
//!
|
||||
//! tty7 is a GUI process with no console of its own, so launching a console
|
||||
//! subsystem program (`git.exe`, `wsl.exe`, …) makes Windows allocate a fresh
|
||||
//! console for it — a black window that pops up and vanishes. That is invisible
|
||||
//! on a one-off invocation and very visible on the git-status probe, which runs
|
||||
//! four `git` calls every time a pane's cwd changes or a command ends.
|
||||
//!
|
||||
//! `CREATE_NO_WINDOW` suppresses the console entirely; stdout/stderr pipes are
|
||||
//! unaffected, so output capture keeps working. Every non-PTY `Command` in the
|
||||
//! app should go through [`hide_console`] (or [`hide_console_tokio`] for the
|
||||
//! async flavor) before it runs. PTY children are not in scope — the daemon
|
||||
//! owns those and passes its own flags (see [`crate::daemon::spawn`]).
|
||||
|
||||
use std::process::Command;
|
||||
|
||||
#[cfg(windows)]
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
|
||||
/// Suppress the console window Windows would otherwise allocate for a console
|
||||
/// subsystem child. No-op on Unix, so callers stay `cfg`-free.
|
||||
pub fn hide_console(cmd: &mut Command) -> &mut Command {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
|
||||
/// [`hide_console`] for `tokio::process::Command`. Separate because tokio's
|
||||
/// builder is a distinct type with its own `creation_flags`, not a `Deref` to
|
||||
/// the std one.
|
||||
pub fn hide_console_tokio(cmd: &mut tokio::process::Command) -> &mut tokio::process::Command {
|
||||
#[cfg(windows)]
|
||||
{
|
||||
cmd.creation_flags(CREATE_NO_WINDOW);
|
||||
}
|
||||
cmd
|
||||
}
|
||||
+5
-9
@@ -319,17 +319,13 @@ fn find_git_bash() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
/// Installed WSL distribution names via `wsl.exe -l -q`, or empty when WSL is
|
||||
/// absent. `CREATE_NO_WINDOW` keeps the probe from flashing a console window
|
||||
/// (we're a GUI process).
|
||||
/// absent. [`hide_console`](crate::core::proc::hide_console) keeps the probe
|
||||
/// from flashing a console window (we're a GUI process).
|
||||
#[cfg(windows)]
|
||||
fn list_wsl_distros() -> Vec<String> {
|
||||
use std::os::windows::process::CommandExt as _;
|
||||
const CREATE_NO_WINDOW: u32 = 0x0800_0000;
|
||||
let Ok(output) = std::process::Command::new("wsl.exe")
|
||||
.args(["-l", "-q"])
|
||||
.creation_flags(CREATE_NO_WINDOW)
|
||||
.output()
|
||||
else {
|
||||
let mut cmd = std::process::Command::new("wsl.exe");
|
||||
cmd.args(["-l", "-q"]);
|
||||
let Ok(output) = crate::core::proc::hide_console(&mut cmd).output() else {
|
||||
return Vec::new();
|
||||
};
|
||||
if !output.status.success() {
|
||||
|
||||
@@ -62,10 +62,9 @@ pub fn is_inside_repo(cwd: &Path) -> bool {
|
||||
/// Run `git -C <dir> <args>`, returning trimmed stdout on success and trimmed
|
||||
/// stderr as the error otherwise.
|
||||
fn git(dir: &Path, args: &[&str]) -> Result<String, String> {
|
||||
let out = std::process::Command::new("git")
|
||||
.arg("-C")
|
||||
.arg(dir)
|
||||
.args(args)
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
cmd.arg("-C").arg(dir).args(args);
|
||||
let out = crate::core::proc::hide_console(&mut cmd)
|
||||
.output()
|
||||
.map_err(|e| format!("failed to run git: {e}"))?;
|
||||
if out.status.success() {
|
||||
|
||||
@@ -173,6 +173,10 @@ fn spawn_proxy_command(
|
||||
.stdout(std::process::Stdio::piped())
|
||||
.stderr(std::process::Stdio::inherit())
|
||||
.kill_on_drop(true);
|
||||
// The daemon is detached and has no console to lend this child, so without
|
||||
// the flag a `ProxyCommand` (`ssh -W`, `connect.exe`, `cloudflared`) gets a
|
||||
// console of its own that stays up for the whole session.
|
||||
crate::core::proc::hide_console_tokio(&mut cmd);
|
||||
let mut child = cmd
|
||||
.spawn()
|
||||
.map_err(|e| anyhow::anyhow!("spawn ProxyCommand failed: {e}"))?;
|
||||
|
||||
@@ -251,18 +251,19 @@ fn diff_numstat(cwd: &Path) -> Option<(u32, u32)> {
|
||||
/// Run `git -C <cwd> <args>` and return stdout on success, `None` on a
|
||||
/// non-zero exit or a missing `git`. `GIT_OPTIONAL_LOCKS=0` makes the read
|
||||
/// truly read-only; stdin is nulled so a misconfigured git can't block on a
|
||||
/// prompt. Shared with [`git_diff`](crate::terminal::git_diff) so every git
|
||||
/// read in the app goes through the same lock-free, prompt-proof invocation.
|
||||
/// prompt; `hide_console` keeps this GUI process from flashing a console window
|
||||
/// on Windows for every probe. Shared with [`git_diff`](crate::terminal::git_diff)
|
||||
/// so every git read in the app goes through the same lock-free, prompt-proof
|
||||
/// invocation.
|
||||
pub(crate) fn git(cwd: &Path, args: &[&str]) -> Option<String> {
|
||||
let out = Command::new("git")
|
||||
.arg("-C")
|
||||
let mut cmd = Command::new("git");
|
||||
cmd.arg("-C")
|
||||
.arg(cwd)
|
||||
.args(args)
|
||||
.env("GIT_OPTIONAL_LOCKS", "0")
|
||||
.stdin(Stdio::null())
|
||||
.stderr(Stdio::null())
|
||||
.output()
|
||||
.ok()?;
|
||||
.stderr(Stdio::null());
|
||||
let out = crate::core::proc::hide_console(&mut cmd).output().ok()?;
|
||||
if !out.status.success() {
|
||||
return None;
|
||||
}
|
||||
|
||||
+3
-3
@@ -3048,9 +3048,9 @@ impl Tty7App {
|
||||
// which is what a review pass wants. Both invocations are quick; the
|
||||
// prompt builder caps runaway diffs.
|
||||
let run = |args: &[&str]| {
|
||||
std::process::Command::new("git")
|
||||
.args(args)
|
||||
.current_dir(&cwd)
|
||||
let mut cmd = std::process::Command::new("git");
|
||||
cmd.args(args).current_dir(&cwd);
|
||||
crate::core::proc::hide_console(&mut cmd)
|
||||
.output()
|
||||
.ok()
|
||||
.filter(|o| o.status.success())
|
||||
|
||||
Reference in New Issue
Block a user