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