File indexing completed on 2024-04-28 16:49:01

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