qt: parity batch 3 — quit-timer-on-natural-close + bell fallback (B1, I9)

Two fixes from PARITY.md:

  B1 — quit-after-last-window-closed-delay now fires when the user
  closes the last window via the WM, not only when libghostty issues
  the QUIT_TIMER action. ~MainWindow detects the empty-s_windows +
  delay-configured case and arms handleQuitTimer(true), then returns
  before tearing down s_app/s_config so the timer can fire on a still-
  alive process. newWindow() cancels the timer so a new window opened
  during the delay window keeps the process alive (mirrors the GTK
  startQuitTimer/stopQuitTimer wiring at application.zig:820-862).
  Previously, with delay > 0, closing the final window via the title-
  bar X kept the process alive forever — quitOnLastWindowClosed was
  intentionally off, but nothing else triggered the timer.

  I9 — bell-features fallback only applies on a real config-get
  failure. The prior code initialised features=BellAttention then
  unconditionally overwrote it from configGet's out param. If a user
  set bell-features = (no flags), configGet wrote 0 and we honored
  that — fine. But the fallback semantics were ambiguous: a future
  ABI drift causing configGet to fail would still leave features
  set to BellAttention, while users who explicitly turned off all
  bell features would get nothing. Now: read configGet's return,
  fall back to BellAttention only when it returns false. Honors
  explicit-zero, falls back only on actual decode failure.

Note: B44 (quick-terminal-position = center) was already implemented
at MainWindow.cpp:766; the audit report was wrong on that one.
Removed from the tracker.

Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
ntomsic 2026-05-20 20:15:23 -05:00
parent a48ff0fb89
commit 7c3868b5b0
1 changed files with 26 additions and 3 deletions

View File

@ -104,6 +104,17 @@ MainWindow::~MainWindow() {
qDeleteAll(m_surfaces);
m_surfaces.clear();
// If this was the last window and a quit delay is configured, arm
// the natural-close quit timer instead of tearing down immediately.
// Qt's setQuitOnLastWindowClosed is off (we did that in initialize
// so the delay can run), so without this the process would stay
// alive forever after closing the final window via the WM.
// Mirrors GTK's application.zig:820-862 startQuitTimer wiring.
if (s_windows.isEmpty() && s_quitDelayMs > 0) {
handleQuitTimer(true);
return; // keep s_app + s_config alive until the timer fires
}
// The shared app and config outlive every window but the last.
if (s_windows.isEmpty()) {
if (s_frameTimer) {
@ -295,6 +306,12 @@ bool MainWindow::initialize() {
}
MainWindow *MainWindow::newWindow(ghostty_surface_t parent) {
// If the natural-close quit timer is running (because the last
// window was closed and we're inside the configured delay), cancel
// it now: the process is no longer headless. macOS/GTK do the
// same.
if (s_quitTimer) handleQuitTimer(false);
auto *w = new MainWindow;
w->setAttribute(Qt::WA_DeleteOnClose); // self-destruct when closed
w->m_firstTabParent = parent; // first tab inherits from `parent`
@ -957,9 +974,15 @@ void MainWindow::moveTab(int amount) {
void MainWindow::ringBell(GhosttySurface *surface) {
// bell-features is a packed struct returned by ghostty_config_get as
// a bitfield (see BellFeature in Util.h).
unsigned int features = BellAttention; // fallback if config-get fails
configGet(s_config, &features, "bell-features");
// a bitfield (see BellFeature in Util.h). If the config-get call
// itself fails (e.g. an ABI drift between Qt frontend and libghostty
// dropping the field), use BellAttention as a sane minimum fallback.
// If config-get succeeds with features=0, the user explicitly opted
// out of every bell feature and we honor that.
unsigned int features = 0;
if (!configGet(s_config, &features, "bell-features")) {
features = BellAttention;
}
if (features & BellAttention) QApplication::alert(this);
if (features & BellSystem) QApplication::beep();
if (features & BellAudio) playBellAudio();