Skip to content
Open
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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions apps/desktop-tauri/src-tauri/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ tracing = "0.1"
uuid = { version = "1", features = ["v4"] }

[target.'cfg(windows)'.dependencies]
# WebView2's `ProcessFailed` event (#410). Already in the lock file through
# wry; a direct dependency only so `shell/webview_lifecycle.rs` can name the
# interfaces `tauri::webview::PlatformWebview::controller()` hands out.
webview2-com = "0.38"
windows = { version = "0.61", features = [
"Win32_Foundation",
"Win32_System_Com",
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src-tauri/src/floatbar/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ pub fn show(
.visible(false)
.build()
.map_err(|e| e.to_string())?;
crate::shell::webview_lifecycle::watch(app, &win);

// Restore prior geometry if we have one. Otherwise, taskbar style opens
// near the bottom while the original floating style keeps its top-center
Expand Down
1 change: 1 addition & 0 deletions apps/desktop-tauri/src-tauri/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,7 @@ fn main() {
}
}
if let Some(window) = app.get_webview_window("main") {
shell::window_recovery::register_main(app.handle(), &window);
shell::dwm::force_dark_caption(&window);
window.hide()?;
}
Expand Down
17 changes: 17 additions & 0 deletions apps/desktop-tauri/src-tauri/src/shell/flyout_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,11 @@ pub fn save_stored_size(width: u32, height: u32) {
/// precedent) — callers must invoke this from an async context (an `async`
/// command, or `tauri::async_runtime::spawn`), never a sync command handler.
pub fn open_or_focus(app: &AppHandle, position: Option<(i32, i32)>) -> Result<(), String> {
// A ProcessFailed teardown of this very window may still be waiting for
// its label; opening into that would show the dying frame or build a
// second window under the label (#410).
super::window_recovery::wait_for_flyout_rebuild();

// A WebView2 process exit leaves the hidden frame with no content; drop it
// so the build path below runs instead of showing a blank window (#410).
crate::webview_recovery::reclaim_dead_window(app, FLYOUT_LABEL)?;
Expand Down Expand Up @@ -98,6 +103,7 @@ pub fn open_or_focus(app: &AppHandle, position: Option<(i32, i32)>) -> Result<()
.disable_drag_drop_handler()
.visible(false);
let win = builder.build().map_err(|e| e.to_string())?;
super::webview_lifecycle::watch(app, &win);

super::dwm::force_dark_caption(&win);

Expand Down Expand Up @@ -176,6 +182,17 @@ pub fn handle_window_event(window: &tauri::Window, event: &tauri::WindowEvent) -
if crate::proof_harness::is_proof_mode(app) {
return true;
}
// A window that has never been shown cannot lose focus in any
// sense the user meant. Windows still reports one when a hidden
// window is activated without foreground rights (e.g. right after
// build, before the frontend's reveal); treating that as a
// dismiss would clear the pending reveal and leave the flyout
// invisible. A failed query (the window is mid-teardown) is
// treated the same way: swallowing one blur is cheaper than
// dismissing a flyout that is about to be rebuilt.
if !window.is_visible().unwrap_or(false) {
return true;
}
let Some(st) = app.try_state::<Mutex<AppState>>() else {
return true;
};
Expand Down
2 changes: 2 additions & 0 deletions apps/desktop-tauri/src-tauri/src/shell/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ mod geometry;
mod position;
pub mod settings_window;
mod transition;
pub(crate) mod webview_lifecycle;
mod window;
pub(crate) mod window_recovery;

#[cfg(test)]
mod tests;
Expand Down
3 changes: 2 additions & 1 deletion apps/desktop-tauri/src-tauri/src/shell/settings_window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tauri::{Emitter, Manager, PhysicalPosition, WebviewUrl};

use crate::surface::SurfaceMode;

const SETTINGS_LABEL: &str = "settings";
pub(crate) const SETTINGS_LABEL: &str = "settings";
const SETTINGS_WIDTH: f64 = 720.0;
const SETTINGS_HEIGHT: f64 = 580.0;

Expand Down Expand Up @@ -45,6 +45,7 @@ pub fn open_or_focus(app: &tauri::AppHandle, tab: &str) -> Result<(), String> {
.resizable(true)
.build()
.map_err(|e| e.to_string())?;
super::webview_lifecycle::watch(app, &win);

// Force DWM caption to dark; keep WS_THICKFRAME since window is resizable
super::dwm::force_dark_caption_resizable(&win);
Expand Down
21 changes: 17 additions & 4 deletions apps/desktop-tauri/src-tauri/src/shell/transition.rs
Original file line number Diff line number Diff line change
Expand Up @@ -249,9 +249,19 @@ fn apply_transition_request(
force_same_mode_apply: bool,
) -> Result<SurfaceMode, String> {
let _transition_guard = SHELL_TRANSITION_SERIAL.lock().unwrap();
let window = app
.get_webview_window("main")
.ok_or_else(|| "main window unavailable".to_string())?;
// #410: a dead `main` would show as an empty transparent frame. The
// guard rebuilds it off-thread and replays this request afterwards, so
// returning here is the recovery starting, not the request being lost.
let Some(window) = super::window_recovery::resolve_live_main(
app,
Some(super::window_recovery::MainRequest {
mode: request.mode,
target: request.target.clone(),
position: request.position,
}),
) else {
return Ok(SurfaceMode::Hidden);
};
let st = app
.try_state::<Mutex<AppState>>()
.ok_or_else(|| "app state unavailable".to_string())?;
Expand Down Expand Up @@ -509,7 +519,10 @@ pub(super) fn restore_surface_snapshot(state: &mut AppState, snapshot: &SurfaceS
}
}

fn commit_surface_snapshot(app: &AppHandle, snapshot: &SurfaceSnapshot) -> Result<(), String> {
pub(super) fn commit_surface_snapshot(
app: &AppHandle,
snapshot: &SurfaceSnapshot,
) -> Result<(), String> {
let st = app
.try_state::<Mutex<AppState>>()
.ok_or_else(|| "app state unavailable".to_string())?;
Expand Down
Loading
Loading