mirror of
https://gitea.toothfairyai.com/ToothFairyAI/tf_code.git
synced 2026-03-30 13:54:01 +00:00
Changes shell spawn flags from -l to -il so that ~/.zshrc and ~/.bashrc are sourced when starting the desktop app on macOS and Linux. This fixes missing PATH and other environment variables that users expect to be available.
216 lines
6.8 KiB
Rust
216 lines
6.8 KiB
Rust
use std::{
|
|
net::{SocketAddr, TcpListener},
|
|
sync::{Arc, Mutex},
|
|
time::{Duration, Instant},
|
|
};
|
|
#[cfg(target_os = "macos")]
|
|
use tauri::TitleBarStyle;
|
|
use tauri::{AppHandle, LogicalSize, Manager, RunEvent, WebviewUrl, WebviewWindow};
|
|
use tauri_plugin_shell::process::{CommandChild, CommandEvent};
|
|
use tauri_plugin_shell::ShellExt;
|
|
use tokio::net::TcpSocket;
|
|
|
|
#[derive(Clone)]
|
|
struct ServerState(Arc<Mutex<Option<CommandChild>>>);
|
|
|
|
#[tauri::command]
|
|
fn kill_sidecar(app: AppHandle) {
|
|
let Some(server_state) = app.try_state::<ServerState>() else {
|
|
println!("Server not running");
|
|
return;
|
|
};
|
|
|
|
let Some(server_state) = server_state
|
|
.0
|
|
.lock()
|
|
.expect("Failed to acquire mutex lock")
|
|
.take()
|
|
else {
|
|
println!("Server state missing");
|
|
return;
|
|
};
|
|
|
|
let _ = server_state.kill();
|
|
|
|
println!("Killed server");
|
|
}
|
|
|
|
fn get_sidecar_port() -> u16 {
|
|
option_env!("OPENCODE_PORT")
|
|
.map(|s| s.to_string())
|
|
.or_else(|| std::env::var("OPENCODE_PORT").ok())
|
|
.and_then(|port_str| port_str.parse().ok())
|
|
.unwrap_or_else(|| {
|
|
TcpListener::bind("127.0.0.1:0")
|
|
.expect("Failed to bind to find free port")
|
|
.local_addr()
|
|
.expect("Failed to get local address")
|
|
.port()
|
|
})
|
|
}
|
|
|
|
fn get_user_shell() -> String {
|
|
std::env::var("SHELL").unwrap_or_else(|_| "/bin/sh".to_string())
|
|
}
|
|
|
|
fn spawn_sidecar(app: &AppHandle, port: u16) -> CommandChild {
|
|
#[cfg(target_os = "windows")]
|
|
let (mut rx, child) = app
|
|
.shell()
|
|
.sidecar("opencode-cli")
|
|
.unwrap()
|
|
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
|
.env("OPENCODE_CLIENT", "desktop")
|
|
.args(["serve", &format!("--port={port}")])
|
|
.spawn()
|
|
.expect("Failed to spawn opencode");
|
|
|
|
#[cfg(not(target_os = "windows"))]
|
|
let (mut rx, child) = {
|
|
let sidecar_path = tauri::utils::platform::current_exe()
|
|
.expect("Failed to get current exe")
|
|
.parent()
|
|
.expect("Failed to get parent dir")
|
|
.join("opencode-cli");
|
|
let shell = get_user_shell();
|
|
app.shell()
|
|
.command(&shell)
|
|
.env("OPENCODE_EXPERIMENTAL_ICON_DISCOVERY", "true")
|
|
.env("OPENCODE_CLIENT", "desktop")
|
|
.args([
|
|
"-il",
|
|
"-c",
|
|
&format!("{} serve --port={}", sidecar_path.display(), port),
|
|
])
|
|
.spawn()
|
|
.expect("Failed to spawn opencode")
|
|
};
|
|
|
|
tauri::async_runtime::spawn(async move {
|
|
while let Some(event) = rx.recv().await {
|
|
match event {
|
|
CommandEvent::Stdout(line_bytes) => {
|
|
let line = String::from_utf8_lossy(&line_bytes);
|
|
print!("{line}");
|
|
}
|
|
CommandEvent::Stderr(line_bytes) => {
|
|
let line = String::from_utf8_lossy(&line_bytes);
|
|
eprint!("{line}");
|
|
}
|
|
_ => {}
|
|
}
|
|
}
|
|
});
|
|
|
|
child
|
|
}
|
|
|
|
async fn is_server_running(port: u16) -> bool {
|
|
TcpSocket::new_v4()
|
|
.unwrap()
|
|
.connect(SocketAddr::new(
|
|
"127.0.0.1".parse().expect("Failed to parse IP"),
|
|
port,
|
|
))
|
|
.await
|
|
.is_ok()
|
|
}
|
|
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run() {
|
|
let updater_enabled = option_env!("TAURI_SIGNING_PRIVATE_KEY").is_some();
|
|
|
|
let mut builder = tauri::Builder::default()
|
|
.plugin(tauri_plugin_os::init())
|
|
.plugin(tauri_plugin_window_state::Builder::new().build())
|
|
.plugin(tauri_plugin_store::Builder::new().build())
|
|
.plugin(tauri_plugin_dialog::init())
|
|
.plugin(tauri_plugin_shell::init())
|
|
.plugin(tauri_plugin_process::init())
|
|
.plugin(tauri_plugin_opener::init())
|
|
.invoke_handler(tauri::generate_handler![kill_sidecar])
|
|
.setup(move |app| {
|
|
let app = app.handle().clone();
|
|
|
|
tauri::async_runtime::spawn(async move {
|
|
let port = get_sidecar_port();
|
|
|
|
let should_spawn_sidecar = !is_server_running(port).await;
|
|
|
|
let child = if should_spawn_sidecar {
|
|
let child = spawn_sidecar(&app, port);
|
|
|
|
let timestamp = Instant::now();
|
|
loop {
|
|
if timestamp.elapsed() > Duration::from_secs(7) {
|
|
todo!("Handle server spawn timeout");
|
|
}
|
|
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
if is_server_running(port).await {
|
|
// give the server a little bit more time to warm up
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
println!("Server ready after {:?}", timestamp.elapsed());
|
|
|
|
Some(child)
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let primary_monitor = app.primary_monitor().ok().flatten();
|
|
let size = primary_monitor
|
|
.map(|m| m.size().to_logical(m.scale_factor()))
|
|
.unwrap_or(LogicalSize::new(1920, 1080));
|
|
|
|
let mut window_builder =
|
|
WebviewWindow::builder(&app, "main", WebviewUrl::App("/".into()))
|
|
.title("OpenCode")
|
|
.inner_size(size.width as f64, size.height as f64)
|
|
.decorations(true)
|
|
.zoom_hotkeys_enabled(true)
|
|
.disable_drag_drop_handler()
|
|
.initialization_script(format!(
|
|
r#"
|
|
window.__OPENCODE__ ??= {{}};
|
|
window.__OPENCODE__.updaterEnabled = {updater_enabled};
|
|
window.__OPENCODE__.port = {port};
|
|
"#
|
|
));
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
window_builder = window_builder
|
|
.title_bar_style(TitleBarStyle::Overlay)
|
|
.hidden_title(true);
|
|
}
|
|
|
|
window_builder.build().expect("Failed to create window");
|
|
|
|
app.manage(ServerState(Arc::new(Mutex::new(child))));
|
|
});
|
|
|
|
Ok(())
|
|
});
|
|
|
|
if updater_enabled {
|
|
builder = builder.plugin(tauri_plugin_updater::Builder::new().build());
|
|
}
|
|
|
|
builder
|
|
.build(tauri::generate_context!())
|
|
.expect("error while running tauri application")
|
|
.run(|app, event| {
|
|
if let RunEvent::Exit = event {
|
|
println!("Received Exit");
|
|
|
|
kill_sidecar(app.clone());
|
|
}
|
|
});
|
|
}
|