From 5ecf40056fa0ade596443d7b95e79534a3770425 Mon Sep 17 00:00:00 2001 From: Lucas Rayan Date: Sat, 31 Oct 2026 08:07:57 -0300 Subject: [PATCH] fix(gui): hide the window until elevation is resolved, not after MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The window used to be visible from launch, so the unprivileged instance's normal window would show, flash while pkexec/osascript asked for the admin password, and only then get replaced by the elevated relaunch's window once it killed this one ~1.2s later — the same mechanism Sabiá already uses under the hood, but with Sabiá's window visible throughout too; Guará's heavier initial UI made the unprivileged flash far more noticeable. tauri.conf.json now starts the window with "visible": false. main.rs shows it explicitly in exactly one of three cases: already elevated (or non-unix, where the OS-level UAC manifest already elevated the process before it started), the relaunch failed and the user needs to see the warning, or this is the already-elevated relaunched instance itself. Only one window is ever shown to the user now. Also fixes a latent bug this surfaced: do_relaunch_elevated only emitted "elevate-failed" from the child-process-exited-nonzero path. If it fails earlier (pkexec/osascript missing, exe path unresolved, log file unwritable) it returns Err directly without emitting anything — with the window now starting hidden, that would have left it hidden forever with no feedback. The setup() closure now emits the same event on that early-Err path too. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01VAyr8d7HuAdUGB6aA5xmDD --- apps/gui/src-tauri/src/main.rs | 44 ++++++++++++++++++++++++------ apps/gui/src-tauri/tauri.conf.json | 3 +- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/apps/gui/src-tauri/src/main.rs b/apps/gui/src-tauri/src/main.rs index 905bb01..aff2e7f 100644 --- a/apps/gui/src-tauri/src/main.rs +++ b/apps/gui/src-tauri/src/main.rs @@ -247,6 +247,7 @@ fn main() { std::thread::spawn(move || responder.respond(serve_file(&eng, &request))); }) .setup(|app| { + use tauri::{Listener, Manager}; // When launched elevated by the unprivileged instance, terminate the old process. #[cfg(unix)] if let Some(pid) = std::env::var("GUARA_REPLACE_PID") @@ -264,15 +265,42 @@ fn main() { }); } - // Reading raw disks requires root: request elevation as soon as the app launches, - // without waiting for the user to look for a button. If authorization fails, - // the window stays open and the UI displays a warning. + // The window starts hidden (tauri.conf.json: "visible": false) so the + // unprivileged instance never flashes on screen before being replaced. + // Reading raw disks requires root: request elevation as soon as the app + // launches, without waiting for the user to look for a button, and only + // show a window once we know which one it is — the elevated relaunch's, + // or this same one if elevation turned out not to be needed/possible. #[cfg(any(target_os = "linux", target_os = "macos"))] - if unsafe { libc::geteuid() != 0 } && std::env::var("GUARA_REPLACE_PID").is_err() { - let handle = app.handle().clone(); - std::thread::spawn(move || { - let _ = do_relaunch_elevated(handle); - }); + { + if unsafe { libc::geteuid() != 0 } && std::env::var("GUARA_REPLACE_PID").is_err() { + use tauri::Emitter; + let handle = app.handle().clone(); + let handle_for_failure = app.handle().clone(); + std::thread::spawn(move || { + // do_relaunch_elevated only emits "elevate-failed" itself once the + // child process has actually spawned and later exits non-zero. If it + // fails earlier (pkexec/osascript missing, exe path unresolved, log + // file unwritable), it returns Err directly without emitting anything + // — emit it here too, or the hidden window would never reappear. + if let Err(e) = do_relaunch_elevated(handle) { + let _ = handle_for_failure.emit("elevate-failed", e); + } + }); + // Whichever path emits it, make sure the window comes back so the + // user sees the warning instead of a silently stuck, invisible app. + if let Some(win) = app.get_webview_window("main") { + app.handle().listen("elevate-failed", move |_| { + let _ = win.show(); + }); + } + } else if let Some(win) = app.get_webview_window("main") { + let _ = win.show(); + } + } + #[cfg(not(any(target_os = "linux", target_os = "macos")))] + if let Some(win) = app.get_webview_window("main") { + let _ = win.show(); } Ok(()) }) diff --git a/apps/gui/src-tauri/tauri.conf.json b/apps/gui/src-tauri/tauri.conf.json index 39706f8..12085b2 100644 --- a/apps/gui/src-tauri/tauri.conf.json +++ b/apps/gui/src-tauri/tauri.conf.json @@ -15,7 +15,8 @@ "height": 780, "minWidth": 420, "minHeight": 560, - "resizable": true + "resizable": true, + "visible": false } ], "security": {