Make a branch to make krunner Good Enough For Aaron™.
[kdebase/uwolfer.git] / workspace / ksmserver / shutdown.cpp
blob0b944b63d800730508d0f0e1463439be79aef337
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 void KSMServer::shutdown( KWorkSpace::ShutdownConfirm confirm,
91 KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode )
93 pendingShutdown.stop();
94 if( dialogActive )
95 return;
96 if( state >= Shutdown ) // already performing shutdown
97 return;
98 if( state != Idle ) // performing startup
100 // perform shutdown as soon as startup is finished, in order to avoid saving partial session
101 if( !pendingShutdown.isActive())
103 pendingShutdown.start( 1000 );
104 pendingShutdown_confirm = confirm;
105 pendingShutdown_sdtype = sdtype;
106 pendingShutdown_sdmode = sdmode;
108 return;
111 KSharedConfig::Ptr config = KGlobal::config();
112 config->reparseConfiguration(); // config may have changed in the KControl module
114 KConfigGroup cg( config, "General");
116 bool logoutConfirmed =
117 (confirm == KWorkSpace::ShutdownConfirmYes) ? false :
118 (confirm == KWorkSpace::ShutdownConfirmNo) ? true :
119 !cg.readEntry( "confirmLogout", true );
120 bool maysd = false;
121 if (cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown())
122 maysd = true;
123 if (!maysd) {
124 if (sdtype != KWorkSpace::ShutdownTypeNone &&
125 sdtype != KWorkSpace::ShutdownTypeDefault &&
126 logoutConfirmed)
127 return; /* unsupported fast shutdown */
128 sdtype = KWorkSpace::ShutdownTypeNone;
129 } else if (sdtype == KWorkSpace::ShutdownTypeDefault)
130 sdtype = (KWorkSpace::ShutdownType)
131 cg.readEntry( "shutdownType", (int)KWorkSpace::ShutdownTypeNone );
132 if (sdmode == KWorkSpace::ShutdownModeDefault)
133 sdmode = KWorkSpace::ShutdownModeInteractive;
135 dialogActive = true;
136 QString bopt;
137 if ( !logoutConfirmed ) {
138 KSMShutdownFeedback::start(); // make the screen gray
139 logoutConfirmed =
140 KSMShutdownDlg::confirmShutdown( maysd, sdtype, bopt );
141 // ###### We can't make the screen remain gray while talking to the apps,
142 // because this prevents interaction ("do you want to save", etc.)
143 // TODO: turn the feedback widget into a list of apps to be closed,
144 // with an indicator of the current status for each.
145 KSMShutdownFeedback::stop(); // make the screen become normal again
148 if ( logoutConfirmed ) {
150 shutdownType = sdtype;
151 shutdownMode = sdmode;
152 bootOption = bopt;
154 // shall we save the session on logout?
155 saveSession = ( cg.readEntry( "loginMode", "restorePreviousLogout" ) == "restorePreviousLogout" );
157 if ( saveSession )
158 sessionGroup = QString("Session: ") + SESSION_PREVIOUS_LOGOUT;
160 // Set the real desktop background to black so that exit looks
161 // clean regardless of what was on "our" desktop.
162 QPalette palette;
163 palette.setColor( kapp->desktop()->backgroundRole(), Qt::black );
164 kapp->desktop()->setPalette(palette);
165 state = Shutdown;
166 wmPhase1WaitingCount = 0;
167 saveType = saveSession?SmSaveBoth:SmSaveGlobal;
168 #ifndef NO_LEGACY_SESSION_MANAGEMENT
169 performLegacySessionSave();
170 #endif
171 startProtection();
172 foreach( KSMClient* c, clients ) {
173 c->resetState();
174 // Whoever came with the idea of phase 2 got it backwards
175 // unfortunately. Window manager should be the very first
176 // one saving session data, not the last one, as possible
177 // user interaction during session save may alter
178 // window positions etc.
179 // Moreover, KWin's focus stealing prevention would lead
180 // to undesired effects while session saving (dialogs
181 // wouldn't be activated), so it needs be assured that
182 // KWin will turn it off temporarily before any other
183 // user interaction takes place.
184 // Therefore, make sure the WM finishes its phase 1
185 // before others a chance to change anything.
186 // KWin will check if the session manager is ksmserver,
187 // and if yes it will save in phase 1 instead of phase 2.
188 if( isWM( c )) {
189 ++wmPhase1WaitingCount;
190 SmsSaveYourself( c->connection(), saveType,
191 true, SmInteractStyleAny, false );
195 if( wmPhase1WaitingCount == 0 ) { // no WM, simply start them all
196 foreach( KSMClient* c, clients )
197 SmsSaveYourself( c->connection(), saveType,
198 true, SmInteractStyleAny, false );
200 if ( clients.isEmpty() )
201 completeShutdownOrCheckpoint();
203 dialogActive = false;
206 void KSMServer::pendingShutdownTimeout()
208 shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode );
211 void KSMServer::saveCurrentSession()
213 if ( state != Idle || dialogActive )
214 return;
216 if ( currentSession().isEmpty() || currentSession() == SESSION_PREVIOUS_LOGOUT )
217 sessionGroup = QString("Session: ") + SESSION_BY_USER;
219 state = Checkpoint;
220 wmPhase1WaitingCount = 0;
221 saveType = SmSaveLocal;
222 saveSession = true;
223 #ifndef NO_LEGACY_SESSION_MANAGEMENT
224 performLegacySessionSave();
225 #endif
226 foreach( KSMClient* c, clients ) {
227 c->resetState();
228 if( isWM( c )) {
229 ++wmPhase1WaitingCount;
230 SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
233 if( wmPhase1WaitingCount == 0 ) {
234 foreach( KSMClient* c, clients )
235 SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
237 if ( clients.isEmpty() )
238 completeShutdownOrCheckpoint();
241 void KSMServer::saveCurrentSessionAs( const QString &session )
243 if ( state != Idle || dialogActive )
244 return;
245 sessionGroup = "Session: " + session;
246 saveCurrentSession();
249 // callbacks
250 void KSMServer::saveYourselfDone( KSMClient* client, bool success )
252 if ( state == Idle ) {
253 // State saving when it's not shutdown or checkpoint. Probably
254 // a shutdown was canceled and the client is finished saving
255 // only now. Discard the saved state in order to avoid
256 // the saved data building up.
257 QStringList discard = client->discardCommand();
258 if( !discard.isEmpty())
259 executeCommand( discard );
260 return;
262 if ( success ) {
263 client->saveYourselfDone = true;
264 completeShutdownOrCheckpoint();
265 } else {
266 // fake success to make KDE's logout not block with broken
267 // apps. A perfect ksmserver would display a warning box at
268 // the very end.
269 client->saveYourselfDone = true;
270 completeShutdownOrCheckpoint();
272 startProtection();
273 if( isWM( client ) && !client->wasPhase2 && wmPhase1WaitingCount > 0 ) {
274 --wmPhase1WaitingCount;
275 // WM finished its phase1, save the rest
276 if( wmPhase1WaitingCount == 0 ) {
277 foreach( KSMClient* c, clients )
278 if( !isWM( c ))
279 SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
280 saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
281 false );
286 void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ )
288 if ( state == Shutdown )
289 client->pendingInteraction = true;
290 else
291 SmsInteract( client->connection() );
293 handlePendingInteractions();
296 void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ )
298 if ( client != clientInteracting )
299 return; // should not happen
300 clientInteracting = 0;
301 if ( cancelShutdown_ )
302 cancelShutdown( client );
303 else
304 handlePendingInteractions();
308 void KSMServer::phase2Request( KSMClient* client )
310 client->waitForPhase2 = true;
311 client->wasPhase2 = true;
312 completeShutdownOrCheckpoint();
313 if( isWM( client ) && wmPhase1WaitingCount > 0 ) {
314 --wmPhase1WaitingCount;
315 // WM finished its phase1 and requests phase2, save the rest
316 if( wmPhase1WaitingCount == 0 ) {
317 foreach( KSMClient* c, clients )
318 if( !isWM( c ))
319 SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
320 saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
321 false );
326 void KSMServer::handlePendingInteractions()
328 if ( clientInteracting )
329 return;
331 foreach( KSMClient* c, clients ) {
332 if ( c->pendingInteraction ) {
333 clientInteracting = c;
334 c->pendingInteraction = false;
335 break;
338 if ( clientInteracting ) {
339 endProtection();
340 SmsInteract( clientInteracting->connection() );
341 } else {
342 startProtection();
347 void KSMServer::cancelShutdown( KSMClient* c )
349 kDebug( 1218 ) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown.";
350 KNotification::event( "cancellogout" , i18n( "Logout canceled by '%1'", c->program()),
351 QPixmap() , 0l , KNotification::DefaultEvent );
352 clientInteracting = 0;
353 foreach( KSMClient* c, clients ) {
354 SmsShutdownCancelled( c->connection() );
355 if( c->saveYourselfDone ) {
356 // Discard also saved state.
357 QStringList discard = c->discardCommand();
358 if( !discard.isEmpty())
359 executeCommand( discard );
362 state = Idle;
365 void KSMServer::startProtection()
367 protectionTimer.setSingleShot( true );
368 protectionTimer.start( 10000 );
371 void KSMServer::endProtection()
373 protectionTimer.stop();
377 Internal protection slot, invoked when clients do not react during
378 shutdown.
380 void KSMServer::protectionTimeout()
382 if ( ( state != Shutdown && state != Checkpoint ) || clientInteracting )
383 return;
385 foreach( KSMClient* c, clients ) {
386 if ( !c->saveYourselfDone && !c->waitForPhase2 ) {
387 kDebug( 1218 ) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")";
388 c->saveYourselfDone = true;
391 completeShutdownOrCheckpoint();
392 startProtection();
395 void KSMServer::completeShutdownOrCheckpoint()
397 if ( state != Shutdown && state != Checkpoint )
398 return;
400 foreach( KSMClient* c, clients ) {
401 if ( !c->saveYourselfDone && !c->waitForPhase2 )
402 return; // not done yet
405 // do phase 2
406 bool waitForPhase2 = false;
407 foreach( KSMClient* c, clients ) {
408 if ( !c->saveYourselfDone && c->waitForPhase2 ) {
409 c->waitForPhase2 = false;
410 SmsSaveYourselfPhase2( c->connection() );
411 waitForPhase2 = true;
414 if ( waitForPhase2 )
415 return;
417 if ( saveSession )
418 storeSession();
419 else
420 discardSession();
422 if ( state == Shutdown ) {
423 #ifdef __GNUC__
424 #warning KNotify TODO
425 #endif
426 /* 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 ?
428 knotifySignals = QDBus::sessionBus().findInterface("org.kde.knotify",
429 "/knotify", "org.kde.KNotify" );
430 if( !knotifySignals->isValid())
431 kWarning() << "knotify not running?" ;
434 KNotification *n = KNotification::event( "exitkde" , QString() , QPixmap() , 0l , KNotification::DefaultEvent ); // KDE says good bye
435 connect(n, SIGNAL( closed() ) , this, SLOT(logoutSoundFinished()) );
436 kDebug( 1218 ) << "Starting logout event";
437 state = WaitingForKNotify;
438 createLogoutEffectWidget();
440 } else if ( state == Checkpoint ) {
441 foreach( KSMClient* c, clients ) {
442 SmsSaveComplete( c->connection());
444 state = Idle;
448 void KSMServer::startKilling()
450 kDebug( 1218 ) << "Starting killing clients";
451 // kill all clients
452 state = Killing;
453 foreach( KSMClient* c, clients ) {
454 if( isWM( c )) // kill the WM as the last one in order to reduce flicker
455 continue;
456 kDebug( 1218 ) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")";
457 SmsDie( c->connection() );
460 kDebug( 1218 ) << " We killed all clients. We have now clients.count()=" <<
461 clients.count() << endl;
462 completeKilling();
463 QTimer::singleShot( 10000, this, SLOT( timeoutQuit() ) );
466 void KSMServer::completeKilling()
468 kDebug( 1218 ) << "KSMServer::completeKilling clients.count()=" <<
469 clients.count() << endl;
470 if( state == Killing ) {
471 bool wait = false;
472 foreach( KSMClient* c, clients ) {
473 if( isWM( c ))
474 continue;
475 wait = true; // still waiting for clients to go away
477 if( wait )
478 return;
479 killWM();
483 void KSMServer::killWM()
485 if( state != Killing )
486 return;
487 delete logoutEffectWidget;
488 kDebug( 1218 ) << "Starting killing WM";
489 state = KillingWM;
490 bool iswm = false;
491 foreach( KSMClient* c, clients ) {
492 if( isWM( c )) {
493 iswm = true;
494 kDebug( 1218 ) << "killWM: client " << c->program() << "(" << c->clientId() << ")";
495 SmsDie( c->connection() );
498 if( iswm ) {
499 completeKillingWM();
500 QTimer::singleShot( 5000, this, SLOT( timeoutWMQuit() ) );
502 else
503 killingCompleted();
506 void KSMServer::completeKillingWM()
508 kDebug( 1218 ) << "KSMServer::completeKillingWM clients.count()=" <<
509 clients.count() << endl;
510 if( state == KillingWM ) {
511 if( clients.isEmpty())
512 killingCompleted();
516 // shutdown is fully complete
517 void KSMServer::killingCompleted()
519 kapp->quit();
522 void KSMServer::logoutSoundFinished( )
524 if( state != WaitingForKNotify )
525 return;
526 kDebug( 1218 ) << "Logout event finished";
527 startKilling();
530 void KSMServer::timeoutQuit()
532 foreach( KSMClient* c, clients ) {
533 kWarning( 1218 ) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" ;
535 killWM();
538 void KSMServer::timeoutWMQuit()
540 if( state == KillingWM ) {
541 kWarning( 1218 ) << "SmsDie WM timeout" ;
543 killingCompleted();
546 void KSMServer::createLogoutEffectWidget()
548 // Ok, this is rather a hack. In order to fade the whole desktop when playing the logout
549 // sound, killing applications and leaving KDE, create a dummy window that triggers
550 // the logout fade effect again.
551 logoutEffectWidget = new QWidget( NULL, Qt::X11BypassWindowManagerHint );
552 logoutEffectWidget->winId(); // workaround for Qt4.3 setWindowRole() assert
553 logoutEffectWidget->setWindowRole( "logouteffect" );
554 //#if !(QT_VERSION >= QT_VERSION_CHECK(4, 3, 3) || defined(QT_KDE_QT_COPY))
555 // Qt doesn't set this on unmanaged windows
556 QByteArray appName = qAppName().toLatin1();
557 XClassHint class_hint;
558 class_hint.res_name = appName.data(); // application name
559 class_hint.res_class = const_cast<char *>(QX11Info::appClass()); // application class
560 XSetWMProperties( QX11Info::display(), logoutEffectWidget->winId(),
561 NULL, NULL, NULL, NULL, NULL, NULL, &class_hint );
562 XChangeProperty( QX11Info::display(), logoutEffectWidget->winId(),
563 XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace,
564 (unsigned char *)"logouteffect", strlen( "logouteffect" ));
565 //#endif
566 logoutEffectWidget->setGeometry( -100, -100, 1, 1 );
567 logoutEffectWidget->show();