KWin
Loading...
Searching...
No Matches
sm.cpp
Go to the documentation of this file.
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 <cstdlib>
14#include <kconfig.h>
15#include <pwd.h>
16#include <unistd.h>
17
18#include "virtualdesktops.h"
19#include "wayland_server.h"
20#include "workspace.h"
21#include "x11window.h"
22#include "xdgshellwindow.h"
23#include <QDebug>
24
25#include <QSessionManager>
26#if KWIN_BUILD_NOTIFICATIONS
27#include <KLocalizedString>
28#include <KNotification>
29#include <KService>
30#endif
31
32#include "sessionadaptor.h"
33
34using namespace Qt::StringLiterals;
35
36namespace KWin
37{
38
39static KConfig *sessionConfig(QString id, QString key)
40{
41 static KConfig *config = nullptr;
42 static QString lastId;
43 static QString lastKey;
44 static QString pattern = QString(QLatin1String("session/%1_%2_%3")).arg(qApp->applicationName());
45 if (id != lastId || key != lastKey) {
46 delete config;
47 config = nullptr;
48 }
49 lastId = id;
50 lastKey = key;
51 if (!config) {
52 config = new KConfig(pattern.arg(id, key), KConfig::SimpleConfig);
53 }
54 return config;
55}
56
57static const char *const window_type_names[] = {
58 "Unknown", "Normal", "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
59 "Override", "TopMenu", "Utility", "Splash"};
60// change also the two functions below when adding new entries
61
62static const char *windowTypeToTxt(NET::WindowType type)
63{
64 if (type >= NET::Unknown && type <= NET::Splash) {
65 return window_type_names[type + 1]; // +1 (unknown==-1)
66 }
67 if (type == -2) { // undefined (not really part of NET::WindowType)
68 return "Undefined";
69 }
70 qFatal("Unknown Window Type");
71 return nullptr;
72}
73
74static NET::WindowType txtToWindowType(const char *txt)
75{
76 for (int i = NET::Unknown;
77 i <= NET::Splash;
78 ++i) {
79 if (qstrcmp(txt, window_type_names[i + 1]) == 0) { // +1
80 return static_cast<NET::WindowType>(i);
81 }
82 }
83 return static_cast<NET::WindowType>(-2); // undefined
84}
85
91void SessionManager::storeSession(const QString &sessionName, SMSavePhase phase)
92{
93 qCDebug(KWIN_CORE) << "storing session" << sessionName << "in phase" << phase;
94 KConfig *config = sessionConfig(sessionName, QString());
95
96 KConfigGroup cg(config, QStringLiteral("Session"));
97 int count = 0;
98 int active_client = -1;
99
100 const QList<Window *> windows = workspace()->windows();
101 for (auto it = windows.begin(); it != windows.end(); ++it) {
102 X11Window *c = qobject_cast<X11Window *>(*it);
103 if (!c || c->isUnmanaged()) {
104 continue;
105 }
106 if (c->windowType() > NET::Splash) {
107 // window types outside this are not tooltips/menus/OSDs
108 // typically these will be unmanaged and not in this list anyway, but that is not enforced
109 continue;
110 }
111 QByteArray sessionId = c->sessionId();
112 QString wmCommand = c->wmCommand();
113 if (sessionId.isEmpty()) {
114 // remember also applications that are not XSMP capable
115 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
116 if (wmCommand.isEmpty()) {
117 continue;
118 }
119 }
120 count++;
121 if (c->isActive()) {
122 active_client = count;
123 }
124 if (phase == SMSavePhase2 || phase == SMSavePhase2Full) {
125 storeClient(cg, count, c);
126 }
127 }
128 if (phase == SMSavePhase0) {
129 // it would be much simpler to save these values to the config file,
130 // but both Qt and KDE treat phase1 and phase2 separately,
131 // which results in different sessionkey and different config file :(
132 m_sessionActiveClient = active_client;
133 m_sessionDesktop = VirtualDesktopManager::self()->current();
134 } else if (phase == SMSavePhase2) {
135 cg.writeEntry("count", count);
136 cg.writeEntry("active", m_sessionActiveClient);
137 cg.writeEntry("desktop", m_sessionDesktop);
138 } else { // SMSavePhase2Full
139 cg.writeEntry("count", count);
140 cg.writeEntry("active", m_sessionActiveClient);
141 cg.writeEntry("desktop", VirtualDesktopManager::self()->current());
142 }
143 config->sync(); // it previously did some "revert to defaults" stuff for phase1 I think
144}
145
146void SessionManager::storeClient(KConfigGroup &cg, int num, X11Window *c)
147{
148 c->setSessionActivityOverride(false); // make sure we get the real values
149 QString n = QString::number(num);
150 cg.writeEntry(QLatin1String("sessionId") + n, c->sessionId().constData());
151 cg.writeEntry(QLatin1String("windowRole") + n, c->windowRole());
152 cg.writeEntry(QLatin1String("wmCommand") + n, c->wmCommand());
153 cg.writeEntry(QLatin1String("resourceName") + n, c->resourceName());
154 cg.writeEntry(QLatin1String("resourceClass") + n, c->resourceClass());
155 cg.writeEntry(QLatin1String("geometry") + n, QRectF(c->calculateGravitation(true), c->clientSize()).toRect()); // FRAME
156 cg.writeEntry(QLatin1String("restore") + n, c->geometryRestore());
157 cg.writeEntry(QLatin1String("fsrestore") + n, c->fullscreenGeometryRestore());
158 cg.writeEntry(QLatin1String("maximize") + n, (int)c->maximizeMode());
159 cg.writeEntry(QLatin1String("fullscreen") + n, (int)c->fullScreenMode());
160 cg.writeEntry(QLatin1String("desktop") + n, c->desktopId());
161 // the config entry is called "iconified" for back. comp. reasons
162 // (kconf_update script for updating session files would be too complicated)
163 cg.writeEntry(QLatin1String("iconified") + n, c->isMinimized());
164 cg.writeEntry(QLatin1String("opacity") + n, c->opacity());
165 // the config entry is called "sticky" for back. comp. reasons
166 cg.writeEntry(QLatin1String("sticky") + n, c->isOnAllDesktops());
167 cg.writeEntry(QLatin1String("shaded") + n, c->isShade());
168 // the config entry is called "staysOnTop" for back. comp. reasons
169 cg.writeEntry(QLatin1String("staysOnTop") + n, c->keepAbove());
170 cg.writeEntry(QLatin1String("keepBelow") + n, c->keepBelow());
171 cg.writeEntry(QLatin1String("skipTaskbar") + n, c->originalSkipTaskbar());
172 cg.writeEntry(QLatin1String("skipPager") + n, c->skipPager());
173 cg.writeEntry(QLatin1String("skipSwitcher") + n, c->skipSwitcher());
174 // not really just set by user, but name kept for back. comp. reasons
175 cg.writeEntry(QLatin1String("userNoBorder") + n, c->userNoBorder());
176 cg.writeEntry(QLatin1String("windowType") + n, windowTypeToTxt(c->windowType()));
177 cg.writeEntry(QLatin1String("shortcut") + n, c->shortcut().toString());
178 cg.writeEntry(QLatin1String("stackingOrder") + n, workspace()->unconstrainedStackingOrder().indexOf(c));
179 cg.writeEntry(QLatin1String("activities") + n, c->activities());
180}
181
182void SessionManager::storeSubSession(const QString &name, QSet<QByteArray> sessionIds)
183{
184 // TODO clear it first
185 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name);
186 int count = 0;
187 int active_client = -1;
188 const QList<Window *> windows = workspace()->windows();
189
190 for (auto it = windows.begin(); it != windows.end(); ++it) {
191 X11Window *c = qobject_cast<X11Window *>(*it);
192 if (!c || c->isUnmanaged()) {
193 continue;
194 }
195 if (c->windowType() > NET::Splash) {
196 continue;
197 }
198 QByteArray sessionId = c->sessionId();
199 QString wmCommand = c->wmCommand();
200 if (sessionId.isEmpty()) {
201 // remember also applications that are not XSMP capable
202 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
203 if (wmCommand.isEmpty()) {
204 continue;
205 }
206 }
207 if (!sessionIds.contains(sessionId)) {
208 continue;
209 }
210
211 qCDebug(KWIN_CORE) << "storing" << sessionId;
212 count++;
213 if (c->isActive()) {
214 active_client = count;
215 }
216 storeClient(cg, count, c);
217 }
218 cg.writeEntry("count", count);
219 cg.writeEntry("active", active_client);
220 // cg.writeEntry( "desktop", currentDesktop());
221}
222
228void SessionManager::loadSession(const QString &sessionName)
229{
230 session.clear();
231 KConfigGroup cg(sessionConfig(sessionName, QString()), QStringLiteral("Session"));
232 Q_EMIT loadSessionRequested(sessionName);
233 addSessionInfo(cg);
234}
235
236void SessionManager::addSessionInfo(KConfigGroup &cg)
237{
238 workspace()->setInitialDesktop(cg.readEntry("desktop", 1));
239 int count = cg.readEntry("count", 0);
240 int active_client = cg.readEntry("active", 0);
241 for (int i = 1; i <= count; i++) {
242 QString n = QString::number(i);
243 SessionInfo *info = new SessionInfo;
244 session.append(info);
245 info->sessionId = cg.readEntry(QLatin1String("sessionId") + n, QString()).toLatin1();
246 info->windowRole = cg.readEntry(QLatin1String("windowRole") + n, QString());
247 info->wmCommand = cg.readEntry(QLatin1String("wmCommand") + n, QString()).toLatin1();
248 info->resourceName = cg.readEntry(QLatin1String("resourceName") + n, QString());
249 info->resourceClass = cg.readEntry(QLatin1String("resourceClass") + n, QString()).toLower();
250 info->geometry = cg.readEntry(QLatin1String("geometry") + n, QRect());
251 info->restore = cg.readEntry(QLatin1String("restore") + n, QRect());
252 info->fsrestore = cg.readEntry(QLatin1String("fsrestore") + n, QRect());
253 info->maximized = cg.readEntry(QLatin1String("maximize") + n, 0);
254 info->fullscreen = cg.readEntry(QLatin1String("fullscreen") + n, 0);
255 info->desktop = cg.readEntry(QLatin1String("desktop") + n, 0);
256 info->minimized = cg.readEntry(QLatin1String("iconified") + n, false);
257 info->opacity = cg.readEntry(QLatin1String("opacity") + n, 1.0);
258 info->onAllDesktops = cg.readEntry(QLatin1String("sticky") + n, false);
259 info->shaded = cg.readEntry(QLatin1String("shaded") + n, false);
260 info->keepAbove = cg.readEntry(QLatin1String("staysOnTop") + n, false);
261 info->keepBelow = cg.readEntry(QLatin1String("keepBelow") + n, false);
262 info->skipTaskbar = cg.readEntry(QLatin1String("skipTaskbar") + n, false);
263 info->skipPager = cg.readEntry(QLatin1String("skipPager") + n, false);
264 info->skipSwitcher = cg.readEntry(QLatin1String("skipSwitcher") + n, false);
265 info->noBorder = cg.readEntry(QLatin1String("userNoBorder") + n, false);
266 info->windowType = txtToWindowType(cg.readEntry(QLatin1String("windowType") + n, QString()).toLatin1().constData());
267 info->shortcut = cg.readEntry(QLatin1String("shortcut") + n, QString());
268 info->active = (active_client == i);
269 info->stackingOrder = cg.readEntry(QLatin1String("stackingOrder") + n, -1);
270 info->activities = cg.readEntry(QLatin1String("activities") + n, QStringList());
271 }
272}
273
274void SessionManager::loadSubSessionInfo(const QString &name)
275{
276 KConfigGroup cg(KSharedConfig::openConfig(), QLatin1String("SubSession: ") + name);
277 addSessionInfo(cg);
278}
279
280static bool sessionInfoWindowTypeMatch(X11Window *c, SessionInfo *info)
281{
282 if (info->windowType == -2) {
283 // undefined (not really part of NET::WindowType)
284 return !c->isSpecialWindow();
285 }
286 return info->windowType == c->windowType();
287}
288
299{
300 SessionInfo *realInfo = nullptr;
301 QByteArray sessionId = c->sessionId();
302 QString windowRole = c->windowRole();
303 QString wmCommand = c->wmCommand();
304 QString resourceName = c->resourceName();
305 QString resourceClass = c->resourceClass();
306
307 // First search ``session''
308 if (!sessionId.isEmpty()) {
309 // look for a real session managed client (algorithm suggested by ICCCM)
310 for (SessionInfo *info : std::as_const(session)) {
311 if (realInfo) {
312 break;
313 }
314 if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) {
315 if (!windowRole.isEmpty()) {
316 if (info->windowRole == windowRole) {
317 realInfo = info;
318 session.removeAll(info);
319 }
320 } else {
321 if (info->windowRole.isEmpty()
322 && info->resourceName == resourceName
323 && info->resourceClass == resourceClass) {
324 realInfo = info;
325 session.removeAll(info);
326 }
327 }
328 }
329 }
330 } else {
331 // look for a sessioninfo with matching features.
332 for (SessionInfo *info : std::as_const(session)) {
333 if (realInfo) {
334 break;
335 }
336 if (info->resourceName == resourceName
337 && info->resourceClass == resourceClass
338 && sessionInfoWindowTypeMatch(c, info)) {
339 if (wmCommand.isEmpty() || info->wmCommand == wmCommand) {
340 realInfo = info;
341 session.removeAll(info);
342 }
343 }
344 }
345 }
346 return realInfo;
347}
348
350 : QObject(parent)
351{
352 new SessionAdaptor(this);
353 QDBusConnection::sessionBus().registerObject(QStringLiteral("/Session"), this);
354}
355
357{
358 qDeleteAll(session);
359}
360
362{
363 return m_sessionState;
364}
365
367{
368 switch (state) {
369 case 0:
371 break;
372 case 1:
374 break;
375 default:
377 }
378}
379
380// TODO should we rethink this now that we have dedicated start end end save methods?
382{
383 if (state == m_sessionState) {
384 return;
385 }
386 // If we're starting to save a session
389 }
390 // If we're ending a save session due to either completion or cancellation
391 if (m_sessionState == SessionState::Saving) {
393 Workspace::self()->forEachClient([](X11Window *client) {
394 client->setSessionActivityOverride(false);
395 });
396 }
397
398 m_sessionState = state;
399 Q_EMIT stateChanged();
400}
401
402void SessionManager::aboutToSaveSession(const QString &name)
403{
404 Q_EMIT prepareSessionSaveRequested(name);
405 storeSession(name, SMSavePhase0);
406}
407
408void SessionManager::finishSaveSession(const QString &name)
409{
410 Q_EMIT finishSessionSaveRequested(name);
411 storeSession(name, SMSavePhase2);
412}
413
415{
416 Q_ASSERT(calledFromDBus());
417 if (!waylandServer()) {
418 return true;
419 }
420
421 if (m_closingWindowsGuard) {
422 sendErrorReply(QDBusError::Failed, u"Operation already in progress"_s);
423 return false;
424 }
425
426 m_closingWindowsGuard = std::make_unique<QObject>();
427 qCDebug(KWIN_CORE) << "Closing windows";
428
429 auto dbusMessage = message();
430 setDelayedReply(true);
431
432 const auto windows = workspace()->windows();
433 m_pendingWindows.clear();
434 m_pendingWindows.reserve(windows.size());
435 for (const auto window : windows) {
436 if (auto toplevelWindow = qobject_cast<XdgToplevelWindow *>(window)) {
437 connect(toplevelWindow, &XdgToplevelWindow::closed, m_closingWindowsGuard.get(), [this, toplevelWindow, dbusMessage] {
438 m_pendingWindows.removeOne(toplevelWindow);
439 if (m_pendingWindows.empty()) {
440 m_closeTimer.stop();
441 m_closingWindowsGuard.reset();
442 QDBusConnection::sessionBus().send(dbusMessage.createReply(true));
443 }
444 });
445 m_pendingWindows.push_back(toplevelWindow);
446 toplevelWindow->closeWindow();
447 }
448 }
449
450 if (m_pendingWindows.empty()) {
451 m_closingWindowsGuard.reset();
452 QDBusConnection::sessionBus().send(dbusMessage.createReply(true));
453 return true;
454 }
455
456 m_closeTimer.start(std::chrono::seconds(10));
457 m_closeTimer.setSingleShot(true);
458 connect(&m_closeTimer, &QTimer::timeout, m_closingWindowsGuard.get(), [this, dbusMessage] {
459#if KWIN_BUILD_NOTIFICATIONS
460 QStringList apps;
461 apps.reserve(m_pendingWindows.size());
462 std::transform(m_pendingWindows.cbegin(), m_pendingWindows.cend(), std::back_inserter(apps), [](const XdgToplevelWindow *window) -> QString {
463 const auto service = KService::serviceByDesktopName(window->desktopFileName());
464 return QChar(u'•') + (service ? service->name() : window->caption());
465 });
466 apps.removeDuplicates();
467 qCDebug(KWIN_CORE) << "Not closed windows" << apps;
468 auto notification = new KNotification("cancellogout", KNotification::DefaultEvent | KNotification::Persistent);
469 notification->setText(i18n("The following applications did not close:\n%1", apps.join('\n')));
470 auto cancel = notification->addAction(i18nc("@action:button", "Cancel Logout"));
471 auto quit = notification->addAction(i18nc("@action::button", "Log Out Anyway"));
472 connect(cancel, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] {
473 m_closingWindowsGuard.reset();
474 QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
475 });
476 connect(quit, &KNotificationAction::activated, m_closingWindowsGuard.get(), [dbusMessage, this] {
477 m_closingWindowsGuard.reset();
478 QDBusConnection::sessionBus().send(dbusMessage.createReply(true));
479 });
480 connect(notification, &KNotification::closed, m_closingWindowsGuard.get(), [dbusMessage, this] {
481 m_closingWindowsGuard.reset();
482 QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
483 });
484 notification->sendEvent();
485#else
486 m_closingWindowsGuard.reset();
487 QDBusConnection::sessionBus().send(dbusMessage.createReply(false));
488#endif
489 });
490 return true;
491}
492
493void SessionManager::quit()
494{
495 qApp->quit();
496}
497
498} // namespace
499
500#include "moc_sm.cpp"
void setUpdatesDisabled(bool disable)
Definition rules.cpp:965
void loadSubSessionInfo(const QString &name)
Definition sm.cpp:274
~SessionManager() override
Definition sm.cpp:356
SessionInfo * takeSessionInfo(X11Window *)
Definition sm.cpp:298
SessionManager(QObject *parent)
Definition sm.cpp:349
void finishSessionSaveRequested(const QString &name)
void finishSaveSession(const QString &name)
Definition sm.cpp:408
void loadSession(const QString &name)
Definition sm.cpp:228
void aboutToSaveSession(const QString &name)
Definition sm.cpp:402
void setState(uint state)
Definition sm.cpp:366
void loadSessionRequested(const QString &name)
SessionState state() const
Definition sm.cpp:361
bool closeWaylandWindows()
Definition sm.cpp:414
void prepareSessionSaveRequested(const QString &name)
void storeSubSession(const QString &name, QSet< QByteArray > sessionIds)
Definition sm.cpp:182
QString resourceClass
Definition window.h:115
bool isActive() const
Definition window.h:882
QString resourceName
Definition window.h:114
bool isSpecialWindow() const
Definition window.cpp:702
void setInitialDesktop(int desktop)
static Workspace * self()
Definition workspace.h:91
RuleBook * rulebook() const
const QList< Window * > windows() const
Definition workspace.h:248
void forEachClient(std::function< void(X11Window *)> func)
QString wmCommand()
bool isUnmanaged() const override
NET::WindowType windowType() const override
QByteArray sessionId() const
QString windowRole() const override
Session::Type type
Definition session.cpp:17
SessionState
Definition globals.h:132
WaylandServer * waylandServer()
Workspace * workspace()
Definition workspace.h:830
QString windowRole
Definition sm.h:88
bool minimized
Definition sm.h:100
QString resourceClass
Definition sm.h:92
bool noBorder
Definition sm.h:108
float opacity
Definition sm.h:113
QRect fsrestore
Definition sm.h:96
QByteArray sessionId
Definition sm.h:87
NET::WindowType windowType
Definition sm.h:109
bool skipSwitcher
Definition sm.h:107
QString shortcut
Definition sm.h:110
QStringList activities
Definition sm.h:115
int maximized
Definition sm.h:97
QString wmCommand
Definition sm.h:89
bool keepBelow
Definition sm.h:104
int stackingOrder
Definition sm.h:112
bool onAllDesktops
Definition sm.h:101
bool skipPager
Definition sm.h:106
bool skipTaskbar
Definition sm.h:105
bool active
Definition sm.h:111
QRect restore
Definition sm.h:95
bool shaded
Definition sm.h:102
QRect geometry
Definition sm.h:94
bool keepAbove
Definition sm.h:103
QString resourceName
Definition sm.h:91
int desktop
Definition sm.h:99
int fullscreen
Definition sm.h:98