qt: parity tier 3 batch 10 — quit semantics + theme + quick-term polish
Five fixes:
B2 — Split QUIT and CLOSE_ALL_WINDOWS. closeAllWindows takes a
thenQuit flag: QUIT always ends the process; CLOSE_ALL_WINDOWS
honors quit-after-last-window-closed. macOS keeps these distinct
so a "close all windows" keybind doesn't unexpectedly terminate
the app. Dialog title/verb track the action ("Quit" vs
"Close All").
B5 — closeAllWindows now respects quit-after-last-window-closed
=false. With the flag off, the process stays alive after
close-all (matching the natural-close path); with a delay
configured, the libghostty QUIT_TIMER action drives the eventual
termination.
B3 — closeEvent consumes m_skipCloseConfirm for THIS attempt
only. The flag was set by callers and never cleared; if a future
closeEvent override ignored the close, the next attempt would
still skip the prompt. macOS resets per-action.
B41 — window-theme `ghostty` mode (luminance-derived dark/light)
now also applies on pre-Qt 6.8 via QApplication::setPalette,
not just Qt 6.8+'s setColorScheme. The 6.8+ path is unchanged;
the older path synthesizes a dark or light palette by hand for
the dark / light / ghostty (luminance) cases. Want::Follow
leaves the desktop's choice in place.
B45 — `quick-terminal-space-behavior` is honored as a no-op.
Wayland's wlr-layer-shell has no per-workspace pin; KWin
always renders layer surfaces on the active workspace, which
corresponds to `move`. `remain` is documented as not
achievable on Linux/Wayland.
B46 — Non-Wayland fallback for the quick terminal. When
LayerShellQt::Window::get() returns null (XWayland / X11 /
nested compositor), set Frameless | StaysOnTop | Tool flags
and place a 60%/40% top-centered window so the dropdown still
works as a normal popup. Previously the code returned and left
the QWindow as a regular Qt main window — bigger, in the
taskbar, decorated.
Co-Authored-By: claude-flow <ruv@ruv.net>
pull/12846/head
parent
3163397ad8
commit
4c903802a9
|
|
@ -800,7 +800,13 @@ void MainWindow::onTabCloseRequested(int index) {
|
|||
void MainWindow::closeEvent(QCloseEvent *e) {
|
||||
// confirm-close-surface: prompt once for the whole window unless this
|
||||
// close was already confirmed (e.g. the last tab/surface closing).
|
||||
if (!m_skipCloseConfirm && !confirmCloseSurfaces(m_surfaces)) {
|
||||
// The skip flag is consumed for THIS close attempt only — if the
|
||||
// close goes on to be ignored (it shouldn't here, but a future
|
||||
// closeEvent override could), the next attempt re-prompts. macOS
|
||||
// resets per-action, mirroring this.
|
||||
const bool skip = m_skipCloseConfirm;
|
||||
m_skipCloseConfirm = false;
|
||||
if (!skip && !confirmCloseSurfaces(m_surfaces)) {
|
||||
e->ignore();
|
||||
return;
|
||||
}
|
||||
|
|
@ -849,23 +855,28 @@ bool MainWindow::confirmCloseSurfaces(
|
|||
return box.clickedButton() == close;
|
||||
}
|
||||
|
||||
void MainWindow::closeAllWindows() {
|
||||
// One process-level prompt covers every window. Destructive Quit
|
||||
// button + plain Cancel default — same style as confirmCloseSurfaces.
|
||||
void MainWindow::closeAllWindows(bool thenQuit) {
|
||||
// One process-level prompt covers every window. Destructive button
|
||||
// + Cancel default — same style as confirmCloseSurfaces. Title /
|
||||
// verb track whether this is a Quit (process ends) or a
|
||||
// Close All Windows (process may stay alive).
|
||||
if (s_app && ghostty_app_needs_confirm_quit(s_app)) {
|
||||
const QString title = thenQuit ? QStringLiteral("Quit")
|
||||
: QStringLiteral("Close All Windows");
|
||||
const QString verb = thenQuit ? QStringLiteral("Quit")
|
||||
: QStringLiteral("Close All");
|
||||
QMessageBox box(s_windows.isEmpty() ? nullptr : s_windows.first());
|
||||
box.setIcon(QMessageBox::Warning);
|
||||
box.setWindowTitle(QStringLiteral("Quit"));
|
||||
box.setWindowTitle(title);
|
||||
box.setText(QStringLiteral("There are still running processes."));
|
||||
box.setInformativeText(
|
||||
QStringLiteral("Quitting will terminate the running processes."));
|
||||
QPushButton *quit = box.addButton(QStringLiteral("Quit"),
|
||||
QMessageBox::DestructiveRole);
|
||||
box.setInformativeText(QStringLiteral(
|
||||
"%1 will terminate the running processes.").arg(title));
|
||||
QPushButton *go = box.addButton(verb, QMessageBox::DestructiveRole);
|
||||
QPushButton *cancel = box.addButton(QStringLiteral("Cancel"),
|
||||
QMessageBox::RejectRole);
|
||||
box.setDefaultButton(cancel);
|
||||
box.exec();
|
||||
if (box.clickedButton() != quit) return;
|
||||
if (box.clickedButton() != go) return;
|
||||
}
|
||||
// Copy: each close() may delete the window and mutate s_windows.
|
||||
const QList<MainWindow *> windows = s_windows;
|
||||
|
|
@ -873,9 +884,22 @@ void MainWindow::closeAllWindows() {
|
|||
w->m_skipCloseConfirm = true;
|
||||
w->close();
|
||||
}
|
||||
// An explicit quit/close-all should end the process even when
|
||||
// quit-after-last-window-closed left quitOnLastWindowClosed off.
|
||||
qApp->quit();
|
||||
if (thenQuit) {
|
||||
// QUIT explicitly ends the process even when
|
||||
// quit-after-last-window-closed left quitOnLastWindowClosed off.
|
||||
qApp->quit();
|
||||
} else {
|
||||
// CLOSE_ALL_WINDOWS without a delay still ends the process if
|
||||
// the user wants quit-on-last-window. With a delay configured,
|
||||
// the QUIT_TIMER action from libghostty drives the eventual
|
||||
// termination, matching the natural-close path. Otherwise the
|
||||
// process stays alive (macOS close-all has the same semantics —
|
||||
// the app remains in the dock and a launcher / global keybind
|
||||
// can re-open windows).
|
||||
bool quitAfter = true;
|
||||
configGet(s_config, &quitAfter, "quit-after-last-window-closed");
|
||||
if (quitAfter && s_quitDelayMs == 0) qApp->quit();
|
||||
}
|
||||
}
|
||||
|
||||
void MainWindow::toggleVisibility() {
|
||||
|
|
@ -980,7 +1004,29 @@ void MainWindow::setupLayerShell() {
|
|||
QWindow *handle = windowHandle();
|
||||
if (!handle) return;
|
||||
LayerShellQt::Window *ls = LayerShellQt::Window::get(handle);
|
||||
if (!ls) return;
|
||||
if (!ls) {
|
||||
// Non-Wayland (XWayland / X11 / nested) or a compositor without
|
||||
// the wlr-layer-shell protocol. Fall back to a regular always-
|
||||
// on-top, undecorated, top-anchored window so the quick
|
||||
// terminal still works as a dropdown — just without
|
||||
// workspace-spanning behaviour. macOS gets a Cocoa window from
|
||||
// the same code path; the quick terminal there is the
|
||||
// equivalent fallback.
|
||||
setWindowFlag(Qt::FramelessWindowHint, true);
|
||||
setWindowFlag(Qt::WindowStaysOnTopHint, true);
|
||||
setWindowFlag(Qt::Tool, true); // stay out of the taskbar
|
||||
if (QScreen *screen = handle->screen()) {
|
||||
const QSize scr = screen->size();
|
||||
// 60% width, 40% height, top-centered — close enough to the
|
||||
// primary layer-shell layout (top-anchored full-width) without
|
||||
// the protocol's fancier anchoring.
|
||||
const QSize size(scr.width() * 6 / 10, scr.height() * 4 / 10);
|
||||
const QRect g = screen->geometry();
|
||||
move(g.left() + (g.width() - size.width()) / 2, g.top());
|
||||
resize(size);
|
||||
}
|
||||
return;
|
||||
}
|
||||
using LSW = LayerShellQt::Window;
|
||||
|
||||
ls->setLayer(LSW::LayerTop);
|
||||
|
|
@ -1007,6 +1053,15 @@ void MainWindow::setupLayerShell() {
|
|||
if (screen && handle->screen() != screen) handle->setScreen(screen);
|
||||
ls->setScreenConfiguration(LSW::ScreenFromQWindow);
|
||||
|
||||
// quick-terminal-space-behavior (`remain` / `move`): macOS
|
||||
// controls whether the dropdown follows the active Space or pins
|
||||
// to the one it was opened on. Wayland's wlr-layer-shell has no
|
||||
// equivalent — the compositor always renders the surface on the
|
||||
// active workspace (KWin behaviour), which corresponds to `move`.
|
||||
// Achieving `remain` would need a per-workspace pin that no
|
||||
// mainstream compositor exposes; honour by no-op and document.
|
||||
Q_UNUSED(configString("quick-terminal-space-behavior"));
|
||||
|
||||
const QSize scr = screen ? screen->size() : QSize(1920, 1080);
|
||||
|
||||
// quick-terminal-size: primary is the edge-perpendicular extent.
|
||||
|
|
@ -1760,24 +1815,58 @@ void MainWindow::applyWindowConfig() {
|
|||
for (QSplitter *s : findChildren<QSplitter *>())
|
||||
s->setStyleSheet(splitterCss);
|
||||
|
||||
// window-theme: force a light/dark scheme, or follow the OS. `auto`
|
||||
// is rewritten to `system` on Linux by libghostty; `ghostty` follows
|
||||
// the configured background colour's luminance.
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
// window-theme: force a light/dark scheme, or follow the OS.
|
||||
//
|
||||
// `auto` / `system` — follow the OS (Qt 6.8+ honours the platform
|
||||
// colour scheme automatically; pre-6.8 we don't override).
|
||||
// `dark` / `light` — force the explicit scheme.
|
||||
// `ghostty` — derive from the configured background colour's
|
||||
// luminance (Rec.601 weighting).
|
||||
//
|
||||
// Qt 6.8 added QStyleHints::setColorScheme. Before that, the only
|
||||
// portable knob is QPalette: derive a dark/light palette and apply
|
||||
// it process-wide. We set the palette in both branches so a forced
|
||||
// theme actually changes button / text colours, not just the
|
||||
// colour-scheme hint that nothing in our chrome reads on its own.
|
||||
const QString theme = configString("window-theme");
|
||||
Qt::ColorScheme scheme = Qt::ColorScheme::Unknown; // Unknown = follow OS
|
||||
if (theme == QLatin1String("dark")) {
|
||||
scheme = Qt::ColorScheme::Dark;
|
||||
} else if (theme == QLatin1String("light")) {
|
||||
scheme = Qt::ColorScheme::Light;
|
||||
} else if (theme == QLatin1String("ghostty")) {
|
||||
enum class Want { Follow, Dark, Light };
|
||||
Want want = Want::Follow;
|
||||
if (theme == QLatin1String("dark")) want = Want::Dark;
|
||||
else if (theme == QLatin1String("light")) want = Want::Light;
|
||||
else if (theme == QLatin1String("ghostty")) {
|
||||
ghostty_config_color_s bg{};
|
||||
if (configGet(s_config, &bg, "background")) {
|
||||
const double luma = 0.299 * bg.r + 0.587 * bg.g + 0.114 * bg.b;
|
||||
scheme = luma < 128.0 ? Qt::ColorScheme::Dark : Qt::ColorScheme::Light;
|
||||
want = luma < 128.0 ? Want::Dark : Want::Light;
|
||||
}
|
||||
}
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(6, 8, 0)
|
||||
Qt::ColorScheme scheme = Qt::ColorScheme::Unknown;
|
||||
if (want == Want::Dark) scheme = Qt::ColorScheme::Dark;
|
||||
else if (want == Want::Light) scheme = Qt::ColorScheme::Light;
|
||||
QGuiApplication::styleHints()->setColorScheme(scheme);
|
||||
#else
|
||||
// Pre-6.8 fallback: synthesize a palette by hand. The forced
|
||||
// light/dark palettes here approximate Qt 6.8's defaults closely
|
||||
// enough for chrome (tab bar, dialogs); the terminal itself
|
||||
// honours its own theme via libghostty. Want::Follow leaves the
|
||||
// application palette untouched so the desktop's choice wins.
|
||||
if (want != Want::Follow) {
|
||||
QPalette p;
|
||||
if (want == Want::Dark) {
|
||||
p.setColor(QPalette::Window, QColor(0x2b, 0x2b, 0x2b));
|
||||
p.setColor(QPalette::WindowText, QColor(0xee, 0xee, 0xee));
|
||||
p.setColor(QPalette::Base, QColor(0x1e, 0x1e, 0x1e));
|
||||
p.setColor(QPalette::AlternateBase, QColor(0x33, 0x33, 0x33));
|
||||
p.setColor(QPalette::Text, QColor(0xee, 0xee, 0xee));
|
||||
p.setColor(QPalette::Button, QColor(0x2b, 0x2b, 0x2b));
|
||||
p.setColor(QPalette::ButtonText, QColor(0xee, 0xee, 0xee));
|
||||
p.setColor(QPalette::Highlight, QColor(0x2a, 0x82, 0xda));
|
||||
p.setColor(QPalette::HighlightedText, Qt::white);
|
||||
} // else: a default-constructed QPalette is light-friendly.
|
||||
QApplication::setPalette(p);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
@ -2116,8 +2205,14 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
return true;
|
||||
|
||||
case GHOSTTY_ACTION_QUIT:
|
||||
post(qApp, []() { MainWindow::closeAllWindows(/*thenQuit=*/true); });
|
||||
return true;
|
||||
case GHOSTTY_ACTION_CLOSE_ALL_WINDOWS:
|
||||
post(qApp, []() { MainWindow::closeAllWindows(); });
|
||||
// Distinct from QUIT: close-all-windows leaves the process
|
||||
// alive when quit-after-last-window-closed is false. macOS
|
||||
// makes the same distinction.
|
||||
post(qApp,
|
||||
[]() { MainWindow::closeAllWindows(/*thenQuit=*/false); });
|
||||
return true;
|
||||
|
||||
case GHOSTTY_ACTION_QUIT_TIMER: {
|
||||
|
|
|
|||
|
|
@ -220,9 +220,13 @@ private:
|
|||
// Returns true if the close may proceed.
|
||||
bool confirmCloseSurfaces(const QList<GhosttySurface *> &surfaces);
|
||||
|
||||
// Close every window, optionally quitting the process; prompts once
|
||||
// via ghostty_app_needs_confirm_quit.
|
||||
static void closeAllWindows();
|
||||
// Close every window, optionally quitting the process. Prompts once
|
||||
// via ghostty_app_needs_confirm_quit. `thenQuit=true` is the QUIT
|
||||
// action's behavior (close everything and end the process);
|
||||
// `thenQuit=false` is CLOSE_ALL_WINDOWS, which leaves the process
|
||||
// alive when `quit-after-last-window-closed=false` is set —
|
||||
// matching macOS where close-all and quit are distinct.
|
||||
static void closeAllWindows(bool thenQuit);
|
||||
|
||||
// Wire the libghostty quit_timer action to a delayed QApplication
|
||||
// quit, gated on `quit-after-last-window-closed`.
|
||||
|
|
|
|||
Loading…
Reference in New Issue