From 289961c5a69d1fbf068644d385ebb77364abf6a6 Mon Sep 17 00:00:00 2001 From: Wess Cope Date: Tue, 4 Aug 2026 17:14:11 -0400 Subject: [PATCH] Stop plugin exec flashing a console window on Windows Sinclair is a GUI app, so a console program it spawns pops a console window for as long as it runs. Every `exec` from a plugin does that - a git panel refreshing on a timer would flicker a console at you each time - and so does the curl behind `fetch`, and the taskkill that ends a timed-out program. All three now pass CREATE_NO_WINDOW. No-op off Windows, where the question does not arise. Also drops the `plugins\notes` staging from the Windows packaging that 1.32.1 fixed, and its matching MSI component, so the comment above the script no longer describes files that stopped existing. --- crates/app/src/wasmhost.rs | 34 ++++++++++++++++++++++++++-------- scripts/windows.ps1 | 4 ++-- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/crates/app/src/wasmhost.rs b/crates/app/src/wasmhost.rs index 713c472..68e2528 100644 --- a/crates/app/src/wasmhost.rs +++ b/crates/app/src/wasmhost.rs @@ -140,6 +140,7 @@ pub(crate) fn exec( if let Some(dir) = request.cwd.as_deref().map(std::path::PathBuf::from).or(default_cwd) { cmd.current_dir(dir); } + no_console(&mut cmd); // Own session, so a timeout kill takes the whole tree rather than leaving // orphaned grandchildren behind (mirrors the pty spawn). #[cfg(unix)] @@ -201,7 +202,24 @@ fn clamp(bytes: &[u8]) -> String { String::from_utf8_lossy(&bytes[..end]).into_owned() } -/// SIGKILL a process group (see the `setsid` above). +/// Keep a spawned console program from flashing a window. Sinclair is a GUI +/// app, so every `exec` from a plugin — a panel refreshing `git status` on a +/// timer, say — would otherwise pop a console for as long as the program runs. +/// No-op off Windows, where the question does not arise. +fn no_console(cmd: &mut Command) { + #[cfg(windows)] + { + use std::os::windows::process::CommandExt; + // CREATE_NO_WINDOW + cmd.creation_flags(0x0800_0000); + } + #[cfg(not(windows))] + let _ = cmd; +} + +/// End the program and anything it started. Unix kills the process group the +/// `setsid` above put it in; Windows has no such grouping, so `taskkill` walks +/// the child tree instead. fn kill_tree(pid: u32) { #[cfg(unix)] unsafe { @@ -209,11 +227,12 @@ fn kill_tree(pid: u32) { } #[cfg(windows)] { - let _ = Command::new("taskkill") - .args(["/PID", &pid.to_string(), "/F", "/T"]) + let mut cmd = Command::new("taskkill"); + cmd.args(["/PID", &pid.to_string(), "/F", "/T"]) .stdout(Stdio::null()) - .stderr(Stdio::null()) - .status(); + .stderr(Stdio::null()); + no_console(&mut cmd); + let _ = cmd.status(); } } @@ -307,10 +326,9 @@ pub(crate) fn fetch(request: HttpRequest) -> Result { } // `--` so a url beginning with `-` can't be read as an option. + cmd.arg("--").arg(&request.url).stdin(Stdio::null()); + no_console(&mut cmd); let out = cmd - .arg("--") - .arg(&request.url) - .stdin(Stdio::null()) .output() .map_err(|e| format!("curl: {e}"))?; diff --git a/scripts/windows.ps1 b/scripts/windows.ps1 index a717e73..c44ee14 100644 --- a/scripts/windows.ps1 +++ b/scripts/windows.ps1 @@ -4,8 +4,8 @@ # # The binary is the `sinclairdev` bin from crates/app, installed as `sinclair.exe`; # the Notes sidecar (`notes.exe`) ships beside it. The version is read from the -# workspace Cargo.toml. Builds natively for the host -# architecture — pass x86_64 or aarch64 only to label artifacts and pick the +# workspace Cargo.toml. Builds natively for the host architecture — pass x86_64 +# or aarch64 only to label artifacts and pick the # target triple. # # The MSI is built with the WiX v4 toolset (installed on demand as a dotnet