add more spacing
[personal-kdebase.git] / workspace / ksmserver / shutdown.cpp
blobe53a9a2d76fb5e08b1624d708978a1f1e84f1a2d
1 /*****************************************************************
2 ksmserver - the KDE session management server
4 Copyright 2000 Matthias Ettrich <ettrich@kde.org>
6 relatively small extensions by Oswald Buddenhagen <ob6@inf.tu-dresden.de>
8 some code taken from the dcopserver (part of the KDE libraries), which is
9 Copyright 1999 Matthias Ettrich <ettrich@kde.org>
10 Copyright 1999 Preston Brown <pbrown@kde.org>
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
19 The above copyright notice and this permission notice shall be included in
20 all copies or substantial portions of the Software.
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
26 AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
27 CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 ******************************************************************/
32 #include <config-workspace.h>
33 #include <config-unix.h> // HAVE_LIMITS_H
35 #include <pwd.h>
36 #include <sys/types.h>
37 #include <sys/param.h>
38 #include <sys/stat.h>
39 #ifdef HAVE_SYS_TIME_H
40 #include <sys/time.h>
41 #endif
42 #include <sys/socket.h>
43 #include <sys/un.h>
45 #include <unistd.h>
46 #include <stdlib.h>
47 #include <signal.h>
48 #include <time.h>
49 #include <errno.h>
50 #include <string.h>
51 #include <assert.h>
53 #ifdef HAVE_LIMITS_H
54 #include <limits.h>
55 #endif
57 #include <QPushButton>
58 #include <QTimer>
59 #include <QtDBus/QtDBus>
61 #include <klocale.h>
62 #include <kglobal.h>
63 #include <kconfig.h>
64 #include <kstandarddirs.h>
65 #include <kapplication.h>
66 #include <ktemporaryfile.h>
67 #include <kconfiggroup.h>
68 #include <knotification.h>
69 #include <kdisplaymanager.h>
70 #include "server.h"
71 #include "global.h"
72 #include "client.h"
73 #include "shutdowndlg.h"
76 #include <kdebug.h>
78 #include <QDesktopWidget>
79 #include <QX11Info>
80 #include <X11/Xutil.h>
81 #include <X11/Xatom.h>
83 void KSMServer::logout( int confirm, int sdtype, int sdmode )
85 shutdown( (KWorkSpace::ShutdownConfirm)confirm,
86 (KWorkSpace::ShutdownType)sdtype,
87 (KWorkSpace::ShutdownMode)sdmode );
90 bool KSMServer::canShutdown()
92 KSharedConfig::Ptr config = KGlobal::config();
93 config->reparseConfiguration(); // config may have changed in the KControl module
94 KConfigGroup cg( config, "General");
96 return cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown();
99 void KSMServer::shutdown( KWorkSpace::ShutdownConfirm confirm,
100 KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode )
102 pendingShutdown.stop();
103 if( dialogActive )
104 return;
105 if( state >= Shutdown ) // already performing shutdown
106 return;
107 if( state != Idle ) // performing startup
109 // perform shutdown as soon as startup is finished, in order to avoid saving partial session
110 if( !pendingShutdown.isActive())
112 pendingShutdown.start( 1000 );
113 pendingShutdown_confirm = confirm;
114 pendingShutdown_sdtype = sdtype;
115 pendingShutdown_sdmode = sdmode;
117 return;
120 KSharedConfig::Ptr config = KGlobal::config();
121 config->reparseConfiguration(); // config may have changed in the KControl module
123 KConfigGroup cg( config, "General");
125 bool logoutConfirmed =
126 (confirm == KWorkSpace::ShutdownConfirmYes) ? false :
127 (confirm == KWorkSpace::ShutdownConfirmNo) ? true :
128 !cg.readEntry( "confirmLogout", true );
129 bool maysd = false;
130 if (cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown())
131 maysd = true;
132 if (!maysd) {
133 if (sdtype != KWorkSpace::ShutdownTypeNone &&
134 sdtype != KWorkSpace::ShutdownTypeDefault &&
135 logoutConfirmed)
136 return; /* unsupported fast shutdown */
137 sdtype = KWorkSpace::ShutdownTypeNone;
138 } else if (sdtype == KWorkSpace::ShutdownTypeDefault)
139 sdtype = (KWorkSpace::ShutdownType)
140 cg.readEntry( "shutdownType", (int)KWorkSpace::ShutdownTypeNone );
141 if (sdmode == KWorkSpace::ShutdownModeDefault)
142 sdmode = KWorkSpace::ShutdownModeInteractive;
144 dialogActive = true;
145 QString bopt;
146 if ( !logoutConfirmed ) {
147 KSMShutdownFeedback::start(); // make the screen gray
148 logoutConfirmed =
149 KSMShutdownDlg::confirmShutdown( maysd, sdtype, bopt );
150 // ###### We can't make the screen remain gray while talking to the apps,
151 // because this prevents interaction ("do you want to save", etc.)
152 // TODO: turn the feedback widget into a list of apps to be closed,
153 // with an indicator of the current status for each.
154 KSMShutdownFeedback::stop(); // make the screen become normal again
157 if ( logoutConfirmed ) {
159 shutdownType = sdtype;
160 shutdownMode = sdmode;
161 bootOption = bopt;
163 // shall we save the session on logout?
164 saveSession = ( cg.readEntry( "loginMode", "restorePreviousLogout" ) == "restorePreviousLogout" );
166 if ( saveSession )
167 sessionGroup = QString("Session: ") + SESSION_PREVIOUS_LOGOUT;
169 // Set the real desktop background to black so that exit looks
170 // clean regardless of what was on "our" desktop.
171 QPalette palette;
172 palette.setColor( kapp->desktop()->backgroundRole(), Qt::black );
173 kapp->desktop()->setPalette(palette);
174 state = Shutdown;
175 wmPhase1WaitingCount = 0;
176 saveType = saveSession?SmSaveBoth:SmSaveGlobal;
177 #ifndef NO_LEGACY_SESSION_MANAGEMENT
178 performLegacySessionSave();
179 #endif
180 startProtection();
181 foreach( KSMClient* c, clients ) {
182 c->resetState();
183 // Whoever came with the idea of phase 2 got it backwards
184 // unfortunately. Window manager should be the very first
185 // one saving session data, not the last one, as possible
186 // user interaction during session save may alter
187 // window positions etc.
188 // Moreover, KWin's focus stealing prevention would lead
189 // to undesired effects while session saving (dialogs
190 // wouldn't be activated), so it needs be assured that
191 // KWin will turn it off temporarily before any other
192 // user interaction takes place.
193 // Therefore, make sure the WM finishes its phase 1
194 // before others a chance to change anything.
195 // KWin will check if the session manager is ksmserver,
196 // and if yes it will save in phase 1 instead of phase 2.
197 if( isWM( c )) {
198 ++wmPhase1WaitingCount;
199 SmsSaveYourself( c->connection(), saveType,
200 true, SmInteractStyleAny, false );
204 if( wmPhase1WaitingCount == 0 ) { // no WM, simply start them all
205 foreach( KSMClient* c, clients )
206 SmsSaveYourself( c->connection(), saveType,
207 true, SmInteractStyleAny, false );
209 if ( clients.isEmpty() )
210 completeShutdownOrCheckpoint();
212 dialogActive = false;
215 void KSMServer::pendingShutdownTimeout()
217 shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode );
220 void KSMServer::saveCurrentSession()
222 if ( state != Idle || dialogActive )
223 return;
225 if ( currentSession().isEmpty() || currentSession() == SESSION_PREVIOUS_LOGOUT )
226 sessionGroup = QString("Session: ") + SESSION_BY_USER;
228 state = Checkpoint;
229 wmPhase1WaitingCount = 0;
230 saveType = SmSaveLocal;
231 saveSession = true;
232 #ifndef NO_LEGACY_SESSION_MANAGEMENT
233 performLegacySessionSave();
234 #endif
235 foreach( KSMClient* c, clients ) {
236 c->resetState();
237 if( isWM( c )) {
238 ++wmPhase1WaitingCount;
239 SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
242 if( wmPhase1WaitingCount == 0 ) {
243 foreach( KSMClient* c, clients )
244 SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
246 if ( clients.isEmpty() )
247 completeShutdownOrCheckpoint();
250 void KSMServer::saveCurrentSessionAs( const QString &session )
252 if ( state != Idle || dialogActive )
253 return;
254 sessionGroup = "Session: " + session;
255 saveCurrentSession();
258 // callbacks
259 void KSMServer::saveYourselfDone( KSMClient* client, bool success )
261 if ( state == Idle ) {
262 // State saving when it's not shutdown or checkpoint. Probably
263 // a shutdown was canceled and the client is finished saving
264 // only now. Discard the saved state in order to avoid
265 // the saved data building up.
266 QStringList discard = client->discardCommand();
267 if( !discard.isEmpty())
268 executeCommand( discard );
269 return;
271 if ( success ) {
272 client->saveYourselfDone = true;
273 completeShutdownOrCheckpoint();
274 } else {
275 // fake success to make KDE's logout not block with broken
276 // apps. A perfect ksmserver would display a warning box at
277 // the very end.
278 client->saveYourselfDone = true;
279 completeShutdownOrCheckpoint();
281 startProtection();
282 if( isWM( client ) && !client->wasPhase2 && wmPhase1WaitingCount > 0 ) {
283 --wmPhase1WaitingCount;
284 // WM finished its phase1, save the rest
285 if( wmPhase1WaitingCount == 0 ) {
286 foreach( KSMClient* c, clients )
287 if( !isWM( c ))
288 SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
289 saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
290 false );
295 void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ )
297 if ( state == Shutdown )
298 client->pendingInteraction = true;
299 else
300 SmsInteract( client->connection() );
302 handlePendingInteractions();
305 void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ )
307 if ( client != clientInteracting )
308 return; // should not happen
309 clientInteracting = 0;
310 if ( cancelShutdown_ )
311 cancelShutdown( client );
312 else
313 handlePendingInteractions();
317 void KSMServer::phase2Request( KSMClient* client )
319 client->waitForPhase2 = true;
320 client->wasPhase2 = true;
321 completeShutdownOrCheckpoint();
322 if( isWM( client ) && wmPhase1WaitingCount > 0 ) {
323 --wmPhase1WaitingCount;
324 // WM finished its phase1 and requests phase2, save the rest
325 if( wmPhase1WaitingCount == 0 ) {
326 foreach( KSMClient* c, clients )
327 if( !isWM( c ))
328 SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
329 saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
330 false );
335 void KSMServer::handlePendingInteractions()
337 if ( clientInteracting )
338 return;
340 foreach( KSMClient* c, clients ) {
341 if ( c->pendingInteraction ) {
342 clientInteracting = c;
343 c->pendingInteraction = false;
344 break;
347 if ( clientInteracting ) {
348 endProtection();
349 SmsInteract( clientInteracting->connection() );
350 } else {
351 startProtection();
356 void KSMServer::cancelShutdown( KSMClient* c )
358 kDebug( 1218 ) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown.";
359 KNotification::event( "cancellogout" , i18n( "Logout canceled by '%1'", c->program()),
360 QPixmap() , 0l , KNotification::DefaultEvent );
361 clientInteracting = 0;
362 foreach( KSMClient* c, clients ) {
363 SmsShutdownCancelled( c->connection() );
364 if( c->saveYourselfDone ) {
365 // Discard also saved state.
366 QStringList discard = c->discardCommand();
367 if( !discard.isEmpty())
368 executeCommand( discard );
371 state = Idle;
374 void KSMServer::startProtection()
376 protectionTimer.setSingleShot( true );
377 protectionTimer.start( 10000 );
380 void KSMServer::endProtection()
382 protectionTimer.stop();
386 Internal protection slot, invoked when clients do not react during
387 shutdown.
389 void KSMServer::protectionTimeout()
391 if ( ( state != Shutdown && state != Checkpoint ) || clientInteracting )
392 return;
394 foreach( KSMClient* c, clients ) {
395 if ( !c->saveYourselfDone && !c->waitForPhase2 ) {
396 kDebug( 1218 ) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")";
397 c->saveYourselfDone = true;
400 completeShutdownOrCheckpoint();
401 startProtection();
404 void KSMServer::completeShutdownOrCheckpoint()
406 if ( state != Shutdown && state != Checkpoint )
407 return;
409 foreach( KSMClient* c, clients ) {
410 if ( !c->saveYourselfDone && !c->waitForPhase2 )
411 return; // not done yet
414 // do phase 2
415 bool waitForPhase2 = false;
416 foreach( KSMClient* c, clients ) {
417 if ( !c->saveYourselfDone && c->waitForPhase2 ) {
418 c->waitForPhase2 = false;
419 SmsSaveYourselfPhase2( c->connection() );
420 waitForPhase2 = true;
423 if ( waitForPhase2 )
424 return;
426 if ( saveSession )
427 storeSession();
428 else
429 discardSession();
431 if ( state == Shutdown ) {
432 #ifdef __GNUC__
433 #warning KNotify TODO
434 #endif
435 /* How to check if the daemon is still running. We will not start the knotify daemon just for playing a sound before shutdown. or do wa want that ?
437 knotifySignals = QDBus::sessionBus().findInterface("org.kde.knotify",
438 "/knotify", "org.kde.KNotify" );
439 if( !knotifySignals->isValid())
440 kWarning() << "knotify not running?" ;
443 KNotification *n = KNotification::event( "exitkde" , QString() , QPixmap() , 0l , KNotification::DefaultEvent ); // KDE says good bye
444 connect(n, SIGNAL( closed() ) , this, SLOT(logoutSoundFinished()) );
445 kDebug( 1218 ) << "Starting logout event";
446 state = WaitingForKNotify;
447 createLogoutEffectWidget();
449 } else if ( state == Checkpoint ) {
450 foreach( KSMClient* c, clients ) {
451 SmsSaveComplete( c->connection());
453 state = Idle;
457 void KSMServer::startKilling()
459 kDebug( 1218 ) << "Starting killing clients";
460 // kill all clients
461 state = Killing;
462 foreach( KSMClient* c, clients ) {
463 if( isWM( c )) // kill the WM as the last one in order to reduce flicker
464 continue;
465 kDebug( 1218 ) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")";
466 SmsDie( c->connection() );
469 kDebug( 1218 ) << " We killed all clients. We have now clients.count()=" <<
470 clients.count() << endl;
471 completeKilling();
472 QTimer::singleShot( 10000, this, SLOT( timeoutQuit() ) );
475 void KSMServer::completeKilling()
477 kDebug( 1218 ) << "KSMServer::completeKilling clients.count()=" <<
478 clients.count() << endl;
479 if( state == Killing ) {
480 bool wait = false;
481 foreach( KSMClient* c, clients ) {
482 if( isWM( c ))
483 continue;
484 wait = true; // still waiting for clients to go away
486 if( wait )
487 return;
488 killWM();
492 void KSMServer::killWM()
494 if( state != Killing )
495 return;
496 delete logoutEffectWidget;
497 kDebug( 1218 ) << "Starting killing WM";
498 state = KillingWM;
499 bool iswm = false;
500 foreach( KSMClient* c, clients ) {
501 if( isWM( c )) {
502 iswm = true;
503 kDebug( 1218 ) << "killWM: client " << c->program() << "(" << c->clientId() << ")";
504 SmsDie( c->connection() );
507 if( iswm ) {
508 completeKillingWM();
509 QTimer::singleShot( 5000, this, SLOT( timeoutWMQuit() ) );
511 else
512 killingCompleted();
515 void KSMServer::completeKillingWM()
517 kDebug( 1218 ) << "KSMServer::completeKillingWM clients.count()=" <<
518 clients.count() << endl;
519 if( state == KillingWM ) {
520 if( clients.isEmpty())
521 killingCompleted();
525 // shutdown is fully complete
526 void KSMServer::killingCompleted()
528 kapp->quit();
531 void KSMServer::logoutSoundFinished( )
533 if( state != WaitingForKNotify )
534 return;
535 kDebug( 1218 ) << "Logout event finished";
536 startKilling();
539 void KSMServer::timeoutQuit()
541 foreach( KSMClient* c, clients ) {
542 kWarning( 1218 ) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" ;
544 killWM();
547 void KSMServer::timeoutWMQuit()
549 if( state == KillingWM ) {
550 kWarning( 1218 ) << "SmsDie WM timeout" ;
552 killingCompleted();
555 void KSMServer::createLogoutEffectWidget()
557 // Ok, this is rather a hack. In order to fade the whole desktop when playing the logout
558 // sound, killing applications and leaving KDE, create a dummy window that triggers
559 // the logout fade effect again.
560 logoutEffectWidget = new QWidget( NULL, Qt::X11BypassWindowManagerHint );
561 logoutEffectWidget->winId(); // workaround for Qt4.3 setWindowRole() assert
562 logoutEffectWidget->setWindowRole( "logouteffect" );
563 //#if !(QT_VERSION >= QT_VERSION_CHECK(4, 3, 3) || defined(QT_KDE_QT_COPY))
564 // Qt doesn't set this on unmanaged windows
565 QByteArray appName = qAppName().toLatin1();
566 XClassHint class_hint;
567 class_hint.res_name = appName.data(); // application name
568 class_hint.res_class = const_cast<char *>(QX11Info::appClass()); // application class
569 XSetWMProperties( QX11Info::display(), logoutEffectWidget->winId(),
570 NULL, NULL, NULL, NULL, NULL, NULL, &class_hint );
571 XChangeProperty( QX11Info::display(), logoutEffectWidget->winId(),
572 XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace,
573 (unsigned char *)"logouteffect", strlen( "logouteffect" ));
574 //#endif
575 logoutEffectWidget->setGeometry( -100, -100, 1, 1 );
576 logoutEffectWidget->show();