From 4cdf27e74c392f4e416dc87cc4a5385ccac4566c Mon Sep 17 00:00:00 2001 From: David Redondo Date: Mon, 11 Sep 2023 09:28:34 +0200 Subject: [PATCH] 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 --- src/org.kde.KWin.Session.xml | 3 ++ src/sm.cpp | 84 +++++++++++++++++++++++++++++++++++- src/sm.h | 9 +++- 3 files changed, 94 insertions(+), 2 deletions(-) diff --git a/src/org.kde.KWin.Session.xml b/src/org.kde.KWin.Session.xml index ad80f052aa..35adf52741 100644 --- a/src/org.kde.KWin.Session.xml +++ b/src/org.kde.KWin.Session.xml @@ -22,6 +22,9 @@ + + + diff --git a/src/sm.cpp b/src/sm.cpp index 4e9e390bc2..2360849960 100644 --- a/src/sm.cpp +++ b/src/sm.cpp @@ -16,13 +16,22 @@ #include #include "virtualdesktops.h" +#include "wayland_server.h" #include "workspace.h" #include "x11window.h" +#include "xdgshellwindow.h" #include + #include +#if KWIN_BUILD_NOTIFICATIONS +#include +#include +#include +#endif #include "sessionadaptor.h" -#include + +using namespace Qt::StringLiterals; namespace KWin { @@ -385,6 +394,7 @@ void SessionManager::setState(SessionState state) client->setSessionActivityOverride(false); }); } + m_sessionState = state; Q_EMIT stateChanged(); } @@ -401,6 +411,78 @@ void SessionManager::finishSaveSession(const QString &name) 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(); + 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(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() { qApp->quit(); diff --git a/src/sm.h b/src/sm.h index c4863adc59..16a2e7c3c4 100644 --- a/src/sm.h +++ b/src/sm.h @@ -10,9 +10,11 @@ #pragma once +#include #include #include #include +#include #include @@ -24,8 +26,9 @@ namespace KWin class X11Window; struct SessionInfo; +class XdgToplevelWindow; -class SessionManager : public QObject +class SessionManager : public QObject, public QDBusContext { Q_OBJECT public: @@ -57,6 +60,7 @@ public Q_SLOTS: // DBus API void loadSession(const QString &name); void aboutToSaveSession(const QString &name); void finishSaveSession(const QString &name); + bool closeWaylandWindows(); void quit(); private: @@ -73,6 +77,9 @@ private: int m_sessionDesktop; QList session; + QList m_pendingWindows; + QTimer m_closeTimer; + std::unique_ptr m_closingWindowsGuard; }; struct SessionInfo