Expose a method to allow closing windows on shutdown.

This allows Plasma to gracefully close windows on shutdown by sending
xdg_toplevel.close. If after 10 seconds windows are still open because
they prompt for unsaved changes or similar cases, a notification is
shown to either prompt or logout regardless.
CCBUG: 461176
This commit is contained in:
David Redondo 2023-09-11 09:28:34 +02:00
parent 40aa5aceb8
commit 4cdf27e74c
3 changed files with 94 additions and 2 deletions

View file

@ -22,6 +22,9 @@
<!-- Shutdown kwin at the end of the session --> <!-- Shutdown kwin at the end of the session -->
<method name="quit"> <method name="quit">
</method> </method>
<method name="closeWaylandWindows">
<arg type="b" direction="out" />
</method>
</interface> </interface>
</node> </node>

View file

@ -16,13 +16,22 @@
#include <unistd.h> #include <unistd.h>
#include "virtualdesktops.h" #include "virtualdesktops.h"
#include "wayland_server.h"
#include "workspace.h" #include "workspace.h"
#include "x11window.h" #include "x11window.h"
#include "xdgshellwindow.h"
#include <QDebug> #include <QDebug>
#include <QSessionManager> #include <QSessionManager>
#if KWIN_BUILD_NOTIFICATIONS
#include <KLocalizedString>
#include <KNotification>
#include <KService>
#endif
#include "sessionadaptor.h" #include "sessionadaptor.h"
#include <QDBusConnection>
using namespace Qt::StringLiterals;
namespace KWin namespace KWin
{ {
@ -385,6 +394,7 @@ void SessionManager::setState(SessionState state)
client->setSessionActivityOverride(false); client->setSessionActivityOverride(false);
}); });
} }
m_sessionState = state; m_sessionState = state;
Q_EMIT stateChanged(); Q_EMIT stateChanged();
} }
@ -401,6 +411,78 @@ void SessionManager::finishSaveSession(const QString &name)
storeSession(name, SMSavePhase2); storeSession(name, SMSavePhase2);
} }
bool SessionManager::closeWaylandWindows()
{
Q_ASSERT(calledFromDBus());
if (!waylandServer()) {
return true;
}
if (m_closingWindowsGuard) {
sendErrorReply(QDBusError::Failed, u"Operation already in progress"_s);
return false;
}
m_closingWindowsGuard = std::make_unique<QObject>();
qCDebug(KWIN_CORE) << "Closing windows";
auto dbusMessage = message();
setDelayedReply(true);
const auto windows = workspace()->windows();
m_pendingWindows.clear();
m_pendingWindows.reserve(windows.size());
for (const auto window : windows) {
if (auto toplevelWindow = qobject_cast<XdgToplevelWindow *>(window)) {
connect(toplevelWindow, &XdgToplevelWindow::closed, m_closingWindowsGuard.get(), [this, toplevelWindow, dbusMessage] {
m_pendingWindows.removeOne(toplevelWindow);
if (m_pendingWindows.empty()) {
m_closeTimer.stop();
m_closingWindowsGuard.reset();
QDBusConnection::sessionBus().send(dbusMessage.createReply(true));
}
});
m_pendingWindows.push_back(toplevelWindow);
toplevelWindow->closeWindow();
}
}
m_closeTimer.start(std::chrono::seconds(10));
m_closeTimer.setSingleShot(true);
connect(&m_closeTimer, &QTimer::timeout, m_closingWindowsGuard.get(), [this, dbusMessage] {
#if KWIN_BUILD_NOTIFICATIONS
QStringList apps;
apps.reserve(m_pendingWindows.size());
std::transform(m_pendingWindows.cbegin(), m_pendingWindows.cend(), std::back_inserter(apps), [](const XdgToplevelWindow *window) -> QString {
const auto service = KService::serviceByDesktopName(window->desktopFileName());
return QChar(u'') + (service ? service->name() : window->caption());
});
apps.removeDuplicates();
qCDebug(KWIN_CORE) << "Not closed windows" << apps;
auto notification = new KNotification("cancellogout", KNotification::DefaultEvent | KNotification::Persistent);
notification->setText(i18n("The following applications did not close:\n%1", apps.join('\n')));
auto cancel = notification->addAction(i18nc("@action:button", "Cancel Logout"));
auto quit = notification->addAction(i18nc("@action::button", "Log Out Anyway"));
connect(cancel, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] {
m_closingWindowsGuard.reset();
QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
});
connect(quit, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] {
m_closingWindowsGuard.reset();
QDBusConnection::sessionBus().send(dbusMessage.createReply(true));
});
connect(notification, &KNotification::closed, m_closingWindowsGuard.get(), [dbusMessage, this] {
m_closingWindowsGuard.reset();
QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
});
notification->sendEvent();
#else
m_closingWindowsGuard.reset();
QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
#endif
});
return true;
}
void SessionManager::quit() void SessionManager::quit()
{ {
qApp->quit(); qApp->quit();

View file

@ -10,9 +10,11 @@
#pragma once #pragma once
#include <QDBusContext>
#include <QDataStream> #include <QDataStream>
#include <QRect> #include <QRect>
#include <QStringList> #include <QStringList>
#include <QTimer>
#include <KConfigGroup> #include <KConfigGroup>
@ -24,8 +26,9 @@ namespace KWin
class X11Window; class X11Window;
struct SessionInfo; struct SessionInfo;
class XdgToplevelWindow;
class SessionManager : public QObject class SessionManager : public QObject, public QDBusContext
{ {
Q_OBJECT Q_OBJECT
public: public:
@ -57,6 +60,7 @@ public Q_SLOTS: // DBus API
void loadSession(const QString &name); void loadSession(const QString &name);
void aboutToSaveSession(const QString &name); void aboutToSaveSession(const QString &name);
void finishSaveSession(const QString &name); void finishSaveSession(const QString &name);
bool closeWaylandWindows();
void quit(); void quit();
private: private:
@ -73,6 +77,9 @@ private:
int m_sessionDesktop; int m_sessionDesktop;
QList<SessionInfo *> session; QList<SessionInfo *> session;
QList<XdgToplevelWindow *> m_pendingWindows;
QTimer m_closeTimer;
std::unique_ptr<QObject> m_closingWindowsGuard;
}; };
struct SessionInfo struct SessionInfo