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
21 changes: 19 additions & 2 deletions BitFun-Installer/src-tauri/src/installer/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ const INSTALLER_STATE_FILE: &str = "installer-state.json";
const EMBEDDED_PAYLOAD_ZIP: &[u8] =
include_bytes!(concat!(env!("OUT_DIR"), "/embedded_payload.zip"));

#[cfg(target_os = "windows")]
fn create_windows_silent_command<S: AsRef<std::ffi::OsStr>>(program: S) -> std::process::Command {
use std::os::windows::process::CommandExt;

const CREATE_NO_WINDOW: u32 = 0x08000000;

let mut command = std::process::Command::new(program);
command.creation_flags(CREATE_NO_WINDOW);
command
}

struct InstallerAppLanguage {
code: &'static str,
aliases: &'static [&'static str],
Expand Down Expand Up @@ -566,14 +577,13 @@ exit /b 1
log_path.display()
));

let child = std::process::Command::new("cmd")
let child = create_windows_silent_command("cmd")
.arg("/C")
.arg("call")
.arg(&script_path)
.arg(uninstall_exe_path)
.arg(&log_path)
.current_dir(&temp_dir)
.creation_flags(CREATE_NO_WINDOW)
.spawn()
.map_err(|e| format!("Failed to schedule uninstall cleanup: {}", e))?;

Expand Down Expand Up @@ -622,6 +632,13 @@ pub fn launch_application(install_path: String) -> Result<(), String> {
PathBuf::from(&install_path).join("bitfun")
};

#[cfg(target_os = "windows")]
create_windows_silent_command(&exe)
.current_dir(&install_path)
.spawn()
.map_err(|e| format!("Failed to launch BitFun: {}", e))?;

#[cfg(not(target_os = "windows"))]
std::process::Command::new(&exe)
.current_dir(&install_path)
.spawn()
Expand Down
10 changes: 6 additions & 4 deletions src/apps/desktop/src/api/remote_connect_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ use bitfun_core::service::remote_connect::{
use regex::Regex;
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use std::process::Command;
use std::sync::{Arc, OnceLock};
use tokio::sync::RwLock;

Expand Down Expand Up @@ -238,7 +237,7 @@ pub struct LanNetworkInfo {
fn detect_default_gateway_ip() -> Option<String> {
#[cfg(target_os = "macos")]
{
let output = Command::new("route")
let output = bitfun_core::util::process_manager::create_command("route")
.args(["-n", "get", "default"])
.output()
.ok()?;
Expand All @@ -254,7 +253,7 @@ fn detect_default_gateway_ip() -> Option<String> {

#[cfg(target_os = "linux")]
{
let output = Command::new("ip")
let output = bitfun_core::util::process_manager::create_command("ip")
.args(["route", "show", "default"])
.output()
.ok()?;
Expand All @@ -270,7 +269,10 @@ fn detect_default_gateway_ip() -> Option<String> {

#[cfg(target_os = "windows")]
{
let output = Command::new("route").args(["print", "-4"]).output().ok()?;
let output = bitfun_core::util::process_manager::create_command("route")
.args(["print", "-4"])
.output()
.ok()?;
if !output.status.success() {
return None;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Detect and launch the user's default browser with CDP debug port enabled.

use crate::util::errors::{BitFunError, BitFunResult};
use crate::util::{errors::{BitFunError, BitFunResult}, process_manager};
#[allow(unused_imports)]
use log::{debug, info};
use serde::{Deserialize, Serialize};
Expand All @@ -10,28 +10,10 @@ use std::time::Duration;
/// Default CDP debug port.
pub const DEFAULT_CDP_PORT: u16 = 9222;

/// Windows: suppress the brief console window that flashes when a GUI process
/// spawns a console subprocess (e.g. `reg.exe`, `tasklist.exe`).
/// Equivalent to passing `CREATE_NO_WINDOW` (0x08000000) to CreateProcess.
#[cfg(target_os = "windows")]
const CREATE_NO_WINDOW: u32 = 0x0800_0000;

/// Build a `Command` that on Windows is configured with `CREATE_NO_WINDOW`
/// so no console window briefly pops up. On other platforms behaves like
/// `Command::new`.
/// Build a `Command` that suppresses transient Windows console windows while
/// preserving normal process behavior on other platforms.
fn silent_command<S: AsRef<std::ffi::OsStr>>(program: S) -> Command {
let cmd = Command::new(program);
#[cfg(target_os = "windows")]
{
use std::os::windows::process::CommandExt;
let mut cmd = cmd;
cmd.creation_flags(CREATE_NO_WINDOW);
cmd
}
#[cfg(not(target_os = "windows"))]
{
cmd
}
process_manager::create_command(program)
}

/// Known browser identifiers and their executable paths per platform.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::agentic::tools::computer_use_host::{
use crate::agentic::tools::framework::{Tool, ToolResult, ToolUseContext};
use crate::util::elapsed_ms_u64;
use crate::util::errors::{BitFunError, BitFunResult};
use crate::util::process_manager;
use serde_json::{json, Value};

use super::control_hub::{err_response, ControlHubError, ErrorCode};
Expand Down Expand Up @@ -1643,7 +1644,7 @@ impl ComputerUseActions {
// the still-running child, leaking a thread + process per
// hung script.
let started = std::time::Instant::now();
let child = tokio::process::Command::new(&program)
let child = process_manager::create_tokio_command(&program)
.args(&args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
Expand Down Expand Up @@ -1910,7 +1911,7 @@ impl ComputerUseActions {
),
_ => ("xdg-open".to_string(), vec![url.to_string()]),
};
let status = std::process::Command::new(&program)
let status = process_manager::create_command(&program)
.args(&args)
.status()
.map_err(|e| {
Expand Down Expand Up @@ -1973,7 +1974,7 @@ impl ComputerUseActions {
),
_ => ("xdg-open".to_string(), vec![path_str.to_string()]),
};
let status = std::process::Command::new(&program)
let status = process_manager::create_command(&program)
.args(&args)
.status()
.map_err(|e| {
Expand Down Expand Up @@ -2111,7 +2112,7 @@ fn hostname() -> std::io::Result<String> {
}
}
}
let out = std::process::Command::new("hostname").output()?;
let out = process_manager::create_command("hostname").output()?;
Ok(String::from_utf8_lossy(&out.stdout).trim().to_string())
}

Expand Down Expand Up @@ -2225,7 +2226,7 @@ pub(crate) fn linux_session_info() -> (Option<String>, Option<String>) {
async fn clipboard_read() -> Result<String, String> {
#[cfg(target_os = "macos")]
{
let out = tokio::process::Command::new("pbpaste")
let out = process_manager::create_tokio_command("pbpaste")
.output()
.await
.map_err(|e| format!("spawn pbpaste: {}", e))?;
Expand All @@ -2236,11 +2237,12 @@ async fn clipboard_read() -> Result<String, String> {
}
#[cfg(target_os = "windows")]
{
let out = tokio::process::Command::new("powershell")
.args(["-NoProfile", "-Command", "Get-Clipboard -Raw"])
let (program, args) = powershell_invocation("Get-Clipboard -Raw");
let out = process_manager::create_tokio_command(&program)
.args(&args)
.output()
.await
.map_err(|e| format!("spawn powershell: {}", e))?;
.map_err(|e| format!("spawn {}: {}", program, e))?;
if !out.status.success() {
return Err(format!("Get-Clipboard exit={:?}", out.status.code()));
}
Expand Down Expand Up @@ -2271,7 +2273,11 @@ async fn clipboard_read() -> Result<String, String> {
]
};
for (bin, args) in candidates {
if let Ok(out) = tokio::process::Command::new(bin).args(*args).output().await {
if let Ok(out) = process_manager::create_tokio_command(bin)
.args(*args)
.output()
.await
{
if out.status.success() {
return Ok(String::from_utf8_lossy(&out.stdout).to_string());
}
Expand All @@ -2292,7 +2298,7 @@ async fn clipboard_write(text: &str) -> Result<(), String> {
use tokio::io::AsyncWriteExt;

async fn pipe(bin: &str, args: &[&str], text: &str) -> Result<(), String> {
let mut child = tokio::process::Command::new(bin)
let mut child = process_manager::create_tokio_command(bin)
.args(args)
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
Expand Down
18 changes: 13 additions & 5 deletions src/crates/core/src/agentic/tools/implementations/glob_tool.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::agentic::tools::framework::{Tool, ToolResult, ToolUseContext};
use crate::service::search::{
get_global_workspace_search_service, workspace_search_runtime_available, GlobSearchRequest,
};
use crate::util::process_manager;
use crate::util::errors::{BitFunError, BitFunResult};
use async_trait::async_trait;
use globset::{GlobBuilder, GlobMatcher};
Expand All @@ -11,7 +12,6 @@ use serde_json::{json, Value};
use std::cmp::Ordering;
use std::collections::BinaryHeap;
use std::path::{Component, Path, PathBuf};
use std::process::Command;

fn extract_glob_base_directory(pattern: &str) -> (String, String) {
let glob_start = pattern.find(['*', '?', '[', '{']);
Expand Down Expand Up @@ -242,7 +242,7 @@ fn call_rg(search_path: &str, pattern: &str, limit: usize) -> Result<Vec<String>
}

let args = build_rg_args(&relative_pattern, apply_gitignore, ignore_hidden_files);
let output = Command::new("rg")
let output = process_manager::create_command("rg")
.current_dir(&walk_root)
.args(&args)
.arg(".")
Expand Down Expand Up @@ -652,9 +652,9 @@ impl Tool for GlobTool {
#[cfg(test)]
mod tests {
use super::{call_rg, derive_walk_root, extract_glob_base_directory};
use crate::util::process_manager;
use std::fs;
use std::path::PathBuf;
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};

fn make_temp_dir(name: &str) -> PathBuf {
Expand Down Expand Up @@ -694,7 +694,11 @@ mod tests {

#[test]
fn keeps_shallowest_matches_from_rg_results() {
if Command::new("rg").arg("--version").output().is_err() {
if process_manager::create_command("rg")
.arg("--version")
.output()
.is_err()
{
return;
}

Expand All @@ -720,7 +724,11 @@ mod tests {

#[test]
fn wildcard_search_now_returns_files_only() {
if Command::new("rg").arg("--version").output().is_err() {
if process_manager::create_command("rg")
.arg("--version")
.output()
.is_err()
{
return;
}

Expand Down
8 changes: 4 additions & 4 deletions src/crates/core/src/service/remote_connect/ngrok.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@
//!
//! Supports macOS (pgrep) and Windows (tasklist) for process detection.

use crate::util::process_manager;
use anyhow::{anyhow, Result};
use log::{info, warn};
use std::path::PathBuf;
use std::process::Stdio;
use std::sync::atomic::{AtomicU32, Ordering};
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;

/// Tracks the PID of the ngrok process we started, so it can be killed
/// synchronously during application exit even if async cleanup didn't run.
Expand Down Expand Up @@ -85,7 +85,7 @@ fn list_ngrok_pids() -> Vec<u32> {

#[cfg(windows)]
fn list_ngrok_pids() -> Vec<u32> {
std::process::Command::new("tasklist")
process_manager::create_command("tasklist")
.args(["/FI", "IMAGENAME eq ngrok.exe", "/FO", "CSV", "/NH"])
.output()
.ok()
Expand Down Expand Up @@ -138,7 +138,7 @@ pub async fn start_ngrok_tunnel(local_port: u16) -> Result<NgrokTunnel> {

info!("Using ngrok at: {}", ngrok_path.display());

let mut child = Command::new(&ngrok_path)
let mut child = process_manager::create_tokio_command(&ngrok_path)
.args([
"http",
&local_port.to_string(),
Expand Down Expand Up @@ -236,7 +236,7 @@ fn kill_process(pid: u32) {

#[cfg(windows)]
fn kill_process(pid: u32) {
let _ = std::process::Command::new("taskkill")
let _ = process_manager::create_command("taskkill")
.args(["/F", "/PID", &pid.to_string()])
.output();
}
Expand Down
5 changes: 3 additions & 2 deletions src/crates/core/src/service/search/flashgrep/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,11 @@ use std::{
time::{Duration, Instant},
};

use crate::util::process_manager;
use serde::Serialize;
use tokio::{
io::{AsyncBufReadExt, AsyncReadExt, AsyncWriteExt, BufReader, BufWriter},
process::{Child, ChildStderr, ChildStdin, ChildStdout, Command},
process::{Child, ChildStderr, ChildStdin, ChildStdout},
sync::{oneshot, Mutex},
time::{sleep, timeout},
};
Expand Down Expand Up @@ -395,7 +396,7 @@ impl AsyncDaemonClient {
.or_else(|| std::env::var_os("FLASHGREP_DAEMON_BIN"))
.unwrap_or_else(|| OsString::from("flashgrep"));

let mut command = Command::new(program);
let mut command = process_manager::create_tokio_command(program);
command
.arg("serve")
.arg("--stdio")
Expand Down
12 changes: 12 additions & 0 deletions src/crates/core/src/service/terminal/src/shell/detection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -331,6 +331,18 @@ impl ShellDetector {

#[allow(dead_code)]
fn get_shell_version(path: &str) -> Option<String> {
#[cfg(windows)]
let output = {
use std::os::windows::process::CommandExt;

const CREATE_NO_WINDOW: u32 = 0x0800_0000;

let mut command = std::process::Command::new(path);
command.creation_flags(CREATE_NO_WINDOW);
command.arg("--version").output().ok()?
};

#[cfg(not(windows))]
let output = std::process::Command::new(path)
.arg("--version")
.output()
Expand Down
Loading