summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Edmundson <[email protected]>2018-11-08 13:28:54 +0000
committerDavid Edmundson <[email protected]>2018-11-08 13:28:54 +0000
commit008d58cfc60dd1af8ca42bd712526c02c118054d (patch)
tree286a7ab9fa96fc9097bedb1f6ed3a2de49a58369
parent722aff93b5bc4efa33066691290416c890b074ee (diff)
[ksmserver] Split xsession logout and shutdown into separate classes
Summary: This commit splits ksmserver's xsession shutdown logic from performing the actual shutdown and running shutdown scripts and implement proposed org.kde.Shutdown interface. Intended longer term target is to move this to a separate executable. KSMServer's existing logout dbus method still exists for compatibility forwarding to the new interface. There are 2 minor behavioural changes. The shutdownMode property (which doesn't seem to do anything and is not exposed in our UI) is not kept. If you shutdown /whilst/ starting up somehow, previously we delayed showing the logout prompt, we now delay performing the actual logout. Test Plan: Logged out / shut down using the old API Logged out / shut down using the new DBus API Reviewers: #plasma, apol, romangg Reviewed By: #plasma, apol, romangg Subscribers: romangg, apol, plasma-devel Tags: #plasma Differential Revision: https://phabricator.kde.org/D16277
-rw-r--r--ksmserver/CMakeLists.txt3
-rw-r--r--ksmserver/logout.cpp703
-rw-r--r--ksmserver/main.cpp2
-rw-r--r--ksmserver/org.kde.Shutdown.xml8
-rw-r--r--ksmserver/server.cpp34
-rw-r--r--ksmserver/server.h14
-rw-r--r--ksmserver/shutdown.cpp701
-rw-r--r--ksmserver/shutdown.h45
8 files changed, 809 insertions, 701 deletions
diff --git a/ksmserver/CMakeLists.txt b/ksmserver/CMakeLists.txt
index bd9da78..ffe74f7 100644
--- a/ksmserver/CMakeLists.txt
+++ b/ksmserver/CMakeLists.txt
@@ -22,6 +22,7 @@ set(ksmserver_KDEINIT_SRCS
legacy.cpp
startup.cpp
autostart.cpp
+ logout.cpp
shutdown.cpp
client.cpp
)
@@ -45,8 +46,10 @@ qt5_add_dbus_interface( ksmserver_KDEINIT_SRCS ${klauncher_xml} klauncher_interf
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/logout.cpp b/ksmserver/logout.cpp
new file mode 100644
index 0000000..6971a6b
--- /dev/null
+++ b/ksmserver/logout.cpp
@@ -0,0 +1,703 @@
+/*****************************************************************
+ksmserver - the KDE session management server
+
+Copyright 2000 Matthias Ettrich <[email protected]>
+
+relatively small extensions by Oswald Buddenhagen <[email protected]>
+
+some code taken from the dcopserver (part of the KDE libraries), which is
+Copyright 1999 Matthias Ettrich <[email protected]>
+Copyright 1999 Preston Brown <[email protected]>
+
+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 <config-workspace.h>
+#include <config-unix.h> // HAVE_LIMITS_H
+#include <config-ksmserver.h>
+
+#include <ksmserver_debug.h>
+
+#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 <QApplication>
+#include <QPushButton>
+#include <QTimer>
+#include <QFile>
+#include <QProcess>
+#include <QFutureWatcher>
+#include <QtConcurrentRun>
+
+#include <KConfig>
+#include <KSharedConfig>
+#include <KConfigGroup>
+#include <KLocalizedString>
+#include <KUserTimestamp>
+#include <KNotification>
+#include <kdisplaymanager.h>
+#include "server.h"
+#include "global.h"
+#include "client.h"
+
+#include <solid/powermanagement.h>
+
+#include "logoutprompt_interface.h"
+#include "shutdown_interface.h"
+
+#include <QDesktopWidget>
+#include <QX11Info>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+
+void KSMServer::logout( int confirm, int sdtype, int sdmode )
+{
+ // KDE5: remove me
+ if (sdtype == KWorkSpace::ShutdownTypeLogout)
+ sdtype = KWorkSpace::ShutdownTypeNone;
+
+ 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");
+
+ return cg.readEntry( "offerShutdown", true ) && KDisplayManager().canShutdown();
+}
+
+bool KSMServer::isShuttingDown() const
+{
+ 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;
+ if( state >= Shutdown ) // already performing shutdown
+ return;
+ if( state != Idle ) // performing startup
+ {
+ 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 {
+ 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"));
+
+ // 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);
+ 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();
+}
+
+void KSMServer::saveCurrentSession()
+{
+ 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();
+}
+
+void KSMServer::saveCurrentSessionAs( const QString &session )
+{
+ if ( state != Idle )
+ return;
+ sessionGroup = QStringLiteral( "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 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();
+ }
+}
+
+
+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 );
+ }
+ }
+ }
+ state = Idle;
+ emit logoutCancelled();
+}
+
+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<KSMClient*> 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();
+}
diff --git a/ksmserver/main.cpp b/ksmserver/main.cpp
index 37d7afb..0467a5d 100644
--- a/ksmserver/main.cpp
+++ b/ksmserver/main.cpp
@@ -42,6 +42,7 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <ksmserver_debug.h>
#include "server.h"
#include "startup.h"
+#include "shutdown.h"
#include <QX11Info>
#include <QApplication>
@@ -313,6 +314,7 @@ extern "C" Q_DECL_EXPORT int kdemain( int argc, char* argv[] )
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
index 0000000..7184f73
--- /dev/null
+++ b/ksmserver/org.kde.Shutdown.xml
@@ -0,0 +1,8 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+ <interface name="org.kde.Shutdown">
+ <method name="logout"/>
+ <method name="logoutAndShutdown"/>
+ <method name="logoutAndReboot"/>
+</interface>
+</node>
diff --git a/ksmserver/server.cpp b/ksmserver/server.cpp
index 971a1ed..d6a5a47 100644
--- a/ksmserver/server.cpp
+++ b/ksmserver/server.cpp
@@ -86,7 +86,6 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
#include <KScreenLocker/KsldApp>
-#include <kdisplaymanager.h>
#include <QX11Info>
#include <krandom.h>
#include <klauncher_interface.h>
@@ -620,8 +619,6 @@ KSMServer::KSMServer( const QString& windowManager, InitFlags flags )
the_server = this;
clean = false;
- shutdownType = KWorkSpace::ShutdownTypeNone;
-
state = Idle;
saveSession = false;
wmPhase1WaitingCount = 0;
@@ -631,8 +628,6 @@ KSMServer::KSMServer( const QString& windowManager, InitFlags flags )
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 @@ void KSMServer::cleanUp()
FreeAuthenticationData(numTransports, authDataEntries);
signal(SIGTERM, SIG_DFL);
signal(SIGINT, SIG_DFL);
-
- runShutdownScripts();
-
- KDisplayManager().shutdown( shutdownType, shutdownMode, bootOption );
}
@@ -1269,28 +1260,3 @@ void KSMServer::openSwitchUserDialog()
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/server.h b/ksmserver/server.h
index e9bc7b5..47d5247 100644
--- a/ksmserver/server.h
+++ b/ksmserver/server.h
@@ -109,6 +109,7 @@ public:
void clientRegistered( const char* previousId );
// public API
+ void performLogout();
void restoreSession( const QString &sessionName );
void startDefaultSession();
void shutdown( KWorkSpace::ShutdownConfirm confirm,
@@ -117,8 +118,10 @@ public:
Q_SIGNALS:
void windowManagerLoaded();
+ void logoutCancelled();
public Q_SLOTS:
+
void cleanUp();
private Q_SLOTS:
@@ -129,7 +132,6 @@ private Q_SLOTS:
void timeoutQuit();
void timeoutWMQuit();
- void pendingShutdownTimeout();
void wmProcessChange();
void defaultLogout();
@@ -187,7 +189,6 @@ private:
void runShutdownScripts();
- void performLogout();
// public dcop interface
@@ -228,10 +229,6 @@ private:
int wmPhase1WaitingCount;
int saveType;
- KWorkSpace::ShutdownType shutdownType;
- KWorkSpace::ShutdownMode shutdownMode;
- QString bootOption;
-
bool clean;
KSMClient* clientInteracting;
QString wm;
@@ -242,12 +239,7 @@ private:
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/shutdown.cpp b/ksmserver/shutdown.cpp
index 27942e5..923c4ad 100644
--- 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 <[email protected]>
-
-relatively small extensions by Oswald Buddenhagen <[email protected]>
-
-some code taken from the dcopserver (part of the KDE libraries), which is
-Copyright 1999 Matthias Ettrich <[email protected]>
-Copyright 1999 Preston Brown <[email protected]>
-
-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 <config-workspace.h>
-#include <config-unix.h> // HAVE_LIMITS_H
-#include <config-ksmserver.h>
-
-#include <ksmserver_debug.h>
-
-#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 <QApplication>
-#include <QPushButton>
-#include <QTimer>
-#include <QFile>
+#include <QDBusConnection>
+#include <QCoreApplication>
+#include <QDir>
#include <QProcess>
-#include <QFutureWatcher>
-#include <QtConcurrentRun>
+#include <QStandardPaths>
-#include <KConfig>
-#include <KSharedConfig>
-#include <KConfigGroup>
-#include <KLocalizedString>
-#include <KUserTimestamp>
-#include <KNotification>
#include <kdisplaymanager.h>
-#include "server.h"
-#include "global.h"
-#include "client.h"
-#include <solid/powermanagement.h>
-#include "logoutprompt_interface.h"
+#include "server.h"
+#include "ksmserver_debug.h"
-#include <QDesktopWidget>
-#include <QX11Info>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-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<KSMClient*> 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();
-}
diff --git a/ksmserver/shutdown.h b/ksmserver/shutdown.h
new file mode 100644
index 0000000..3e66267
--- /dev/null
+++ b/ksmserver/shutdown.h
@@ -0,0 +1,45 @@
+/*****************************************************************
+ksmserver - the KDE session management server
+
+Copyright 2018 David Edmundson <[email protected]>
+
+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 <QObject>
+#include <kworkspace.h>
+
+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;
+};