File indexing completed on 2025-02-16 11:23:20
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 "workspace.h" 0020 #include "x11window.h" 0021 #include <QDebug> 0022 #include <QSessionManager> 0023 0024 #include "sessionadaptor.h" 0025 #include <QDBusConnection> 0026 0027 namespace KWin 0028 { 0029 0030 static KConfig *sessionConfig(QString id, QString key) 0031 { 0032 static KConfig *config = nullptr; 0033 static QString lastId; 0034 static QString lastKey; 0035 static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName()); 0036 if (id != lastId || key != lastKey) { 0037 delete config; 0038 config = nullptr; 0039 } 0040 lastId = id; 0041 lastKey = key; 0042 if (!config) { 0043 config = new KConfig(pattern.arg(id, key), KConfig::SimpleConfig); 0044 } 0045 return config; 0046 } 0047 0048 static const char *const window_type_names[] = { 0049 "Unknown", "Normal", "Desktop", "Dock", "Toolbar", "Menu", "Dialog", 0050 "Override", "TopMenu", "Utility", "Splash"}; 0051 // change also the two functions below when adding new entries 0052 0053 static const char *windowTypeToTxt(NET::WindowType type) 0054 { 0055 if (type >= NET::Unknown && type <= NET::Splash) { 0056 return window_type_names[type + 1]; // +1 (unknown==-1) 0057 } 0058 if (type == -2) { // undefined (not really part of NET::WindowType) 0059 return "Undefined"; 0060 } 0061 qFatal("Unknown Window Type"); 0062 return nullptr; 0063 } 0064 0065 static NET::WindowType txtToWindowType(const char *txt) 0066 { 0067 for (int i = NET::Unknown; 0068 i <= NET::Splash; 0069 ++i) { 0070 if (qstrcmp(txt, window_type_names[i + 1]) == 0) { // +1 0071 return static_cast<NET::WindowType>(i); 0072 } 0073 } 0074 return static_cast<NET::WindowType>(-2); // undefined 0075 } 0076 0077 /** 0078 * Stores the current session in the config file 0079 * 0080 * @see loadSessionInfo 0081 */ 0082 void SessionManager::storeSession(const QString &sessionName, SMSavePhase phase) 0083 { 0084 qCDebug(KWIN_CORE) << "storing session" << sessionName << "in phase" << phase; 0085 KConfig *config = sessionConfig(sessionName, QString()); 0086 0087 KConfigGroup cg(config, "Session"); 0088 int count = 0; 0089 int active_client = -1; 0090 0091 const QList<X11Window *> x11Clients = workspace()->clientList(); 0092 for (auto it = x11Clients.begin(); it != x11Clients.end(); ++it) { 0093 X11Window *c = (*it); 0094 if (c->windowType() > NET::Splash) { 0095 // window types outside this are not tooltips/menus/OSDs 0096 // typically these will be unmanaged and not in this list anyway, but that is not enforced 0097 continue; 0098 } 0099 QByteArray sessionId = c->sessionId(); 0100 QString wmCommand = c->wmCommand(); 0101 if (sessionId.isEmpty()) { 0102 // remember also applications that are not XSMP capable 0103 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF 0104 if (wmCommand.isEmpty()) { 0105 continue; 0106 } 0107 } 0108 count++; 0109 if (c->isActive()) { 0110 active_client = count; 0111 } 0112 if (phase == SMSavePhase2 || phase == SMSavePhase2Full) { 0113 storeClient(cg, count, c); 0114 } 0115 } 0116 if (phase == SMSavePhase0) { 0117 // it would be much simpler to save these values to the config file, 0118 // but both Qt and KDE treat phase1 and phase2 separately, 0119 // which results in different sessionkey and different config file :( 0120 m_sessionActiveClient = active_client; 0121 m_sessionDesktop = VirtualDesktopManager::self()->current(); 0122 } else if (phase == SMSavePhase2) { 0123 cg.writeEntry("count", count); 0124 cg.writeEntry("active", m_sessionActiveClient); 0125 cg.writeEntry("desktop", m_sessionDesktop); 0126 } else { // SMSavePhase2Full 0127 cg.writeEntry("count", count); 0128 cg.writeEntry("active", m_sessionActiveClient); 0129 cg.writeEntry("desktop", VirtualDesktopManager::self()->current()); 0130 } 0131 config->sync(); // it previously did some "revert to defaults" stuff for phase1 I think 0132 } 0133 0134 void SessionManager::storeClient(KConfigGroup &cg, int num, X11Window *c) 0135 { 0136 c->setSessionActivityOverride(false); // make sure we get the real values 0137 QString n = QString::number(num); 0138 cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData()); 0139 cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole()); 0140 cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand()); 0141 cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName()); 0142 cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass()); 0143 cg.writeEntry(QLatin1String("geometry") + n, QRectF(c->calculateGravitation(true), c->clientSize()).toRect()); // FRAME 0144 cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore()); 0145 cg.writeEntry(QLatin1String("fsrestore") + n, c->fullscreenGeometryRestore()); 0146 cg.writeEntry(QLatin1String("maximize") + n, (int)c->maximizeMode()); 0147 cg.writeEntry(QLatin1String("fullscreen") + n, (int)c->fullScreenMode()); 0148 cg.writeEntry(QLatin1String("desktop") + n, c->desktop()); 0149 // the config entry is called "iconified" for back. comp. reasons 0150 // (kconf_update script for updating session files would be too complicated) 0151 cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized()); 0152 cg.writeEntry(QLatin1String("opacity") + n, c->opacity()); 0153 // the config entry is called "sticky" for back. comp. reasons 0154 cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops()); 0155 cg.writeEntry(QLatin1String("shaded") + n, c->isShade()); 0156 // the config entry is called "staysOnTop" for back. comp. reasons 0157 cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove()); 0158 cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow()); 0159 cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar()); 0160 cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager()); 0161 cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher()); 0162 // not really just set by user, but name kept for back. comp. reasons 0163 cg.writeEntry(QLatin1String("userNoBorder") + n, c->userNoBorder()); 0164 cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType())); 0165 cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString()); 0166 cg.writeEntry(QLatin1String("stackingOrder") + n, workspace()->unconstrainedStackingOrder().indexOf(c)); 0167 cg.writeEntry(QLatin1String("activities") + n, c->activities()); 0168 } 0169 0170 void SessionManager::storeSubSession(const QString &name, QSet<QByteArray> sessionIds) 0171 { 0172 // TODO clear it first 0173 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); 0174 int count = 0; 0175 int active_client = -1; 0176 const QList<X11Window *> x11Clients = workspace()->clientList(); 0177 0178 for (auto it = x11Clients.begin(); it != x11Clients.end(); ++it) { 0179 X11Window *c = (*it); 0180 if (c->windowType() > NET::Splash) { 0181 continue; 0182 } 0183 QByteArray sessionId = c->sessionId(); 0184 QString wmCommand = c->wmCommand(); 0185 if (sessionId.isEmpty()) { 0186 // remember also applications that are not XSMP capable 0187 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF 0188 if (wmCommand.isEmpty()) { 0189 continue; 0190 } 0191 } 0192 if (!sessionIds.contains(sessionId)) { 0193 continue; 0194 } 0195 0196 qCDebug(KWIN_CORE) << "storing" << sessionId; 0197 count++; 0198 if (c->isActive()) { 0199 active_client = count; 0200 } 0201 storeClient(cg, count, c); 0202 } 0203 cg.writeEntry("count", count); 0204 cg.writeEntry("active", active_client); 0205 // cg.writeEntry( "desktop", currentDesktop()); 0206 } 0207 0208 /** 0209 * Loads the session information from the config file. 0210 * 0211 * @see storeSession 0212 */ 0213 void SessionManager::loadSession(const QString &sessionName) 0214 { 0215 session.clear(); 0216 KConfigGroup cg(sessionConfig(sessionName, QString()), "Session"); 0217 Q_EMIT loadSessionRequested(sessionName); 0218 addSessionInfo(cg); 0219 } 0220 0221 void SessionManager::addSessionInfo(KConfigGroup &cg) 0222 { 0223 workspace()->setInitialDesktop(cg.readEntry("desktop", 1)); 0224 int count = cg.readEntry("count", 0); 0225 int active_client = cg.readEntry("active", 0); 0226 for (int i = 1; i <= count; i++) { 0227 QString n = QString::number(i); 0228 SessionInfo *info = new SessionInfo; 0229 session.append(info); 0230 info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1(); 0231 info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString()); 0232 info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1(); 0233 info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString()); 0234 info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower(); 0235 info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect()); 0236 info->restore = cg.readEntry(QLatin1String("restore") + n, QRect()); 0237 info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect()); 0238 info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0); 0239 info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0); 0240 info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0); 0241 info->minimized = cg.readEntry(QLatin1String("iconified") + n, false); 0242 info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0); 0243 info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false); 0244 info->shaded = cg.readEntry(QLatin1String("shaded") + n, false); 0245 info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false); 0246 info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false); 0247 info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false); 0248 info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false); 0249 info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false); 0250 info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false); 0251 info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData()); 0252 info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString()); 0253 info->active = (active_client == i); 0254 info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1); 0255 info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList()); 0256 } 0257 } 0258 0259 void SessionManager::loadSubSessionInfo(const QString &name) 0260 { 0261 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name); 0262 addSessionInfo(cg); 0263 } 0264 0265 static bool sessionInfoWindowTypeMatch(X11Window *c, SessionInfo *info) 0266 { 0267 if (info->windowType == -2) { 0268 // undefined (not really part of NET::WindowType) 0269 return !c->isSpecialWindow(); 0270 } 0271 return info->windowType == c->windowType(); 0272 } 0273 0274 /** 0275 * Returns a SessionInfo for client \a c. The returned session 0276 * info is removed from the storage. It's up to the caller to delete it. 0277 * 0278 * This function is called when a new window is mapped and must be managed. 0279 * We try to find a matching entry in the session. 0280 * 0281 * May return 0 if there's no session info for the client. 0282 */ 0283 SessionInfo *SessionManager::takeSessionInfo(X11Window *c) 0284 { 0285 SessionInfo *realInfo = nullptr; 0286 QByteArray sessionId = c->sessionId(); 0287 QString windowRole = c->windowRole(); 0288 QString wmCommand = c->wmCommand(); 0289 QString resourceName = c->resourceName(); 0290 QString resourceClass = c->resourceClass(); 0291 0292 // First search ``session'' 0293 if (!sessionId.isEmpty()) { 0294 // look for a real session managed client (algorithm suggested by ICCCM) 0295 for (SessionInfo *info : std::as_const(session)) { 0296 if (realInfo) { 0297 break; 0298 } 0299 if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) { 0300 if (!windowRole.isEmpty()) { 0301 if (info->windowRole == windowRole) { 0302 realInfo = info; 0303 session.removeAll(info); 0304 } 0305 } else { 0306 if (info->windowRole.isEmpty() 0307 && info->resourceName == resourceName 0308 && info->resourceClass == resourceClass) { 0309 realInfo = info; 0310 session.removeAll(info); 0311 } 0312 } 0313 } 0314 } 0315 } else { 0316 // look for a sessioninfo with matching features. 0317 for (SessionInfo *info : std::as_const(session)) { 0318 if (realInfo) { 0319 break; 0320 } 0321 if (info->resourceName == resourceName 0322 && info->resourceClass == resourceClass 0323 && sessionInfoWindowTypeMatch(c, info)) { 0324 if (wmCommand.isEmpty() || info->wmCommand == wmCommand) { 0325 realInfo = info; 0326 session.removeAll(info); 0327 } 0328 } 0329 } 0330 } 0331 return realInfo; 0332 } 0333 0334 SessionManager::SessionManager(QObject *parent) 0335 : QObject(parent) 0336 { 0337 new SessionAdaptor(this); 0338 QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this); 0339 } 0340 0341 SessionManager::~SessionManager() 0342 { 0343 qDeleteAll(session); 0344 } 0345 0346 SessionState SessionManager::state() const 0347 { 0348 return m_sessionState; 0349 } 0350 0351 void SessionManager::setState(uint state) 0352 { 0353 switch (state) { 0354 case 0: 0355 setState(SessionState::Saving); 0356 break; 0357 case 1: 0358 setState(SessionState::Quitting); 0359 break; 0360 default: 0361 setState(SessionState::Normal); 0362 } 0363 } 0364 0365 // TODO should we rethink this now that we have dedicated start end end save methods? 0366 void SessionManager::setState(SessionState state) 0367 { 0368 if (state == m_sessionState) { 0369 return; 0370 } 0371 // If we're starting to save a session 0372 if (state == SessionState::Saving) { 0373 workspace()->rulebook()->setUpdatesDisabled(true); 0374 } 0375 // If we're ending a save session due to either completion or cancellation 0376 if (m_sessionState == SessionState::Saving) { 0377 workspace()->rulebook()->setUpdatesDisabled(false); 0378 Workspace::self()->forEachClient([](X11Window *client) { 0379 client->setSessionActivityOverride(false); 0380 }); 0381 } 0382 m_sessionState = state; 0383 Q_EMIT stateChanged(); 0384 } 0385 0386 void SessionManager::aboutToSaveSession(const QString &name) 0387 { 0388 Q_EMIT prepareSessionSaveRequested(name); 0389 storeSession(name, SMSavePhase0); 0390 } 0391 0392 void SessionManager::finishSaveSession(const QString &name) 0393 { 0394 Q_EMIT finishSessionSaveRequested(name); 0395 storeSession(name, SMSavePhase2); 0396 } 0397 0398 void SessionManager::quit() 0399 { 0400 qApp->quit(); 0401 } 0402 0403 } // namespace