diff --git a/packages/tauri-app/Cargo.lock b/packages/tauri-app/Cargo.lock index e388ea42..d5bc8ce7 100644 --- a/packages/tauri-app/Cargo.lock +++ b/packages/tauri-app/Cargo.lock @@ -626,7 +626,7 @@ dependencies = [ [[package]] name = "codenomad-tauri" -version = "0.1.0" +version = "0.12.3" dependencies = [ "anyhow", "dirs 5.0.1", @@ -646,6 +646,7 @@ dependencies = [ "thiserror 1.0.69", "url", "which", + "windows-sys 0.59.0", ] [[package]] diff --git a/packages/tauri-app/src-tauri/Cargo.toml b/packages/tauri-app/src-tauri/Cargo.toml index f119c846..f697d3f4 100644 --- a/packages/tauri-app/src-tauri/Cargo.toml +++ b/packages/tauri-app/src-tauri/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "codenomad-tauri" -version = "0.1.0" +version = "0.12.3" edition = "2021" license = "MIT" @@ -25,3 +25,6 @@ tauri-plugin-opener = "2" url = "2" tauri-plugin-keepawake = "0.1.1" tauri-plugin-notification = "2" + +[target.'cfg(windows)'.dependencies] +windows-sys = { version = "0.59", features = ["Win32_UI_Shell"] } diff --git a/packages/tauri-app/src-tauri/src/cli_manager.rs b/packages/tauri-app/src-tauri/src/cli_manager.rs index e193ef83..c06c3361 100644 --- a/packages/tauri-app/src-tauri/src/cli_manager.rs +++ b/packages/tauri-app/src-tauri/src/cli_manager.rs @@ -17,10 +17,24 @@ use std::thread; use std::time::{Duration, Instant}; use tauri::{webview::cookie::Cookie, AppHandle, Emitter, Manager, Url}; +#[cfg(windows)] +use std::os::windows::process::CommandExt; + +#[cfg(windows)] +const CREATE_NO_WINDOW: u32 = 0x08000000; + fn log_line(message: &str) { println!("[tauri-cli] {message}"); } +#[cfg(windows)] +fn configure_spawn(command: &mut Command) { + command.creation_flags(CREATE_NO_WINDOW); +} + +#[cfg(not(windows))] +fn configure_spawn(_command: &mut Command) {} + fn workspace_root() -> Option { std::env::current_dir().ok().and_then(|mut dir| { for _ in 0..3 { @@ -36,6 +50,13 @@ const SESSION_COOKIE_NAME: &str = "codenomad_session"; const CLI_STOP_GRACE_SECS: u64 = 30; +#[cfg(windows)] +fn kill_process_tree_windows(pid: u32) { + let mut command = Command::new("taskkill"); + command.args(["/PID", &pid.to_string(), "/T", "/F"]); + configure_spawn(&mut command); + let _ = command.status(); +} fn navigate_main(app: &AppHandle, url: &str) { if let Some(win) = app.webview_windows().get("main") { let mut display = url.to_string(); @@ -352,7 +373,7 @@ impl CliProcessManager { } #[cfg(windows)] { - let _ = child.kill(); + kill_process_tree_windows(child.id()); } let start = Instant::now(); @@ -372,7 +393,7 @@ impl CliProcessManager { } #[cfg(windows)] { - let _ = child.kill(); + kill_process_tree_windows(child.id()); } break; } @@ -450,6 +471,7 @@ impl CliProcessManager { .env("ELECTRON_RUN_AS_NODE", "1") .stdout(Stdio::piped()) .stderr(Stdio::piped()); + configure_spawn(&mut c); if let Some(ref cwd) = cwd { c.current_dir(cwd); } @@ -462,6 +484,7 @@ impl CliProcessManager { .env("ELECTRON_RUN_AS_NODE", "1") .stdout(Stdio::piped()) .stderr(Stdio::piped()); + configure_spawn(&mut c); if let Some(ref cwd) = cwd { c.current_dir(cwd); } @@ -537,7 +560,12 @@ impl CliProcessManager { locked.error = Some("CLI did not start in time".to_string()); log_line("timeout waiting for CLI readiness"); if let Some(child) = child_holder_clone.lock().as_mut() { - let _ = child.kill(); + #[cfg(windows)] + kill_process_tree_windows(child.id()); + #[cfg(not(windows))] + { + let _ = child.kill(); + } } let _ = app_clone.emit("cli:error", json!({"message": "CLI did not start in time"})); Self::emit_status(&app_clone, &locked); diff --git a/packages/tauri-app/src-tauri/src/main.rs b/packages/tauri-app/src-tauri/src/main.rs index ac402932..832c51f9 100644 --- a/packages/tauri-app/src-tauri/src/main.rs +++ b/packages/tauri-app/src-tauri/src/main.rs @@ -12,8 +12,20 @@ use tauri::{AppHandle, Emitter, Manager, Runtime, Wry}; use tauri_plugin_opener::OpenerExt; use url::Url; +#[cfg(windows)] +use std::ffi::OsStr; +#[cfg(windows)] +use std::iter; +#[cfg(windows)] +use std::os::windows::ffi::OsStrExt; +#[cfg(windows)] +use windows_sys::Win32::UI::Shell::SetCurrentProcessExplicitAppUserModelID; + static QUIT_REQUESTED: AtomicBool = AtomicBool::new(false); +#[cfg(windows)] +const WINDOWS_APP_USER_MODEL_ID: &str = "ai.neuralnomads.codenomad.client"; + #[derive(Clone)] pub struct AppState { pub manager: CliProcessManager, @@ -101,6 +113,22 @@ fn emit_folder_drop_event( } } +#[cfg(windows)] +fn set_windows_app_user_model_id() { + let app_id: Vec = OsStr::new(WINDOWS_APP_USER_MODEL_ID) + .encode_wide() + .chain(iter::once(0)) + .collect(); + + let result = unsafe { SetCurrentProcessExplicitAppUserModelID(app_id.as_ptr()) }; + if result < 0 { + eprintln!("[tauri] failed to set AppUserModelID: {result}"); + } +} + +#[cfg(not(windows))] +fn set_windows_app_user_model_id() {} + fn main() { let navigation_guard: TauriPlugin = PluginBuilder::new("external-link-guard") .on_navigation(|webview, url| intercept_navigation(webview, url)) @@ -116,6 +144,7 @@ fn main() { manager: CliProcessManager::new(), }) .setup(|app| { + set_windows_app_user_model_id(); build_menu(&app.handle())?; let dev_mode = is_dev_mode(); let app_handle = app.handle().clone(); diff --git a/packages/tauri-app/src-tauri/tauri.conf.json b/packages/tauri-app/src-tauri/tauri.conf.json index e847379d..c8f965ff 100644 --- a/packages/tauri-app/src-tauri/tauri.conf.json +++ b/packages/tauri-app/src-tauri/tauri.conf.json @@ -1,8 +1,8 @@ { "$schema": "https://schema.tauri.app/config/2", "productName": "CodeNomad", - "version": "0.1.0", - "identifier": "ai.opencode.client", + "version": "0.12.3", + "identifier": "ai.neuralnomads.codenomad.client", "build": { "beforeDevCommand": "npm run dev:bootstrap", "beforeBuildCommand": "npm run bundle:server",