qt: parity batch 1 — action handlers (B4, B6, B7, B11, B16)
Five fixes from PARITY.md tier 1, all in MainWindow: B4 — confirm-close-surface config now honored. Read the config string and respect 'false' (never prompt), 'true' (prompt only on live processes — the prior behavior), and 'always' (prompt regardless). Previously the config was ignored entirely. B6 — CLOSE_TAB now honors close_tab_mode. New closeTabsByMode() handles THIS / OTHER / RIGHT, single confirm covering every affected surface, descending iteration so removeTab doesn't shift indices. Previously every CLOSE_TAB collapsed to mode=THIS, silently dropping 'close other tabs' and 'close tabs to the right' keybinds. B7 — INITIAL_SIZE no longer halves the window on HiDPI. The action carries logical pixels; the prior code divided by devicePixelRatioF, producing half-size windows on 2x displays. B11 — RELOAD_CONFIG is now app-scoped. New static reloadConfigGlobal() runs the same logic but as a static, posted via post(qApp, ...) so the reload still happens if the source window is closed mid-dispatch. The config is process-wide already (statics in MainWindow); this just removes the per-window cancel semantics. B16 — COPY_TITLE_TO_CLIPBOARD now copies the source surface's tab title, not whatever tab is currently visible. Triggering the binding from a non-current tab used to copy the wrong title. Co-Authored-By: claude-flow <ruv@ruv.net>pull/12846/head
parent
b7e94c01cc
commit
33b5dee468
|
|
@ -475,6 +475,40 @@ void MainWindow::closeTab(int index) {
|
|||
}
|
||||
}
|
||||
|
||||
// Honor libghostty's close_tab_mode (THIS / OTHER / RIGHT) for the
|
||||
// CLOSE_TAB action. macOS supports all three; GTK supports all three
|
||||
// via adw.TabView.closeOtherPages / closePagesAfter.
|
||||
void MainWindow::closeTabsByMode(GhosttySurface *src,
|
||||
ghostty_action_close_tab_mode_e mode) {
|
||||
const int srcTab = tabIndexForSurface(src);
|
||||
if (srcTab < 0) return;
|
||||
|
||||
// Build the list of tab indices to close, in DESCENDING order so
|
||||
// removeTab doesn't shift later indices out from under us.
|
||||
QList<int> indices;
|
||||
switch (mode) {
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_THIS:
|
||||
indices = {srcTab};
|
||||
break;
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_OTHER:
|
||||
for (int i = m_tabs->count() - 1; i >= 0; --i)
|
||||
if (i != srcTab) indices.append(i);
|
||||
break;
|
||||
case GHOSTTY_ACTION_CLOSE_TAB_MODE_RIGHT:
|
||||
for (int i = m_tabs->count() - 1; i > srcTab; --i) indices.append(i);
|
||||
break;
|
||||
}
|
||||
if (indices.isEmpty()) return;
|
||||
|
||||
// Single confirm prompt covering every surface affected.
|
||||
QList<GhosttySurface *> affected;
|
||||
for (int i : indices)
|
||||
for (GhosttySurface *s : surfacesInTab(i)) affected.append(s);
|
||||
if (!confirmCloseSurfaces(affected)) return;
|
||||
|
||||
for (int i : indices) closeTab(i);
|
||||
}
|
||||
|
||||
void MainWindow::adoptTab(MainWindow *src, QWidget *page) {
|
||||
const int srcIndex = src->m_tabs->indexOf(page);
|
||||
if (srcIndex < 0 || src == this) return;
|
||||
|
|
@ -549,8 +583,13 @@ void MainWindow::setTabTitleOverride(GhosttySurface *surface,
|
|||
updateTabText(index);
|
||||
}
|
||||
|
||||
void MainWindow::copyTitleToClipboard() {
|
||||
const int tab = m_tabs->currentIndex();
|
||||
void MainWindow::copyTitleToClipboard(GhosttySurface *src) {
|
||||
// Per-surface: copy the title of the tab containing `src`, not
|
||||
// whatever tab is currently visible. macOS does the same; otherwise
|
||||
// a binding triggered from a non-current tab copies the wrong title.
|
||||
// Falls back to the current tab if the source is unknown.
|
||||
int tab = src ? tabIndexForSurface(src) : -1;
|
||||
if (tab < 0) tab = m_tabs->currentIndex();
|
||||
if (tab < 0) return;
|
||||
const TabData data = m_tabs->tabBar()->tabData(tab).value<TabData>();
|
||||
const QString title =
|
||||
|
|
@ -586,10 +625,22 @@ void MainWindow::closeEvent(QCloseEvent *e) {
|
|||
|
||||
bool MainWindow::confirmCloseSurfaces(
|
||||
const QList<GhosttySurface *> &surfaces) {
|
||||
bool needsConfirm = false;
|
||||
for (GhosttySurface *s : surfaces)
|
||||
if (s->surface() && ghostty_surface_needs_confirm_quit(s->surface()))
|
||||
needsConfirm = true;
|
||||
// Honor the `confirm-close-surface` config:
|
||||
// false -> never prompt
|
||||
// true -> prompt only when libghostty says a process is running
|
||||
// always -> always prompt, even for surfaces with no live process
|
||||
// (libghostty Config.zig: ConfirmCloseSurface enum.)
|
||||
const QString mode = configString("confirm-close-surface");
|
||||
if (mode == QLatin1String("false")) return true;
|
||||
|
||||
bool needsConfirm = (mode == QLatin1String("always"));
|
||||
if (!needsConfirm) {
|
||||
for (GhosttySurface *s : surfaces)
|
||||
if (s->surface() && ghostty_surface_needs_confirm_quit(s->surface())) {
|
||||
needsConfirm = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!needsConfirm) return true;
|
||||
|
||||
const auto choice = QMessageBox::question(
|
||||
|
|
@ -975,7 +1026,10 @@ void MainWindow::refreshChrome() {
|
|||
}
|
||||
}
|
||||
|
||||
void MainWindow::reloadConfig() {
|
||||
void MainWindow::reloadConfig() { reloadConfigGlobal(); }
|
||||
|
||||
void MainWindow::reloadConfigGlobal() {
|
||||
if (!s_app) return;
|
||||
// Re-read the config from disk in the same order as initialize().
|
||||
ghostty_config_t config = ghostty_config_new();
|
||||
ghostty_config_load_default_files(config);
|
||||
|
|
@ -1238,13 +1292,15 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
return true;
|
||||
}
|
||||
|
||||
case GHOSTTY_ACTION_CLOSE_TAB:
|
||||
if (src)
|
||||
post(win, [winp, srcp]() {
|
||||
if (!winp || !srcp) return;
|
||||
if (winp->confirmCloseSurfaces({srcp})) winp->removeSurface(srcp);
|
||||
});
|
||||
case GHOSTTY_ACTION_CLOSE_TAB: {
|
||||
if (!src) return false;
|
||||
const ghostty_action_close_tab_mode_e mode = action.action.close_tab_mode;
|
||||
post(win, [winp, srcp, mode]() {
|
||||
if (!winp || !srcp) return;
|
||||
winp->closeTabsByMode(srcp, mode);
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
case GHOSTTY_ACTION_SET_TITLE: {
|
||||
const char *title = action.action.set_title.title;
|
||||
|
|
@ -1278,8 +1334,8 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
}
|
||||
|
||||
case GHOSTTY_ACTION_COPY_TITLE_TO_CLIPBOARD:
|
||||
post(win, [winp]() {
|
||||
if (winp) winp->copyTitleToClipboard();
|
||||
post(win, [winp, srcp]() {
|
||||
if (winp) winp->copyTitleToClipboard(srcp);
|
||||
});
|
||||
return true;
|
||||
|
||||
|
|
@ -1408,9 +1464,11 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
}
|
||||
|
||||
case GHOSTTY_ACTION_RELOAD_CONFIG:
|
||||
post(win, [winp]() {
|
||||
if (winp) winp->reloadConfig();
|
||||
});
|
||||
// Reload is app-scoped (the config is process-wide). Post to
|
||||
// qApp instead of the originating window so the reload still
|
||||
// happens if the window that issued the action is closed
|
||||
// between the dispatch and the queued slot.
|
||||
post(qApp, []() { MainWindow::reloadConfigGlobal(); });
|
||||
return true;
|
||||
|
||||
case GHOSTTY_ACTION_CONFIG_CHANGE:
|
||||
|
|
@ -1425,10 +1483,11 @@ bool MainWindow::onAction(ghostty_app_t, ghostty_target_s target,
|
|||
const ghostty_action_initial_size_s sz = action.action.initial_size;
|
||||
post(win, [winp, sz]() {
|
||||
if (!winp) return;
|
||||
// The action carries device pixels; resize() takes logical.
|
||||
const double dpr = winp->devicePixelRatioF();
|
||||
const QSize logical(static_cast<int>(sz.width / dpr),
|
||||
static_cast<int>(sz.height / dpr));
|
||||
// The action carries logical pixels; resize() takes the same.
|
||||
// The previous code divided by devicePixelRatioF, halving the
|
||||
// window on a 2x display.
|
||||
const QSize logical(static_cast<int>(sz.width),
|
||||
static_cast<int>(sz.height));
|
||||
winp->m_defaultWindowSize = logical; // for RESET_WINDOW_SIZE
|
||||
winp->resize(logical);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -98,6 +98,9 @@ private:
|
|||
static void frame();
|
||||
|
||||
void closeTab(int index);
|
||||
// Honor close-tab-mode (THIS / OTHER / RIGHT) from libghostty.
|
||||
void closeTabsByMode(GhosttySurface *src,
|
||||
ghostty_action_close_tab_mode_e mode);
|
||||
// Tear tab `index` out into a new window (tabTornOff signal).
|
||||
void detachTab(int index);
|
||||
// Move `page` (a tab and its surfaces) from `src` into this window.
|
||||
|
|
@ -129,10 +132,15 @@ private:
|
|||
// while set, SET_TITLE no longer changes the tab text.
|
||||
void setTabTitleOverride(GhosttySurface *surface, const QString &title);
|
||||
// Copy the current tab's effective title to the clipboard.
|
||||
void copyTitleToClipboard();
|
||||
void copyTitleToClipboard(GhosttySurface *src);
|
||||
|
||||
// Rebuild the config from disk and push it to libghostty.
|
||||
void reloadConfig();
|
||||
// App-scoped reload entry point. The config is process-wide (statics
|
||||
// in this class), so reload from any window has the same effect; the
|
||||
// RELOAD_CONFIG action posts to qApp via this static so the reload
|
||||
// can't be cancelled by the source window closing mid-dispatch.
|
||||
static void reloadConfigGlobal();
|
||||
// Refresh every window's chrome from the current config (used after a
|
||||
// reload and on the CONFIG_CHANGE notification).
|
||||
static void refreshChrome();
|
||||
|
|
|
|||
Loading…
Reference in New Issue