fix(windows): hide subprocess console windows

This commit is contained in:
t8y2
2026-07-10 09:50:18 +08:00
parent b44882d7ba
commit 6671400fd4
15 changed files with 63 additions and 74 deletions
+1 -1
View File
@@ -1325,7 +1325,7 @@ fn db_type_for_native_offline_entry(registry: &AgentRegistry, platform: &str, na
fn extract_tar_gz(archive: &Path, dest: &Path) -> Result<(), String> {
std::fs::create_dir_all(dest).map_err(|e| e.to_string())?;
let status = std::process::Command::new("tar")
let status = crate::process::new_std_command("tar")
.args(["xzf", &archive.to_string_lossy(), "-C", &dest.to_string_lossy(), "--strip-components=1"])
.status()
.map_err(|e| format!("Failed to extract archive: {e}"))?;
+1 -16
View File
@@ -8,9 +8,6 @@ use tokio::io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader};
use tokio::process::Command;
use tokio::sync::Notify;
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
#[derive(Debug, Clone)]
pub struct CliAgentRunOptions {
pub connection_id: String,
@@ -103,19 +100,7 @@ pub fn model_infos(ids: &[&str]) -> Vec<AiModelInfo> {
}
pub fn cli_command(program: impl AsRef<OsStr>) -> Command {
let command = Command::new(program);
configure_cli_command(command)
}
#[cfg(windows)]
fn configure_cli_command(mut command: Command) -> Command {
command.creation_flags(CREATE_NO_WINDOW);
command
}
#[cfg(not(windows))]
fn configure_cli_command(command: Command) -> Command {
command
crate::process::new_tokio_command(program)
}
pub async fn list_json_models_or_default(
+2 -3
View File
@@ -11,7 +11,6 @@ use std::env;
use std::path::{Path, PathBuf};
use std::process::Stdio;
use std::time::Instant;
use tokio::process::Command;
use tokio::sync::Notify;
const DEFAULT_CODEX_MODELS: &[&str] = &["default", "gpt-5.5", "gpt-5.4-mini"];
@@ -186,7 +185,7 @@ fn program_path_candidates(dir: &Path, program: &str) -> Vec<PathBuf> {
#[cfg(not(windows))]
async fn shell_program_path(program: &str) -> Option<String> {
let script = shell_resolve_script(program);
let mut command = Command::new(user_shell());
let mut command = cli_command(user_shell());
command.args(user_shell_args(&script));
command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::null());
let output = command.output().await.ok()?;
@@ -204,7 +203,7 @@ async fn shell_program_path(program: &str) -> Option<String> {
#[cfg(windows)]
async fn shell_program_path(program: &str) -> Option<String> {
let script = format!("(Get-Command -All {} -ErrorAction SilentlyContinue).Source", windows_shell_quote(program));
let mut command = Command::new("powershell.exe");
let mut command = cli_command("powershell.exe");
command.args(["-NoProfile", "-Command", &script]);
command.stdin(Stdio::null()).stdout(Stdio::piped()).stderr(Stdio::null());
let output = command.output().await.ok()?;
+1 -9
View File
@@ -19,8 +19,6 @@ const AGENT_EXIT_DIAGNOSTIC_POLL_MS: u64 = 10;
const AGENT_JAVA_OPTS_ENV: &str = "DBX_AGENT_JAVA_OPTS";
const AGENT_JAVA_TOO_OLD_MESSAGE: &str =
"Agent requires Java 21, but DBX started it with an older Java runtime. Use DBX managed JRE 21 or select a Java 21 executable in Driver Manager.";
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
pub struct AgentDriverClient {
child: Child,
@@ -360,19 +358,13 @@ impl AgentDriverClient {
/// they speak the DBX stdin/stdout JSON-RPC protocol.
/// Blocks (async) until the agent writes `{"ready":true}` to stdout.
pub async fn spawn(launch: AgentLaunchSpec) -> Result<Self, String> {
let mut command = Command::new(&launch.program);
let mut command = crate::process::new_std_command(&launch.program);
command.args(&launch.args).stdin(Stdio::piped()).stdout(Stdio::piped()).stderr(Stdio::piped());
if let Some(working_dir) = &launch.working_dir {
command.current_dir(working_dir);
}
remove_agent_proxy_env(&mut command);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
command.creation_flags(CREATE_NO_WINDOW);
}
let mut child =
command.spawn().map_err(|e| format!("Failed to spawn agent process {}: {e}", launch_display(&launch)))?;
@@ -8,7 +8,7 @@ use std::sync::{Arc, Mutex as StdMutex, OnceLock};
use std::time::Duration;
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::process::{Child, ChildStdin, Command};
use tokio::process::{Child, ChildStdin};
use tokio::sync::{oneshot, Mutex, OwnedSemaphorePermit, Semaphore};
use tokio_util::sync::CancellationToken;
@@ -389,7 +389,7 @@ impl DuckDbWorkerClient {
.map_err(|_| "DuckDB worker process limiter is closed".to_string())?;
log::info!("[duckdb-worker:start] executable={}", self.inner.executable.display());
let mut child = Command::new(&self.inner.executable)
let mut child = crate::process::new_tokio_command(&self.inner.executable)
.arg("--duckdb-worker")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
+1 -2
View File
@@ -5,7 +5,6 @@ use crate::update::{fetch_latest_release, is_newer_version, JdbcPluginLatest};
use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use std::path::{Path, PathBuf};
use tokio::process::Command;
const JDBC_PLUGIN_DOWNLOAD_URL: &str =
"https://github.com/t8y2/dbx/releases/latest/download/dbx-jdbc-plugin-latest.zip";
@@ -153,7 +152,7 @@ pub async fn install_jdbc_driver_from_maven(
let local_repo = plugin_dir.join("maven-cache");
std::fs::create_dir_all(&local_repo).map_err(|err| err.to_string())?;
let mut command = Command::new(&resolver);
let mut command = crate::process::new_tokio_command(&resolver);
env.apply_to(&mut command);
command.arg("resolve").arg("--coordinate").arg(&coordinate).arg("--local-repo").arg(&local_repo);
for repo in &repositories {
+1
View File
@@ -34,6 +34,7 @@ pub mod nacos;
pub mod object_source_sql;
pub mod path_utils;
pub mod plugins;
pub mod process;
pub mod query;
pub mod query_cancel;
pub mod query_execution_sql;
+1 -10
View File
@@ -12,8 +12,6 @@ use tokio::sync::Mutex;
use tokio::time::timeout;
const PLUGIN_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
pub const SUPPORTED_PLUGIN_PROTOCOL_VERSION: u32 = 1;
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -412,7 +410,7 @@ fn spawn_plugin_child(plugin: &InstalledPlugin, env: &PluginRuntimeEnv) -> Resul
.ok_or_else(|| format!("Plugin '{}' does not declare an executable", plugin.manifest.id))?;
let executable_path = resolve_plugin_executable(&plugin.path, executable);
let mut command = Command::new(&executable_path);
let mut command = crate::process::new_tokio_command(&executable_path);
command
.current_dir(&plugin.path)
.stdin(Stdio::piped())
@@ -421,13 +419,6 @@ fn spawn_plugin_child(plugin: &InstalledPlugin, env: &PluginRuntimeEnv) -> Resul
.kill_on_drop(true);
env.apply_to(&mut command);
#[cfg(windows)]
{
#[allow(unused_imports)]
use std::os::windows::process::CommandExt;
command.creation_flags(CREATE_NO_WINDOW);
}
command.spawn().map_err(|err| format!("Failed to start plugin '{}': {err}", plugin.manifest.id))
}
+37
View File
@@ -0,0 +1,37 @@
use std::ffi::OsStr;
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
pub fn new_std_command(program: impl AsRef<OsStr>) -> std::process::Command {
let mut command = std::process::Command::new(program);
hide_std_console_window(&mut command);
command
}
pub fn hide_std_console_window(command: &mut std::process::Command) {
#[cfg(not(windows))]
let _ = command;
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
// DBX is a GUI app; console subprocesses should not flash a window.
command.creation_flags(CREATE_NO_WINDOW);
}
}
pub fn new_tokio_command(program: impl AsRef<OsStr>) -> tokio::process::Command {
let mut command = tokio::process::Command::new(program);
hide_tokio_console_window(&mut command);
command
}
pub fn hide_tokio_console_window(command: &mut tokio::process::Command) {
#[cfg(not(windows))]
let _ = command;
#[cfg(windows)]
{
// Keep async child processes consistent with std::process::Command.
command.creation_flags(CREATE_NO_WINDOW);
}
}
+5 -14
View File
@@ -113,7 +113,7 @@ pub fn system_proxy_url() -> Option<String> {
#[cfg(target_os = "macos")]
fn system_proxy_url_from_platform() -> Option<String> {
let output = std::process::Command::new("scutil").arg("--proxy").output().ok()?;
let output = crate::process::new_std_command("scutil").arg("--proxy").output().ok()?;
if !output.status.success() {
return None;
}
@@ -123,20 +123,11 @@ fn system_proxy_url_from_platform() -> Option<String> {
#[cfg(target_os = "windows")]
fn system_proxy_url_from_platform() -> Option<String> {
use std::os::windows::process::CommandExt;
let key = r"HKCU\Software\Microsoft\Windows\CurrentVersion\Internet Settings";
const CREATE_NO_WINDOW: u32 = 0x08000000;
let proxy_enable = std::process::Command::new("reg")
.args(["query", key, "/v", "ProxyEnable"])
.creation_flags(CREATE_NO_WINDOW)
.output()
.ok()?;
let proxy_server = std::process::Command::new("reg")
.args(["query", key, "/v", "ProxyServer"])
.creation_flags(CREATE_NO_WINDOW)
.output()
.ok()?;
let proxy_enable =
crate::process::new_std_command("reg").args(["query", key, "/v", "ProxyEnable"]).output().ok()?;
let proxy_server =
crate::process::new_std_command("reg").args(["query", key, "/v", "ProxyServer"]).output().ok()?;
if !proxy_enable.status.success() || !proxy_server.status.success() {
return None;
}
+3 -3
View File
@@ -16,7 +16,7 @@ use dbx_core::path_utils::expand_tilde;
pub fn reveal_in_file_manager(path: &Path) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
std::process::Command::new("open")
dbx_core::process::new_std_command("open")
.arg("-R")
.arg(path)
.spawn()
@@ -30,7 +30,7 @@ pub fn reveal_in_file_manager(path: &Path) -> Result<(), String> {
// after the comma. `Command::arg` does not invoke a shell, so the path
// is forwarded as-is — spaces and non-ASCII characters survive.
let arg = format!("/select,{}", path.display());
std::process::Command::new("explorer")
dbx_core::process::new_std_command("explorer")
.arg(arg)
.spawn()
.map(|_| ())
@@ -40,7 +40,7 @@ pub fn reveal_in_file_manager(path: &Path) -> Result<(), String> {
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
{
let target: PathBuf = path.parent().map(|p| p.to_path_buf()).unwrap_or_else(|| path.to_path_buf());
std::process::Command::new("xdg-open")
dbx_core::process::new_std_command("xdg-open")
.arg(&target)
.spawn()
.map(|_| ())
+1 -1
View File
@@ -18,7 +18,7 @@ pub async fn read_keychain_password(service: String, account: Option<String>) ->
#[cfg(target_os = "macos")]
fn read_keychain_password_blocking(service: String, account: Option<String>) -> Result<String, String> {
let mut cmd = std::process::Command::new("security");
let mut cmd = dbx_core::process::new_std_command("security");
cmd.args(["find-generic-password", "-s", &service, "-w"]);
if let Some(ref acct) = account {
cmd.args(["-a", acct]);
+1 -9
View File
@@ -1,7 +1,6 @@
#[cfg(not(windows))]
use std::env;
use std::path::Path;
use std::process::Command;
use std::time::Duration;
use serde::{Deserialize, Serialize};
@@ -10,8 +9,6 @@ const MCP_PACKAGE_NAME: &str = "@dbx-app/mcp-server";
const MCP_LATEST_URL: &str = "https://registry.npmjs.org/@dbx-app%2fmcp-server/latest";
const MCP_INSTALL_COMMAND: &str = "npm install -g @dbx-app/mcp-server@latest --registry=https://registry.npmjs.org";
const SHELL_COMMAND_MARKER: &str = "__DBX_MCP_COMMAND_OUTPUT_START__";
#[cfg(windows)]
const CREATE_NO_WINDOW: u32 = 0x08000000;
#[derive(Debug, Serialize)]
pub struct McpServerStatus {
@@ -238,13 +235,8 @@ fn command_output(command: &str, args: &[&str]) -> Result<CommandOutput, String>
}
fn run_command(command: &str, args: &[&str]) -> Result<CommandOutput, String> {
let mut cmd = Command::new(command);
let mut cmd = dbx_core::process::new_std_command(command);
cmd.args(args);
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
cmd.creation_flags(CREATE_NO_WINDOW);
}
let output = cmd.output().map_err(|e| e.to_string())?;
Ok(CommandOutput {
success: output.status.success(),
+3 -3
View File
@@ -206,21 +206,21 @@ fn unique_file_path(dir: &Path, file_name: &str) -> PathBuf {
fn open_path(path: &Path) -> Result<(), String> {
#[cfg(target_os = "macos")]
let mut command = {
let mut command = std::process::Command::new("open");
let mut command = dbx_core::process::new_std_command("open");
command.arg(path);
command
};
#[cfg(target_os = "windows")]
let mut command = {
let mut command = std::process::Command::new("explorer");
let mut command = dbx_core::process::new_std_command("explorer");
command.arg(path);
command
};
#[cfg(all(not(target_os = "macos"), not(target_os = "windows")))]
let mut command = {
let mut command = std::process::Command::new("xdg-open");
let mut command = dbx_core::process::new_std_command("xdg-open");
command.arg(path);
command
};
+3 -1
View File
@@ -127,7 +127,9 @@ fn platform_product_version() -> Option<String> {
#[cfg(any(target_os = "macos", target_os = "windows"))]
fn command_first_line(command: &str, args: &[&str]) -> Option<String> {
let output = std::process::Command::new(command).args(args).output().ok()?;
let mut process = dbx_core::process::new_std_command(command);
process.args(args);
let output = process.output().ok()?;
if !output.status.success() {
return None;
}