qt: parity tier 3 batch 5 — config reload + inspector autosave + initial-window
Five fixes: B12 / B47 — refreshChrome now propagates window-decoration, fullscreen, and maximize to running windows on reload-config. Toggling Qt::FramelessWindowHint hides+reshows the window so we gate on a real change. Fullscreen exits via showNormal() before reverting; maximize is one-way (don't undo a user's manual maximize on reload). B48 — `quit-after-last-window-closed[-delay]` is now refreshed on reload too. Previously s_quitDelayMs was cached at init, so changing the delay required a process restart. B43 / C11 — quick-terminal-screen honored. `main` (default) → primary screen; `mouse` → screen under cursor. Implemented by setting handle->setScreen() before LayerShellQt's setScreenConfiguration(ScreenFromQWindow). `macos-menu-bar` falls through to primary on Linux (it's a macOS-only mode). B49 — Inspector window geometry autosaves via QSettings. First reveal restores the prior size+position; the prior 800x600 hard-coded resize() is the first-run fallback. main.cpp now sets QCoreApplication::setApplicationName + setOrganizationDomain so QSettings has stable storage keys. C20 — `initial-window: false` honored. main.cpp creates a bootstrap window so libghostty's app+config exists, then closes it if the user opted out. New helpers: MainWindow::wantsInitialWindow / closeInitialWindow. Polish: confirm-paste dialog uses destructive Paste / Cancel buttons, matching the I1 close-confirmation styling. C16 (clipboard-trim-trailing-spaces) is silently honored — libghostty applies it inside Surface.zig before invoking write_clipboard_cb. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
bfd39a4dd9
commit
6d700c36b3
|
|
@ -11,6 +11,7 @@
|
|||
#include <QOpenGLFramebufferObject>
|
||||
#include <QOpenGLFunctions>
|
||||
#include <QPainter>
|
||||
#include <QSettings>
|
||||
#include <QSurfaceFormat>
|
||||
#include <QTimer>
|
||||
#include <QWheelEvent>
|
||||
|
|
@ -46,7 +47,15 @@ InspectorWindow::InspectorWindow(ghostty_surface_t surface)
|
|||
setWindowTitle(QStringLiteral("Ghastty Inspector"));
|
||||
setFocusPolicy(Qt::StrongFocus);
|
||||
setMouseTracking(true);
|
||||
resize(800, 600);
|
||||
|
||||
// Restore the last saved size/position. macOS uses NSWindow's
|
||||
// autosaveName; Qt has no built-in equivalent, so we persist via
|
||||
// QSettings ourselves. First-run default matches the prior
|
||||
// hard-coded 800x600.
|
||||
QSettings s;
|
||||
const QByteArray geom =
|
||||
s.value(QStringLiteral("inspector/geometry")).toByteArray();
|
||||
if (!restoreGeometry(geom)) resize(800, 600);
|
||||
|
||||
m_inspector = ghostty_surface_inspector(m_surface);
|
||||
|
||||
|
|
@ -150,6 +159,8 @@ void InspectorWindow::closeEvent(QCloseEvent *e) {
|
|||
// deleted only when the surface is destroyed. Stop the redraw
|
||||
// timer too — a hidden inspector has no work to do.
|
||||
if (m_timer) m_timer->stop();
|
||||
// Persist size + position so the next reveal restores them.
|
||||
QSettings().setValue(QStringLiteral("inspector/geometry"), saveGeometry());
|
||||
hide();
|
||||
e->ignore();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -943,7 +943,23 @@ void MainWindow::setupLayerShell() {
|
|||
: ki == QLatin1String("none") ? LSW::KeyboardInteractivityNone
|
||||
: LSW::KeyboardInteractivityOnDemand);
|
||||
|
||||
// quick-terminal-screen: pick which output to anchor on. `main`
|
||||
// (the default) maps to the primary screen; `mouse` to the screen
|
||||
// under the cursor; `macos-menu-bar` is macOS-only and falls
|
||||
// through to primary on Linux. LayerShellQt reads the QWindow's
|
||||
// QScreen when ScreenFromQWindow is set, so we just set the
|
||||
// window's screen before anchoring.
|
||||
const QString screenMode = configString("quick-terminal-screen");
|
||||
QScreen *screen = handle->screen();
|
||||
if (screenMode == QLatin1String("mouse")) {
|
||||
if (QScreen *s = QGuiApplication::screenAt(QCursor::pos())) screen = s;
|
||||
} else if (screenMode == QLatin1String("main") ||
|
||||
screenMode == QLatin1String("macos-menu-bar")) {
|
||||
if (QScreen *s = QGuiApplication::primaryScreen()) screen = s;
|
||||
}
|
||||
if (screen && handle->screen() != screen) handle->setScreen(screen);
|
||||
ls->setScreenConfiguration(LSW::ScreenFromQWindow);
|
||||
|
||||
const QSize scr = screen ? screen->size() : QSize(1920, 1080);
|
||||
|
||||
// quick-terminal-size: primary is the edge-perpendicular extent.
|
||||
|
|
@ -1268,12 +1284,66 @@ void MainWindow::playBellAudio() {
|
|||
m_bellPlayer->play();
|
||||
}
|
||||
|
||||
// Refresh every window's chrome (tab-bar policy, colour scheme, blur)
|
||||
// from the current s_config.
|
||||
// Refresh every window's chrome from the current s_config: tab-bar
|
||||
// policy, colour scheme, blur — plus window-level state that
|
||||
// previously only applied at startup (window-decoration, fullscreen,
|
||||
// maximize) and the quit-after-last-window-closed delay.
|
||||
void MainWindow::refreshChrome() {
|
||||
// Refresh app-scoped state. quit-after-last-window-closed[-delay]
|
||||
// can change the delay or the quitOnLastWindowClosed strategy at
|
||||
// runtime; mirrors the calculation in initialize().
|
||||
if (s_config) {
|
||||
bool quitAfter = true;
|
||||
configGet(s_config, &quitAfter, "quit-after-last-window-closed");
|
||||
unsigned long long delayNs = 0;
|
||||
configGet(s_config, &delayNs, "quit-after-last-window-closed-delay");
|
||||
s_quitDelayMs = quitAfter ? static_cast<int>(delayNs / 1000000ULL) : 0;
|
||||
QApplication::setQuitOnLastWindowClosed(quitAfter && s_quitDelayMs == 0);
|
||||
}
|
||||
|
||||
for (MainWindow *w : s_windows) {
|
||||
w->applyWindowConfig();
|
||||
w->applyBlur();
|
||||
|
||||
// Quick terminal is layer-shell-anchored and window flags don't
|
||||
// apply; the rest of the per-window state is config-driven and
|
||||
// only the static initialize() ever touched it before. This
|
||||
// brings reload-time changes through to live windows.
|
||||
if (w->m_quickTerminal) continue;
|
||||
|
||||
// window-decoration: `none` → frameless, anything else → decorated.
|
||||
// Toggling Qt::FramelessWindowHint hides+reshows the window, so
|
||||
// gate on a real change.
|
||||
const bool wantFrameless =
|
||||
w->configString("window-decoration") == QLatin1String("none");
|
||||
const bool isFrameless =
|
||||
w->windowFlags().testFlag(Qt::FramelessWindowHint);
|
||||
if (wantFrameless != isFrameless) {
|
||||
const bool wasVisible = w->isVisible();
|
||||
w->setWindowFlag(Qt::FramelessWindowHint, wantFrameless);
|
||||
if (wasVisible) {
|
||||
w->show();
|
||||
w->activateWindow();
|
||||
}
|
||||
}
|
||||
|
||||
// fullscreen / maximize: `fullscreen=true` wins over `maximize`.
|
||||
// Setting back to a non-fullscreen window goes through showNormal
|
||||
// first so the WM lets us out of fullscreen cleanly.
|
||||
const QString fs = w->configString("fullscreen");
|
||||
const bool wantFullscreen = !fs.isEmpty() && fs != QLatin1String("false");
|
||||
const bool wantMax = w->configBool("maximize", false);
|
||||
if (wantFullscreen) {
|
||||
if (!w->isFullScreen()) w->showFullScreen();
|
||||
} else if (w->isFullScreen()) {
|
||||
w->showNormal();
|
||||
}
|
||||
if (!wantFullscreen) {
|
||||
if (wantMax && !w->isMaximized()) w->showMaximized();
|
||||
// No "un-maximize on reload" path: a user who removed `maximize`
|
||||
// from their config probably doesn't want their existing
|
||||
// maximized window snapped back to its non-maximized geometry.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1303,6 +1373,26 @@ void MainWindow::reloadConfigGlobal() {
|
|||
refreshChrome();
|
||||
}
|
||||
|
||||
bool MainWindow::wantsInitialWindow() {
|
||||
// s_config exists once the bootstrap window has called initialize().
|
||||
if (!s_config) return true;
|
||||
bool wanted = true;
|
||||
configGet(s_config, &wanted, "initial-window");
|
||||
return wanted;
|
||||
}
|
||||
|
||||
void MainWindow::closeInitialWindow() {
|
||||
if (s_windows.isEmpty()) return;
|
||||
// Close the bootstrap window without re-prompting; nothing has run
|
||||
// in it yet so confirmCloseSurfaces would return true anyway, but
|
||||
// m_skipCloseConfirm avoids any chrome flicker. closeAllWindows
|
||||
// also resets the quit-on-last-window flag to keep the process
|
||||
// alive until the user binds the quick-terminal shortcut.
|
||||
MainWindow *first = s_windows.first();
|
||||
first->m_skipCloseConfirm = true;
|
||||
first->close();
|
||||
}
|
||||
|
||||
QString MainWindow::configString(const char *key) const {
|
||||
const char *value = nullptr;
|
||||
if (!s_config ||
|
||||
|
|
@ -2353,15 +2443,22 @@ void MainWindow::onConfirmReadClipboard(void *ud, const char *str, void *state,
|
|||
while (cut > 0 && preview.at(cut - 1).isHighSurrogate()) --cut;
|
||||
preview = preview.left(cut) + QStringLiteral("…");
|
||||
}
|
||||
const auto reply = QMessageBox::warning(
|
||||
sp->owner(), QStringLiteral("Confirm Paste"),
|
||||
QStringLiteral("The text being pasted may be unsafe:\n\n%1\n\n"
|
||||
"Paste anyway?")
|
||||
.arg(preview),
|
||||
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
|
||||
// Destructive Paste / Cancel buttons, default Cancel —
|
||||
// mirrors the close-confirmation styling.
|
||||
QMessageBox box(sp->owner());
|
||||
box.setIcon(QMessageBox::Warning);
|
||||
box.setWindowTitle(QStringLiteral("Confirm Paste"));
|
||||
box.setText(QStringLiteral("The text being pasted may be unsafe."));
|
||||
box.setInformativeText(preview);
|
||||
QPushButton *paste = box.addButton(QStringLiteral("Paste"),
|
||||
QMessageBox::DestructiveRole);
|
||||
QPushButton *cancel = box.addButton(QStringLiteral("Cancel"),
|
||||
QMessageBox::RejectRole);
|
||||
box.setDefaultButton(cancel);
|
||||
box.exec();
|
||||
ghostty_surface_complete_clipboard_request(
|
||||
sp->surface(), content.constData(), state,
|
||||
reply == QMessageBox::Yes);
|
||||
box.clickedButton() == paste);
|
||||
},
|
||||
Qt::QueuedConnection);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -74,6 +74,15 @@ public:
|
|||
// The live libghostty config (for keybind lookups, etc.).
|
||||
ghostty_config_t config() const { return s_config; }
|
||||
|
||||
// initial-window config plumbing. The libghostty app+config is
|
||||
// built by the first MainWindow::initialize, so main.cpp opens a
|
||||
// bootstrap window and asks afterwards whether the user actually
|
||||
// wanted one — closing it cleanly if not. Headless start-up is
|
||||
// how a user runs ghastty as a daemon for the global quick-
|
||||
// terminal shortcut.
|
||||
static bool wantsInitialWindow();
|
||||
static void closeInitialWindow();
|
||||
|
||||
// UNDO / REDO close-tab/window. The libghostty actions carry no
|
||||
// payload — the apprt is responsible for tracking what was closed
|
||||
// and reviving it. macOS uses NSUndoManager; we keep a small bounded
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#include <cstdio>
|
||||
|
||||
#include <QApplication>
|
||||
#include <QCoreApplication>
|
||||
#include <QIcon>
|
||||
#include <QSurfaceFormat>
|
||||
|
||||
|
|
@ -28,6 +29,13 @@ int main(int argc, char **argv) {
|
|||
|
||||
QApplication app(argc, argv);
|
||||
|
||||
// QSettings storage path keys: applicationName + organizationDomain.
|
||||
// Used by the inspector window's geometry autosave (and any future
|
||||
// QSettings-backed UI state) — the keys go to
|
||||
// ~/.config/ghastty/ghastty.conf.
|
||||
QCoreApplication::setApplicationName(QStringLiteral("ghastty"));
|
||||
QCoreApplication::setOrganizationDomain(QStringLiteral("ghastty"));
|
||||
|
||||
// Match the installed ghastty.desktop: this becomes the Wayland app-id
|
||||
// (and X11 WM_CLASS), so the compositor associates the window with the
|
||||
// desktop entry — taskbar icon, launcher identity.
|
||||
|
|
@ -53,12 +61,22 @@ int main(int argc, char **argv) {
|
|||
return 1;
|
||||
}
|
||||
|
||||
// The first window; further windows are opened on demand by the
|
||||
// new_window action. Each window owns itself (WA_DeleteOnClose).
|
||||
// initial-window: when false, start headless (no window opens at
|
||||
// launch). Combined with quit-after-last-window-closed=false this
|
||||
// is how a user runs ghastty as a daemon for the global quick-
|
||||
// terminal shortcut. We need the libghostty app first, so spin up
|
||||
// a temporary "config bootstrap" by opening + immediately closing
|
||||
// a window — but cheaper: peek at the config directly here.
|
||||
// ghostty_init has already run, but the libghostty app is built
|
||||
// by the first MainWindow::initialize. There is no app-less
|
||||
// accessor for the config, so we open the window and close if the
|
||||
// bool is false. Cheaper alternative: set a static flag and have
|
||||
// initialize() bail before show.
|
||||
if (!MainWindow::newWindow(nullptr)) {
|
||||
std::fprintf(stderr, "[ghastty] window initialization failed\n");
|
||||
return 1;
|
||||
}
|
||||
if (!MainWindow::wantsInitialWindow()) MainWindow::closeInitialWindow();
|
||||
|
||||
// Register global shortcuts via the XDG portal so the quick terminal
|
||||
// can be toggled while Ghostty is unfocused. Keys are assigned by the
|
||||
|
|
|
|||
Loading…
Reference in New Issue