File indexing completed on 2024-11-10 04:57:57
0001 /* 0002 KWin - the KDE window manager 0003 This file is part of the KDE project. 0004 0005 SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org> 0006 SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org> 0007 0008 SPDX-License-Identifier: GPL-2.0-or-later 0009 */ 0010 0011 #include "sm.h" 0012 0013 #include <cstdlib> 0014 #include <kconfig.h> 0015 #include <pwd.h> 0016 #include <unistd.h> 0017 0018 #include "virtualdesktops.h" 0019 #include "wayland_server.h" 0020 #include "workspace.h" 0021 #include "x11window.h" 0022 #include "xdgshellwindow.h" 0023 #include <QDebug> 0024 0025 #include <QSessionManager> 0026 #if KWIN_BUILD_NOTIFICATIONS 0027 #include <KLocalizedString> 0028 #include <KNotification> 0029 #include <KService> 0030 #endif 0031 0032 #include "sessionadaptor.h" 0033 0034 using namespace Qt::StringLiterals; 0035 0036 namespace KWin 0037 { 0038 0039 static KConfig *sessionConfig(QString id, QString key) 0040 { 0041 static KConfig *config = nullptr; 0042 static QString lastId; 0043 static QString lastKey; 0044 static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); 0045 if (id != lastId || key != lastKey) { 0046 delete config; 0047 config = nullptr; 0048 } 0049 lastId = id; 0050 lastKey = key; 0051 if (!config) { 0052 config = new KConfig(pattern.arg(id, key), KConfig::SimpleConfig); 0053 } 0054 return config; 0055 } 0056 0057 static const char *const window_type_names[] = { 0058 "Unknown", "Normal", "Desktop", "Dock", "Toolbar", "Menu", "Dialog", 0059 "Override", "TopMenu", "Utility", "Splash"}; 0060 // change also the two functions below when adding new entries 0061 0062 static const char *windowTypeToTxt(NET::WindowType type) 0063 { 0064 if (type >= NET::Unknown && type <= NET::Splash) { 0065 return window_type_names[type + 1]; // +1 (unknown==-1) 0066 } 0067 if (type == -2) { // undefined (not really part of NET::WindowType) 0068 return "Undefined"; 0069 } 0070 qFatal("Unknown Window Type"); 0071 return nullptr; 0072 } 0073 0074 static NET::WindowType txtToWindowType(const char *txt) 0075 { 0076 for (int i = NET::Unknown; 0077 i <= NET::Splash; 0078 ++i) { 0079 if (qstrcmp(txt, window_type_names[i + 1]) == 0) { // +1 0080 return static_cast<NET::WindowType>(i); 0081 } 0082 } 0083 return static_cast<NET::WindowType>(-2); // undefined 0084 } 0085 0086 /** 0087 * Stores the current session in the config file 0088 * 0089 * @see loadSessionInfo 0090 */ 0091 void SessionManager::storeSession(const QString &sessionName, SMSavePhase phase) 0092 { 0093 qCDebug(KWIN_CORE) << "storing session" << sessionName << "in phase" << phase; 0094 KConfig *config = sessionConfig(sessionName, QString()); 0095 0096 KConfigGroup cg(config, QStringLiteral("Session")); 0097 int count = 0; 0098 int active_client = -1; 0099 0100 const QList<Window *> windows = workspace()->windows(); 0101 for (auto it = windows.begin(); it != windows.end(); ++it) { 0102 X11Window *c = qobject_cast<X11Window *>(*it); 0103 if (!c || c->isUnmanaged()) { 0104 continue; 0105 } 0106 if (c->windowType() > NET::Splash) { 0107 // window types outside this are not tooltips/menus/OSDs 0108 // typically these will be unmanaged and not in this list anyway, but that is not enforced 0109 continue; 0110 } 0111 QByteArray sessionId = c->sessionId(); 0112 QString wmCommand = c->wmCommand(); 0113 if (sessionId.isEmpty()) { 0114 // remember also applications that are not XSMP capable 0115 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF 0116 if (wmCommand.isEmpty()) { 0117 continue; 0118 } 0119 } 0120 count++; 0121 if (c->isActive()) { 0122 active_client = count; 0123 } 0124 if (phase == SMSavePhase2 || phase == SMSavePhase2Full) { 0125 storeClient(cg, count, c); 0126 } 0127 } 0128 if (phase == SMSavePhase0) { 0129 // it would be much simpler to save these values to the config file, 0130 // but both Qt and KDE treat phase1 and phase2 separately, 0131 // which results in different sessionkey and different config file :( 0132 m_sessionActiveClient = active_client; 0133 m_sessionDesktop = VirtualDesktopManager::self()->current(); 0134 } else if (phase == SMSavePhase2) { 0135 cg.writeEntry("count", count); 0136 cg.writeEntry("active", m_sessionActiveClient); 0137 cg.writeEntry("desktop", m_sessionDesktop); 0138 } else { // SMSavePhase2Full 0139 cg.writeEntry("count", count); 0140 cg.writeEntry("active", m_sessionActiveClient); 0141 cg.writeEntry("desktop", VirtualDesktopManager::self()->current()); 0142 } 0143 config->sync(); // it previously did some "revert to defaults" stuff for phase1 I think 0144 } 0145 0146 void SessionManager::storeClient(KConfigGroup &cg, int num, X11Window *c) 0147 { 0148 c->setSessionActivityOverride(false); // make sure we get the real values 0149 QString n = QString::number(num); 0150 cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData()); 0151 cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole()); 0152 cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand()); 0153 cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName()); 0154 cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass()); 0155 cg.writeEntry(QLatin1String("geometry") + n, QRectF(c->calculateGravitation(true), c->clientSize()).toRect()); // FRAME 0156 cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore()); 0157 cg.writeEntry(QLatin1String("fsrestore") + n, c->fullscreenGeometryRestore()); 0158 cg.writeEntry(QLatin1String("maximize") + n, (int)c->maximizeMode()); 0159 cg.writeEntry(QLatin1String("fullscreen") + n, (int)c->fullScreenMode()); 0160 cg.writeEntry(QLatin1String("desktop") + n, c->desktopId()); 0161 // the config entry is called "iconified" for back. comp. reasons 0162 // (kconf_update script for updating session files would be too complicated) 0163 cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized()); 0164 cg.writeEntry(QLatin1String("opacity") + n, c->opacity()); 0165 // the config entry is called "sticky" for back. comp. reasons 0166 cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops()); 0167 cg.writeEntry(QLatin1String("shaded") + n, c->isShade()); 0168 // the config entry is called "staysOnTop" for back. comp. reasons 0169 cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove()); 0170 cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow()); 0171 cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar()); 0172 cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager()); 0173 cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher()); 0174 // not really just set by user, but name kept for back. comp. reasons 0175 cg.writeEntry(QLatin1String("userNoBorder") + n, c->userNoBorder()); 0176 cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType())); 0177 cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString()); 0178 cg.writeEntry(QLatin1String("stackingOrder") + n, workspace()->unconstrainedStackingOrder().indexOf(c)); 0179 cg.writeEntry(QLatin1String("activities") + n, c->activities()); 0180 } 0181 0182 void SessionManager::storeSubSession(const QString &name, QSet<QByteArray> sessionIds) 0183 { 0184 // TODO clear it first 0185 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); 0186 int count = 0; 0187 int active_client = -1; 0188 const QList<Window *> windows = workspace()->windows(); 0189 0190 for (auto it = windows.begin(); it != windows.end(); ++it) { 0191 X11Window *c = qobject_cast<X11Window *>(*it); 0192 if (!c || c->isUnmanaged()) { 0193 continue; 0194 } 0195 if (c->windowType() > NET::Splash) { 0196 continue; 0197 } 0198 QByteArray sessionId = c->sessionId(); 0199 QString wmCommand = c->wmCommand(); 0200 if (sessionId.isEmpty()) { 0201 // remember also applications that are not XSMP capable 0202 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF 0203 if (wmCommand.isEmpty()) { 0204 continue; 0205 } 0206 } 0207 if (!sessionIds.contains(sessionId)) { 0208 continue; 0209 } 0210 0211 qCDebug(KWIN_CORE) << "storing" << sessionId; 0212 count++; 0213 if (c->isActive()) { 0214 active_client = count; 0215 } 0216 storeClient(cg, count, c); 0217 } 0218 cg.writeEntry("count", count); 0219 cg.writeEntry("active", active_client); 0220 // cg.writeEntry( "desktop", currentDesktop()); 0221 } 0222 0223 /** 0224 * Loads the session information from the config file. 0225 * 0226 * @see storeSession 0227 */ 0228 void SessionManager::loadSession(const QString &sessionName) 0229 { 0230 session.clear(); 0231 KConfigGroup cg(sessionConfig(sessionName, QString()), QStringLiteral("Session")); 0232 Q_EMIT loadSessionRequested(sessionName); 0233 addSessionInfo(cg); 0234 } 0235 0236 void SessionManager::addSessionInfo(KConfigGroup &cg) 0237 { 0238 workspace()->setInitialDesktop(cg.readEntry("desktop", 1)); 0239 int count = cg.readEntry("count", 0); 0240 int active_client = cg.readEntry("active", 0); 0241 for (int i = 1; i <= count; i++) { 0242 QString n = QString::number(i); 0243 SessionInfo *info = new SessionInfo; 0244 session.append(info); 0245 info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1(); 0246 info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString()); 0247 info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1(); 0248 info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString()); 0249 info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower(); 0250 info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect()); 0251 info->restore = cg.readEntry(QLatin1String("restore") + n, QRect()); 0252 info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect()); 0253 info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0); 0254 info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0); 0255 info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0); 0256 info->minimized = cg.readEntry(QLatin1String("iconified") + n, false); 0257 info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0); 0258 info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false); 0259 info->shaded = cg.readEntry(QLatin1String("shaded") + n, false); 0260 info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false); 0261 info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false); 0262 info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false); 0263 info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false); 0264 info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false); 0265 info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false); 0266 info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData()); 0267 info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString()); 0268 info->active = (active_client == i); 0269 info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1); 0270 info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList()); 0271 } 0272 } 0273 0274 void SessionManager::loadSubSessionInfo(const QString &name) 0275 { 0276 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); 0277 addSessionInfo(cg); 0278 } 0279 0280 static bool sessionInfoWindowTypeMatch(X11Window *c, SessionInfo *info) 0281 { 0282 if (info->windowType == -2) { 0283 // undefined (not really part of NET::WindowType) 0284 return !c->isSpecialWindow(); 0285 } 0286 return info->windowType == c->windowType(); 0287 } 0288 0289 /** 0290 * Returns a SessionInfo for client \a c. The returned session 0291 * info is removed from the storage. It's up to the caller to delete it. 0292 * 0293 * This function is called when a new window is mapped and must be managed. 0294 * We try to find a matching entry in the session. 0295 * 0296 * May return 0 if there's no session info for the client. 0297 */ 0298 SessionInfo *SessionManager::takeSessionInfo(X11Window *c) 0299 { 0300 SessionInfo *realInfo = nullptr; 0301 QByteArray sessionId = c->sessionId(); 0302 QString windowRole = c->windowRole(); 0303 QString wmCommand = c->wmCommand(); 0304 QString resourceName = c->resourceName(); 0305 QString resourceClass = c->resourceClass(); 0306 0307 // First search ``session'' 0308 if (!sessionId.isEmpty()) { 0309 // look for a real session managed client (algorithm suggested by ICCCM) 0310 for (SessionInfo *info : std::as_const(session)) { 0311 if (realInfo) { 0312 break; 0313 } 0314 if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) { 0315 if (!windowRole.isEmpty()) { 0316 if (info->windowRole == windowRole) { 0317 realInfo = info; 0318 session.removeAll(info); 0319 } 0320 } else { 0321 if (info->windowRole.isEmpty() 0322 && info->resourceName == resourceName 0323 && info->resourceClass == resourceClass) { 0324 realInfo = info; 0325 session.removeAll(info); 0326 } 0327 } 0328 } 0329 } 0330 } else { 0331 // look for a sessioninfo with matching features. 0332 for (SessionInfo *info : std::as_const(session)) { 0333 if (realInfo) { 0334 break; 0335 } 0336 if (info->resourceName == resourceName 0337 && info->resourceClass == resourceClass 0338 && sessionInfoWindowTypeMatch(c, info)) { 0339 if (wmCommand.isEmpty() || info->wmCommand == wmCommand) { 0340 realInfo = info; 0341 session.removeAll(info); 0342 } 0343 } 0344 } 0345 } 0346 return realInfo; 0347 } 0348 0349 SessionManager::SessionManager(QObject *parent) 0350 : QObject(parent) 0351 { 0352 new SessionAdaptor(this); 0353 QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this); 0354 } 0355 0356 SessionManager::~SessionManager() 0357 { 0358 qDeleteAll(session); 0359 } 0360 0361 SessionState SessionManager::state() const 0362 { 0363 return m_sessionState; 0364 } 0365 0366 void SessionManager::setState(uint state) 0367 { 0368 switch (state) { 0369 case 0: 0370 setState(SessionState::Saving); 0371 break; 0372 case 1: 0373 setState(SessionState::Quitting); 0374 break; 0375 default: 0376 setState(SessionState::Normal); 0377 } 0378 } 0379 0380 // TODO should we rethink this now that we have dedicated start end end save methods? 0381 void SessionManager::setState(SessionState state) 0382 { 0383 if (state == m_sessionState) { 0384 return; 0385 } 0386 // If we're starting to save a session 0387 if (state == SessionState::Saving) { 0388 workspace()->rulebook()->setUpdatesDisabled(true); 0389 } 0390 // If we're ending a save session due to either completion or cancellation 0391 if (m_sessionState == SessionState::Saving) { 0392 workspace()->rulebook()->setUpdatesDisabled(false); 0393 Workspace::self()->forEachClient([](X11Window *client) { 0394 client->setSessionActivityOverride(false); 0395 }); 0396 } 0397 0398 m_sessionState = state; 0399 Q_EMIT stateChanged(); 0400 } 0401 0402 void SessionManager::aboutToSaveSession(const QString &name) 0403 { 0404 Q_EMIT prepareSessionSaveRequested(name); 0405 storeSession(name, SMSavePhase0); 0406 } 0407 0408 void SessionManager::finishSaveSession(const QString &name) 0409 { 0410 Q_EMIT finishSessionSaveRequested(name); 0411 storeSession(name, SMSavePhase2); 0412 } 0413 0414 bool SessionManager::closeWaylandWindows() 0415 { 0416 Q_ASSERT(calledFromDBus()); 0417 if (!waylandServer()) { 0418 return true; 0419 } 0420 0421 if (m_closingWindowsGuard) { 0422 sendErrorReply(QDBusError::Failed, u"Operation already in progress"_s); 0423 return false; 0424 } 0425 0426 m_closingWindowsGuard = std::make_unique<QObject>(); 0427 qCDebug(KWIN_CORE) << "Closing windows"; 0428 0429 auto dbusMessage = message(); 0430 setDelayedReply(true); 0431 0432 const auto windows = workspace()->windows(); 0433 m_pendingWindows.clear(); 0434 m_pendingWindows.reserve(windows.size()); 0435 for (const auto window : windows) { 0436 if (auto toplevelWindow = qobject_cast<XdgToplevelWindow *>(window)) { 0437 connect(toplevelWindow, &XdgToplevelWindow::closed, m_closingWindowsGuard.get(), [this, toplevelWindow, dbusMessage] { 0438 m_pendingWindows.removeOne(toplevelWindow); 0439 if (m_pendingWindows.empty()) { 0440 m_closeTimer.stop(); 0441 m_closingWindowsGuard.reset(); 0442 QDBusConnection::sessionBus().send(dbusMessage.createReply(true)); 0443 } 0444 }); 0445 m_pendingWindows.push_back(toplevelWindow); 0446 toplevelWindow->closeWindow(); 0447 } 0448 } 0449 0450 if (m_pendingWindows.empty()) { 0451 m_closingWindowsGuard.reset(); 0452 QDBusConnection::sessionBus().send(dbusMessage.createReply(true)); 0453 return true; 0454 } 0455 0456 m_closeTimer.start(std::chrono::seconds(10)); 0457 m_closeTimer.setSingleShot(true); 0458 connect(&m_closeTimer, &QTimer::timeout, m_closingWindowsGuard.get(), [this, dbusMessage] { 0459 #if KWIN_BUILD_NOTIFICATIONS 0460 QStringList apps; 0461 apps.reserve(m_pendingWindows.size()); 0462 std::transform(m_pendingWindows.cbegin(), m_pendingWindows.cend(), std::back_inserter(apps), [](const XdgToplevelWindow *window) -> QString { 0463 const auto service = KService::serviceByDesktopName(window->desktopFileName()); 0464 return QChar(u'•') + (service ? service->name() : window->caption()); 0465 }); 0466 apps.removeDuplicates(); 0467 qCDebug(KWIN_CORE) << "Not closed windows" << apps; 0468 auto notification = new KNotification("cancellogout", KNotification::DefaultEvent | KNotification::Persistent); 0469 notification->setText(i18n("The following applications did not close:\n%1", apps.join('\n'))); 0470 auto cancel = notification->addAction(i18nc("@action:button", "Cancel Logout")); 0471 auto quit = notification->addAction(i18nc("@action::button", "Log Out Anyway")); 0472 connect(cancel, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] { 0473 m_closingWindowsGuard.reset(); 0474 QDBusConnection::sessionBus().send(dbusMessage.createReply(false)); 0475 }); 0476 connect(quit, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] { 0477 m_closingWindowsGuard.reset(); 0478 QDBusConnection::sessionBus().send(dbusMessage.createReply(true)); 0479 }); 0480 connect(notification, &KNotification::closed, m_closingWindowsGuard.get(), [dbusMessage, this] { 0481 m_closingWindowsGuard.reset(); 0482 QDBusConnection::sessionBus().send(dbusMessage.createReply(false)); 0483 }); 0484 notification->sendEvent(); 0485 #else 0486 m_closingWindowsGuard.reset(); 0487 QDBusConnection::sessionBus().send(dbusMessage.createReply(false)); 0488 #endif 0489 }); 0490 return true; 0491 } 0492 0493 void SessionManager::quit() 0494 { 0495 qApp->quit(); 0496 } 0497 0498 } // namespace 0499 0500 #include "moc_sm.cpp"