Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions src-tauri/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -354,11 +357,14 @@ pub fn get_product_json(app_handle: tauri::AppHandle) -> Result<ProductPackageJs
.unwrap_or("")
.is_empty()
{
if let Ok(output) = std::process::Command::new("git")
let mut git_cmd = std::process::Command::new("git");
git_cmd
.args(["rev-parse", "HEAD"])
.current_dir(&project_root)
.output()
{
.current_dir(&project_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() {
Expand Down Expand Up @@ -476,10 +482,12 @@ fn detect_runtime_label() -> 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}")
Expand All @@ -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,
}

Expand Down
26 changes: 26 additions & 0 deletions src-tauri/src/commands/native_host/clipboard.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<String> {
use std::process::Command;
Expand All @@ -193,6 +200,10 @@ fn macos_read_find_pasteboard() -> Option<String> {
}
}

/// 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;
Expand All @@ -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;
Expand All @@ -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
Expand All @@ -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() {
Expand All @@ -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")
Expand Down
4 changes: 4 additions & 0 deletions src-tauri/src/commands/native_host/shell.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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() {
Expand Down
49 changes: 31 additions & 18 deletions src-tauri/src/exthost/sidecar.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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() {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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!(
Expand Down