diff --git a/ksmserver/CMakeLists.txt b/ksmserver/CMakeLists.txt --- a/ksmserver/CMakeLists.txt +++ b/ksmserver/CMakeLists.txt @@ -22,6 +22,7 @@ legacy.cpp startup.cpp autostart.cpp + logout.cpp shutdown.cpp client.cpp ) @@ -45,8 +46,10 @@ qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${KSCREENLOCKER_DBUS_INTERFACES_DIR}/org.kde.screensaver.xml kscreenlocker_interface ) qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS org.kde.LogoutPrompt.xml logoutprompt_interface) +qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS org.kde.Shutdown.xml shutdown_interface) qt5_add_dbus_adaptor( ksmserver_KDEINIT_SRCS org.kde.KSMServerInterface.xml server.h KSMServer ) +qt5_add_dbus_adaptor( ksmserver_KDEINIT_SRCS org.kde.Shutdown.xml shutdown.h Shutdown) kf5_add_kdeinit_executable( ksmserver ${ksmserver_KDEINIT_SRCS}) diff --git a/ksmserver/shutdown.cpp b/ksmserver/logout.cpp copy from ksmserver/shutdown.cpp copy to ksmserver/logout.cpp --- a/ksmserver/shutdown.cpp +++ b/ksmserver/logout.cpp @@ -77,7 +77,9 @@ #include "client.h" #include + #include "logoutprompt_interface.h" +#include "shutdown_interface.h" #include #include @@ -109,24 +111,16 @@ return state >= Shutdown; } +//this method exists purely for compatibility void KSMServer::shutdown( KWorkSpace::ShutdownConfirm confirm, KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode ) { qCDebug(KSMSERVER) << "Shutdown called with confirm " << confirm << " type " << sdtype << " and mode " << sdmode; - pendingShutdown.stop(); 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; } @@ -158,14 +152,35 @@ break; } } else { - shutdownType = sdtype; - shutdownMode = sdmode; - performLogout(); + OrgKdeShutdownInterface shutdownIface(QStringLiteral("org.kde.Shutdown"), + QStringLiteral("/Shutdown"), + QDBusConnection::sessionBus()); + switch (sdtype) { + case KWorkSpace::ShutdownTypeHalt: + shutdownIface.logoutAndShutdown(); + break; + case KWorkSpace::ShutdownTypeReboot: + shutdownIface.logoutAndReboot(); + break; + case KWorkSpace::ShutdownTypeNone: + Q_FALLTHROUGH(); + default: + shutdownIface.logout(); + break; + } } } void KSMServer::performLogout() { + if( state >= Shutdown ) { // already performing shutdown + return; + } + if (state != Idle) { + QTimer::singleShot(1000, this, &KSMServer::performLogout); + } + state = Shutdown; + // If the logout was confirmed, let's start a powermanagement inhibition. // We store the cookie so we can interrupt it if the logout will be canceled inhibitCookie = Solid::PowerManagement::beginSuppressingSleep(QStringLiteral("Shutting down system")); @@ -186,7 +201,6 @@ QPalette palette; palette.setColor( QApplication::desktop()->backgroundRole(), Qt::black ); QApplication::setPalette(palette); - state = Shutdown; wmPhase1WaitingCount = 0; saveType = saveSession?SmSaveBoth:SmSaveGlobal; #ifndef NO_LEGACY_SESSION_MANAGEMENT @@ -228,11 +242,6 @@ completeShutdownOrCheckpoint(); } -void KSMServer::pendingShutdownTimeout() -{ - shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode ); -} - void KSMServer::saveCurrentSession() { if ( state != Idle ) @@ -397,6 +406,7 @@ } } state = Idle; + emit logoutCancelled(); } void KSMServer::startProtection() diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp --- a/ksmserver/main.cpp +++ b/ksmserver/main.cpp @@ -42,6 +42,7 @@ #include #include "server.h" #include "startup.h" +#include "shutdown.h" #include #include @@ -313,6 +314,7 @@ KSMServer *server = new KSMServer( wm, flags); auto startup = new Startup(server); + new Shutdown(a); // for the KDE-already-running check in startkde KSelectionOwner kde_running( "_KDE_RUNNING", 0 ); diff --git a/ksmserver/org.kde.Shutdown.xml b/ksmserver/org.kde.Shutdown.xml new file mode 100644 --- /dev/null +++ b/ksmserver/org.kde.Shutdown.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/ksmserver/server.h b/ksmserver/server.h --- a/ksmserver/server.h +++ b/ksmserver/server.h @@ -109,16 +109,19 @@ void clientRegistered( const char* previousId ); // public API + void performLogout(); void restoreSession( const QString &sessionName ); void startDefaultSession(); void shutdown( KWorkSpace::ShutdownConfirm confirm, KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode ); Q_SIGNALS: void windowManagerLoaded(); + void logoutCancelled(); public Q_SLOTS: + void cleanUp(); private Q_SLOTS: @@ -129,7 +132,6 @@ void timeoutQuit(); void timeoutWMQuit(); - void pendingShutdownTimeout(); void wmProcessChange(); void defaultLogout(); @@ -187,7 +189,6 @@ void runShutdownScripts(); - void performLogout(); // public dcop interface @@ -228,10 +229,6 @@ int wmPhase1WaitingCount; int saveType; - KWorkSpace::ShutdownType shutdownType; - KWorkSpace::ShutdownMode shutdownMode; - QString bootOption; - bool clean; KSMClient* clientInteracting; QString wm; @@ -242,12 +239,7 @@ QTimer protectionTimer; QTimer restoreTimer; QString xonCommand; - QTimer pendingShutdown; QWidget* logoutEffectWidget; - KWorkSpace::ShutdownConfirm pendingShutdown_confirm; - KWorkSpace::ShutdownType pendingShutdown_sdtype; - KWorkSpace::ShutdownMode pendingShutdown_sdmode; - // sequential startup int appsToStart; int lastAppStarted; diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp --- a/ksmserver/server.cpp +++ b/ksmserver/server.cpp @@ -86,7 +86,6 @@ #include -#include #include #include #include @@ -620,8 +619,6 @@ the_server = this; clean = false; - shutdownType = KWorkSpace::ShutdownTypeNone; - state = Idle; saveSession = false; wmPhase1WaitingCount = 0; @@ -631,8 +628,6 @@ selectWm( windowManager ); - connect(&pendingShutdown, &QTimer::timeout, this, &KSMServer::pendingShutdownTimeout); - only_local = flags.testFlag(InitFlag::OnlyLocal); #ifdef HAVE__ICETRANSNOLISTEN if (only_local) @@ -757,10 +752,6 @@ FreeAuthenticationData(numTransports, authDataEntries); signal(SIGTERM, SIG_DFL); signal(SIGINT, SIG_DFL); - - runShutdownScripts(); - - KDisplayManager().shutdown( shutdownType, shutdownMode, bootOption ); } @@ -1269,28 +1260,3 @@ OrgKdeScreensaverInterface iface(QStringLiteral("org.freedesktop.ScreenSaver"), QStringLiteral("/ScreenSaver"), QDBusConnection::sessionBus()); iface.SwitchUser(); } - -void KSMServer::runShutdownScripts() -{ - const QStringList shutdownFolders = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("plasma-workspace/shutdown"), QStandardPaths::LocateDirectory); - foreach (const QString &shutDownFolder, shutdownFolders) { - QDir dir(shutDownFolder); - if (!dir.exists()) { - continue; - } - - const QStringList entries = dir.entryList(QDir::Files); - foreach (const QString &file, entries) { - // Don't execute backup files - if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QStringLiteral(".bak")) && - (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && - (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) - { - const QString fullPath = dir.absolutePath() + QLatin1Char('/') + file; - - qCDebug(KSMSERVER) << "running shutdown script" << fullPath; - QProcess::execute(fullPath, QStringList()); - } - } - } -} diff --git a/ksmserver/shutdown.h b/ksmserver/shutdown.h new file mode 100644 --- /dev/null +++ b/ksmserver/shutdown.h @@ -0,0 +1,45 @@ +/***************************************************************** +ksmserver - the KDE session management server + +Copyright 2018 David Edmundson + +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. + +******************************************************************/ + +#pragma once + +#include +#include + +class Shutdown: public QObject +{ + Q_OBJECT +public: + Shutdown(QObject *parent = nullptr); + void logout(); + void logoutAndShutdown(); + void logoutAndReboot(); +private Q_SLOTS: + void logoutCancelled(); + void logoutComplete(); +private: + void startLogout(KWorkSpace::ShutdownType shutdownType); + void runShutdownScripts(); + KWorkSpace::ShutdownType m_shutdownType; +}; diff --git a/ksmserver/shutdown.cpp b/ksmserver/shutdown.cpp --- a/ksmserver/shutdown.cpp +++ b/ksmserver/shutdown.cpp @@ -1,693 +1,82 @@ -/***************************************************************** -ksmserver - the KDE session management server +#include "shutdown.h" +#include "shutdownadaptor.h" -Copyright 2000 Matthias Ettrich - -relatively small extensions by Oswald Buddenhagen - -some code taken from the dcopserver (part of the KDE libraries), which is -Copyright 1999 Matthias Ettrich -Copyright 1999 Preston Brown - -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. - -******************************************************************/ - - -#include -#include // HAVE_LIMITS_H -#include - -#include - -#include -#include -#include -#include -#ifdef HAVE_SYS_TIME_H -#include -#endif -#include -#include - -#include -#include -#include -#include -#include -#include -#include - -#ifdef HAVE_LIMITS_H -#include -#endif - -#include -#include -#include -#include +#include +#include +#include #include -#include -#include +#include -#include -#include -#include -#include -#include -#include #include -#include "server.h" -#include "global.h" -#include "client.h" -#include -#include "logoutprompt_interface.h" +#include "server.h" +#include "ksmserver_debug.h" -#include -#include -#include -#include -void KSMServer::logout( int confirm, int sdtype, int sdmode ) +Shutdown::Shutdown(QObject *parent): + QObject(parent) { - // KDE5: remove me - if (sdtype == KWorkSpace::ShutdownTypeLogout) - sdtype = KWorkSpace::ShutdownTypeNone; + new ShutdownAdaptor(this); + QDBusConnection::sessionBus().registerObject(QStringLiteral("/Shutdown"), QStringLiteral("org.kde.Shutdown"), this); - shutdown( (KWorkSpace::ShutdownConfirm)confirm, - (KWorkSpace::ShutdownType)sdtype, - (KWorkSpace::ShutdownMode)sdmode ); -} - -bool KSMServer::canShutdown() -{ - KSharedConfig::Ptr config = KSharedConfig::openConfig(); - config->reparseConfiguration(); // config may have changed in the KControl module - KConfigGroup cg( config, "General"); + //registered as a new service name for easy moving to new process + QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Shutdown")); - return cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown(); + connect(qApp, &QCoreApplication::aboutToQuit, this, &Shutdown::logoutComplete); + connect(KSMServer::self(), &KSMServer::logoutCancelled, this, &Shutdown::logoutCancelled); } -bool KSMServer::isShuttingDown() const +void Shutdown::logout() { - return state >= Shutdown; + startLogout(KWorkSpace::ShutdownTypeNone); } -void KSMServer::shutdown( KWorkSpace::ShutdownConfirm confirm, - KWorkSpace::ShutdownType sdtype, KWorkSpace::ShutdownMode sdmode ) +void Shutdown::logoutAndShutdown() { - qCDebug(KSMSERVER) << "Shutdown called with confirm " << confirm - << " type " << sdtype << " and mode " << sdmode; - pendingShutdown.stop(); - 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; - } - - KSharedConfig::Ptr config = KSharedConfig::openConfig(); - config->reparseConfiguration(); // config may have changed in the KControl module - - KConfigGroup cg( config, "General"); - - bool logoutConfirmed = - (confirm == KWorkSpace::ShutdownConfirmYes) ? false : - (confirm == KWorkSpace::ShutdownConfirmNo) ? true : - !cg.readEntry( "confirmLogout", true ); - - if (!logoutConfirmed) { - OrgKdeLogoutPromptInterface logoutPrompt(QStringLiteral("org.kde.LogoutPrompt"), - QStringLiteral("/LogoutPrompt"), - QDBusConnection::sessionBus()); - switch (sdtype) { - case KWorkSpace::ShutdownTypeHalt: - logoutPrompt.promptShutDown(); - break; - case KWorkSpace::ShutdownTypeReboot: - logoutPrompt.promptReboot(); - break; - case KWorkSpace::ShutdownTypeNone: - Q_FALLTHROUGH(); - default: - logoutPrompt.promptLogout(); - break; - } - } else { - shutdownType = sdtype; - shutdownMode = sdmode; - performLogout(); - } + startLogout(KWorkSpace::ShutdownTypeHalt); } -void KSMServer::performLogout() +void Shutdown::logoutAndReboot() { - // If the logout was confirmed, let's start a powermanagement inhibition. - // We store the cookie so we can interrupt it if the logout will be canceled - inhibitCookie = Solid::PowerManagement::beginSuppressingSleep(QStringLiteral("Shutting down system")); - - // shall we save the session on logout? - KConfigGroup cg(KSharedConfig::openConfig(), "General"); - saveSession = ( cg.readEntry( "loginMode", - QStringLiteral( "restorePreviousLogout" ) ) - == QStringLiteral( "restorePreviousLogout" ) ); - - qCDebug(KSMSERVER) << "saveSession is " << saveSession; - - if ( saveSession ) - sessionGroup = QStringLiteral( "Session: " ) + QString::fromLocal8Bit( SESSION_PREVIOUS_LOGOUT ); - - // Set the real desktop background to black so that exit looks - // clean regardless of what was on "our" desktop. - QPalette palette; - palette.setColor( QApplication::desktop()->backgroundRole(), Qt::black ); - QApplication::setPalette(palette); - state = Shutdown; - wmPhase1WaitingCount = 0; - saveType = saveSession?SmSaveBoth:SmSaveGlobal; -#ifndef NO_LEGACY_SESSION_MANAGEMENT - performLegacySessionSave(); -#endif - startProtection(); - foreach( KSMClient* c, clients ) { - 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; - } - if (wmPhase1WaitingCount > 0) { - foreach( KSMClient* c, clients ) { - if( isWM( c ) ) - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); - } - } else { // no WM, simply start them all - foreach( KSMClient* c, clients ) - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); - } - qCDebug(KSMSERVER) << "clients should be empty, " << clients.isEmpty(); - if ( clients.isEmpty() ) - completeShutdownOrCheckpoint(); + startLogout(KWorkSpace::ShutdownTypeReboot); } -void KSMServer::pendingShutdownTimeout() +void Shutdown::startLogout(KWorkSpace::ShutdownType shutdownType) { - shutdown( pendingShutdown_confirm, pendingShutdown_sdtype, pendingShutdown_sdmode ); + auto ksmserver = KSMServer::self(); + m_shutdownType = shutdownType; + ksmserver->performLogout(); } -void KSMServer::saveCurrentSession() +void Shutdown::logoutCancelled() { - if ( state != Idle ) - return; - - if ( currentSession().isEmpty() || currentSession() == QString::fromLocal8Bit( SESSION_PREVIOUS_LOGOUT ) ) - sessionGroup = QStringLiteral("Session: ") + QString::fromLocal8Bit( SESSION_BY_USER ); - - state = Checkpoint; - wmPhase1WaitingCount = 0; - saveType = SmSaveLocal; - saveSession = true; -#ifndef NO_LEGACY_SESSION_MANAGEMENT - performLegacySessionSave(); -#endif - foreach( KSMClient* c, clients ) { - c->resetState(); - if( isWM( c ) ) - ++wmPhase1WaitingCount; - } - if (wmPhase1WaitingCount > 0) { - foreach( KSMClient* c, clients ) { - if( isWM( c ) ) - SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); - } - } else { - foreach( KSMClient* c, clients ) - SmsSaveYourself( c->connection(), saveType, false, SmInteractStyleNone, false ); - } - if ( clients.isEmpty() ) - completeShutdownOrCheckpoint(); + m_shutdownType = KWorkSpace::ShutdownTypeNone; } -void KSMServer::saveCurrentSessionAs( const QString &session ) -{ - if ( state != Idle ) - return; - sessionGroup = QStringLiteral( "Session: " ) + session; - saveCurrentSession(); +void Shutdown::logoutComplete() { + runShutdownScripts(); + KDisplayManager().shutdown( m_shutdownType, KWorkSpace::ShutdownModeDefault); } -// callbacks -void KSMServer::saveYourselfDone( KSMClient* client, bool success ) +void Shutdown::runShutdownScripts() { - if ( state == Idle ) { - // State saving when it's not shutdown or checkpoint. Probably - // a shutdown was canceled 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 ) { - foreach( KSMClient* c, clients ) - if( !isWM( c )) - SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, - saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, - false ); - } - } -} - -void KSMServer::interactRequest( KSMClient* client, int /*dialogType*/ ) -{ - if ( state == Shutdown || state == ClosingSubSession ) - client->pendingInteraction = true; - else - SmsInteract( client->connection() ); - - handlePendingInteractions(); -} - -void KSMServer::interactDone( KSMClient* client, bool cancelShutdown_ ) -{ - if ( client != clientInteracting ) - return; // should not happen - clientInteracting = nullptr; - 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 ) { - foreach( KSMClient* c, clients ) - if( !isWM( c )) - SmsSaveYourself( c->connection(), saveType, saveType != SmSaveLocal, - saveType != SmSaveLocal ? SmInteractStyleAny : SmInteractStyleNone, - false ); - } - } -} - -void KSMServer::handlePendingInteractions() -{ - if ( clientInteracting ) - return; - - foreach( KSMClient* c, clients ) { - if ( c->pendingInteraction ) { - clientInteracting = c; - c->pendingInteraction = false; - break; - } - } - if ( clientInteracting ) { - endProtection(); - SmsInteract( clientInteracting->connection() ); - } else { - startProtection(); - } -} + const QStringList shutdownFolders = QStandardPaths::locateAll(QStandardPaths::GenericConfigLocation, QStringLiteral("plasma-workspace/shutdown"), QStandardPaths::LocateDirectory); + foreach (const QString &shutDownFolder, shutdownFolders) { + QDir dir(shutDownFolder); + const QStringList entries = dir.entryList(QDir::Files); + foreach (const QString &file, entries) { + // Don't execute backup files + if (!file.endsWith(QLatin1Char('~')) && !file.endsWith(QStringLiteral(".bak")) && + (file[0] != QLatin1Char('%') || !file.endsWith(QLatin1Char('%'))) && + (file[0] != QLatin1Char('#') || !file.endsWith(QLatin1Char('#')))) + { + const QString fullPath = dir.absolutePath() + QLatin1Char('/') + file; -void KSMServer::cancelShutdown( KSMClient* c ) -{ - clientInteracting = nullptr; - qCDebug(KSMSERVER) << state; - if ( state == ClosingSubSession ) { - clientsToKill.clear(); - clientsToSave.clear(); - emit subSessionCloseCanceled(); - } else { - Solid::PowerManagement::stopSuppressingSleep(inhibitCookie); - qCDebug(KSMSERVER) << "Client " << c->program() << " (" << c->clientId() << ") canceled shutdown."; - KNotification::event( QStringLiteral( "cancellogout" ), - i18n( "Logout canceled by '%1'", c->program()), - QPixmap() , nullptr , KNotification::DefaultEvent ); - foreach( KSMClient* c, clients ) { - SmsShutdownCancelled( c->connection() ); - if( c->saveYourselfDone ) { - // Discard also saved state. - QStringList discard = c->discardCommand(); - if( !discard.isEmpty()) - executeCommand( discard ); + qCDebug(KSMSERVER) << "running shutdown script" << fullPath; + QProcess::execute(fullPath, QStringList()); } } } - state = Idle; } -void KSMServer::startProtection() -{ - KSharedConfig::Ptr config = KSharedConfig::openConfig(); - config->reparseConfiguration(); // config may have changed in the KControl module - KConfigGroup cg( config, "General" ); - - int timeout = cg.readEntry( "clientShutdownTimeoutSecs", 15 ) * 1000; - - protectionTimer.setSingleShot( true ); - protectionTimer.start( timeout ); -} - -void KSMServer::endProtection() -{ - protectionTimer.stop(); -} - -/* -Internal protection slot, invoked when clients do not react during -shutdown. -*/ -void KSMServer::protectionTimeout() -{ - if ( ( state != Shutdown && state != Checkpoint && state != ClosingSubSession ) || clientInteracting ) - return; - - foreach( KSMClient* c, clients ) { - if ( !c->saveYourselfDone && !c->waitForPhase2 ) { - qCDebug(KSMSERVER) << "protectionTimeout: client " << c->program() << "(" << c->clientId() << ")"; - c->saveYourselfDone = true; - } - } - completeShutdownOrCheckpoint(); - startProtection(); -} - -void KSMServer::completeShutdownOrCheckpoint() -{ - qCDebug(KSMSERVER) << "completeShutdownOrCheckpoint called"; - if ( state != Shutdown && state != Checkpoint && state != ClosingSubSession ) - return; - - QList pendingClients; - if (state == ClosingSubSession) - pendingClients = clientsToSave; - else - pendingClients = clients; - - foreach( KSMClient* c, pendingClients ) { - if ( !c->saveYourselfDone && !c->waitForPhase2 ) - return; // not done yet - } - - // do phase 2 - bool waitForPhase2 = false; - foreach( KSMClient* c, pendingClients ) { - if ( !c->saveYourselfDone && c->waitForPhase2 ) { - c->waitForPhase2 = false; - SmsSaveYourselfPhase2( c->connection() ); - waitForPhase2 = true; - } - } - if ( waitForPhase2 ) - return; - - if ( saveSession ) - storeSession(); - else - discardSession(); - - qCDebug(KSMSERVER) << "state is " << state; - if ( state == Shutdown ) { - KNotification *n = KNotification::event(QStringLiteral("exitkde"), QString(), QPixmap(), nullptr, KNotification::DefaultEvent); // Plasma says good bye - connect(n, &KNotification::closed, this, &KSMServer::startKilling); - state = WaitingForKNotify; - // https://bugs.kde.org/show_bug.cgi?id=228005 - // if sound is not working for some reason (e.g. no phonon - // backends are installed) the closed() signal never happens - // and logoutSoundFinished() never gets called. Add this timer to make - // sure the shutdown procedure continues even if sound system is broken. - QTimer::singleShot(5000, this, [=]{ - if (state == WaitingForKNotify) { - n->deleteLater(); - startKilling(); - } - }); - createLogoutEffectWidget(); - - } else if ( state == Checkpoint ) { - foreach( KSMClient* c, clients ) { - SmsSaveComplete( c->connection()); - } - state = Idle; - } else { //ClosingSubSession - startKillingSubSession(); - } - -} - -void KSMServer::startKilling() -{ - qCDebug(KSMSERVER) << "Starting killing clients"; - if (state == Killing) { - // we are already killing - return; - } - // kill all clients - state = Killing; - foreach( KSMClient* c, clients ) { - if( isWM( c )) // kill the WM as the last one in order to reduce flicker - continue; - qCDebug(KSMSERVER) << "startKilling: client " << c->program() << "(" << c->clientId() << ")"; - SmsDie( c->connection() ); - } - - qCDebug(KSMSERVER) << " We killed all clients. We have now clients.count()=" << - clients.count() << endl; - completeKilling(); - QTimer::singleShot( 10000, this, &KSMServer::timeoutQuit ); -} - -void KSMServer::completeKilling() -{ - qCDebug(KSMSERVER) << "KSMServer::completeKilling clients.count()=" << - clients.count() << endl; - if( state == Killing ) { - bool wait = false; - foreach( KSMClient* c, clients ) { - if( isWM( c )) - continue; - wait = true; // still waiting for clients to go away - } - if( wait ) - return; - killWM(); - } -} - -void KSMServer::killWM() -{ - if( state != Killing ) - return; - delete logoutEffectWidget; - - qCDebug(KSMSERVER) << "Starting killing WM"; - state = KillingWM; - bool iswm = false; - foreach( KSMClient* c, clients ) { - if( isWM( c )) { - iswm = true; - qCDebug(KSMSERVER) << "killWM: client " << c->program() << "(" << c->clientId() << ")"; - SmsDie( c->connection() ); - } - } - if( iswm ) { - completeKillingWM(); - QTimer::singleShot( 5000, this, &KSMServer::timeoutWMQuit ); - } - else - killingCompleted(); -} - -void KSMServer::completeKillingWM() -{ - qCDebug(KSMSERVER) << "KSMServer::completeKillingWM clients.count()=" << - clients.count() << endl; - if( state == KillingWM ) { - if( clients.isEmpty()) - killingCompleted(); - } -} - -// shutdown is fully complete -void KSMServer::killingCompleted() -{ - qApp->quit(); -} - -void KSMServer::timeoutQuit() -{ - foreach( KSMClient* c, clients ) { - qCWarning(KSMSERVER) << "SmsDie timeout, client " << c->program() << "(" << c->clientId() << ")" ; - } - killWM(); -} - -void KSMServer::timeoutWMQuit() -{ - if( state == KillingWM ) { - qCWarning(KSMSERVER) << "SmsDie WM timeout" ; - } - killingCompleted(); -} - -void KSMServer::createLogoutEffectWidget() -{ -// Ok, this is rather a hack. In order to fade the whole desktop when playing the logout -// sound, killing applications and leaving KDE, create a dummy window that triggers -// the logout fade effect again. - logoutEffectWidget = new QWidget( nullptr, Qt::X11BypassWindowManagerHint ); - logoutEffectWidget->winId(); // workaround for Qt4.3 setWindowRole() assert - logoutEffectWidget->setWindowRole( QStringLiteral( "logouteffect" ) ); - - // Qt doesn't set this on unmanaged windows - //FIXME: or does it? - XChangeProperty( QX11Info::display(), logoutEffectWidget->winId(), - XInternAtom( QX11Info::display(), "WM_WINDOW_ROLE", False ), XA_STRING, 8, PropModeReplace, - (unsigned char *)"logouteffect", strlen( "logouteffect" )); - - logoutEffectWidget->setGeometry( -100, -100, 1, 1 ); - logoutEffectWidget->show(); -} - -void KSMServer::saveSubSession(const QString &name, QStringList saveAndClose, QStringList saveOnly) -{ - if( state != Idle ) { // performing startup - qCDebug(KSMSERVER) << "not idle!" << state; - return; - } - qCDebug(KSMSERVER) << name << saveAndClose << saveOnly; - state = ClosingSubSession; - saveType = SmSaveBoth; //both or local? what oes it mean? - saveSession = true; - sessionGroup = QStringLiteral( "SubSession: " ) + name; - -#ifndef NO_LEGACY_SESSION_MANAGEMENT - //performLegacySessionSave(); FIXME -#endif - - startProtection(); - foreach( KSMClient* c, clients ) { - if (saveAndClose.contains(QString::fromLocal8Bit(c->clientId()))) { - c->resetState(); - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); - clientsToSave << c; - clientsToKill << c; - } else if (saveOnly.contains(QString::fromLocal8Bit(c->clientId()))) { - c->resetState(); - SmsSaveYourself( c->connection(), saveType, - true, SmInteractStyleAny, false ); - clientsToSave << c; - } - } - completeShutdownOrCheckpoint(); -} - -void KSMServer::startKillingSubSession() -{ - qCDebug(KSMSERVER) << "Starting killing clients"; - // kill all clients - state = KillingSubSession; - foreach( KSMClient* c, clientsToKill ) { - qCDebug(KSMSERVER) << "completeShutdown: client " << c->program() << "(" << c->clientId() << ")"; - SmsDie( c->connection() ); - } - - qCDebug(KSMSERVER) << " We killed some clients. We have now clients.count()=" << - clients.count() << endl; - completeKillingSubSession(); - QTimer::singleShot( 10000, this, &KSMServer::signalSubSessionClosed ); -} - -void KSMServer::completeKillingSubSession() -{ - qCDebug(KSMSERVER) << "KSMServer::completeKillingSubSession clients.count()=" << - clients.count() << endl; - if( state == KillingSubSession ) { - bool wait = false; - foreach( KSMClient* c, clientsToKill ) { - if( isWM( c )) - continue; - wait = true; // still waiting for clients to go away - } - if( wait ) - return; - signalSubSessionClosed(); - } -} - -void KSMServer::signalSubSessionClosed() -{ - if( state != KillingSubSession ) - return; - clientsToKill.clear(); - clientsToSave.clear(); - //TODO tell the subSession manager the close request was carried out - //so that plasma can close its stuff - state = Idle; - qCDebug(KSMSERVER) << state; - emit subSessionClosed(); -}