From ff152737c49ec8a0a8e8ea4c6d28646d83defd02 Mon Sep 17 00:00:00 2001 From: tsujan Date: Mon, 2 Dec 2024 01:57:01 +0330 Subject: [PATCH] Prompt on closing with a running process (#1200) Previously, the closing prompt was useful only when there was a running process in the terminal, but the code didn't check that. A prompt without a running process was redundant. This patch gives a real job to the closing prompt by making it work only when there is a running process, whether when the main window or a tab is going to be closed, or when a subterminal is going to be collapsed. To detect a running process, the code makes use of @marcusbritanicus's commit https://github.com/lxqt/qtermwidget/commit/ccb3b2132d3c7d4b19b9a3221f9c4dcf13eee9dc by comparing `getForegroundProcessId()` with `getShellPID()`. In addition, it checks whether `TerminalConfig` has a command (e.g., when an app is executed in the terminal by the file manager). Therefore, the config text "Ask for confirmation when closing" is also changed to "Prompt on closing with a running process". --- src/forms/propertiesdialog.ui | 2 +- src/mainwindow.cpp | 62 ++++++++++++++++++------------ src/mainwindow.h | 2 + src/tabwidget.cpp | 72 ++++++++++++++++++++++++++++------- src/tabwidget.h | 4 +- src/terminalconfig.cpp | 5 +++ src/terminalconfig.h | 1 + src/termwidget.cpp | 2 + src/termwidget.h | 5 +++ src/termwidgetholder.cpp | 16 ++++++++ src/termwidgetholder.h | 2 + 11 files changed, 133 insertions(+), 40 deletions(-) diff --git a/src/forms/propertiesdialog.ui b/src/forms/propertiesdialog.ui index 17caae4b..81297477 100644 --- a/src/forms/propertiesdialog.ui +++ b/src/forms/propertiesdialog.ui @@ -825,7 +825,7 @@ - Ask for confirmation when closing + Prompt on closing with a running process diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index fc1481b8..7be3948c 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -306,7 +306,7 @@ void MainWindow::setup_ActionsMenu_Actions() data.setValue(checkSubterminals); - setup_Action(SUB_COLLAPSE, new QAction(tr("&Collapse Subterminal"), settingOwner), + setup_Action(SUB_COLLAPSE, new QAction(tr("&Close Subterminal"), settingOwner), nullptr, consoleTabulator, SLOT(splitCollapse()), menu_Actions, data); setup_Action(SUB_TOP, new QAction(QIcon::fromTheme(QStringLiteral("go-up")), tr("&Top Subterminal"), settingOwner), @@ -621,13 +621,44 @@ void MainWindow::toggleBookmarks() } } +bool MainWindow::closePrompt(const QString &title, const QString &text) +{ + QDialog * dia = new QDialog(this); + dia->setObjectName(QStringLiteral("exitDialog")); + dia->setWindowTitle(title); + + QCheckBox * dontAskCheck = new QCheckBox(tr("Do not ask again"), dia); + QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, Qt::Horizontal, dia); + buttonBox->button(QDialogButtonBox::Yes)->setDefault(true); + + connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); + connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); + + QVBoxLayout * lay = new QVBoxLayout(); + lay->addWidget(new QLabel(QStringLiteral("
") + text + QStringLiteral("
"))); + lay->addWidget(new QLabel(QStringLiteral("
") + tr("A process is running.") + QStringLiteral("
"))); + lay->addStretch(); + lay->addWidget(dontAskCheck); + lay->addWidget(buttonBox); + dia->setLayout(lay); + + bool res(dia->exec() == QDialog::Accepted); + if (res) + { + Properties::Instance()->askOnExit = !dontAskCheck->isChecked(); + } + dia->deleteLater(); + return res; +} void MainWindow::closeEvent(QCloseEvent *ev) { if (!Properties::Instance()->askOnExit || consoleTabulator->count() == 0 // the session is ended explicitly (e.g., by ctrl-d); prompt doesn't make sense - || consoleTabulator->terminalHolder()->findChildren().count() == 0) + || consoleTabulator->terminalHolder()->findChildren().count() == 0 + // there is no running process + || !consoleTabulator->hasRunningProcess()) { disconnect(m_bookmarksDock, &QDockWidget::visibilityChanged, this, &MainWindow::bookmarksDock_visibilityChanged); // prevent crash @@ -652,30 +683,13 @@ void MainWindow::closeEvent(QCloseEvent *ev) } // ask user for cancel only when there is at least one terminal active in this window - QDialog * dia = new QDialog(this); - dia->setObjectName(QStringLiteral("exitDialog")); - dia->setWindowTitle(tr("Exit QTerminal")); - - QCheckBox * dontAskCheck = new QCheckBox(tr("Do not ask again"), dia); - QDialogButtonBox * buttonBox = new QDialogButtonBox(QDialogButtonBox::Yes | QDialogButtonBox::No, Qt::Horizontal, dia); - buttonBox->button(QDialogButtonBox::Yes)->setDefault(true); - - connect(buttonBox, &QDialogButtonBox::accepted, dia, &QDialog::accept); - connect(buttonBox, &QDialogButtonBox::rejected, dia, &QDialog::reject); - - QVBoxLayout * lay = new QVBoxLayout(); - lay->addWidget(new QLabel(tr("Are you sure you want to exit?"))); - lay->addWidget(dontAskCheck); - lay->addWidget(buttonBox); - dia->setLayout(lay); - - if (dia->exec() == QDialog::Accepted) { + if (closePrompt(tr("Exit QTerminal"), tr("Are you sure you want to exit?"))) + { disconnect(m_bookmarksDock, &QDockWidget::visibilityChanged, this, &MainWindow::bookmarksDock_visibilityChanged); Properties::Instance()->mainWindowPosition = pos(); Properties::Instance()->mainWindowSize = size(); Properties::Instance()->mainWindowState = saveState(); - Properties::Instance()->askOnExit = !dontAskCheck->isChecked(); Properties::Instance()->windowMaximized = isMaximized(); rebuildActions(); Properties::Instance()->saveSettings(); @@ -683,11 +697,11 @@ void MainWindow::closeEvent(QCloseEvent *ev) consoleTabulator->removeTab(i - 1); } ev->accept(); - } else { + } + else + { ev->ignore(); } - - dia->deleteLater(); } void MainWindow::actAbout_triggered() diff --git a/src/mainwindow.h b/src/mainwindow.h index e67baed0..1088aca1 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -49,6 +49,8 @@ class MainWindow : public QMainWindow, private Ui::mainWindow, public DBusAddres void rebuildActions(); + bool closePrompt(const QString &title, const QString &text); + #ifdef HAVE_QDBUS QDBusObjectPath getActiveTab(); QList getTabs(); diff --git a/src/tabwidget.cpp b/src/tabwidget.cpp index 11373d33..2035a1d8 100644 --- a/src/tabwidget.cpp +++ b/src/tabwidget.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include "mainwindow.h" #include "termwidgetholder.h" @@ -56,7 +57,9 @@ TabWidget::TabWidget(QWidget* parent) : QTabWidget(parent), tabNumerator(0), mTa tabBar()->installEventFilter(this); - connect(this, &TabWidget::tabCloseRequested, this, &TabWidget::removeTab); + connect(this, &TabWidget::tabCloseRequested, this, [this](int indx) { + removeTab(indx, true); + }); connect(tabBar(), &QTabBar::tabMoved, this, &TabWidget::updateTabIndices); connect(this, &TabWidget::tabRenameRequested, this, &TabWidget::renameSession); connect(this, &TabWidget::tabTitleColorChangeRequested, this, &TabWidget::setTitleColor); @@ -136,8 +139,23 @@ void TabWidget::splitVertically() void TabWidget::splitCollapse() { + auto win = findParent(this); + if (Properties::Instance()->askOnExit) + { + if (auto impl = terminalHolder()->currentTerminal()->impl()) + { + if (impl->hasCommand() || impl->getForegroundProcessId() != impl->getShellPID()) + { + if (!win->closePrompt(tr("Close Subterminal"), tr("Are you sure you want to close this subterminal?"))) + { + return; + } + } + } + } + terminalHolder()->splitCollapse(terminalHolder()->currentTerminal()); - findParent(this)->updateDisabledActions(); + win->updateDisabledActions(); } void TabWidget::copySelection() @@ -270,11 +288,13 @@ bool TabWidget::eventFilter(QObject *obj, QEvent *event) { QMouseEvent *e = reinterpret_cast(event); if (e->button() == Qt::MiddleButton) { - if (event->type() == QEvent::MouseButtonRelease && Properties::Instance()->closeTabOnMiddleClick) { + if (event->type() == QEvent::MouseButtonRelease && Properties::Instance()->closeTabOnMiddleClick) + { // close the tab on middle clicking int index = tabBar()->tabAt(e->pos()); - if (index > -1){ - removeTab(index); + if (index > -1) + { + removeTab(index, true); return true; } } @@ -322,9 +342,24 @@ void TabWidget::removeFinished() } } -void TabWidget::removeTab(int index) +void TabWidget::removeTab(int index, bool prompt) { - if (count() > 1) { + if (count() > 1) + { + if (prompt && Properties::Instance()->askOnExit) + { + if (TermWidgetHolder* term = reinterpret_cast(widget(index))) + { + if (term->hasRunningProcess()) + { + if (!findParent(this)->closePrompt(tr("Close tab"), tr("Are you sure you want to close this tab?"))) + { + return; + } + } + } + } + setUpdatesEnabled(false); QWidget * w = widget(index); @@ -378,14 +413,8 @@ const QList& TabWidget::history() const void TabWidget::removeCurrentTab() { - // question disabled due user requests. Yes I agree it was annoying. -// if (QMessageBox::question(this, -// tr("Close current session"), -// tr("Are you sure you want to close current sesstion?"), -// QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) -// { if (count() > 1) { - removeTab(currentIndex()); + removeTab(currentIndex(), true); } else { emit closeLastTabNotification(); } @@ -594,3 +623,18 @@ void TabWidget::showHideTabBar() else tabBar()->setVisible(!Properties::Instance()->hideTabBarWithOneTab || count() > 1); } + +bool TabWidget::hasRunningProcess() const +{ + for (int i = 0; i < count(); i++) + { + if (TermWidgetHolder* term = reinterpret_cast(widget(i))) + { + if (term->hasRunningProcess()) + { + return true; + } + } + } + return false; +} diff --git a/src/tabwidget.h b/src/tabwidget.h index 2be62484..06acfb0a 100644 --- a/src/tabwidget.h +++ b/src/tabwidget.h @@ -49,9 +49,11 @@ Q_OBJECT void showHideTabBar(); const QList& history() const; + bool hasRunningProcess() const; + public slots: int addNewTab(TerminalConfig cfg); - void removeTab(int); + void removeTab(int index, bool prompt = false); void switchTab(int); void onAction(); void onCurrentChanged(int); diff --git a/src/terminalconfig.cpp b/src/terminalconfig.cpp index 2c105fb7..d1a3607b 100644 --- a/src/terminalconfig.cpp +++ b/src/terminalconfig.cpp @@ -46,6 +46,11 @@ QStringList TerminalConfig::getShell() return QStringList(); } +bool TerminalConfig::hasCommand() const +{ + return !m_shell.isEmpty(); +} + void TerminalConfig::setWorkingDirectory(const QString &val) { m_workingDirectory = val; diff --git a/src/terminalconfig.h b/src/terminalconfig.h index 4afd18fc..023935e0 100644 --- a/src/terminalconfig.h +++ b/src/terminalconfig.h @@ -17,6 +17,7 @@ class TerminalConfig QString getWorkingDirectory(); QStringList getShell(); + bool hasCommand() const; void setWorkingDirectory(const QString &val); void setShell(const QStringList &val); diff --git a/src/termwidget.cpp b/src/termwidget.cpp index 7407160a..4ac7ed62 100644 --- a/src/termwidget.cpp +++ b/src/termwidget.cpp @@ -57,6 +57,8 @@ TermWidgetImpl::TermWidgetImpl(TerminalConfig &cfg, QWidget * parent) setFlowControlEnabled(FLOW_CONTROL_ENABLED); setFlowControlWarningEnabled(FLOW_CONTROL_WARNING_ENABLED); + m_hasCommand = cfg.hasCommand(); + propertiesChanged(); setWorkingDirectory(cfg.getWorkingDirectory()); diff --git a/src/termwidget.h b/src/termwidget.h index b7d6b59a..30ee23bf 100644 --- a/src/termwidget.h +++ b/src/termwidget.h @@ -43,6 +43,10 @@ class TermWidgetImpl : public QTermWidget virtual ~TermWidgetImpl(); void propertiesChanged(); + bool hasCommand() const { + return m_hasCommand; + } + signals: void renameSession(); void removeCurrentSession(); @@ -58,6 +62,7 @@ class TermWidgetImpl : public QTermWidget void bell(); private: + bool m_hasCommand; #ifdef HAVE_LIBCANBERRA ca_context* libcanberra_context; #endif diff --git a/src/termwidgetholder.cpp b/src/termwidgetholder.cpp index cf63a936..572c115c 100644 --- a/src/termwidgetholder.cpp +++ b/src/termwidgetholder.cpp @@ -409,6 +409,22 @@ void TermWidgetHolder::onTermTitleChanged(QString title, QString icon) const emit termTitleChanged(std::move(title), std::move(icon)); } +bool TermWidgetHolder::hasRunningProcess() const +{ + const QList list = findChildren(); + for (const auto &term : list) + { + if (auto impl = term->impl()) + { + if (impl->hasCommand() || impl->getForegroundProcessId() != impl->getShellPID()) + { + return true; + } + } + } + return false; +} + #ifdef HAVE_QDBUS QDBusObjectPath TermWidgetHolder::getActiveTerminal() diff --git a/src/termwidgetholder.h b/src/termwidgetholder.h index 9299300f..2435d7d2 100644 --- a/src/termwidgetholder.h +++ b/src/termwidgetholder.h @@ -65,6 +65,8 @@ class TermWidgetHolder : public QWidget TermWidget* currentTerminal(); TermWidget* split(TermWidget * term, Qt::Orientation orientation, TerminalConfig cfg); + bool hasRunningProcess() const; + #ifdef HAVE_QDBUS QDBusObjectPath getActiveTerminal(); QList getTerminals();