1/********************************************************************
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5Copyright (C) 1999, 2000 Matthias Ettrich <ettrich@kde.org>
6Copyright (C) 2003 Lubos Lunak <l.lunak@kde.org>
7
8This program is free software; you can redistribute it and/or modify
9it under the terms of the GNU General Public License as published by
10the Free Software Foundation; either version 2 of the License, or
11(at your option) any later version.
12
13This program is distributed in the hope that it will be useful,
14but WITHOUT ANY WARRANTY; without even the implied warranty of
15MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16GNU General Public License for more details.
17
18You should have received a copy of the GNU General Public License
19along with this program. If not, see <http://www.gnu.org/licenses/>.
20*********************************************************************/
21
22#include "sm.h"
23
24#include <unistd.h>
25#include <stdlib.h>
26#include <pwd.h>
27#include <fixx11h.h>
28#include <kconfig.h>
29#include <kglobal.h>
30
31#include "workspace.h"
32#include "client.h"
33#include <QSocketNotifier>
34#include <QSessionManager>
35#include <kdebug.h>
36
37namespace KWin
38{
39
40bool SessionManager::saveState(QSessionManager& sm)
41{
42 // If the session manager is ksmserver, save stacking
43 // order, active window, active desktop etc. in phase 1,
44 // as ksmserver assures no interaction will be done
45 // before the WM finishes phase 1. Saving in phase 2 is
46 // too late, as possible user interaction may change some things.
47 // Phase2 is still needed though (ICCCM 5.2)
48 char* sm_vendor = SmcVendor(static_cast< SmcConn >(sm.handle()));
49 bool ksmserver = qstrcmp(sm_vendor, "KDE") == 0;
50 free(sm_vendor);
51 if (!sm.isPhase2()) {
52 Workspace::self()->sessionSaveStarted();
53 if (ksmserver) // save stacking order etc. before "save file?" etc. dialogs change it
54 Workspace::self()->storeSession(kapp->sessionConfig(), SMSavePhase0);
55 sm.release(); // Qt doesn't automatically release in this case (bug?)
56 sm.requestPhase2();
57 return true;
58 }
59 Workspace::self()->storeSession(kapp->sessionConfig(), ksmserver ? SMSavePhase2 : SMSavePhase2Full);
60 kapp->sessionConfig()->sync();
61 return true;
62}
63
64// I bet this is broken, just like everywhere else in KDE
65bool SessionManager::commitData(QSessionManager& sm)
66{
67 if (!sm.isPhase2())
68 Workspace::self()->sessionSaveStarted();
69 return true;
70}
71
72// Workspace
73
74/*!
75 Stores the current session in the config file
76
77 \sa loadSessionInfo()
78 */
79void Workspace::storeSession(KConfig* config, SMSavePhase phase)
80{
81 KConfigGroup cg(config, "Session");
82 int count = 0;
83 int active_client = -1;
84
85 for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) {
86 Client* c = (*it);
87 QByteArray sessionId = c->sessionId();
88 QByteArray wmCommand = c->wmCommand();
89 if (sessionId.isEmpty())
90 // remember also applications that are not XSMP capable
91 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
92 if (wmCommand.isEmpty())
93 continue;
94 count++;
95 if (c->isActive())
96 active_client = count;
97 if (phase == SMSavePhase2 || phase == SMSavePhase2Full)
98 storeClient(cg, count, c);
99 }
100 if (phase == SMSavePhase0) {
101 // it would be much simpler to save these values to the config file,
102 // but both Qt and KDE treat phase1 and phase2 separately,
103 // which results in different sessionkey and different config file :(
104 session_active_client = active_client;
105 session_desktop = VirtualDesktopManager::self()->current();
106 } else if (phase == SMSavePhase2) {
107 cg.writeEntry("count", count);
108 cg.writeEntry("active", session_active_client);
109 cg.writeEntry("desktop", session_desktop);
110 } else { // SMSavePhase2Full
111 cg.writeEntry("count", count);
112 cg.writeEntry("active", session_active_client);
113 cg.writeEntry("desktop", VirtualDesktopManager::self()->current());
114 }
115}
116
117void Workspace::storeClient(KConfigGroup &cg, int num, Client *c)
118{
119 c->setSessionInteract(false); //make sure we get the real values
120 QString n = QString::number(num);
121 cg.writeEntry(QString("sessionId") + n, c->sessionId().constData());
122 cg.writeEntry(QString("windowRole") + n, c->windowRole().constData());
123 cg.writeEntry(QString("wmCommand") + n, c->wmCommand().constData());
124 cg.writeEntry(QString("resourceName") + n, c->resourceName().constData());
125 cg.writeEntry(QString("resourceClass") + n, c->resourceClass().constData());
126 cg.writeEntry(QString("geometry") + n, QRect(c->calculateGravitation(true), c->clientSize())); // FRAME
127 cg.writeEntry(QString("restore") + n, c->geometryRestore());
128 cg.writeEntry(QString("fsrestore") + n, c->geometryFSRestore());
129 cg.writeEntry(QString("maximize") + n, (int) c->maximizeMode());
130 cg.writeEntry(QString("fullscreen") + n, (int) c->fullScreenMode());
131 cg.writeEntry(QString("desktop") + n, c->desktop());
132 // the config entry is called "iconified" for back. comp. reasons
133 // (kconf_update script for updating session files would be too complicated)
134 cg.writeEntry(QString("iconified") + n, c->isMinimized());
135 cg.writeEntry(QString("opacity") + n, c->opacity());
136 // the config entry is called "sticky" for back. comp. reasons
137 cg.writeEntry(QString("sticky") + n, c->isOnAllDesktops());
138 cg.writeEntry(QString("shaded") + n, c->isShade());
139 // the config entry is called "staysOnTop" for back. comp. reasons
140 cg.writeEntry(QString("staysOnTop") + n, c->keepAbove());
141 cg.writeEntry(QString("keepBelow") + n, c->keepBelow());
142 cg.writeEntry(QString("skipTaskbar") + n, c->skipTaskbar(true));
143 cg.writeEntry(QString("skipPager") + n, c->skipPager());
144 cg.writeEntry(QString("skipSwitcher") + n, c->skipSwitcher());
145 // not really just set by user, but name kept for back. comp. reasons
146 cg.writeEntry(QString("userNoBorder") + n, c->noBorder());
147 cg.writeEntry(QString("windowType") + n, windowTypeToTxt(c->windowType()));
148 cg.writeEntry(QString("shortcut") + n, c->shortcut().toString());
149 cg.writeEntry(QString("stackingOrder") + n, unconstrained_stacking_order.indexOf(c));
150 // KConfig doesn't support long so we need to live with less precision on 64-bit systems
151 cg.writeEntry(QString("tabGroup") + n, static_cast<int>(reinterpret_cast<long>(c->tabGroup())));
152 cg.writeEntry(QString("activities") + n, c->activities());
153}
154
155void Workspace::storeSubSession(const QString &name, QSet<QByteArray> sessionIds)
156{
157 //TODO clear it first
158 KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + name);
159 int count = 0;
160 int active_client = -1;
161 for (ClientList::Iterator it = clients.begin(); it != clients.end(); ++it) {
162 Client* c = (*it);
163 QByteArray sessionId = c->sessionId();
164 QByteArray wmCommand = c->wmCommand();
165 if (sessionId.isEmpty())
166 // remember also applications that are not XSMP capable
167 // and use the obsolete WM_COMMAND / WM_SAVE_YOURSELF
168 if (wmCommand.isEmpty())
169 continue;
170 if (!sessionIds.contains(sessionId))
171 continue;
172
173 kDebug() << "storing" << sessionId;
174 count++;
175 if (c->isActive())
176 active_client = count;
177 storeClient(cg, count, c);
178 }
179 cg.writeEntry("count", count);
180 cg.writeEntry("active", active_client);
181 //cg.writeEntry( "desktop", currentDesktop());
182}
183
184/*!
185 Loads the session information from the config file.
186
187 \sa storeSession()
188 */
189void Workspace::loadSessionInfo()
190{
191 session.clear();
192 KConfigGroup cg(kapp->sessionConfig(), "Session");
193
194 addSessionInfo(cg);
195}
196
197void Workspace::addSessionInfo(KConfigGroup &cg)
198{
199 int count = cg.readEntry("count", 0);
200 int active_client = cg.readEntry("active", 0);
201 for (int i = 1; i <= count; i++) {
202 QString n = QString::number(i);
203 SessionInfo* info = new SessionInfo;
204 session.append(info);
205 info->sessionId = cg.readEntry(QString("sessionId") + n, QString()).toLatin1();
206 info->windowRole = cg.readEntry(QString("windowRole") + n, QString()).toLatin1();
207 info->wmCommand = cg.readEntry(QString("wmCommand") + n, QString()).toLatin1();
208 info->resourceName = cg.readEntry(QString("resourceName") + n, QString()).toLatin1();
209 info->resourceClass = cg.readEntry(QString("resourceClass") + n, QString()).toLower().toLatin1();
210 info->geometry = cg.readEntry(QString("geometry") + n, QRect());
211 info->restore = cg.readEntry(QString("restore") + n, QRect());
212 info->fsrestore = cg.readEntry(QString("fsrestore") + n, QRect());
213 info->maximized = cg.readEntry(QString("maximize") + n, 0);
214 info->fullscreen = cg.readEntry(QString("fullscreen") + n, 0);
215 info->desktop = cg.readEntry(QString("desktop") + n, 0);
216 info->minimized = cg.readEntry(QString("iconified") + n, false);
217 info->opacity = cg.readEntry(QString("opacity") + n, 1.0);
218 info->onAllDesktops = cg.readEntry(QString("sticky") + n, false);
219 info->shaded = cg.readEntry(QString("shaded") + n, false);
220 info->keepAbove = cg.readEntry(QString("staysOnTop") + n, false);
221 info->keepBelow = cg.readEntry(QString("keepBelow") + n, false);
222 info->skipTaskbar = cg.readEntry(QString("skipTaskbar") + n, false);
223 info->skipPager = cg.readEntry(QString("skipPager") + n, false);
224 info->skipSwitcher = cg.readEntry(QString("skipSwitcher") + n, false);
225 info->noBorder = cg.readEntry(QString("userNoBorder") + n, false);
226 info->windowType = txtToWindowType(cg.readEntry(QString("windowType") + n, QString()).toLatin1());
227 info->shortcut = cg.readEntry(QString("shortcut") + n, QString());
228 info->active = (active_client == i);
229 info->stackingOrder = cg.readEntry(QString("stackingOrder") + n, -1);
230 info->tabGroup = cg.readEntry(QString("tabGroup") + n, 0);
231 info->tabGroupClient = NULL;
232 info->activities = cg.readEntry(QString("activities") + n, QStringList());
233 }
234}
235
236void Workspace::loadSubSessionInfo(const QString &name)
237{
238 KConfigGroup cg(KGlobal::config(), QString("SubSession: ") + name);
239 addSessionInfo(cg);
240}
241
242/*!
243 Returns a SessionInfo for client \a c. The returned session
244 info is removed from the storage. It's up to the caller to delete it.
245
246 This function is called when a new window is mapped and must be managed.
247 We try to find a matching entry in the session.
248
249 May return 0 if there's no session info for the client.
250 */
251SessionInfo* Workspace::takeSessionInfo(Client* c)
252{
253 SessionInfo *realInfo = 0;
254 QByteArray sessionId = c->sessionId();
255 QByteArray windowRole = c->windowRole();
256 QByteArray wmCommand = c->wmCommand();
257 QByteArray resourceName = c->resourceName();
258 QByteArray resourceClass = c->resourceClass();
259
260 // First search ``session''
261 if (! sessionId.isEmpty()) {
262 // look for a real session managed client (algorithm suggested by ICCCM)
263 foreach (SessionInfo * info, session) {
264 if (realInfo)
265 break;
266 if (info->sessionId == sessionId && sessionInfoWindowTypeMatch(c, info)) {
267 if (! windowRole.isEmpty()) {
268 if (info->windowRole == windowRole) {
269 realInfo = info;
270 session.removeAll(info);
271 }
272 } else {
273 if (info->windowRole.isEmpty()
274 && info->resourceName == resourceName
275 && info->resourceClass == resourceClass) {
276 realInfo = info;
277 session.removeAll(info);
278 }
279 }
280 }
281 }
282 } else {
283 // look for a sessioninfo with matching features.
284 foreach (SessionInfo * info, session) {
285 if (realInfo)
286 break;
287 if (info->resourceName == resourceName
288 && info->resourceClass == resourceClass
289 && sessionInfoWindowTypeMatch(c, info)) {
290 if (wmCommand.isEmpty() || info->wmCommand == wmCommand) {
291 realInfo = info;
292 session.removeAll(info);
293 }
294 }
295 }
296 }
297
298 // Set tabGroupClient for other clients in the same group
299 if (realInfo && realInfo->tabGroup) {
300 foreach (SessionInfo * info, session) {
301 if (!info->tabGroupClient && info->tabGroup == realInfo->tabGroup)
302 info->tabGroupClient = c;
303 }
304 }
305
306 return realInfo;
307}
308
309bool Workspace::sessionInfoWindowTypeMatch(Client* c, SessionInfo* info)
310{
311 if (info->windowType == -2) {
312 // undefined (not really part of NET::WindowType)
313 return !c->isSpecialWindow();
314 }
315 return info->windowType == c->windowType();
316}
317
318static const char* const window_type_names[] = {
319 "Unknown", "Normal" , "Desktop", "Dock", "Toolbar", "Menu", "Dialog",
320 "Override", "TopMenu", "Utility", "Splash"
321};
322// change also the two functions below when adding new entries
323
324const char* Workspace::windowTypeToTxt(NET::WindowType type)
325{
326 if (type >= NET::Unknown && type <= NET::Splash)
327 return window_type_names[ type + 1 ]; // +1 (unknown==-1)
328 if (type == -2) // undefined (not really part of NET::WindowType)
329 return "Undefined";
330 kFatal(1212) << "Unknown Window Type" ;
331 return NULL;
332}
333
334NET::WindowType Workspace::txtToWindowType(const char* txt)
335{
336 for (int i = NET::Unknown;
337 i <= NET::Splash;
338 ++i)
339 if (qstrcmp(txt, window_type_names[ i + 1 ]) == 0) // +1
340 return static_cast< NET::WindowType >(i);
341 return static_cast< NET::WindowType >(-2); // undefined
342}
343
344
345
346
347// KWin's focus stealing prevention causes problems with user interaction
348// during session save, as it prevents possible dialogs from getting focus.
349// Therefore it's temporarily disabled during session saving. Start of
350// session saving can be detected in SessionManager::saveState() above,
351// but Qt doesn't have API for saying when session saved finished (either
352// successfully, or was canceled). Therefore, create another connection
353// to session manager, that will provide this information.
354// Similarly the remember feature of window-specific settings should be disabled
355// during KDE shutdown when windows may move e.g. because of Kicker going away
356// (struts changing). When session saving starts, it can be cancelled, in which
357// case the shutdown_cancelled callback is invoked, or it's a checkpoint that
358// is immediatelly followed by save_complete, or finally it's a shutdown that
359// is immediatelly followed by die callback. So getting save_yourself with shutdown
360// set disables window-specific settings remembering, getting shutdown_cancelled
361// re-enables, otherwise KWin will go away after die.
362static void save_yourself(SmcConn conn_P, SmPointer ptr, int, Bool shutdown, int, Bool)
363{
364 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
365 if (conn_P != session->connection())
366 return;
367 if (shutdown)
368 RuleBook::self()->setUpdatesDisabled(true);
369 SmcSaveYourselfDone(conn_P, True);
370}
371
372static void die(SmcConn conn_P, SmPointer ptr)
373{
374 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
375 if (conn_P != session->connection())
376 return;
377 // session->saveDone(); we will quit anyway
378 session->close();
379}
380
381static void save_complete(SmcConn conn_P, SmPointer ptr)
382{
383 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
384 if (conn_P != session->connection())
385 return;
386 session->saveDone();
387}
388
389static void shutdown_cancelled(SmcConn conn_P, SmPointer ptr)
390{
391 SessionSaveDoneHelper* session = reinterpret_cast< SessionSaveDoneHelper* >(ptr);
392 if (conn_P != session->connection())
393 return;
394 RuleBook::self()->setUpdatesDisabled(false); // re-enable
395 // no need to differentiate between successful finish and cancel
396 session->saveDone();
397}
398
399void SessionSaveDoneHelper::saveDone()
400{
401 Workspace::self()->sessionSaveDone();
402}
403
404SessionSaveDoneHelper::SessionSaveDoneHelper()
405{
406 SmcCallbacks calls;
407 calls.save_yourself.callback = save_yourself;
408 calls.save_yourself.client_data = reinterpret_cast< SmPointer >(this);
409 calls.die.callback = die;
410 calls.die.client_data = reinterpret_cast< SmPointer >(this);
411 calls.save_complete.callback = save_complete;
412 calls.save_complete.client_data = reinterpret_cast< SmPointer >(this);
413 calls.shutdown_cancelled.callback = shutdown_cancelled;
414 calls.shutdown_cancelled.client_data = reinterpret_cast< SmPointer >(this);
415 char* id = NULL;
416 char err[ 11 ];
417 conn = SmcOpenConnection(NULL, 0, 1, 0,
418 SmcSaveYourselfProcMask | SmcDieProcMask | SmcSaveCompleteProcMask
419 | SmcShutdownCancelledProcMask, &calls, NULL, &id, 10, err);
420 if (id != NULL)
421 free(id);
422 if (conn == NULL)
423 return; // no SM
424 // set the required properties, mostly dummy values
425 SmPropValue propvalue[ 5 ];
426 SmProp props[ 5 ];
427 propvalue[ 0 ].length = sizeof(unsigned char);
428 unsigned char value0 = SmRestartNever; // so that this extra SM connection doesn't interfere
429 propvalue[ 0 ].value = &value0;
430 props[ 0 ].name = const_cast< char* >(SmRestartStyleHint);
431 props[ 0 ].type = const_cast< char* >(SmCARD8);
432 props[ 0 ].num_vals = 1;
433 props[ 0 ].vals = &propvalue[ 0 ];
434 struct passwd* entry = getpwuid(geteuid());
435 propvalue[ 1 ].length = entry != NULL ? strlen(entry->pw_name) : 0;
436 propvalue[ 1 ].value = (SmPointer)(entry != NULL ? entry->pw_name : "");
437 props[ 1 ].name = const_cast< char* >(SmUserID);
438 props[ 1 ].type = const_cast< char* >(SmARRAY8);
439 props[ 1 ].num_vals = 1;
440 props[ 1 ].vals = &propvalue[ 1 ];
441 propvalue[ 2 ].length = 0;
442 propvalue[ 2 ].value = (SmPointer)("");
443 props[ 2 ].name = const_cast< char* >(SmRestartCommand);
444 props[ 2 ].type = const_cast< char* >(SmLISTofARRAY8);
445 props[ 2 ].num_vals = 1;
446 props[ 2 ].vals = &propvalue[ 2 ];
447 propvalue[ 3 ].length = strlen("kwinsmhelper");
448 propvalue[ 3 ].value = (SmPointer)"kwinsmhelper";
449 props[ 3 ].name = const_cast< char* >(SmProgram);
450 props[ 3 ].type = const_cast< char* >(SmARRAY8);
451 props[ 3 ].num_vals = 1;
452 props[ 3 ].vals = &propvalue[ 3 ];
453 propvalue[ 4 ].length = 0;
454 propvalue[ 4 ].value = (SmPointer)("");
455 props[ 4 ].name = const_cast< char* >(SmCloneCommand);
456 props[ 4 ].type = const_cast< char* >(SmLISTofARRAY8);
457 props[ 4 ].num_vals = 1;
458 props[ 4 ].vals = &propvalue[ 4 ];
459 SmProp* p[ 5 ] = { &props[ 0 ], &props[ 1 ], &props[ 2 ], &props[ 3 ], &props[ 4 ] };
460 SmcSetProperties(conn, 5, p);
461 notifier = new QSocketNotifier(IceConnectionNumber(SmcGetIceConnection(conn)),
462 QSocketNotifier::Read, this);
463 connect(notifier, SIGNAL(activated(int)), SLOT(processData()));
464}
465
466SessionSaveDoneHelper::~SessionSaveDoneHelper()
467{
468 close();
469}
470
471void SessionSaveDoneHelper::close()
472{
473 if (conn != NULL) {
474 delete notifier;
475 SmcCloseConnection(conn, 0, NULL);
476 }
477 conn = NULL;
478}
479
480void SessionSaveDoneHelper::processData()
481{
482 if (conn != NULL)
483 IceProcessMessages(SmcGetIceConnection(conn), 0, 0);
484}
485
486void Workspace::sessionSaveDone()
487{
488 session_saving = false;
489 //remove sessionInteract flag from all clients
490 foreach (Client * c, clients) {
491 c->setSessionInteract(false);
492 }
493}
494
495} // namespace
496
497#include "sm.moc"
498