host: route graceful closes of helper windows to the app window (Windows)
tao's thread event target and the single-instance message window are visible 1 px unowned top-level windows. taskkill /PID (WM_CLOSE, no /F) closes only the first such window in z-order; a minimized app window sinks below them, so the close destroyed a helper and the app kept running. route_helper_closes subclasses those helpers and forwards WM_CLOSE to the app window. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
0344c55fa8
commit
395b27c937
2 changed files with 72 additions and 1 deletions
|
|
@ -185,6 +185,77 @@ pub fn system_menu<R: Runtime>(webview: &Webview<R>, x: f64, y: f64) -> Result<(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
/// Windows: a graceful close sent to one of the process's helper windows closes the app window
|
||||
/// instead. Call once from `setup`, on the main thread, after the main window is built. No-op
|
||||
/// elsewhere.
|
||||
///
|
||||
/// tao's thread event target and the single-instance plugin's message window are visible (1 px,
|
||||
/// layered, transparent) unowned top-level windows. `taskkill /PID` without `/F` posts one
|
||||
/// `WM_CLOSE` to the first such window in z-order; a minimized window sinks to the bottom, so the
|
||||
/// close hit a helper, `DefWindowProc` destroyed it (a second instance could no longer find us,
|
||||
/// or the event loop lost its target) and the app kept running.
|
||||
pub fn route_helper_closes<R: Runtime>(window: &tauri::Window<R>) {
|
||||
#[cfg(windows)]
|
||||
helper_close::install(window);
|
||||
#[cfg(not(windows))]
|
||||
let _ = window;
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
mod helper_close {
|
||||
use windows::core::BOOL;
|
||||
use windows::Win32::Foundation::{HWND, LPARAM, LRESULT, WPARAM};
|
||||
use windows::Win32::System::Threading::GetCurrentThreadId;
|
||||
use windows::Win32::UI::Shell::{DefSubclassProc, RemoveWindowSubclass, SetWindowSubclass};
|
||||
use windows::Win32::UI::WindowsAndMessaging::{
|
||||
EnumThreadWindows, GetWindow, IsWindow, IsWindowVisible, PostMessageW, GW_OWNER, WM_CLOSE, WM_NCDESTROY,
|
||||
};
|
||||
|
||||
const SUBCLASS_ID: usize = 0x7762_636c; // "wbcl"
|
||||
|
||||
pub(super) fn install<R: tauri::Runtime>(window: &tauri::Window<R>) {
|
||||
let Ok(main) = window.hwnd() else { return };
|
||||
// SAFETY: enumerates and subclasses windows of the calling (event loop) thread only;
|
||||
// `main` is passed by value through the LPARAM / ref data.
|
||||
unsafe {
|
||||
let _ = EnumThreadWindows(GetCurrentThreadId(), Some(visit), LPARAM(main.0 as isize));
|
||||
}
|
||||
}
|
||||
|
||||
unsafe extern "system" fn visit(hwnd: HWND, main: LPARAM) -> BOOL {
|
||||
let main = HWND(main.0 as *mut _);
|
||||
// Visible, unowned, not the app window: what `taskkill` may pick. IME windows are hidden.
|
||||
if hwnd != main && IsWindowVisible(hwnd).as_bool() && GetWindow(hwnd, GW_OWNER).is_err() {
|
||||
let _ = SetWindowSubclass(hwnd, Some(forward), SUBCLASS_ID, main.0 as usize);
|
||||
}
|
||||
BOOL(1)
|
||||
}
|
||||
|
||||
unsafe extern "system" fn forward(
|
||||
hwnd: HWND,
|
||||
msg: u32,
|
||||
wparam: WPARAM,
|
||||
lparam: LPARAM,
|
||||
id: usize,
|
||||
main: usize,
|
||||
) -> LRESULT {
|
||||
match msg {
|
||||
WM_CLOSE => {
|
||||
let main = HWND(main as *mut _);
|
||||
if IsWindow(Some(main)).as_bool() {
|
||||
let _ = PostMessageW(Some(main), WM_CLOSE, WPARAM(0), LPARAM(0));
|
||||
return LRESULT(0);
|
||||
}
|
||||
}
|
||||
WM_NCDESTROY => {
|
||||
let _ = RemoveWindowSubclass(hwnd, Some(forward), id);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
DefSubclassProc(hwnd, msg, wparam, lparam)
|
||||
}
|
||||
}
|
||||
|
||||
/// Places a child webview (tab, popover) at a rectangle of its window, in logical px.
|
||||
///
|
||||
/// Windows/macOS: the native child bounds. Linux: tauri 2 packs every child webview into the
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue