Skip to content

Commit

Permalink
Prompt on closing with a running process (#1200)
Browse files Browse the repository at this point in the history
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 lxqt/qtermwidget@ccb3b21 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".
  • Loading branch information
tsujan authored Dec 1, 2024
1 parent 3c38f6d commit ff15273
Show file tree
Hide file tree
Showing 11 changed files with 133 additions and 40 deletions.
2 changes: 1 addition & 1 deletion src/forms/propertiesdialog.ui
Original file line number Diff line number Diff line change
Expand Up @@ -825,7 +825,7 @@
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="askOnExitCheckBox">
<property name="text">
<string>Ask for confirmation when closing</string>
<string>Prompt on closing with a running process</string>
</property>
</widget>
</item>
Expand Down
62 changes: 38 additions & 24 deletions src/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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("<center>") + text + QStringLiteral("</center>")));
lay->addWidget(new QLabel(QStringLiteral("<center><i>") + tr("A process is running.") + QStringLiteral("</i></center>")));
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<TermWidget*>().count() == 0)
|| consoleTabulator->terminalHolder()->findChildren<TermWidget*>().count() == 0
// there is no running process
|| !consoleTabulator->hasRunningProcess())
{
disconnect(m_bookmarksDock, &QDockWidget::visibilityChanged,
this, &MainWindow::bookmarksDock_visibilityChanged); // prevent crash
Expand All @@ -652,42 +683,25 @@ 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();
for (int i = consoleTabulator->count(); i > 0; --i) {
consoleTabulator->removeTab(i - 1);
}
ev->accept();
} else {
}
else
{
ev->ignore();
}

dia->deleteLater();
}

void MainWindow::actAbout_triggered()
Expand Down
2 changes: 2 additions & 0 deletions src/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<QDBusObjectPath> getTabs();
Expand Down
72 changes: 58 additions & 14 deletions src/tabwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include <QMouseEvent>
#include <QMenu>
#include <QActionGroup>
#include <QMessageBox>

#include "mainwindow.h"
#include "termwidgetholder.h"
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -136,8 +139,23 @@ void TabWidget::splitVertically()

void TabWidget::splitCollapse()
{
auto win = findParent<MainWindow>(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<MainWindow>(this)->updateDisabledActions();
win->updateDisabledActions();
}

void TabWidget::copySelection()
Expand Down Expand Up @@ -270,11 +288,13 @@ bool TabWidget::eventFilter(QObject *obj, QEvent *event)
{
QMouseEvent *e = reinterpret_cast<QMouseEvent*>(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;
}
}
Expand Down Expand Up @@ -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<TermWidgetHolder*>(widget(index)))
{
if (term->hasRunningProcess())
{
if (!findParent<MainWindow>(this)->closePrompt(tr("Close tab"), tr("Are you sure you want to close this tab?")))
{
return;
}
}
}
}

setUpdatesEnabled(false);

QWidget * w = widget(index);
Expand Down Expand Up @@ -378,14 +413,8 @@ const QList<QWidget*>& 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();
}
Expand Down Expand Up @@ -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<TermWidgetHolder*>(widget(i)))
{
if (term->hasRunningProcess())
{
return true;
}
}
}
return false;
}
4 changes: 3 additions & 1 deletion src/tabwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,9 +49,11 @@ Q_OBJECT
void showHideTabBar();
const QList<QWidget*>& 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);
Expand Down
5 changes: 5 additions & 0 deletions src/terminalconfig.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions src/terminalconfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ class TerminalConfig

QString getWorkingDirectory();
QStringList getShell();
bool hasCommand() const;

void setWorkingDirectory(const QString &val);
void setShell(const QStringList &val);
Expand Down
2 changes: 2 additions & 0 deletions src/termwidget.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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());
Expand Down
5 changes: 5 additions & 0 deletions src/termwidget.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ class TermWidgetImpl : public QTermWidget
virtual ~TermWidgetImpl();
void propertiesChanged();

bool hasCommand() const {
return m_hasCommand;
}

signals:
void renameSession();
void removeCurrentSession();
Expand All @@ -58,6 +62,7 @@ class TermWidgetImpl : public QTermWidget
void bell();

private:
bool m_hasCommand;
#ifdef HAVE_LIBCANBERRA
ca_context* libcanberra_context;
#endif
Expand Down
16 changes: 16 additions & 0 deletions src/termwidgetholder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TermWidget*> list = findChildren<TermWidget*>();
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()
Expand Down
2 changes: 2 additions & 0 deletions src/termwidgetholder.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<QDBusObjectPath> getTerminals();
Expand Down

0 comments on commit ff15273

Please sign in to comment.