diff --git a/src-tauri/src/commands/mod.rs b/src-tauri/src/commands/mod.rs index 2cb6e24f..c78bee2a 100644 --- a/src-tauri/src/commands/mod.rs +++ b/src-tauri/src/commands/mod.rs @@ -22,6 +22,9 @@ pub mod window; use serde::Serialize; use std::path::Path; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + /// Basic native host information for the workbench bootstrap. /// This replaces the subset of `INativeWindowConfiguration` needed at startup. #[derive(Serialize)] @@ -354,11 +357,14 @@ pub fn get_product_json(app_handle: tauri::AppHandle) -> Result String { _ => return "Unknown".to_string(), }; - match std::process::Command::new(&bun_path) - .arg("--version") - .output() - { + let mut bun_cmd = std::process::Command::new(&bun_path); + bun_cmd.arg("--version"); + #[cfg(windows)] + bun_cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + + match bun_cmd.output() { Ok(output) if output.status.success() => { let ver = String::from_utf8_lossy(&output.stdout).trim().to_string(); format!("Bun {ver}") @@ -495,7 +503,9 @@ use tauri::Manager; /// Cached theme colors persisted between sessions for the splash screen. #[derive(serde::Serialize, serde::Deserialize, Default)] struct ThemeColorCache { + /// CSS background color string (e.g. `"#1E1E1E"`). background: String, + /// CSS foreground color string (e.g. `"#CCCCCC"`). foreground: String, } diff --git a/src-tauri/src/commands/native_host/clipboard.rs b/src-tauri/src/commands/native_host/clipboard.rs index 64c02dca..c2ee2488 100644 --- a/src-tauri/src/commands/native_host/clipboard.rs +++ b/src-tauri/src/commands/native_host/clipboard.rs @@ -7,6 +7,9 @@ use super::error::NativeHostError; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + // ─── Existing commands (moved from native_host.rs) ────────────────────── /// Read text from the system clipboard. @@ -179,6 +182,10 @@ pub async fn trigger_paste() -> Result<(), NativeHostError> { // ─── Platform-specific helpers ────────────────────────────────────────── +/// Read text from the macOS "Find" pasteboard using `pbpaste -pboard find`. +/// +/// Returns `Some(text)` if the pasteboard contains text, `None` on failure +/// or if the pasteboard is empty. #[cfg(target_os = "macos")] fn macos_read_find_pasteboard() -> Option { use std::process::Command; @@ -193,6 +200,10 @@ fn macos_read_find_pasteboard() -> Option { } } +/// Write text to the macOS "Find" pasteboard using `pbcopy -pboard find`. +/// +/// Pipes the given `text` to `pbcopy` via stdin so that the "Find" pasteboard +/// is updated with the new search string. #[cfg(target_os = "macos")] fn macos_write_find_pasteboard(text: &str) -> Result<(), NativeHostError> { use std::io::Write; @@ -215,6 +226,11 @@ fn macos_write_find_pasteboard(text: &str) -> Result<(), NativeHostError> { Ok(()) } +/// Simulate a paste (Cmd+V) on macOS via AppleScript. +/// +/// Uses `osascript` to ask System Events to keystroke "v" with the command +/// modifier held down. This triggers a native paste in whichever application +/// currently has focus. #[cfg(target_os = "macos")] fn macos_trigger_paste() -> Result<(), NativeHostError> { use std::process::Command; @@ -233,6 +249,11 @@ fn macos_trigger_paste() -> Result<(), NativeHostError> { Ok(()) } +/// Simulate a paste (Ctrl+V) on Windows via PowerShell's `SendKeys`. +/// +/// Uses `System.Windows.Forms.SendKeys::SendWait('^v')` to synthesize a +/// Ctrl+V keystroke in the currently focused window. The PowerShell process +/// is spawned with `CREATE_NO_WINDOW` to avoid a visible console window. #[cfg(target_os = "windows")] fn windows_trigger_paste() -> Result<(), NativeHostError> { // Use PowerShell to send Ctrl+V @@ -241,6 +262,7 @@ fn windows_trigger_paste() -> Result<(), NativeHostError> { "-Command", "Add-Type -AssemblyName System.Windows.Forms; [System.Windows.Forms.SendKeys]::SendWait('^v')", ]) + .creation_flags(0x08000000) // CREATE_NO_WINDOW .output() .map_err(|e| NativeHostError::Other(format!("Failed to trigger paste: {e}")))?; if !output.status.success() { @@ -252,6 +274,10 @@ fn windows_trigger_paste() -> Result<(), NativeHostError> { Ok(()) } +/// Simulate a paste (Ctrl+V) on Linux via `xdotool`. +/// +/// Invokes `xdotool key ctrl+v` to synthesize a Ctrl+V keystroke in the +/// currently focused window. Requires `xdotool` to be installed on the system. #[cfg(target_os = "linux")] fn linux_trigger_paste() -> Result<(), NativeHostError> { let output = std::process::Command::new("xdotool") diff --git a/src-tauri/src/commands/native_host/shell.rs b/src-tauri/src/commands/native_host/shell.rs index 16025e18..13a73974 100644 --- a/src-tauri/src/commands/native_host/shell.rs +++ b/src-tauri/src/commands/native_host/shell.rs @@ -8,6 +8,9 @@ use super::error::NativeHostError; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + // ─── Existing commands (moved from native_host.rs) ────────────────────── /// Open a URL in the system's default browser/application. @@ -49,6 +52,7 @@ pub fn kill_process(pid: u32, code: String) -> Result<(), NativeHostError> { let _ = code; let output = std::process::Command::new("taskkill") .args(["/PID", &pid.to_string(), "/F"]) + .creation_flags(0x08000000) // CREATE_NO_WINDOW .output() .map_err(NativeHostError::Io)?; if !output.status.success() { diff --git a/src-tauri/src/exthost/sidecar.rs b/src-tauri/src/exthost/sidecar.rs index d0e091ff..43b49d61 100644 --- a/src-tauri/src/exthost/sidecar.rs +++ b/src-tauri/src/exthost/sidecar.rs @@ -13,6 +13,9 @@ use std::path::Path; use std::sync::Arc; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + use tokio::process::{Child, Command}; use tokio::sync::Mutex; @@ -61,12 +64,16 @@ fn resolve_runtime_binary() -> String { } // 3. System bun (development) - let which_cmd = if cfg!(target_os = "windows") { - "where" - } else { - "which" - }; - if let Ok(output) = std::process::Command::new(which_cmd).arg("bun").output() { + #[cfg(windows)] + let resolve_result = std::process::Command::new("where") + .arg("bun") + .creation_flags(0x08000000) // CREATE_NO_WINDOW + .output(); + + #[cfg(not(windows))] + let resolve_result = std::process::Command::new("which").arg("bun").output(); + + if let Ok(output) = resolve_result { if output.status.success() { let bun_path = String::from_utf8_lossy(&output.stdout).trim().to_string(); if !bun_path.is_empty() { @@ -322,11 +329,12 @@ fn augment_product_json(app_root: &Path) -> Option<(std::path::PathBuf, String)> .unwrap_or("") .is_empty() { - if let Ok(output) = std::process::Command::new("git") - .args(["rev-parse", "HEAD"]) - .current_dir(app_root) - .output() - { + let mut git_cmd = std::process::Command::new("git"); + git_cmd.args(["rev-parse", "HEAD"]).current_dir(app_root); + #[cfg(windows)] + git_cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + + if let Ok(output) = git_cmd.output() { if output.status.success() { let commit = String::from_utf8_lossy(&output.stdout).trim().to_string(); if let Some(obj) = product.as_object_mut() { @@ -511,8 +519,8 @@ fn spawn_child_process( let ext_nm_paths = build_ext_node_modules_paths(app_root, resource_dir); - let mut child = Command::new(&runtime_bin) - .arg("out/bootstrap-fork.js") + let mut cmd = Command::new(&runtime_bin); + cmd.arg("out/bootstrap-fork.js") .arg("--type=extensionHost") .current_dir(app_root) .env("PATH", &enriched_path) @@ -543,11 +551,16 @@ fn spawn_child_process( // NOT set (selecting wrong transport): // VSCODE_WILL_SEND_MESSAGE_PORT — Electron MessagePort path // VSCODE_EXTHOST_WILL_SEND_SOCKET — process.send() socket path - // Capture stderr to log ExtHost errors; inherit stdout for console output. - .stdout(std::process::Stdio::inherit()) - .stderr(std::process::Stdio::piped()) - .spawn() - .map_err(ExtHostError::Spawn)?; + // Discard stdout — the parent is a GUI process with no console in + // release builds; inheriting would trigger AllocConsole on Windows. + .stdout(std::process::Stdio::null()) + .stderr(std::process::Stdio::piped()); + + // Prevent Windows from allocating a console window for the child process. + #[cfg(windows)] + cmd.creation_flags(0x08000000); // CREATE_NO_WINDOW + + let mut child = cmd.spawn().map_err(ExtHostError::Spawn)?; let pid = child.id().unwrap_or(0); log::info!(