1
/********************************************************************
2
KWin - the KDE window manager
3
This file is part of the KDE project.
5
Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6
Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
8
This program is free software; you can redistribute it and/or modify
9
it under the terms of the GNU General Public License as published by
10
the Free Software Foundation; either version 2 of the License, or
11
(at your option) any later version.
13
This program is distributed in the hope that it will be useful,
14
but WITHOUT ANY WARRANTY; without even the implied warranty of
15
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16
GNU General Public License for more details.
18
You should have received a copy of the GNU General Public License
19
along with this program. If not, see <http://www.gnu.org/licenses/>.
20
*********************************************************************/
31
#include "workspace.h"
33
#include <QDBusInterface>
34
#include <QSocketNotifier>
35
#include <QSessionManager>
41
bool SessionManager::saveState(QSessionManager& sm)
43
// If the session manager is ksmserver, save stacking
44
// order, active window, active desktop etc. in phase 1,
45
// as ksmserver assures no interaction will be done
46
// before the WM finishes phase 1. Saving in phase 2 is
47
// too late, as possible user interaction may change some things.
48
// Phase2 is still needed though (ICCCM 5.2)
49
char* sm_vendor = SmcVendor(static_cast< SmcConn >(sm.handle()));
50
bool ksmserver = qstrcmp(sm_vendor, "KDE") == 0;
53
Workspace::self()->sessionSaveStarted();
54
if (ksmserver) // save stacking order etc. before "save file?" etc. dialogs change it
55
Workspace::self()->storeSession(kapp->sessionConfig(), SMSavePhase0);
56
sm.release(); // Qt doesn't automatically release in this case (bug?)
60
Workspace::self()->storeSession(kapp->sessionConfig(), ksmserver ? SMSavePhase2 : SMSavePhase2Full);
61
kapp->sessionConfig()->sync();
65
// I bet this is broken, just like everywhere else in KDE
66
bool SessionManager::commitData(QSessionManager& sm)
69
Workspace::self()->sessionSaveStarted();
76
Stores the current session in the config file
80
void Workspace::storeSession(KConfig* config, SMSavePhase phase)
82
KConfigGroup cg(config, "Session");
84
int active_client = -1;
86
if (phase == SMSavePhase2 || phase == SMSavePhase2Full) {
87
cg.writeEntry("tiling", tilingEnabled());
88
if (tilingEnabled()) {
89
kDebug(1212) << "Tiling was ON";
90
setTilingEnabled(false);
94
for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) {
96
QByteArray sessionId = c->sessionId();
97
QByteArray wmCommand = c->wmCommand();
98
if (sessionId.isEmpty())
99
// remember also applications that are not XSMP capable
100
// and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
101
if (wmCommand.isEmpty())
105
active_client = count;
106
if (phase == SMSavePhase2 || phase == SMSavePhase2Full)
107
storeClient(cg, count, c);
109
if (phase == SMSavePhase0) {
110
// it would be much simpler to save these values to the config file,
111
// but both Qt and KDE treat phase1 and phase2 separately,
112
// which results in different sessionkey and different config file :(
113
session_active_client = active_client;
114
session_desktop = currentDesktop();
115
} else if (phase == SMSavePhase2) {
116
cg.writeEntry("count", count);
117
cg.writeEntry("active", session_active_client);
118
cg.writeEntry("desktop", session_desktop);
119
} else { // SMSavePhase2Full
120
cg.writeEntry("count", count);
121
cg.writeEntry("active", session_active_client);
122
cg.writeEntry("desktop", currentDesktop());
126
void Workspace::storeClient(KConfigGroup &cg, int num, Client *c)
128
c->setSessionInteract(false); //make sure we get the real values
129
QString n = QString::number(num);
130
cg.writeEntry(QString("sessionId") + n, c->sessionId().constData());
131
cg.writeEntry(QString("windowRole") + n, c->windowRole().constData());
132
cg.writeEntry(QString("wmCommand") + n, c->wmCommand().constData());
133
cg.writeEntry(QString("wmClientMachine") + n, c->wmClientMachine(true).constData());
134
cg.writeEntry(QString("resourceName") + n, c->resourceName().constData());
135
cg.writeEntry(QString("resourceClass") + n, c->resourceClass().constData());
136
cg.writeEntry(QString("geometry") + n, QRect(c->calculateGravitation(true), c->clientSize())); // FRAME
137
cg.writeEntry(QString("restore") + n, c->geometryRestore());
138
cg.writeEntry(QString("fsrestore") + n, c->geometryFSRestore());
139
cg.writeEntry(QString("maximize") + n, (int) c->maximizeMode());
140
cg.writeEntry(QString("fullscreen") + n, (int) c->fullScreenMode());
141
cg.writeEntry(QString("desktop") + n, c->desktop());
142
// the config entry is called "iconified" for back. comp. reasons
143
// (kconf_update script for updating session files would be too complicated)
144
cg.writeEntry(QString("iconified") + n, c->isMinimized());
145
cg.writeEntry(QString("opacity") + n, c->opacity());
146
// the config entry is called "sticky" for back. comp. reasons
147
cg.writeEntry(QString("sticky") + n, c->isOnAllDesktops());
148
cg.writeEntry(QString("shaded") + n, c->isShade());
149
// the config entry is called "staysOnTop" for back. comp. reasons
150
cg.writeEntry(QString("staysOnTop") + n, c->keepAbove());
151
cg.writeEntry(QString("keepBelow") + n, c->keepBelow());
152
cg.writeEntry(QString("skipTaskbar") + n, c->skipTaskbar(true));
153
cg.writeEntry(QString("skipPager") + n, c->skipPager());
154
cg.writeEntry(QString("skipSwitcher") + n, c->skipSwitcher());
155
// not really just set by user, but name kept for back. comp. reasons
156
cg.writeEntry(QString("userNoBorder") + n, c->noBorder());
157
cg.writeEntry(QString("windowType") + n, windowTypeToTxt(c->windowType()));
158
cg.writeEntry(QString("shortcut") + n, c->shortcut().toString());
159
cg.writeEntry(QString("stackingOrder") + n, unconstrained_stacking_order.indexOf(c));
161
if (c->clientGroup())
162
group = c->clientGroup()->clients().count() > 1 ?
163
// KConfig doesn't support long so we need to live with less precision on 64-bit systems
164
static_cast<int>(reinterpret_cast<long>(c->clientGroup())) : 0;
165
cg.writeEntry(QString("clientGroup") + n, group);
166
cg.writeEntry(QString("activities") + n, c->activities());
169
void Workspace::storeSubSession(const QString &name, QSet<QByteArray> sessionIds)
171
//TODO clear it first
172
KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + name);
174
int active_client = -1;
175
for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) {
177
QByteArray sessionId = c->sessionId();
178
QByteArray wmCommand = c->wmCommand();
179
if (sessionId.isEmpty())
180
// remember also applications that are not XSMP capable
181
// and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
182
if (wmCommand.isEmpty())
184
if (!sessionIds.contains(sessionId))
187
kDebug() << "storing" << sessionId;
190
active_client = count;
191
storeClient(cg, count, c);
193
cg.writeEntry("count", count);
194
cg.writeEntry("active", active_client);
195
//cg.writeEntry( "desktop", currentDesktop());
198
bool Workspace::stopActivity(const QString &id)
200
if (sessionSaving()) {
201
return false; //ksmserver doesn't queue requests (yet)
202
//FIXME what about session *loading*?
204
//ugly hack to avoid dbus deadlocks
205
QMetaObject::invokeMethod(this, "reallyStopActivity", Qt::QueuedConnection, Q_ARG(QString, id));
206
//then lie and assume it worked.
210
void Workspace::reallyStopActivity(const QString &id)
212
QStringList openActivities = openActivityList();
214
QSet<QByteArray> saveSessionIds;
215
QSet<QByteArray> dontCloseSessionIds;
217
for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) {
219
QByteArray sessionId = c->sessionId();
220
if (sessionId.isEmpty())
221
continue; //TODO support old wm_command apps too?
223
kDebug() << sessionId;
225
//if it's on the activity that's closing, it needs saving
226
//but if a process is on some other open activity, I don't wanna close it yet
227
//this is, of course, complicated by a process having many windows.
228
if (c->isOnAllActivities()) {
229
dontCloseSessionIds << sessionId;
232
QStringList activities = c->activities();
233
foreach (const QString & activityId, activities) {
234
if (activityId == id)
235
saveSessionIds << sessionId;
236
else if (openActivities.contains(activityId))
237
dontCloseSessionIds << sessionId;
241
storeSubSession(id, saveSessionIds);
243
QStringList saveAndClose;
244
QStringList saveOnly;
245
foreach (const QByteArray & sessionId, saveSessionIds) {
246
if (dontCloseSessionIds.contains(sessionId))
247
saveOnly << sessionId;
249
saveAndClose << sessionId;
252
kDebug() << "saveActivity" << id << saveAndClose << saveOnly;
254
//pass off to ksmserver
255
QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
256
if (ksmserver.isValid()) {
257
QDBusMessage reply = ksmserver.call("saveSubSession", id, saveAndClose, saveOnly);
258
if (reply.type() == QDBusMessage::ErrorMessage)
259
kDebug() << "dbus error:" << reply.errorMessage();
261
kDebug() << "dbus succeeded";
263
kDebug() << "couldn't get ksmserver interface";
267
Loads the session information from the config file.
271
void Workspace::loadSessionInfo()
274
KConfigGroup cg(kapp->sessionConfig(), "Session");
276
setTilingEnabled(cg.readEntry("tiling", false));
281
void Workspace::addSessionInfo(KConfigGroup &cg)
283
int count = cg.readEntry("count", 0);
284
int active_client = cg.readEntry("active", 0);
285
for (int i = 1; i <= count; i++) {
286
QString n = QString::number(i);
287
SessionInfo* info = new SessionInfo;
288
session.append(info);
289
info->sessionId = cg.readEntry(QString("sessionId") + n, QString()).toLatin1();
290
info->windowRole = cg.readEntry(QString("windowRole") + n, QString()).toLatin1();
291
info->wmCommand = cg.readEntry(QString("wmCommand") + n, QString()).toLatin1();
292
info->wmClientMachine = cg.readEntry(QString("wmClientMachine") + n, QString()).toLatin1();
293
info->resourceName = cg.readEntry(QString("resourceName") + n, QString()).toLatin1();
294
info->resourceClass = cg.readEntry(QString("resourceClass") + n, QString()).toLower().toLatin1();
295
info->geometry = cg.readEntry(QString("geometry") + n, QRect());
296
info->restore = cg.readEntry(QString("restore") + n, QRect());
297
info->fsrestore = cg.readEntry(QString("fsrestore") + n, QRect());
298
info->maximized = cg.readEntry(QString("maximize") + n, 0);
299
info->fullscreen = cg.readEntry(QString("fullscreen") + n, 0);
300
info->desktop = cg.readEntry(QString("desktop") + n, 0);
301
info->minimized = cg.readEntry(QString("iconified") + n, false);
302
info->opacity = cg.readEntry(QString("opacity") + n, 1.0);
303
info->onAllDesktops = cg.readEntry(QString("sticky") + n, false);
304
info->shaded = cg.readEntry(QString("shaded") + n, false);
305
info->keepAbove = cg.readEntry(QString("staysOnTop") + n, false);
306
info->keepBelow = cg.readEntry(QString("keepBelow") + n, false);
307
info->skipTaskbar = cg.readEntry(QString("skipTaskbar") + n, false);
308
info->skipPager = cg.readEntry(QString("skipPager") + n, false);
309
info->skipSwitcher = cg.readEntry(QString("skipSwitcher") + n, false);
310
info->noBorder = cg.readEntry(QString("userNoBorder") + n, false);
311
info->windowType = txtToWindowType(cg.readEntry(QString("windowType") + n, QString()).toLatin1());
312
info->shortcut = cg.readEntry(QString("shortcut") + n, QString());
313
info->active = (active_client == i);
314
info->stackingOrder = cg.readEntry(QString("stackingOrder") + n, -1);
315
info->clientGroup = cg.readEntry(QString("clientGroup") + n, 0);
316
info->clientGroupClient = NULL;
317
info->activities = cg.readEntry(QString("activities") + n, QStringList());
321
void Workspace::loadSubSessionInfo(const QString &name)
323
KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + name);
327
bool Workspace::startActivity(const QString &id)
329
if (sessionSaving()) {
330
return false; //ksmserver doesn't queue requests (yet)
332
if (!allActivities_.contains(id)) {
333
return false; //bogus id
336
loadSubSessionInfo(id);
338
QDBusInterface ksmserver("org.kde.ksmserver", "/KSMServer", "org.kde.KSMServerInterface");
339
if (ksmserver.isValid()) {
340
QDBusMessage reply = ksmserver.call("restoreSubSession", id);
341
if (reply.type() == QDBusMessage::ErrorMessage) {
342
kDebug() << "dbus error:" << reply.errorMessage();
346
kDebug() << "couldn't get ksmserver interface";
353
Returns a SessionInfo for client \a c. The returned session
354
info is removed from the storage. It's up to the caller to delete it.
356
This function is called when a new window is mapped and must be managed.
357
We try to find a matching entry in the session.
359
May return 0 if there's no session info for the client.
361
SessionInfo* Workspace::takeSessionInfo(Client* c)
363
SessionInfo *realInfo = 0;
364
QByteArray sessionId = c->sessionId();
365
QByteArray windowRole = c->windowRole();
366
QByteArray wmCommand = c->wmCommand();
367
QByteArray wmClientMachine = c->wmClientMachine(true);
368
QByteArray resourceName = c->resourceName();
369
QByteArray resourceClass = c->resourceClass();
371
// First search ``session''
372
if (! sessionId.isEmpty()) {
373
// look for a real session managed client (algorithm suggested by ICCCM)
374
foreach (SessionInfo * info, session) {
377
if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) {
378
if (! windowRole.isEmpty()) {
379
if (info->windowRole == windowRole) {
381
session.removeAll(info);
384
if (info->windowRole.isEmpty()
385
&& info->resourceName == resourceName
386
&& info->resourceClass == resourceClass) {
388
session.removeAll(info);
394
// look for a sessioninfo with matching features.
395
foreach (SessionInfo * info, session) {
398
if (info->resourceName == resourceName
399
&& info->resourceClass == resourceClass
400
&& info->wmClientMachine == wmClientMachine
401
&& sessionInfoWindowTypeMatch(c, info)) {
402
if (wmCommand.isEmpty() || info->wmCommand == wmCommand) {
404
session.removeAll(info);
410
// Set clientGroupClient for other clients in the same group
411
if (realInfo && realInfo->clientGroup)
412
foreach (SessionInfo * info, session)
413
if (!info->clientGroupClient && info->clientGroup == realInfo->clientGroup)
414
info->clientGroupClient = c;
419
bool Workspace::sessionInfoWindowTypeMatch(Client* c, SessionInfo* info)
421
if (info->windowType == -2) {
422
// undefined (not really part of NET::WindowType)
423
return !c->isSpecialWindow();
425
return info->windowType == c->windowType();
428
static const char* const window_type_names[] = {
429
"Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
430
"Override", "TopMenu", "Utility", "Splash"
432
// change also the two functions below when adding new entries
434
const char* Workspace::windowTypeToTxt(NET::WindowType type)
436
if (type >= NET::Unknown && type <= NET::Splash)
437
return window_type_names[ type + 1 ]; // +1 (unknown==-1)
438
if (type == -2) // undefined (not really part of NET::WindowType)
440
kFatal(1212) << "Unknown Window Type" ;
444
NET::WindowType Workspace::txtToWindowType(const char* txt)
446
for (int i = NET::Unknown;
449
if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1
450
return static_cast< NET::WindowType >(i);
451
return static_cast< NET::WindowType >(-2); // undefined
457
// KWin's focus stealing prevention causes problems with user interaction
458
// during session save, as it prevents possible dialogs from getting focus.
459
// Therefore it's temporarily disabled during session saving. Start of
460
// session saving can be detected in SessionManager::saveState() above,
461
// but Qt doesn't have API for saying when session saved finished (either
462
// successfully, or was canceled). Therefore, create another connection
463
// to session manager, that will provide this information.
464
// Similarly the remember feature of window-specific settings should be disabled
465
// during KDE shutdown when windows may move e.g. because of Kicker going away
466
// (struts changing). When session saving starts, it can be cancelled, in which
467
// case the shutdown_cancelled callback is invoked, or it's a checkpoint that
468
// is immediatelly followed by save_complete, or finally it's a shutdown that
469
// is immediatelly followed by die callback. So getting save_yourself with shutdown
470
// set disables window-specific settings remembering, getting shutdown_cancelled
471
// re-enables, otherwise KWin will go away after die.
472
static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool)
474
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
475
if (conn_P != session->connection())
478
Workspace::self()->disableRulesUpdates(true);
479
SmcSaveYourselfDone(conn_P, True);
482
static void die(SmcConn conn_P, SmPointer ptr)
484
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
485
if (conn_P != session->connection())
487
// session->saveDone(); we will quit anyway
491
static void save_complete(SmcConn conn_P, SmPointer ptr)
493
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
494
if (conn_P != session->connection())
499
static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr)
501
SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
502
if (conn_P != session->connection())
504
Workspace::self()->disableRulesUpdates(false); // re-enable
505
// no need to differentiate between successful finish and cancel
509
void SessionSaveDoneHelper::saveDone()
511
Workspace::self()->sessionSaveDone();
514
SessionSaveDoneHelper::SessionSaveDoneHelper()
517
calls.save_yourself.callback = save_yourself;
518
calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this);
519
calls.die.callback = die;
520
calls.die.client_data = reinterpret_cast< SmPointer >(this);
521
calls.save_complete.callback = save_complete;
522
calls.save_complete.client_data = reinterpret_cast< SmPointer >(this);
523
calls.shutdown_cancelled.callback = shutdown_cancelled;
524
calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this);
527
conn = SmcOpenConnection(NULL, 0, 1, 0,
528
SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask
529
| SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err);
534
// set the required properties, mostly dummy values
535
SmPropValue propvalue[ 5 ];
537
propvalue[ 0 ].length = sizeof(unsigned char);
538
unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere
539
propvalue[ 0 ].value = &value0;
540
props[ 0 ].name = const_cast< char* >(SmRestartStyleHint);
541
props[ 0 ].type = const_cast< char* >(SmCARD8);
542
props[ 0 ].num_vals = 1;
543
props[ 0 ].vals = &propvalue[ 0 ];
544
struct passwd* entry = getpwuid(geteuid());
545
propvalue[ 1 ].length = entry != NULL ? strlen(entry->pw_name) : 0;
546
propvalue[ 1 ].value = (SmPointer)(entry != NULL ? entry->pw_name : "");
547
props[ 1 ].name = const_cast< char* >(SmUserID);
548
props[ 1 ].type = const_cast< char* >(SmARRAY8);
549
props[ 1 ].num_vals = 1;
550
props[ 1 ].vals = &propvalue[ 1 ];
551
propvalue[ 2 ].length = 0;
552
propvalue[ 2 ].value = (SmPointer)("");
553
props[ 2 ].name = const_cast< char* >(SmRestartCommand);
554
props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8);
555
props[ 2 ].num_vals = 1;
556
props[ 2 ].vals = &propvalue[ 2 ];
557
propvalue[ 3 ].length = strlen("kwinsmhelper");
558
propvalue[ 3 ].value = (SmPointer)"kwinsmhelper";
559
props[ 3 ].name = const_cast< char* >(SmProgram);
560
props[ 3 ].type = const_cast< char* >(SmARRAY8);
561
props[ 3 ].num_vals = 1;
562
props[ 3 ].vals = &propvalue[ 3 ];
563
propvalue[ 4 ].length = 0;
564
propvalue[ 4 ].value = (SmPointer)("");
565
props[ 4 ].name = const_cast< char* >(SmCloneCommand);
566
props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8);
567
props[ 4 ].num_vals = 1;
568
props[ 4 ].vals = &propvalue[ 4 ];
569
SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] };
570
SmcSetProperties(conn, 5, p);
571
notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)),
572
QSocketNotifier::Read, this);
573
connect(notifier, SIGNAL(activated(int)), SLOT(processData()));
576
SessionSaveDoneHelper::~SessionSaveDoneHelper()
581
void SessionSaveDoneHelper::close()
585
SmcCloseConnection(conn, 0, NULL);
590
void SessionSaveDoneHelper::processData()
593
IceProcessMessages(SmcGetIceConnection(conn), 0, 0);
596
void Workspace::sessionSaveDone()
598
session_saving = false;
599
//remove sessionInteract flag from all clients
600
foreach (Client * c, clients) {
601
c->setSessionInteract(false);