fix(qt): Qt.quit() was vetoed by our own windows, so Quit never quit
The owner reported it three times and their second wording was the whole
diagnosis: "Quit QBZ debe cerrar la aplicación, NO SOLO LA VENTANA."
ROOT CAUSE. `Qt.quit()` in Qt 6 is a REQUEST, not an order. It posts
QEvent::Quit, and QGuiApplication answers it by calling close() on every
top-level window that has a platform handle — hidden ones included. If any
window refuses its close event, the quit is silently cancelled. Two of ours
refuse:
1. Main.qml's closeOrHide refuses whenever trayLive && closeToTray, and
hides the window on the way out. That IS "cierra la ventana, no la app".
2. MiniWindow's onClosing refuses unconditionally, because for the mini a
close means "leave the mini" — so once the mini had ever been opened,
quit was blocked regardless of any setting.
It also explains "Close to tray does nothing": with the setting ON, the X
hid (correct) and Quit hid too (the bug), so the two were indistinguishable
and the toggle looked inert. Nothing was ever wrong with that plumbing.
FIX. Both quit sites call Qt.exit(0), which exits the event loops directly —
no QEvent::Quit, no window to veto it, and no need to touch the miniplayer's
onClosing, whose refusal is correct for what it means. A one-shot 5 s
watchdog thread, armed only at the three quit sites, flushes the log and
_exit(0)s if anything downstream ever wedges; it cannot fire in a live
session. main() now names each teardown step, so a future hang says which
statement it hung on instead of going quiet.
closeOrHide reports its decision through the bridge — tray_live,
close_to_tray and the arm taken — because a report of "the tray does
nothing" was not previously separable from "the signal arrived and QML
ignored it".
MEASURED, with waitpid on a child rather than /proc (a zombie reads as alive
and that produced a false diagnosis earlier in this session). Old binary:
alive ≥25 s with the window visible, hidden, and with the mini open. Fixed:
exit 0 in 0.35-0.68 s across eleven driven scenarios — the tray Quit in all
three window states, the drawn X and the app-menu Close with the setting
both ON and OFF, the mini's own close button, and the MPRIS Quit method.
One arm remains static-only: a real WM close event with close-to-tray OFF.
VNC delivers no such event, so the owner's first titlebar X is its first
real execution.
cc
This commit is contained in:
6 files changed
+171
-7
No files matched your search
@@ -454,7 +454,8 @@ ApplicationWindow {
|
||||
// sites, both inside QWindow::event and both gated on the accepted flag, so
|
||||
// hiding never arms Qt's quit check but an ACCEPTED close does.
|
||||
//
|
||||
// `onAboutToQuit` is still not a duplicate of it: `Qt.quit()` never raises
|
||||
// `onAboutToQuit` is still not a duplicate of it: neither `Qt.exit(0)`
|
||||
// (what every quit arm calls since 2026-08-04) nor `Qt.quit()` raises
|
||||
// `closing` (§15 trap 26), so the quit arm of `closeOrHide`, the tray's
|
||||
// `quit_requested` handler and any teardown that skips QML reach the
|
||||
// geometry flush only through here. Where both do run the second write is
|
||||
@@ -537,14 +538,35 @@ ApplicationWindow {
|
||||
// `closeEvent` is the QQuickCloseEvent on the one path that carries one and
|
||||
// null on the other four.
|
||||
function closeOrHide(closeEvent) {
|
||||
if (QbzTray.trayLive && QbzTray.closeToTray) {
|
||||
var hide = QbzTray.trayLive && QbzTray.closeToTray
|
||||
// The evidence line (2026-08-04): logged RUST-side so it lands in
|
||||
// qbz.log — console.log only reaches stderr, which owner reports
|
||||
// never carry. It prints both operands and the arm taken, which is
|
||||
// the whole diagnosis of any future "Close to tray does nothing".
|
||||
QbzTray.closeDecision(hide)
|
||||
if (hide) {
|
||||
if (closeEvent)
|
||||
closeEvent.accepted = false
|
||||
window.hideToTray()
|
||||
return
|
||||
}
|
||||
// QUIT — via Qt.exit(0), NEVER Qt.quit(). Qt.quit() delivers
|
||||
// QEvent::Quit, and QGuiApplication's handler first closes every
|
||||
// top-level window, silently CANCELING the whole quit if any window
|
||||
// refuses its close — this very function's hide arm refuses whenever
|
||||
// the tray is live with close-to-tray on, and MiniWindow.qml's
|
||||
// onClosing refuses unconditionally (its close means "exit the
|
||||
// mini"). That veto is how "Quit QBZ" hid the window and left the
|
||||
// process running (owner-reported three times; reproduced under VNC
|
||||
// 2026-08-04: quit requested + Qt.quit() both logged, event loop
|
||||
// still alive 25s later). Qt.exit() stops the event loops directly:
|
||||
// no close events, no veto. Geometry is flushed explicitly below,
|
||||
// and onAboutToQuit still runs when exec() returns.
|
||||
if (closeEvent)
|
||||
closeEvent.accepted = true
|
||||
QbzTray.armQuitWatchdog()
|
||||
window.persistWindowGeometryOnExit()
|
||||
Qt.quit()
|
||||
Qt.exit(0)
|
||||
}
|
||||
|
||||
// The tray's four signals (src/tray_bridge.rs:87-100). Rust owns no window
|
||||
@@ -582,8 +604,15 @@ ApplicationWindow {
|
||||
function onQuitRequested() {
|
||||
console.log("[qml] tray -> quit: persisting geometry")
|
||||
window.persistWindowGeometryOnExit()
|
||||
console.log("[qml] tray -> quit: calling Qt.quit()")
|
||||
Qt.quit()
|
||||
console.log("[qml] tray -> quit: calling Qt.exit(0)")
|
||||
// Qt.exit(0), NEVER Qt.quit() — see closeOrHide's quit arm: a
|
||||
// Qt.quit() here is vetoable by any window's onClosing (the
|
||||
// close-to-tray arm, the mini's unconditional refuse), and that
|
||||
// veto is exactly how the tray Quit hid the window and left the
|
||||
// process alive (2026-08-04, three owner reports). The hard-exit
|
||||
// watchdog for this path was armed Rust-side in tray_qt::quit(),
|
||||
// before the signal was even emitted.
|
||||
Qt.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user