From 96a595f1b8602838f065f312bd5b6ae24f8d6ac1 Mon Sep 17 00:00:00 2001 From: David Edmundson Date: Tue, 30 Oct 2018 16:44:31 +0000 Subject: [PATCH] [ksmserver] Rewrite Startup Summary: The code is effectively 3 startup phases each of which has some parallel methods that need to be complete before moving onto the next phase. The old code had a tonne of methods all of which tracking what state we're in and trying to start the next method in the chain handling things out of order. This simplifies everything into 3 composite kjobs. A lot more classes, but each one tiny with the flow more readable. Code should be effectively the same. The startup sound is moved ever so slightly earlier to be when phase 2 starts rather than just after kcminit2 finishes (midway through the old phase 2), but no significant behavioural changes here. Test Plan: Logged in with session restore and without Reviewers: #plasma, apol Reviewed By: apol Subscribers: apol, broulik, anthonyfieroni, plasma-devel Tags: #plasma Differential Revision: https://phabricator.kde.org/D16231 --- ksmserver/server.h | 1 + ksmserver/startup.cpp | 436 +++++++++++++++++------------------------- ksmserver/startup.h | 72 ++++--- 3 files changed, 224 insertions(+), 285 deletions(-) diff --git a/ksmserver/server.h b/ksmserver/server.h index 3ed05ad20..e9bc7b5e8 100644 --- a/ksmserver/server.h +++ b/ksmserver/server.h @@ -267,6 +267,7 @@ private: int sockets[2]; friend bool readFromPipe(int pipe); + friend class RestoreSessionJob; friend class Startup; }; diff --git a/ksmserver/startup.cpp b/ksmserver/startup.cpp index 3eaeae104..10f6677da 100644 --- a/ksmserver/startup.cpp +++ b/ksmserver/startup.cpp @@ -3,6 +3,8 @@ ksmserver - the KDE session management server Copyright 2000 Matthias Ettrich Copyright 2005 Lubos Lunak +Copyright 2018 David Edmundson + relatively small extensions by Oswald Buddenhagen @@ -32,69 +34,99 @@ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "startup.h" #include "server.h" -#include -#include #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 "kcminit_interface.h" +#include + +#include +#include +#include +#include +#include +#include +#include -#include -#include -#include -#include #include #include #include -#include -#include -#include -#include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include +#include -#include "global.h" -#include "server.h" -#include "client.h" +class Phase: public KCompositeJob +{ +Q_OBJECT +public: + Phase(QObject *parent): + KCompositeJob(parent) + {} + + bool addSubjob(KJob *job) override { + bool rc = KCompositeJob::addSubjob(job); + job->start(); + return rc; + } -#include + void slotResult(KJob *job) override { + KCompositeJob::slotResult(job); + if (!hasSubjobs()) { + emitResult(); + } + } +}; -//#include "kdesktop_interface.h" -#include -#include -#include "kcminit_interface.h" +class StartupPhase0: public Phase +{ +Q_OBJECT +public: + StartupPhase0(QObject *parent) : Phase(parent) + {} + void start() override { + qCDebug(KSMSERVER) << "Phase 0"; + addSubjob(new AutoStartAppsJob(0)); + addSubjob(new KCMInitJob(1)); + } +}; -//#define KSMSERVER_STARTUP_DEBUG1 +class StartupPhase1: public Phase +{ +Q_OBJECT +public: + StartupPhase1(QObject *parent) : Phase(parent) + {} + void start() override { + qCDebug(KSMSERVER) << "Phase 1"; + addSubjob(new AutoStartAppsJob(1)); + } +}; -#ifdef KSMSERVER_STARTUP_DEBUG1 -static QTime t; -#endif +class StartupPhase2: public Phase +{ +Q_OBJECT +public: + StartupPhase2(QObject *parent) : Phase(parent) + {} + void runUserAutostart(); + bool migrateKDE4Autostart(const QString &folder); + + void start() override { + qCDebug(KSMSERVER) << "Phase 2"; + addSubjob(new AutoStartAppsJob(2)); + addSubjob(new KDEDInitJob()); + addSubjob(new KCMInitJob(2)); + runUserAutostart(); + } +}; // Put the notification in its own thread as it can happen that // PulseAudio will start initializing with this, so let's not @@ -152,157 +184,121 @@ class NotificationThread : public QThread Startup::Startup(KSMServer *parent): QObject(parent), - ksmserver(parent), - state(Waiting) + ksmserver(parent) { - connect(ksmserver, &KSMServer::windowManagerLoaded, this, &Startup::autoStart0); + auto phase0 = new StartupPhase0(this); + auto phase1 = new StartupPhase1(this); + auto phase2 = new StartupPhase2(this); + auto restoreSession = new RestoreSessionJob(ksmserver); + + connect(ksmserver, &KSMServer::windowManagerLoaded, phase0, &KJob::start); + connect(phase0, &KJob::finished, phase1, &KJob::start); + + connect(phase1, &KJob::finished, this, [=]() { + ksmserver->setupShortcuts(); // done only here, because it needs kglobalaccel :-/ + }); + + if (ksmserver->defaultSession()) { + connect(phase1, &KJob::finished, phase2, &KJob::start); + } else { + connect(phase1, &KJob::finished, restoreSession, &KJob::start); + connect(restoreSession, &KJob::finished, phase2, &KJob::start); + } + connect(phase1, &KJob::finished, this, []() { + NotificationThread *loginSound = new NotificationThread(); + connect(loginSound, &NotificationThread::finished, loginSound, &NotificationThread::deleteLater); + loginSound->start();}); + connect(phase2, &KJob::finished, this, &Startup::finishStartup); } -void Startup::autoStart0() +void Startup::upAndRunning( const QString& msg ) { - disconnect(ksmserver, &KSMServer::windowManagerLoaded, this, &Startup::autoStart0); - state = AutoStart0; -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER) << t.elapsed(); -#endif + QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), + QStringLiteral("/KSplash"), + QStringLiteral("org.kde.KSplash"), + QStringLiteral("setStage")); + ksplashProgressMessage.setArguments(QList() << msg); + QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); +} - autoStart(0); +void Startup::finishStartup() +{ + qCDebug(KSMSERVER) << "Finished"; + ksmserver->state = KSMServer::Idle; + ksmserver->setupXIOErrorHandler(); + upAndRunning(QStringLiteral("ready")); } -void Startup::autoStart0Done() +KCMInitJob::KCMInitJob(int phase) + :m_phase(phase) { - if( state != AutoStart0 ) - return; - qCDebug(KSMSERVER) << "Autostart 0 done"; -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER) << t.elapsed(); -#endif +} - state = KcmInitPhase1; - kcminitSignals = new QDBusInterface( QStringLiteral( "org.kde.kcminit"), +void KCMInitJob::start() { + //FIXME - replace all this with just a DBus call with a timeout and make kcminit delay the reply till it's done + + auto kcminitSignals = new QDBusInterface( QStringLiteral( "org.kde.kcminit"), QStringLiteral( "/kcminit" ), QStringLiteral( "org.kde.KCMInit" ), QDBusConnection::sessionBus(), this ); if( !kcminitSignals->isValid()) { qCWarning(KSMSERVER) << "kcminit not running? If we are running with mobile profile or in another platform other than X11 this is normal."; - delete kcminitSignals; - kcminitSignals = nullptr; - QTimer::singleShot(0, this, &Startup::kcmPhase1Done); + QTimer::singleShot(0, this, &KCMInitJob::done); return; } - connect( kcminitSignals, SIGNAL(phase1Done()), SLOT(kcmPhase1Done())); - QTimer::singleShot( 10000, this, &Startup::kcmPhase1Timeout); // protection + if (m_phase == 1) { + connect( kcminitSignals, SIGNAL(phase1Done()), this, SLOT(done())); + } else { + connect( kcminitSignals, SIGNAL(phase2Done()), this, SLOT(done())); + } + QTimer::singleShot( 10000, this, &KCMInitJob::done); // protection org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus()); - kcminit.runPhase1(); -} -void Startup::kcmPhase1Done() -{ - if( state != KcmInitPhase1 ) - return; - qCDebug(KSMSERVER) << "Kcminit phase 1 done"; - if (kcminitSignals) { - disconnect( kcminitSignals, SIGNAL(phase1Done()), this, SLOT(kcmPhase1Done())); + if (m_phase == 1) { + kcminit.runPhase1(); + } else { + kcminit.runPhase2(); } - autoStart1(); -} - -void Startup::kcmPhase1Timeout() -{ - if( state != KcmInitPhase1 ) - return; - qCDebug(KSMSERVER) << "Kcminit phase 1 timeout"; - kcmPhase1Done(); } -void Startup::autoStart1() +void KCMInitJob::done() { - if( state != KcmInitPhase1 ) - return; - state = AutoStart1; -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER)<< t.elapsed(); -#endif - autoStart(1); + emitResult(); } -void Startup::autoStart1Done() +KDEDInitJob::KDEDInitJob() { - if( state != AutoStart1 ) - return; - qCDebug(KSMSERVER) << "Autostart 1 done"; - ksmserver->setupShortcuts(); // done only here, because it needs kglobalaccel :-/ - ksmserver->lastAppStarted = 0; - ksmserver->lastIdStarted.clear(); - ksmserver->state = KSMServer::Restoring; -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER)<< t.elapsed(); -#endif - if( ksmserver->defaultSession()) { - autoStart2(); - return; - } - ksmserver->tryRestoreNext(); - connect(ksmserver, &KSMServer::sessionRestored, this, &Startup::autoStart2); } -void Startup::autoStart2() -{ - if( ksmserver->state != KSMServer::Restoring ) - return; - ksmserver->startupDone(); - - state = FinishingStartup; -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER)<< t.elapsed(); -#endif - waitAutoStart2 = true; - waitKcmInit2 = true; - autoStart(2); - +void KDEDInitJob::start() { + qCDebug(KSMSERVER()); QDBusInterface kded( QStringLiteral( "org.kde.kded5" ), QStringLiteral( "/kded" ), QStringLiteral( "org.kde.kded5" ) ); auto pending = kded.asyncCall( QStringLiteral( "loadSecondPhase" ) ); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Startup::secondKDEDPhaseLoaded); - QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); - runUserAutostart(); - - if (kcminitSignals) { - connect( kcminitSignals, SIGNAL(phase2Done()), SLOT(kcmPhase2Done())); - QTimer::singleShot( 10000, this, &Startup::kcmPhase2Timeout); // protection - org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), - QStringLiteral("/kcminit"), - QDBusConnection::sessionBus()); - kcminit.runPhase2(); - } else { - QTimer::singleShot(0, this, &Startup::kcmPhase2Done); - } + connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {emitResult();}); + connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater); } -void Startup::secondKDEDPhaseLoaded() -{ - -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER)<< "kded" << t.elapsed(); -#endif +RestoreSessionJob::RestoreSessionJob(KSMServer *server): KJob(), + m_ksmserver(server) +{} - if( !ksmserver->defaultSession()) - ksmserver->restoreLegacySession(KSharedConfig::openConfig().data()); - - qCDebug(KSMSERVER) << "Starting notification thread"; - NotificationThread *loginSound = new NotificationThread(); - // Delete the thread when finished - connect(loginSound, &NotificationThread::finished, loginSound, &NotificationThread::deleteLater); - loginSound->start(); +void RestoreSessionJob::start() +{ + m_ksmserver->lastAppStarted = 0; + m_ksmserver->lastIdStarted.clear(); + m_ksmserver->state = KSMServer::Restoring; + connect(m_ksmserver, &KSMServer::sessionRestored, this, [this]() {emitResult();}); + m_ksmserver->tryRestoreNext(); } -void Startup::runUserAutostart() +void StartupPhase2::runUserAutostart() { // Now let's execute the scripts in the KDE-specific autostart-scripts folder. const QString autostartFolder = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QDir::separator() + QStringLiteral("autostart-scripts"); @@ -338,7 +334,7 @@ void Startup::runUserAutostart() } } -bool Startup::migrateKDE4Autostart(const QString &autostartFolder) +bool StartupPhase2::migrateKDE4Autostart(const QString &autostartFolder) { // Migrate user autostart from kde4 Kdelibs4Migration migration; @@ -374,110 +370,38 @@ bool Startup::migrateKDE4Autostart(const QString &autostartFolder) return true; } -void Startup::autoStart2Done() -{ - if( state != FinishingStartup ) - return; - qCDebug(KSMSERVER) << "Autostart 2 done"; - waitAutoStart2 = false; - finishStartup(); -} - -void Startup::kcmPhase2Done() -{ - if( state != FinishingStartup ) - return; - qCDebug(KSMSERVER) << "Kcminit phase 2 done"; - if (kcminitSignals) { - disconnect( kcminitSignals, SIGNAL(phase2Done()), this, SLOT(kcmPhase2Done())); - delete kcminitSignals; - kcminitSignals = nullptr; - } - waitKcmInit2 = false; - finishStartup(); -} - -void Startup::kcmPhase2Timeout() +AutoStartAppsJob::AutoStartAppsJob(int phase) { - if( !waitKcmInit2 ) - return; - qCDebug(KSMSERVER) << "Kcminit phase 2 timeout"; - kcmPhase2Done(); -} - -void Startup::finishStartup() -{ - if( state != FinishingStartup ) - return; - if( waitAutoStart2 || waitKcmInit2 ) - return; - - upAndRunning( QStringLiteral( "ready" ) ); -#ifdef KSMSERVER_STARTUP_DEBUG1 - qCDebug(KSMSERVER)<< t.elapsed(); -#endif - - state = Waiting; - ksmserver->setupXIOErrorHandler(); // From now on handle X errors as normal shutdown. -} - -void Startup::upAndRunning( const QString& msg ) -{ - QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), - QStringLiteral("/KSplash"), - QStringLiteral("org.kde.KSplash"), - QStringLiteral("setStage")); - ksplashProgressMessage.setArguments(QList() << msg); - QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); -} - - -void Startup::autoStart(int phase) -{ - if (m_autoStart.phase() >= phase) { - return; - } + m_autoStart.loadAutoStartList(); //FIXME, share this between jobs m_autoStart.setPhase(phase); - if (phase == 0) { - m_autoStart.loadAutoStartList(); - } - QTimer::singleShot(0, this, &Startup::slotAutoStart); } -void Startup::slotAutoStart() -{ - do { - QString serviceName = m_autoStart.startService(); - if (serviceName.isEmpty()) { - // Done - if (!m_autoStart.phaseDone()) { - m_autoStart.setPhaseDone(); - switch (m_autoStart.phase()) { - case 0: - autoStart0Done(); - break; - case 1: - autoStart1Done(); - break; - case 2: - autoStart2Done(); - break; +void AutoStartAppsJob::start() { + qCDebug(KSMSERVER()); + + QTimer::singleShot(0, this, [=]() { + do { + QString serviceName = m_autoStart.startService(); + if (serviceName.isEmpty()) { + // Done + if (!m_autoStart.phaseDone()) { + m_autoStart.setPhaseDone(); } + emitResult(); + return; } - return; - } - KService service(serviceName); - auto arguments = KIO::DesktopExecParser(service, QList()).resultingArguments(); - if (arguments.isEmpty()) { - qCWarning(KSMSERVER) << "failed to parse" << serviceName << "for autostart"; - continue; - } - qCInfo(KSMSERVER) << "Starting autostart service " << serviceName << arguments; - auto program = arguments.takeFirst(); - if (!QProcess::startDetached(program, arguments)) - qCWarning(KSMSERVER) << "could not start" << serviceName << ":" << program << arguments; - } while (true); - // Loop till we find a service that we can start. + KService service(serviceName); + auto arguments = KIO::DesktopExecParser(service, QList()).resultingArguments(); + if (arguments.isEmpty()) { + qCWarning(KSMSERVER) << "failed to parse" << serviceName << "for autostart"; + continue; + } + qCInfo(KSMSERVER) << "Starting autostart service " << serviceName << arguments; + auto program = arguments.takeFirst(); + if (!QProcess::startDetached(program, arguments)) + qCWarning(KSMSERVER) << "could not start" << serviceName << ":" << program << arguments; + } while (true); + }); } #include "startup.moc" diff --git a/ksmserver/startup.h b/ksmserver/startup.h index ce11446ae..17cd398bf 100644 --- a/ksmserver/startup.h +++ b/ksmserver/startup.h @@ -1,51 +1,65 @@ #pragma once #include -#include +#include + #include "autostart.h" class KProcess; class KSMServer; +class KCompositeJob; class Startup : public QObject { +Q_OBJECT public: Startup(KSMServer *parent); void upAndRunning( const QString& msg ); -private Q_SLOTS: - - void runUserAutostart(); - bool migrateKDE4Autostart(const QString &autostartFolder); - - void autoStart0(); - void autoStart1(); - void autoStart2(); - void autoStart0Done(); - void autoStart1Done(); - void autoStart2Done(); - void kcmPhase1Done(); - void kcmPhase2Done(); - // ksplash interface void finishStartup(); - void slotAutoStart(); - void secondKDEDPhaseLoaded(); - void kcmPhase1Timeout(); - void kcmPhase2Timeout(); - private: void autoStart(int phase); private: - AutoStart m_autoStart; KSMServer *ksmserver = nullptr; - enum State - { - Waiting, AutoStart0, KcmInitPhase1, AutoStart1, FinishingStartup, // startup - }; - State state; +}; + +class KCMInitJob: public KJob +{ +Q_OBJECT +public: + KCMInitJob(int phase); + void start() override; +public Q_SLOTS: + void done(); +private: + int m_phase; +}; + +class KDEDInitJob: public KJob +{ +Q_OBJECT +public: + KDEDInitJob(); + void start() override; +}; - bool waitAutoStart2 = true; - bool waitKcmInit2 = true; - QDBusInterface* kcminitSignals = nullptr; +class AutoStartAppsJob: public KJob +{ +Q_OBJECT +public: + AutoStartAppsJob(int phase); + void start() override; +private: + AutoStart m_autoStart; +}; +class RestoreSessionJob: public KJob +{ +Q_OBJECT +public: + RestoreSessionJob(KSMServer *ksmserver); + void start() override; +private: + KSMServer *m_ksmserver; }; +