File indexing completed on 2024-05-19 05:39:09

0001 /*
0002     SPDX-FileCopyrightText: 2000 Matthias Ettrich <ettrich@kde.org>
0003     SPDX-FileCopyrightText: 2005 Lubos Lunak <l.lunak@kde.org>
0004     SPDX-FileCopyrightText: 2018 David Edmundson <davidedmundson@kde.org>
0005 
0006     SPDX-FileContributor: Oswald Buddenhagen <ob6@inf.tu-dresden.de>
0007 
0008     some code taken from the dcopserver (part of the KDE libraries), which is
0009     SPDX-FileCopyrightText: 1999 Matthias Ettrich <ettrich@kde.org>
0010     SPDX-FileCopyrightText: 1999 Preston Brown <pbrown@kde.org>
0011 
0012     SPDX-License-Identifier: MIT
0013 */
0014 
0015 #include "startup.h"
0016 
0017 #include "debug.h"
0018 
0019 #include <unistd.h>
0020 
0021 #include "kcminit_interface.h"
0022 #include "ksmserver_interface.h"
0023 
0024 #include <KCompositeJob>
0025 #include <KConfig>
0026 #include <KConfigGroup>
0027 #include <KIO/ApplicationLauncherJob>
0028 #include <KProcess>
0029 #include <KService>
0030 
0031 #include <QDBusConnection>
0032 #include <QDBusMessage>
0033 #include <QDBusPendingCall>
0034 #include <QDir>
0035 #include <QProcess>
0036 #include <QStandardPaths>
0037 #include <QTimer>
0038 
0039 #include "sessiontrack.h"
0040 #include "startupadaptor.h"
0041 
0042 #include "../config-startplasma.h"
0043 #include "startplasma.h"
0044 
0045 class Phase : public KCompositeJob
0046 {
0047     Q_OBJECT
0048 public:
0049     Phase(const AutoStart &autostart, QObject *parent)
0050         : KCompositeJob(parent)
0051         , m_autostart(autostart)
0052     {
0053     }
0054 
0055     bool addSubjob(KJob *job) override
0056     {
0057         bool rc = KCompositeJob::addSubjob(job);
0058         job->start();
0059         return rc;
0060     }
0061 
0062     void slotResult(KJob *job) override
0063     {
0064         KCompositeJob::slotResult(job);
0065         if (!hasSubjobs()) {
0066             emitResult();
0067         }
0068     }
0069 
0070 protected:
0071     const AutoStart m_autostart;
0072 };
0073 
0074 class StartupPhase0 : public Phase
0075 {
0076     Q_OBJECT
0077 public:
0078     StartupPhase0(const AutoStart &autostart, QObject *parent)
0079         : Phase(autostart, parent)
0080     {
0081     }
0082     void start() override
0083     {
0084         qCDebug(PLASMA_SESSION) << "Phase 0";
0085         addSubjob(new AutoStartAppsJob(m_autostart, 0));
0086         addSubjob(new KCMInitJob());
0087         addSubjob(new SleepJob());
0088     }
0089 };
0090 
0091 class StartupPhase1 : public Phase
0092 {
0093     Q_OBJECT
0094 public:
0095     StartupPhase1(const AutoStart &autostart, QObject *parent)
0096         : Phase(autostart, parent)
0097     {
0098     }
0099     void start() override
0100     {
0101         qCDebug(PLASMA_SESSION) << "Phase 1";
0102         addSubjob(new AutoStartAppsJob(m_autostart, 1));
0103     }
0104 };
0105 
0106 class StartupPhase2 : public Phase
0107 {
0108     Q_OBJECT
0109 public:
0110     StartupPhase2(const AutoStart &autostart, QObject *parent)
0111         : Phase(autostart, parent)
0112     {
0113     }
0114 
0115     void start() override
0116     {
0117         qCDebug(PLASMA_SESSION) << "Phase 2";
0118         addSubjob(new AutoStartAppsJob(m_autostart, 2));
0119     }
0120 };
0121 
0122 SleepJob::SleepJob()
0123 {
0124 }
0125 
0126 void SleepJob::start()
0127 {
0128     auto t = new QTimer(this);
0129     connect(t, &QTimer::timeout, this, [this]() {
0130         emitResult();
0131     });
0132     t->start(100);
0133 }
0134 
0135 Startup::Startup(QObject *parent)
0136     : QObject(parent)
0137 {
0138     Q_ASSERT(!s_self);
0139     s_self = this;
0140     new StartupAdaptor(this);
0141     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Startup"), QStringLiteral("org.kde.Startup"), this);
0142     QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.Startup"));
0143 
0144     const AutoStart autostart;
0145 
0146     KJob *x11WindowManagerJob = nullptr;
0147     if (qEnvironmentVariable("XDG_SESSION_TYPE") != QLatin1String("wayland")) {
0148         QString windowManager;
0149         if (qEnvironmentVariableIsSet("KDEWM")) {
0150             windowManager = qEnvironmentVariable("KDEWM");
0151         }
0152         if (windowManager.isEmpty()) {
0153             windowManager = QStringLiteral(KWIN_BIN);
0154         }
0155 
0156         if (windowManager == QLatin1String(KWIN_BIN)) {
0157             x11WindowManagerJob = new StartServiceJob(windowManager, {}, QStringLiteral("org.kde.KWin"));
0158         } else {
0159             x11WindowManagerJob = new StartServiceJob(windowManager, {}, {});
0160         }
0161     } else {
0162         // This must block until started as it sets the WAYLAND_DISPLAY/DISPLAY env variables needed for the rest of the boot
0163         // fortunately it's very fast as it's just starting a wrapper
0164         StartServiceJob kwinWaylandJob(QStringLiteral("kwin_wayland_wrapper"), {QStringLiteral("--xwayland")}, QStringLiteral("org.kde.KWinWrapper"));
0165         kwinWaylandJob.exec();
0166         // kslpash is only launched in plasma-session from the wayland mode, for X it's in startplasma-x11
0167 
0168         const KConfig cfg(QStringLiteral("ksplashrc"));
0169         // the splashscreen and progress indicator
0170         KConfigGroup ksplashCfg = cfg.group(QStringLiteral("KSplash"));
0171         if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) {
0172             QProcess::startDetached(QStringLiteral("ksplashqml"), {});
0173         }
0174     }
0175 
0176     // Keep for KF5; remove in KF6 (KInit will be gone then)
0177     QProcess::execute(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR_KF6 "/start_kdeinit_wrapper"), QStringList());
0178 
0179     KJob *phase1 = nullptr;
0180     m_lock.reset(new QEventLoopLocker);
0181 
0182     const QList<KJob *> sequence = {
0183         new StartProcessJob(QStringLiteral("kcminit_startup"), {}),
0184         new StartServiceJob(QStringLiteral("kded6"), {}, QStringLiteral("org.kde.kded6"), {}),
0185         x11WindowManagerJob,
0186         new StartServiceJob(QStringLiteral("ksmserver"), QCoreApplication::instance()->arguments().mid(1), QStringLiteral("org.kde.ksmserver")),
0187         new StartupPhase0(autostart, this),
0188         phase1 = new StartupPhase1(autostart, this),
0189         new RestoreSessionJob(),
0190         new StartupPhase2(autostart, this),
0191     };
0192     KJob *last = nullptr;
0193     for (KJob *job : sequence) {
0194         if (!job) {
0195             continue;
0196         }
0197         if (last) {
0198             connect(last, &KJob::finished, job, &KJob::start);
0199         }
0200         last = job;
0201     }
0202 
0203     connect(sequence.last(), &KJob::finished, this, &Startup::finishStartup);
0204     sequence.first()->start();
0205 
0206     // app will be closed when all KJobs finish thanks to the QEventLoopLocker in each KJob
0207 }
0208 
0209 void Startup::upAndRunning(const QString &msg)
0210 {
0211     QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"),
0212                                                                          QStringLiteral("/KSplash"),
0213                                                                          QStringLiteral("org.kde.KSplash"),
0214                                                                          QStringLiteral("setStage"));
0215     ksplashProgressMessage.setArguments(QList<QVariant>() << msg);
0216     QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage);
0217 }
0218 
0219 void Startup::finishStartup()
0220 {
0221     qCDebug(PLASMA_SESSION) << "Finished";
0222     upAndRunning(QStringLiteral("ready"));
0223 
0224     playStartupSound();
0225     new SessionTrack(m_processes);
0226     deleteLater();
0227 }
0228 
0229 void Startup::updateLaunchEnv(const QString &key, const QString &value)
0230 {
0231     qputenv(key.toLatin1(), value.toLatin1());
0232 }
0233 
0234 bool Startup::startDetached(QProcess *process)
0235 {
0236     process->setProcessChannelMode(QProcess::ForwardedChannels);
0237     process->start();
0238     const bool ret = process->waitForStarted();
0239     if (ret) {
0240         m_processes << process;
0241     }
0242     return ret;
0243 }
0244 
0245 Startup *Startup::s_self = nullptr;
0246 
0247 KCMInitJob::KCMInitJob()
0248     : KJob()
0249 {
0250 }
0251 
0252 void KCMInitJob::start()
0253 {
0254     org::kde::KCMInit kcminit(QStringLiteral("org.kde.kcminit"), QStringLiteral("/kcminit"), QDBusConnection::sessionBus());
0255     kcminit.setTimeout(10 * 1000);
0256 
0257     QDBusPendingReply<void> pending = kcminit.runPhase1();
0258     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this);
0259     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {
0260         emitResult();
0261     });
0262     connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater);
0263 }
0264 
0265 RestoreSessionJob::RestoreSessionJob()
0266     : KJob()
0267 {
0268 }
0269 
0270 void RestoreSessionJob::start()
0271 {
0272     OrgKdeKSMServerInterfaceInterface ksmserverIface(QStringLiteral("org.kde.ksmserver"), QStringLiteral("/KSMServer"), QDBusConnection::sessionBus());
0273     auto pending = ksmserverIface.restoreSession();
0274 
0275     QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pending, this);
0276     connect(watcher, &QDBusPendingCallWatcher::finished, this, [this]() {
0277         emitResult();
0278     });
0279     connect(watcher, &QDBusPendingCallWatcher::finished, watcher, &QObject::deleteLater);
0280 }
0281 
0282 AutoStartAppsJob::AutoStartAppsJob(const AutoStart &autostart, int phase)
0283     : m_autoStart(autostart)
0284 {
0285     m_autoStart.setPhase(phase);
0286 }
0287 
0288 void AutoStartAppsJob::start()
0289 {
0290     qCDebug(PLASMA_SESSION);
0291 
0292     QTimer::singleShot(0, this, [this]() {
0293         do {
0294             QString serviceName = m_autoStart.startService();
0295             if (serviceName.isEmpty()) {
0296                 // Done
0297                 if (!m_autoStart.phaseDone()) {
0298                     m_autoStart.setPhaseDone();
0299                 }
0300                 emitResult();
0301                 return;
0302             }
0303             auto job = new KIO::ApplicationLauncherJob(KService::Ptr(new KService(serviceName)), this);
0304             job->start();
0305         } while (true);
0306     });
0307 }
0308 
0309 StartServiceJob::StartServiceJob(const QString &process, const QStringList &args, const QString &serviceId, const QProcessEnvironment &additionalEnv)
0310     : KJob()
0311     , m_process(new QProcess)
0312     , m_serviceId(serviceId)
0313     , m_additionalEnv(additionalEnv)
0314 {
0315     m_process->setProgram(process);
0316     m_process->setArguments(args);
0317 
0318     auto watcher = new QDBusServiceWatcher(serviceId, QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForRegistration, this);
0319     connect(watcher, &QDBusServiceWatcher::serviceRegistered, this, &StartServiceJob::emitResult);
0320 }
0321 
0322 void StartServiceJob::start()
0323 {
0324     auto env = QProcessEnvironment::systemEnvironment();
0325     env.insert(m_additionalEnv);
0326     m_process->setProcessEnvironment(env);
0327 
0328     if (!m_serviceId.isEmpty() && QDBusConnection::sessionBus().interface()->isServiceRegistered(m_serviceId)) {
0329         qCDebug(PLASMA_SESSION) << m_process << "already running";
0330         emitResult();
0331         return;
0332     }
0333     qCDebug(PLASMA_SESSION) << "Starting " << m_process->program() << m_process->arguments();
0334     if (!Startup::self()->startDetached(m_process)) {
0335         qCWarning(PLASMA_SESSION) << "error starting process" << m_process->program() << m_process->arguments();
0336         emitResult();
0337     }
0338 
0339     if (m_serviceId.isEmpty()) {
0340         emitResult();
0341     }
0342 }
0343 
0344 StartProcessJob::StartProcessJob(const QString &process, const QStringList &args, const QProcessEnvironment &additionalEnv)
0345     : KJob()
0346     , m_process(new QProcess(this))
0347 {
0348     m_process->setProgram(process);
0349     m_process->setArguments(args);
0350     m_process->setProcessChannelMode(QProcess::ForwardedChannels);
0351     auto env = QProcessEnvironment::systemEnvironment();
0352     env.insert(additionalEnv);
0353     m_process->setProcessEnvironment(env);
0354 
0355     connect(m_process, &QProcess::finished, [this](int exitCode) {
0356         qCInfo(PLASMA_SESSION) << "process job " << m_process->program() << "finished with exit code " << exitCode;
0357         emitResult();
0358     });
0359 }
0360 
0361 void StartProcessJob::start()
0362 {
0363     qCDebug(PLASMA_SESSION) << "Starting " << m_process->program() << m_process->arguments();
0364 
0365     m_process->start();
0366 }
0367 
0368 #include "startup.moc"