1 /*
2     KWin - the KDE window manager
3     This file is part of the KDE project.
4 
5     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
7 
8     SPDX-License-Identifier: GPL-2.0-or-later
9 */
10 
11 #include "sm.h"
12 
13 #include <unistd.h>
14 #include <cstdlib>
15 #include <pwd.h>
16 #include <kconfig.h>
17 
18 #include "virtualdesktops.h"
19 #include "workspace.h"
20 #include "x11client.h"
21 #include <QDebug>
22 #include <QSessionManager>
23 
24 #include <QDBusConnection>
25 #include "sessionadaptor.h"
26 
27 namespace KWin
28 {
29 
sessionConfig(QString id,QString key)30 static KConfig *sessionConfig(QString id, QString key)
31 {
32     static KConfig *config = nullptr;
33     static QString lastId;
34     static QString lastKey;
35     static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName());
36     if (id != lastId || key != lastKey) {
37         delete config;
38         config = nullptr;
39     }
40     lastId = id;
41     lastKey = key;
42     if (!config) {
43         config = new KConfig(pattern.arg(id, key), KConfig::SimpleConfig);
44     }
45     return config;
46 }
47 
48 static const char* const window_type_names[] = {
49     "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
50     "Override", "TopMenu", "Utility", "Splash"
51 };
52 // change also the two functions below when adding new entries
53 
windowTypeToTxt(NET::WindowType type)54 static const char* windowTypeToTxt(NET::WindowType type)
55 {
56     if (type >= NET::Unknown && type <= NET::Splash)
57         return window_type_names[ type + 1 ]; // +1 (unknown==-1)
58     if (type == -2)   // undefined (not really part of NET::WindowType)
59         return "Undefined";
60     qFatal("Unknown Window Type");
61     return nullptr;
62 }
63 
txtToWindowType(const char * txt)64 static NET::WindowType txtToWindowType(const char* txt)
65 {
66     for (int i = NET::Unknown;
67             i <= NET::Splash;
68             ++i)
69         if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0)     // +1
70             return static_cast< NET::WindowType >(i);
71     return static_cast< NET::WindowType >(-2);   // undefined
72 }
73 
74 /**
75  * Stores the current session in the config file
76  *
77  * @see loadSessionInfo
78  */
storeSession(const QString & sessionName,SMSavePhase phase)79 void Workspace::storeSession(const QString &sessionName, SMSavePhase phase)
80 {
81     qCDebug(KWIN_CORE) << "storing session" << sessionName << "in phase" << phase;
82     KConfig *config = sessionConfig(sessionName, QString());
83 
84     KConfigGroup cg(config, "Session");
85     int count =  0;
86     int active_client = -1;
87 
88     for (auto it = m_x11Clients.begin(); it != m_x11Clients.end(); ++it) {
89         X11Client *c = (*it);
90         if (c->windowType() > NET::Splash) {
91             //window types outside this are not tooltips/menus/OSDs
92             //typically these will be unmanaged and not in this list anyway, but that is not enforced
93             continue;
94         }
95         QByteArray sessionId = c->sessionId();
96         QByteArray wmCommand = c->wmCommand();
97         if (sessionId.isEmpty())
98             // remember also applications that are not XSMP capable
99             // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
100             if (wmCommand.isEmpty())
101                 continue;
102         count++;
103         if (c->isActive())
104             active_client = count;
105         if (phase == SMSavePhase2 || phase == SMSavePhase2Full)
106             storeClient(cg, count, c);
107     }
108     if (phase == SMSavePhase0) {
109         // it would be much simpler to save these values to the config file,
110         // but both Qt and KDE treat phase1 and phase2 separately,
111         // which results in different sessionkey and different config file :(
112         session_active_client = active_client;
113         session_desktop = VirtualDesktopManager::self()->current();
114     } else if (phase == SMSavePhase2) {
115         cg.writeEntry("count", count);
116         cg.writeEntry("active", session_active_client);
117         cg.writeEntry("desktop", session_desktop);
118     } else { // SMSavePhase2Full
119         cg.writeEntry("count", count);
120         cg.writeEntry("active", session_active_client);
121         cg.writeEntry("desktop", VirtualDesktopManager::self()->current());
122     }
123     config->sync(); // it previously did some "revert to defaults" stuff for phase1 I think
124 }
125 
storeClient(KConfigGroup & cg,int num,X11Client * c)126 void Workspace::storeClient(KConfigGroup &cg, int num, X11Client *c)
127 {
128     c->setSessionActivityOverride(false); //make sure we get the real values
129     QString n = QString::number(num);
130     cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData());
131     cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole().constData());
132     cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand().constData());
133     cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName().constData());
134     cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass().constData());
135     cg.writeEntry(QLatin1String("geometry") + n, QRect(c->calculateGravitation(true), c->clientSize()));   // FRAME
136     cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore());
137     cg.writeEntry(QLatin1String("fsrestore") + n, c->fullscreenGeometryRestore());
138     cg.writeEntry(QLatin1String("maximize") + n, (int) c->maximizeMode());
139     cg.writeEntry(QLatin1String("fullscreen") + n, (int) c->fullScreenMode());
140     cg.writeEntry(QLatin1String("desktop") + n, c->desktop());
141     // the config entry is called "iconified" for back. comp. reasons
142     // (kconf_update script for updating session files would be too complicated)
143     cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized());
144     cg.writeEntry(QLatin1String("opacity") + n, c->opacity());
145     // the config entry is called "sticky" for back. comp. reasons
146     cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops());
147     cg.writeEntry(QLatin1String("shaded") + n, c->isShade());
148     // the config entry is called "staysOnTop" for back. comp. reasons
149     cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove());
150     cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow());
151     cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar());
152     cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager());
153     cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher());
154     // not really just set by user, but name kept for back. comp. reasons
155     cg.writeEntry(QLatin1String("userNoBorder") + n, c->userNoBorder());
156     cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType()));
157     cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString());
158     cg.writeEntry(QLatin1String("stackingOrder") + n, unconstrained_stacking_order.indexOf(c));
159     cg.writeEntry(QLatin1String("activities") + n, c->activities());
160 }
161 
storeSubSession(const QString & name,QSet<QByteArray> sessionIds)162 void Workspace::storeSubSession(const QString &name, QSet<QByteArray> sessionIds)
163 {
164     //TODO clear it first
165     KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name);
166     int count =  0;
167     int active_client = -1;
168     for (auto it = m_x11Clients.begin(); it != m_x11Clients.end(); ++it) {
169         X11Client *c = (*it);
170         if (c->windowType() > NET::Splash) {
171             continue;
172         }
173         QByteArray sessionId = c->sessionId();
174         QByteArray wmCommand = c->wmCommand();
175         if (sessionId.isEmpty())
176             // remember also applications that are not XSMP capable
177             // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
178             if (wmCommand.isEmpty())
179                 continue;
180         if (!sessionIds.contains(sessionId))
181             continue;
182 
183         qCDebug(KWIN_CORE) << "storing" << sessionId;
184         count++;
185         if (c->isActive())
186             active_client = count;
187         storeClient(cg, count, c);
188     }
189     cg.writeEntry("count", count);
190     cg.writeEntry("active", active_client);
191     //cg.writeEntry( "desktop", currentDesktop());
192 }
193 
194 /**
195  * Loads the session information from the config file.
196  *
197  * @see storeSession
198  */
loadSessionInfo(const QString & sessionName)199 void Workspace::loadSessionInfo(const QString &sessionName)
200 {
201     session.clear();
202     KConfigGroup cg(sessionConfig(sessionName, QString()), "Session");
203     addSessionInfo(cg);
204 }
205 
addSessionInfo(KConfigGroup & cg)206 void Workspace::addSessionInfo(KConfigGroup &cg)
207 {
208     m_initialDesktop = cg.readEntry("desktop", 1);
209     int count =  cg.readEntry("count", 0);
210     int active_client = cg.readEntry("active", 0);
211     for (int i = 1; i <= count; i++) {
212         QString n = QString::number(i);
213         SessionInfo* info = new SessionInfo;
214         session.append(info);
215         info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1();
216         info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString()).toLatin1();
217         info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1();
218         info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString()).toLatin1();
219         info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower().toLatin1();
220         info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect());
221         info->restore = cg.readEntry(QLatin1String("restore") + n, QRect());
222         info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect());
223         info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0);
224         info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0);
225         info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0);
226         info->minimized = cg.readEntry(QLatin1String("iconified") + n, false);
227         info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0);
228         info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false);
229         info->shaded = cg.readEntry(QLatin1String("shaded") + n, false);
230         info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false);
231         info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false);
232         info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false);
233         info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false);
234         info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false);
235         info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false);
236         info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData());
237         info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString());
238         info->active = (active_client == i);
239         info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1);
240         info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList());
241     }
242 }
243 
loadSubSessionInfo(const QString & name)244 void Workspace::loadSubSessionInfo(const QString &name)
245 {
246     KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name);
247     addSessionInfo(cg);
248 }
249 
sessionInfoWindowTypeMatch(X11Client * c,SessionInfo * info)250 static bool sessionInfoWindowTypeMatch(X11Client *c, SessionInfo* info)
251 {
252     if (info->windowType == -2) {
253         // undefined (not really part of NET::WindowType)
254         return !c->isSpecialWindow();
255     }
256     return info->windowType == c->windowType();
257 }
258 
259 /**
260  * Returns a SessionInfo for client \a c. The returned session
261  * info is removed from the storage. It's up to the caller to delete it.
262  *
263  * This function is called when a new window is mapped and must be managed.
264  * We try to find a matching entry in the session.
265  *
266  * May return 0 if there's no session info for the client.
267  */
takeSessionInfo(X11Client * c)268 SessionInfo* Workspace::takeSessionInfo(X11Client *c)
269 {
270     SessionInfo *realInfo = nullptr;
271     QByteArray sessionId = c->sessionId();
272     QByteArray windowRole = c->windowRole();
273     QByteArray wmCommand = c->wmCommand();
274     QByteArray resourceName = c->resourceName();
275     QByteArray resourceClass = c->resourceClass();
276 
277     // First search ``session''
278     if (! sessionId.isEmpty()) {
279         // look for a real session managed client (algorithm suggested by ICCCM)
280         Q_FOREACH (SessionInfo * info, session) {
281             if (realInfo)
282                 break;
283             if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) {
284                 if (! windowRole.isEmpty()) {
285                     if (info->windowRole == windowRole) {
286                         realInfo = info;
287                         session.removeAll(info);
288                     }
289                 } else {
290                     if (info->windowRole.isEmpty()
291                             && info->resourceName == resourceName
292                             && info->resourceClass == resourceClass) {
293                         realInfo = info;
294                         session.removeAll(info);
295                     }
296                 }
297             }
298         }
299     } else {
300         // look for a sessioninfo with matching features.
301         Q_FOREACH (SessionInfo * info, session) {
302             if (realInfo)
303                 break;
304             if (info->resourceName == resourceName
305                     && info->resourceClass == resourceClass
306                     && sessionInfoWindowTypeMatch(c, info)) {
307                 if (wmCommand.isEmpty() || info->wmCommand == wmCommand) {
308                     realInfo = info;
309                     session.removeAll(info);
310                 }
311             }
312         }
313     }
314     return realInfo;
315 }
316 
SessionManager(QObject * parent)317 SessionManager::SessionManager(QObject *parent)
318     : QObject(parent)
319 {
320     new SessionAdaptor(this);
321     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this);
322 }
323 
~SessionManager()324 SessionManager::~SessionManager()
325 {
326 }
327 
state() const328 SessionState SessionManager::state() const
329 {
330     return m_sessionState;
331 }
332 
setState(uint state)333 void SessionManager::setState(uint state)
334 {
335     switch (state) {
336     case 0:
337         setState(SessionState::Saving);
338         break;
339     case 1:
340         setState(SessionState::Quitting);
341         break;
342     default:
343         setState(SessionState::Normal);
344     }
345 }
346 
347 // TODO should we rethink this now that we have dedicated start end end save methods?
setState(SessionState state)348 void SessionManager::setState(SessionState state)
349 {
350     if (state == m_sessionState) {
351         return;
352     }
353     // If we're starting to save a session
354     if (state == SessionState::Saving) {
355         RuleBook::self()->setUpdatesDisabled(true);
356     }
357     // If we're ending a save session due to either completion or cancellation
358     if (m_sessionState == SessionState::Saving) {
359         RuleBook::self()->setUpdatesDisabled(false);
360         Workspace::self()->forEachClient([](X11Client *client) {
361             client->setSessionActivityOverride(false);
362         });
363     }
364     m_sessionState = state;
365     Q_EMIT stateChanged();
366 }
367 
loadSession(const QString & name)368 void SessionManager::loadSession(const QString &name)
369 {
370     Q_EMIT loadSessionRequested(name);
371 }
372 
aboutToSaveSession(const QString & name)373 void SessionManager::aboutToSaveSession(const QString &name)
374 {
375     Q_EMIT prepareSessionSaveRequested(name);
376 }
377 
finishSaveSession(const QString & name)378 void SessionManager::finishSaveSession(const QString &name)
379 {
380     Q_EMIT finishSessionSaveRequested(name);
381 }
382 
quit()383 void SessionManager::quit()
384 {
385     qApp->quit();
386 }
387 
388 } // namespace
389 
390