summaryrefslogtreecommitdiffstats
path: root/ksmserver/shutdown.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'ksmserver/shutdown.cpp')
-rw-r--r--ksmserver/shutdown.cpp563
1 files changed, 563 insertions, 0 deletions
diff --git a/ksmserver/shutdown.cpp b/ksmserver/shutdown.cpp
new file mode 100644
index 000000000..1c3906e44
--- /dev/null
+++ b/ksmserver/shutdown.cpp
@@ -0,0 +1,563 @@
+/*****************************************************************
+ksmserver - the KDE session management server
+
+Copyright (C) 2000 Matthias Ettrich <ettrich@kde.org>
+Copyright (C) 2005 Lubos Lunak <l.lunak@kde.org>
+
+relatively small extensions by Oswald Buddenhagen <ob6@inf.tu-dresden.de>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright (c) 1999 Matthias Ettrich <ettrich@kde.org>
+Copyright (c) 1999 Preston Brown <pbrown@kde.org>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+******************************************************************/
+
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#ifdef HAVE_SYS_TIME_H
+#include <sys/time.h>
+#endif
+#include <sys/socket.h>
+#include <sys/un.h>
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <time.h>
+#include <errno.h>
+#include <string.h>
+#include <assert.h>
+
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+
+#include <qfile.h>
+#include <qtextstream.h>
+#include <qdatastream.h>
+#include <qptrstack.h>
+#include <qpushbutton.h>
+#include <qmessagebox.h>
+#include <qguardedptr.h>
+#include <qtimer.h>
+#include <qregexp.h>
+
+#include <klocale.h>
+#include <kglobal.h>
+#include <kconfig.h>
+#include <kstandarddirs.h>
+#include <unistd.h>
+#include <kapplication.h>
+#include <kstaticdeleter.h>
+#include <ktempfile.h>
+#include <kprocess.h>
+#include <dcopclient.h>
+#include <dcopref.h>
+#include <dmctl.h>
+#include <kdebug.h>
+#include <knotifyclient.h>
+
+#include "server.h"
+#include "global.h"
+#include "shutdowndlg.h"
+#include "client.h"
+
+void KSMServer::logout( int confirm, int sdtype, int sdmode )
+{
+ shutdown( (KApplication::ShutdownConfirm)confirm,
+ (KApplication::ShutdownType)sdtype,
+ (KApplication::ShutdownMode)sdmode );
+}
+
+void KSMServer::shutdown( KApplication::ShutdownConfirm confirm,
+ KApplication::ShutdownType sdtype, KApplication::ShutdownMode sdmode )
+{
+ pendingShutdown.stop();
+ if( dialogActive )
+ return;
+ if( state >= Shutdown ) // already performing shutdown
+ return;
+ if( state != Idle ) // performing startup
+ {
+ // perform shutdown as soon as startup is finished, in order to avoid saving partial session
+ if( !pendingShutdown.isActive())
+ {
+ pendingShutdown.start( 1000 );
+ pendingShutdown_confirm = confirm;
+ pendingShutdown_sdtype = sdtype;
+ pendingShutdown_sdmode = sdmode;
+ }
+ return;
+ }
+
+ KConfig *config = KGlobal::config();
+ config->reparseConfiguration(); // config may have changed in the KControl module
+
+ config->setGroup("General" );
+ bool logoutConfirmed =
+ (confirm == KApplication::ShutdownConfirmYes) ? false :
+ (confirm == KApplication::ShutdownConfirmNo) ? true :
+ !config->readBoolEntry( "confirmLogout", true );
+ bool maysd = false;
+ if (config->readBoolEntry( "offerShutdown", true ) && DM().canShutdown())
+ maysd = true;
+ if (!maysd) {
+ if (sdtype != KApplication::ShutdownTypeNone &&
+ sdtype != KApplication::ShutdownTypeDefault &&
+ logoutConfirmed)
+ return; /* unsupported fast shutdown */
+ sdtype = KApplication::ShutdownTypeNone;
+ } else if (sdtype == KApplication::ShutdownTypeDefault)
+ sdtype = (KApplication::ShutdownType)
+ config->readNumEntry( "shutdownType", (int)KApplication::ShutdownTypeNone );
+ if (sdmode == KApplication::ShutdownModeDefault)
+ sdmode = KApplication::ShutdownModeInteractive;
+
+ dialogActive = true;
+ QString bopt;
+ if ( !logoutConfirmed ) {
+ KSMShutdownFeedback::start(); // make the screen gray
+ logoutConfirmed =
+ KSMShutdownDlg::confirmShutdown( maysd, sdtype, bopt );
+ // ###### We can't make the screen remain gray while talking to the apps,
+ // because this prevents interaction ("do you want to save", etc.)
+ // TODO: turn the feedback widget into a list of apps to be closed,
+ // with an indicator of the current status for each.
+ KSMShutdownFeedback::stop(); // make the screen become normal again
+ }
+
+ if ( logoutConfirmed ) {
+
+ shutdownType = sdtype;
+ shutdownMode = sdmode;
+ bootOption = bopt;
+
+ // shall we save the session on logout?
+ saveSession = ( config->readEntry( "loginMode", "restorePreviousLogout" ) == "restorePreviousLogout" );
+
+ if ( saveSession )
+ sessionGroup = QString("Session: ") + SESSION_PREVIOUS_LOGOUT;
+
+ // Set the real desktop background to black so that exit looks
+ // clean regardless of what was on "our" desktop.
+ kapp->desktop()->setBackgroundColor( Qt::black );
+ state = Shutdown;
+ wmPhase1WaitingCount = 0;
+ saveType = saveSession?SmSaveBoth:SmSaveGlobal;
+ performLegacySessionSave();
+ startProtection();
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ c->resetState();
+ // Whoever came with the idea of phase 2 got it backwards
+ // unfortunately. Window manager should be the very first
+ // one saving session data, not the last one, as possible
+ // user interaction during session save may alter
+ // window positions etc.
+ // Moreover, KWin's focus stealing prevention would lead
+ // to undesired effects while session saving (dialogs
+ // wouldn't be activated), so it needs be assured that
+ // KWin will turn it off temporarily before any other
+ // user interaction takes place.
+ // Therefore, make sure the WM finishes its phase 1
+ // before others a chance to change anything.
+ // KWin will check if the session manager is ksmserver,
+ // and if yes it will save in phase 1 instead of phase 2.
+ if( isWM( c )) {
+ ++wmPhase1WaitingCount;
+ SmsSaveYourself( c->connection(), saveType,
+ true, SmInteractStyleAny, false );
+ }
+
+ }
+ if( wmPhase1WaitingCount == 0 ) { // no WM, simply start them all
+ for ( KSMClient* c = clients.first(); c; c = clients.next() )
+ SmsSaveYourself( c->connection(), saveType,
+ true, SmInteractStyleAny, false );
+ }
+ if ( clients.isEmpty() )
+ completeShutdownOrCheckpoint();
+ }
+ dialogActive = false;
+}
+
+void KSMServer::pendingShutdownTimeout()
+{
+ shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode );
+}
+
+void KSMServer::saveCurrentSession()
+{
+ if ( state != Idle || dialogActive )
+ return;
+
+ if ( currentSession().isEmpty() || currentSession() == SESSION_PREVIOUS_LOGOUT )
+ sessionGroup = QString("Session: ") + SESSION_BY_USER;
+
+ state = Checkpoint;
+ wmPhase1WaitingCount = 0;
+ saveType = SmSaveLocal;
+ saveSession = true;
+ performLegacySessionSave();
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ c->resetState();
+ if( isWM( c )) {
+ ++wmPhase1WaitingCount;
+ SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
+ }
+ }
+ if( wmPhase1WaitingCount == 0 ) {
+ for ( KSMClient* c = clients.first(); c; c = clients.next() )
+ SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false );
+ }
+ if ( clients.isEmpty() )
+ completeShutdownOrCheckpoint();
+}
+
+void KSMServer::saveCurrentSessionAs( QString session )
+{
+ if ( state != Idle || dialogActive )
+ return;
+ sessionGroup = "Session: " + session;
+ saveCurrentSession();
+}
+
+// callbacks
+void KSMServer::saveYourselfDone( KSMClient* client, bool success )
+{
+ if ( state == Idle ) {
+ // State saving when it's not shutdown or checkpoint. Probably
+ // a shutdown was cancelled and the client is finished saving
+ // only now. Discard the saved state in order to avoid
+ // the saved data building up.
+ QStringList discard = client->discardCommand();
+ if( !discard.isEmpty())
+ executeCommand( discard );
+ return;
+ }
+ if ( success ) {
+ client->saveYourselfDone = true;
+ completeShutdownOrCheckpoint();
+ } else {
+ // fake success to make KDE's logout not block with broken
+ // apps. A perfect ksmserver would display a warning box at
+ // the very end.
+ client->saveYourselfDone = true;
+ completeShutdownOrCheckpoint();
+ }
+ startProtection();
+ if( isWM( client ) && !client->wasPhase2 && wmPhase1WaitingCount > 0 ) {
+ --wmPhase1WaitingCount;
+ // WM finished its phase1, save the rest
+ if( wmPhase1WaitingCount == 0 ) {
+ for ( KSMClient* c = clients.first(); c; c = clients.next() )
+ if( !isWM( c ))
+ SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
+ saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
+ false );
+ }
+ }
+}
+
+void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ )
+{
+ if ( state == Shutdown )
+ client->pendingInteraction = true;
+ else
+ SmsInteract( client->connection() );
+
+ handlePendingInteractions();
+}
+
+void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ )
+{
+ if ( client != clientInteracting )
+ return; // should not happen
+ clientInteracting = 0;
+ if ( cancelShutdown_ )
+ cancelShutdown( client );
+ else
+ handlePendingInteractions();
+}
+
+
+void KSMServer::phase2Request( KSMClient* client )
+{
+ client->waitForPhase2 = true;
+ client->wasPhase2 = true;
+ completeShutdownOrCheckpoint();
+ if( isWM( client ) && wmPhase1WaitingCount > 0 ) {
+ --wmPhase1WaitingCount;
+ // WM finished its phase1 and requests phase2, save the rest
+ if( wmPhase1WaitingCount == 0 ) {
+ for ( KSMClient* c = clients.first(); c; c = clients.next() )
+ if( !isWM( c ))
+ SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal,
+ saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone,
+ false );
+ }
+ }
+}
+
+void KSMServer::handlePendingInteractions()
+{
+ if ( clientInteracting )
+ return;
+
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if ( c->pendingInteraction ) {
+ clientInteracting = c;
+ c->pendingInteraction = false;
+ break;
+ }
+ }
+ if ( clientInteracting ) {
+ endProtection();
+ SmsInteract( clientInteracting->connection() );
+ } else {
+ startProtection();
+ }
+}
+
+
+void KSMServer::cancelShutdown( KSMClient* c )
+{
+ kdDebug( 1218 ) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown." << endl;
+ KNotifyClient::event( 0, "cancellogout", i18n( "Logout canceled by '%1'" ).arg( c->program()));
+ clientInteracting = 0;
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ SmsShutdownCancelled( c->connection() );
+ if( c->saveYourselfDone ) {
+ // Discard also saved state.
+ QStringList discard = c->discardCommand();
+ if( !discard.isEmpty())
+ executeCommand( discard );
+ }
+ }
+ state = Idle;
+}
+
+void KSMServer::startProtection()
+{
+ protectionTimer.start( 10000, true );
+}
+
+void KSMServer::endProtection()
+{
+ protectionTimer.stop();
+}
+
+/*
+ Internal protection slot, invoked when clients do not react during
+ shutdown.
+ */
+void KSMServer::protectionTimeout()
+{
+ if ( ( state != Shutdown && state != Checkpoint ) || clientInteracting )
+ return;
+
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if ( !c->saveYourselfDone && !c->waitForPhase2 ) {
+ kdDebug( 1218 ) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")" << endl;
+ c->saveYourselfDone = true;
+ }
+ }
+ completeShutdownOrCheckpoint();
+ startProtection();
+}
+
+void KSMServer::completeShutdownOrCheckpoint()
+{
+ if ( state != Shutdown && state != Checkpoint )
+ return;
+
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if ( !c->saveYourselfDone && !c->waitForPhase2 )
+ return; // not done yet
+ }
+
+ // do phase 2
+ bool waitForPhase2 = false;
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if ( !c->saveYourselfDone && c->waitForPhase2 ) {
+ c->waitForPhase2 = false;
+ SmsSaveYourselfPhase2( c->connection() );
+ waitForPhase2 = true;
+ }
+ }
+ if ( waitForPhase2 )
+ return;
+
+ if ( saveSession )
+ storeSession();
+ else
+ discardSession();
+
+ if ( state == Shutdown ) {
+ bool waitForKNotify = true;
+ if( !kapp->dcopClient()->connectDCOPSignal( "knotify", "",
+ "notifySignal(QString,QString,QString,QString,QString,int,int,int,int)",
+ "ksmserver", "notifySlot(QString,QString,QString,QString,QString,int,int,int,int)", false )) {
+ waitForKNotify = false;
+ }
+ if( !kapp->dcopClient()->connectDCOPSignal( "knotify", "",
+ "playingFinished(int,int)",
+ "ksmserver", "logoutSoundFinished(int,int)", false )) {
+ waitForKNotify = false;
+ }
+ // event() can return -1 if KNotifyClient short-circuits and avoids KNotify
+ logoutSoundEvent = KNotifyClient::event( 0, "exitkde" ); // KDE says good bye
+ if( logoutSoundEvent <= 0 )
+ waitForKNotify = false;
+ if( waitForKNotify ) {
+ state = WaitingForKNotify;
+ knotifyTimeoutTimer.start( 20000, true );
+ return;
+ }
+ startKilling();
+ } else if ( state == Checkpoint ) {
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ SmsSaveComplete( c->connection());
+ }
+ state = Idle;
+ }
+}
+
+void KSMServer::startKilling()
+{
+ knotifyTimeoutTimer.stop();
+ // kill all clients
+ state = Killing;
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if( isWM( c )) // kill the WM as the last one in order to reduce flicker
+ continue;
+ kdDebug( 1218 ) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")" << endl;
+ SmsDie( c->connection() );
+ }
+
+ kdDebug( 1218 ) << " We killed all clients. We have now clients.count()=" <<
+ clients.count() << endl;
+ completeKilling();
+ QTimer::singleShot( 10000, this, SLOT( timeoutQuit() ) );
+}
+
+void KSMServer::completeKilling()
+{
+ kdDebug( 1218 ) << "KSMServer::completeKilling clients.count()=" <<
+ clients.count() << endl;
+ if( state == Killing ) {
+ bool wait = false;
+ for( KSMClient* c = clients.first(); c; c = clients.next()) {
+ if( isWM( c ))
+ continue;
+ wait = true; // still waiting for clients to go away
+ }
+ if( wait )
+ return;
+ killWM();
+ }
+}
+
+void KSMServer::killWM()
+{
+ state = KillingWM;
+ bool iswm = false;
+ for ( KSMClient* c = clients.first(); c; c = clients.next() ) {
+ if( isWM( c )) {
+ iswm = true;
+ kdDebug( 1218 ) << "killWM: client " << c->program() << "(" << c->clientId() << ")" << endl;
+ SmsDie( c->connection() );
+ }
+ }
+ if( iswm ) {
+ completeKillingWM();
+ QTimer::singleShot( 5000, this, SLOT( timeoutWMQuit() ) );
+ }
+ else
+ killingCompleted();
+}
+
+void KSMServer::completeKillingWM()
+{
+ kdDebug( 1218 ) << "KSMServer::completeKillingWM clients.count()=" <<
+ clients.count() << endl;
+ if( state == KillingWM ) {
+ if( clients.isEmpty())
+ killingCompleted();
+ }
+}
+
+// shutdown is fully complete
+void KSMServer::killingCompleted()
+{
+ kapp->quit();
+}
+
+// called when KNotify performs notification for logout (not when sound is finished though)
+void KSMServer::notifySlot(QString event ,QString app,QString,QString,QString,int present,int,int,int)
+{
+ if( state != WaitingForKNotify )
+ return;
+ if( event != "exitkde" || app != "ksmserver" )
+ return;
+ if( present & KNotifyClient::Sound ) // logoutSoundFinished() will be called
+ return;
+ startKilling();
+}
+
+// This is stupid. The normal DCOP signal connected to notifySlot() above should be simply
+// emitted in KNotify only after the sound is finished playing.
+void KSMServer::logoutSoundFinished( int event, int )
+{
+ if( state != WaitingForKNotify )
+ return;
+ if( event != logoutSoundEvent )
+ return;
+ startKilling();
+}
+
+void KSMServer::knotifyTimeout()
+{
+ if( state != WaitingForKNotify )
+ return;
+ startKilling();
+}
+
+void KSMServer::timeoutQuit()
+{
+ for (KSMClient *c = clients.first(); c; c = clients.next()) {
+ kdWarning( 1218 ) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" << endl;
+ }
+ killWM();
+}
+
+void KSMServer::timeoutWMQuit()
+{
+ if( state == KillingWM ) {
+ kdWarning( 1218 ) << "SmsDie WM timeout" << endl;
+ }
+ killingCompleted();
+}