Warning, file /plasma/plasma-workspace/startkde/startplasma.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /*
0002     SPDX-FileCopyrightText: 2019 Aleix Pol Gonzalez <aleixpol@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include <config-startplasma.h>
0008 
0009 #include <QDir>
0010 #include <QEventLoop>
0011 #include <QProcess>
0012 #include <QStandardPaths>
0013 #include <QTextStream>
0014 
0015 #include <QDBusConnectionInterface>
0016 #include <QDBusServiceWatcher>
0017 
0018 #include <KConfig>
0019 #include <KConfigGroup>
0020 #include <KNotifyConfig>
0021 #include <KPackage/Package>
0022 #include <KPackage/PackageLoader>
0023 #include <KSharedConfig>
0024 
0025 #include <phonon/audiooutput.h>
0026 #include <phonon/mediaobject.h>
0027 #include <phonon/mediasource.h>
0028 
0029 #include <unistd.h>
0030 
0031 #include <autostartscriptdesktopfile.h>
0032 #include <updatelaunchenvjob.h>
0033 
0034 #include "startplasma.h"
0035 
0036 #include "../config-workspace.h"
0037 #include "../kcms/lookandfeel/lookandfeelmanager.h"
0038 #include "debug.h"
0039 
0040 QTextStream out(stderr);
0041 
0042 void sigtermHandler(int signalNumber)
0043 {
0044     Q_UNUSED(signalNumber)
0045     if (QCoreApplication::instance()) {
0046         QCoreApplication::instance()->exit(-1);
0047     }
0048 }
0049 
0050 void messageBox(const QString &text)
0051 {
0052     out << text;
0053     runSync(QStringLiteral("xmessage"), {QStringLiteral("-geometry"), QStringLiteral("500x100"), text});
0054 }
0055 
0056 QStringList allServices(const QLatin1String &prefix)
0057 {
0058     const QStringList services = QDBusConnection::sessionBus().interface()->registeredServiceNames();
0059     QStringList names;
0060 
0061     std::copy_if(services.cbegin(), services.cend(), std::back_inserter(names), [&prefix](const QString &serviceName) {
0062         return serviceName.startsWith(prefix);
0063     });
0064 
0065     return names;
0066 }
0067 
0068 void gentleTermination(QProcess *p)
0069 {
0070     if (p->state() != QProcess::Running) {
0071         return;
0072     }
0073 
0074     p->terminate();
0075 
0076     // Wait longer for a session than a greeter
0077     if (!p->waitForFinished(5000)) {
0078         p->kill();
0079         if (!p->waitForFinished(5000)) {
0080             qCWarning(PLASMA_STARTUP) << "Could not fully finish the process" << p->program();
0081         }
0082     }
0083 }
0084 
0085 int runSync(const QString &program, const QStringList &args, const QStringList &env)
0086 {
0087     QProcess p;
0088     if (!env.isEmpty())
0089         p.setEnvironment(QProcess::systemEnvironment() << env);
0090     p.setProcessChannelMode(QProcess::ForwardedChannels);
0091     p.start(program, args);
0092 
0093     QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &p, [&p] {
0094         gentleTermination(&p);
0095     });
0096     //     qCDebug(PLASMA_STARTUP) << "started..." << program << args;
0097     p.waitForFinished(-1);
0098     if (p.exitCode()) {
0099         qCWarning(PLASMA_STARTUP) << program << args << "exited with code" << p.exitCode();
0100     }
0101     return p.exitCode();
0102 }
0103 
0104 bool isShellVariable(const QByteArray &name)
0105 {
0106     return name == "_" || name == "SHELL" || name.startsWith("SHLVL");
0107 }
0108 
0109 bool isSessionVariable(const QByteArray &name)
0110 {
0111     // Check is variable is specific to session.
0112     return name == "DISPLAY" || name == "XAUTHORITY" || //
0113         name == "WAYLAND_DISPLAY" || name == "WAYLAND_SOCKET" || //
0114         name.startsWith("XDG_");
0115 }
0116 
0117 void setEnvironmentVariable(const QByteArray &name, const QByteArray &value)
0118 {
0119     if (qgetenv(name) != value) {
0120         qputenv(name, value);
0121     }
0122 }
0123 
0124 void sourceFiles(const QStringList &files)
0125 {
0126     QStringList filteredFiles;
0127     std::copy_if(files.begin(), files.end(), std::back_inserter(filteredFiles), [](const QString &i) {
0128         return QFileInfo(i).isReadable();
0129     });
0130 
0131     if (filteredFiles.isEmpty())
0132         return;
0133 
0134     filteredFiles.prepend(QStringLiteral(CMAKE_INSTALL_FULL_LIBEXECDIR "/plasma-sourceenv.sh"));
0135 
0136     QProcess p;
0137     p.start(QStringLiteral("/bin/sh"), filteredFiles);
0138     p.waitForFinished(-1);
0139 
0140     const auto fullEnv = p.readAllStandardOutput();
0141     auto envs = fullEnv.split('\0');
0142 
0143     for (auto &env : envs) {
0144         const int idx = env.indexOf('=');
0145         if (Q_UNLIKELY(idx <= 0)) {
0146             continue;
0147         }
0148 
0149         const auto name = env.left(idx);
0150         if (isShellVariable(name)) {
0151             continue;
0152         }
0153         setEnvironmentVariable(name, env.mid(idx + 1));
0154     }
0155 }
0156 
0157 void createConfigDirectory()
0158 {
0159     const QString configDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation);
0160     if (!QDir().mkpath(configDir))
0161         out << "Could not create config directory XDG_CONFIG_HOME: " << configDir << '\n';
0162 }
0163 
0164 void runStartupConfig()
0165 {
0166     // export LC_* variables set by kcmshell5 formats into environment
0167     // so it can be picked up by QLocale and friends.
0168     KConfig config(QStringLiteral("plasma-localerc"));
0169     KConfigGroup formatsConfig = KConfigGroup(&config, "Formats");
0170 
0171     const auto lcValues = {"LANG", "LC_NUMERIC", "LC_TIME", "LC_MONETARY", "LC_MEASUREMENT", "LC_COLLATE", "LC_CTYPE"};
0172     for (auto lc : lcValues) {
0173         const QString value = formatsConfig.readEntry(lc, QString());
0174         if (!value.isEmpty()) {
0175             qputenv(lc, value.toUtf8());
0176         }
0177     }
0178 
0179     KConfigGroup languageConfig = KConfigGroup(&config, "Translations");
0180     const QString value = languageConfig.readEntry("LANGUAGE", QString());
0181     if (!value.isEmpty()) {
0182         qputenv("LANGUAGE", value.toUtf8());
0183     }
0184 
0185     if (!formatsConfig.hasKey("LANG") && !qEnvironmentVariableIsEmpty("LANG")) {
0186         formatsConfig.writeEntry("LANG", qgetenv("LANG"));
0187         formatsConfig.sync();
0188     }
0189 }
0190 
0191 void setupCursor(bool wayland)
0192 {
0193     const KConfig cfg(QStringLiteral("kcminputrc"));
0194     const KConfigGroup inputCfg = cfg.group("Mouse");
0195 
0196     const auto kcminputrc_mouse_cursorsize = inputCfg.readEntry("cursorSize", 24);
0197     const auto kcminputrc_mouse_cursortheme = inputCfg.readEntry("cursorTheme", QStringLiteral("breeze_cursors"));
0198     if (!kcminputrc_mouse_cursortheme.isEmpty()) {
0199 #ifdef XCURSOR_PATH
0200         QByteArray path(XCURSOR_PATH);
0201         path.replace("$XCURSOR_PATH", qgetenv("XCURSOR_PATH"));
0202         qputenv("XCURSOR_PATH", path);
0203 #endif
0204     }
0205 
0206     // TODO: consider linking directly
0207     const int applyMouseStatus =
0208         wayland ? 0 : runSync(QStringLiteral("kapplymousetheme"), {kcminputrc_mouse_cursortheme, QString::number(kcminputrc_mouse_cursorsize)});
0209     if (applyMouseStatus == 10) {
0210         qputenv("XCURSOR_THEME", "breeze_cursors");
0211     } else if (!kcminputrc_mouse_cursortheme.isEmpty()) {
0212         qputenv("XCURSOR_THEME", kcminputrc_mouse_cursortheme.toUtf8());
0213     }
0214     qputenv("XCURSOR_SIZE", QByteArray::number(kcminputrc_mouse_cursorsize));
0215 }
0216 
0217 std::optional<QProcessEnvironment> getSystemdEnvironment()
0218 {
0219     auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0220                                               QStringLiteral("/org/freedesktop/systemd1"),
0221                                               QStringLiteral("org.freedesktop.DBus.Properties"),
0222                                               QStringLiteral("Get"));
0223     msg << QStringLiteral("org.freedesktop.systemd1.Manager") << QStringLiteral("Environment");
0224     auto reply = QDBusConnection::sessionBus().call(msg);
0225     if (reply.type() == QDBusMessage::ErrorMessage) {
0226         return std::nullopt;
0227     }
0228 
0229     // Make sure the returned type is correct.
0230     auto arguments = reply.arguments();
0231     if (arguments.isEmpty() || arguments[0].userType() != qMetaTypeId<QDBusVariant>()) {
0232         return std::nullopt;
0233     }
0234     auto variant = qdbus_cast<QVariant>(arguments[0]);
0235     if (variant.type() != QVariant::StringList) {
0236         return std::nullopt;
0237     }
0238 
0239     const auto assignmentList = variant.toStringList();
0240     QProcessEnvironment ret;
0241     for (auto &env : assignmentList) {
0242         const int idx = env.indexOf(QLatin1Char('='));
0243         if (Q_LIKELY(idx > 0)) {
0244             ret.insert(env.left(idx), env.mid(idx + 1));
0245         }
0246     }
0247 
0248     return ret;
0249 }
0250 
0251 // Import systemd user environment.
0252 //
0253 // Systemd read ~/.config/environment.d which applies to all systemd user unit.
0254 // But it won't work if plasma is not started by systemd.
0255 void importSystemdEnvrionment()
0256 {
0257     const auto environment = getSystemdEnvironment();
0258     if (!environment) {
0259         return;
0260     }
0261 
0262     for (auto &nameStr : environment.value().keys()) {
0263         const auto name = nameStr.toLocal8Bit();
0264         if (!isShellVariable(name) && !isSessionVariable(name)) {
0265             setEnvironmentVariable(name, environment.value().value(nameStr).toLocal8Bit());
0266         }
0267     }
0268 }
0269 
0270 // Source scripts found in <config locations>/plasma-workspace/env/*.sh
0271 // (where <config locations> correspond to the system and user's configuration
0272 // directory.
0273 //
0274 // Scripts are sourced in reverse order of priority of their directory, as defined
0275 // by `QStandardPaths::standardLocations`. This ensures that high-priority scripts
0276 // (such as those in the user's home directory) are sourced last and take precedence
0277 // over lower-priority scripts (such as system defaults). Scripts in the same
0278 // directory are sourced in lexical order of their filename.
0279 //
0280 // This is where you can define environment variables that will be available to
0281 // all KDE programs, so this is where you can run agents using e.g. eval `ssh-agent`
0282 // or eval `gpg-agent --daemon`.
0283 // Note: if you do that, you should also put "ssh-agent -k" as a shutdown script
0284 //
0285 // (see end of this file).
0286 // For anything else (that doesn't set env vars, or that needs a window manager),
0287 // better use the Autostart folder.
0288 
0289 void runEnvironmentScripts()
0290 {
0291     QStringList scripts;
0292     auto locations = QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation);
0293 
0294     //`standardLocations()` returns locations sorted by "order of priority". We iterate in reverse
0295     // order so that high-priority scripts are sourced last and their modifications take precedence.
0296     for (auto loc = locations.crbegin(); loc != locations.crend(); loc++) {
0297         QDir dir(*loc);
0298         if (!dir.cd(QStringLiteral("./plasma-workspace/env"))) {
0299             // Skip location if plasma-workspace/env subdirectory does not exist
0300             continue;
0301         }
0302         const auto dirScripts = dir.entryInfoList({QStringLiteral("*.sh")}, QDir::Files, QDir::Name);
0303         for (const auto &script : dirScripts) {
0304             scripts << script.absoluteFilePath();
0305         }
0306     }
0307     sourceFiles(scripts);
0308 }
0309 
0310 // Mark that full KDE session is running (e.g. Konqueror preloading works only
0311 // with full KDE running). The KDE_FULL_SESSION property can be detected by
0312 // any X client connected to the same X session, even if not launched
0313 // directly from the KDE session but e.g. using "ssh -X", kdesu. $KDE_FULL_SESSION
0314 // however guarantees that the application is launched in the same environment
0315 // like the KDE session and that e.g. KDE utilities/libraries are available.
0316 // KDE_FULL_SESSION property is also only available since KDE 3.5.5.
0317 // The matching tests are:
0318 //   For $KDE_FULL_SESSION:
0319 //     if test -n "$KDE_FULL_SESSION"; then ... whatever
0320 //   For KDE_FULL_SESSION property (on X11):
0321 //     xprop -root | grep "^KDE_FULL_SESSION" >/dev/null 2>/dev/null
0322 //     if test $? -eq 0; then ... whatever
0323 //
0324 // Additionally there is $KDE_SESSION_UID with the uid
0325 // of the user running the KDE session. It should be rarely needed (e.g.
0326 // after sudo to prevent desktop-wide functionality in the new user's kded).
0327 //
0328 // Since KDE4 there is also KDE_SESSION_VERSION, containing the major version number.
0329 //
0330 
0331 void setupPlasmaEnvironment()
0332 {
0333     // Manually disable auto scaling because we are scaling above
0334     // otherwise apps that manually opt in for high DPI get auto scaled by the developer AND manually scaled by us
0335     qputenv("QT_AUTO_SCREEN_SCALE_FACTOR", "0");
0336 
0337     qputenv("KDE_FULL_SESSION", "true");
0338     qputenv("KDE_SESSION_VERSION", "5");
0339     qputenv("KDE_SESSION_UID", QByteArray::number(getuid()));
0340     qputenv("XDG_CURRENT_DESKTOP", "KDE");
0341 
0342     qputenv("KDE_APPLICATIONS_AS_SCOPE", "1");
0343 
0344     // Add kdedefaults dir to allow config defaults overriding from a writable location
0345     QByteArray currentConfigDirs = qgetenv("XDG_CONFIG_DIRS");
0346     if (currentConfigDirs.isEmpty()) {
0347         currentConfigDirs = "/etc/xdg";
0348     }
0349     const QString extraConfigDir = QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation) + QLatin1String("/kdedefaults");
0350     QDir().mkpath(extraConfigDir);
0351     qputenv("XDG_CONFIG_DIRS", QFile::encodeName(extraConfigDir) + ':' + currentConfigDirs);
0352 
0353     const KConfig globals;
0354     const QString currentLnf = KConfigGroup(&globals, QStringLiteral("KDE")).readEntry("LookAndFeelPackage", QStringLiteral("org.kde.breeze.desktop"));
0355     QFile activeLnf(extraConfigDir + QLatin1String("/package"));
0356     activeLnf.open(QIODevice::ReadOnly);
0357     if (activeLnf.readLine() != currentLnf.toUtf8()) {
0358         KPackage::Package package = KPackage::PackageLoader::self()->loadPackage(QStringLiteral("Plasma/LookAndFeel"), currentLnf);
0359         LookAndFeelManager lnfManager;
0360         lnfManager.setMode(LookAndFeelManager::Mode::Defaults);
0361         lnfManager.save(package, KPackage::Package());
0362     }
0363     // check if colors changed, if so apply them and discard palsma cache
0364     {
0365         LookAndFeelManager lnfManager;
0366         lnfManager.setMode(LookAndFeelManager::Mode::Apply);
0367         KConfig globals(QStringLiteral("kdeglobals")); // Reload the config
0368         KConfigGroup generalGroup(&globals, QStringLiteral("General"));
0369         const QString colorScheme = generalGroup.readEntry("ColorScheme", QStringLiteral("BreezeLight"));
0370         QString path = lnfManager.colorSchemeFile(colorScheme);
0371 
0372         if (!path.isEmpty()) {
0373             QFile f(path);
0374             QCryptographicHash hash(QCryptographicHash::Sha1);
0375             if (f.open(QFile::ReadOnly) && hash.addData(&f)) {
0376                 const QString fileHash = QString::fromUtf8(hash.result().toHex());
0377                 if (fileHash != generalGroup.readEntry("ColorSchemeHash", QString())) {
0378                     lnfManager.setColors(colorScheme, path);
0379                     generalGroup.writeEntry("ColorSchemeHash", fileHash);
0380                     generalGroup.sync();
0381                     const QString svgCache =
0382                         QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation) + QLatin1Char('/') + QStringLiteral("plasma-svgelements");
0383                     if (!svgCache.isEmpty()) {
0384                         QFile::remove(svgCache);
0385                     }
0386                 }
0387             }
0388         }
0389     }
0390 }
0391 
0392 void cleanupPlasmaEnvironment(const std::optional<QProcessEnvironment> &oldSystemdEnvironment)
0393 {
0394     qunsetenv("KDE_FULL_SESSION");
0395     qunsetenv("KDE_SESSION_VERSION");
0396     qunsetenv("KDE_SESSION_UID");
0397 
0398     if (!oldSystemdEnvironment) {
0399         return;
0400     }
0401 
0402     auto currentEnv = getSystemdEnvironment();
0403     if (!currentEnv) {
0404         return;
0405     }
0406 
0407     // According to systemd documentation:
0408     // If a variable is listed in both, the variable is set after this method returns, i.e. the set list overrides the unset list.
0409     // So this will effectively restore the state to the values in oldSystemdEnvironment.
0410     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0411                                                           QStringLiteral("/org/freedesktop/systemd1"),
0412                                                           QStringLiteral("org.freedesktop.systemd1.Manager"),
0413                                                           QStringLiteral("UnsetAndSetEnvironment"));
0414     message.setArguments({currentEnv.value().keys(), oldSystemdEnvironment.value().toStringList()});
0415 
0416     // The session program gonna quit soon, ensure the message is flushed.
0417     auto reply = QDBusConnection::sessionBus().asyncCall(message);
0418     reply.waitForFinished();
0419 }
0420 
0421 // Drop session-specific variables from the systemd environment.
0422 // Those can be leftovers from previous sessions, which can interfere with the session
0423 // we want to start now, e.g. $DISPLAY might break kwin_wayland.
0424 static void dropSessionVarsFromSystemdEnvironment()
0425 {
0426     const auto environment = getSystemdEnvironment();
0427     if (!environment) {
0428         return;
0429     }
0430 
0431     QStringList varsToDrop;
0432     for (auto &nameStr : environment.value().keys()) {
0433         // If it's set in this process, it'll be overwritten by the following UpdateLaunchEnvJob
0434         const auto name = nameStr.toLocal8Bit();
0435         if (!qEnvironmentVariableIsSet(name) && isSessionVariable(name)) {
0436             varsToDrop.append(nameStr);
0437         }
0438     }
0439 
0440     auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0441                                               QStringLiteral("/org/freedesktop/systemd1"),
0442                                               QStringLiteral("org.freedesktop.systemd1.Manager"),
0443                                               QStringLiteral("UnsetEnvironment"));
0444     msg << varsToDrop;
0445     auto reply = QDBusConnection::sessionBus().call(msg);
0446     if (reply.type() == QDBusMessage::ErrorMessage) {
0447         qCWarning(PLASMA_STARTUP) << "Failed to unset systemd environment variables:" << reply.errorName() << reply.errorMessage();
0448     }
0449 }
0450 
0451 // kwin_wayland can possibly also start dbus-activated services which need env variables.
0452 // In that case, the update in startplasma might be too late.
0453 bool syncDBusEnvironment()
0454 {
0455     dropSessionVarsFromSystemdEnvironment();
0456 
0457     // Shell variables are filtered out of things we explicitly load, but they
0458     // still might have been inherited from the parent process
0459     QProcessEnvironment environment = QProcessEnvironment::systemEnvironment();
0460     for (auto &name : environment.keys()) {
0461         if (isShellVariable(name.toLocal8Bit())) {
0462             environment.remove(name);
0463         }
0464     }
0465 
0466     // At this point all environment variables are set, let's send it to the DBus session server to update the activation environment
0467     auto job = new UpdateLaunchEnvJob(environment);
0468     return job->exec();
0469 }
0470 
0471 static bool desktopLockedAtStart = false;
0472 
0473 QProcess *setupKSplash()
0474 {
0475     const auto dlstr = qgetenv("DESKTOP_LOCKED");
0476     desktopLockedAtStart = dlstr == "true" || dlstr == "1";
0477     qunsetenv("DESKTOP_LOCKED"); // Don't want it in the environment
0478 
0479     QProcess *p = nullptr;
0480     if (!desktopLockedAtStart) {
0481         const KConfig cfg(QStringLiteral("ksplashrc"));
0482         // the splashscreen and progress indicator
0483         KConfigGroup ksplashCfg = cfg.group("KSplash");
0484         if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) {
0485             p = new QProcess;
0486             p->setProcessChannelMode(QProcess::ForwardedChannels);
0487             p->start(QStringLiteral("ksplashqml"), {ksplashCfg.readEntry("Theme", QStringLiteral("Breeze"))});
0488         }
0489     }
0490     return p;
0491 }
0492 
0493 // If something went on an endless restart crash loop it will get blacklisted, as this is a clean login we will want to reset those counters
0494 // This is independent of whether we use the Plasma systemd boot
0495 void resetSystemdFailedUnits()
0496 {
0497     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0498                                                           QStringLiteral("/org/freedesktop/systemd1"),
0499                                                           QStringLiteral("org.freedesktop.systemd1.Manager"),
0500                                                           QStringLiteral("ResetFailed"));
0501     QDBusConnection::sessionBus().call(message);
0502 }
0503 
0504 // Reload systemd to make sure the current configuration is active, which also reruns generators.
0505 // Needed for e.g. XDG autostart changes to become effective.
0506 void reloadSystemd()
0507 {
0508     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0509                                                           QStringLiteral("/org/freedesktop/systemd1"),
0510                                                           QStringLiteral("org.freedesktop.systemd1.Manager"),
0511                                                           QStringLiteral("Reload"));
0512     QDBusConnection::sessionBus().call(message);
0513 }
0514 
0515 bool hasSystemdService(const QString &serviceName)
0516 {
0517     qDBusRegisterMetaType<QPair<QString, QString>>();
0518     qDBusRegisterMetaType<QList<QPair<QString, QString>>>();
0519     auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0520                                               QStringLiteral("/org/freedesktop/systemd1"),
0521                                               QStringLiteral("org.freedesktop.systemd1.Manager"),
0522                                               QStringLiteral("ListUnitFilesByPatterns"));
0523     msg << QStringList({QStringLiteral("enabled"), QStringLiteral("static"), QStringLiteral("linked"), QStringLiteral("linked-runtime")});
0524     msg << QStringList({serviceName});
0525     QDBusReply<QList<QPair<QString, QString>>> reply = QDBusConnection::sessionBus().call(msg);
0526     if (!reply.isValid()) {
0527         return false;
0528     }
0529     // if we have a service returned then it must have found it
0530     return !reply.value().isEmpty();
0531 }
0532 
0533 bool useSystemdBoot()
0534 {
0535     auto config = KSharedConfig::openConfig(QStringLiteral("startkderc"), KConfig::NoGlobals);
0536     const QString configValue = config->group(QStringLiteral("General")).readEntry("systemdBoot", QStringLiteral("true")).toLower();
0537 
0538     if (configValue == QLatin1String("false")) {
0539         return false;
0540     }
0541 
0542     if (configValue == QLatin1String("force")) {
0543         qInfo() << "Systemd boot forced";
0544         return true;
0545     }
0546 
0547     if (!hasSystemdService(QStringLiteral("plasma-workspace.target"))) {
0548         return false;
0549     }
0550 
0551     // xdg-desktop-autostart.target is shipped with an systemd 246 and provides a generator
0552     // for creating units out of existing autostart files
0553     // only enable our systemd boot if that exists, unless the user has forced the systemd boot above
0554     return hasSystemdService(QStringLiteral("xdg-desktop-autostart.target"));
0555 }
0556 
0557 void startKSplashViaSystemd()
0558 {
0559     const KConfig cfg(QStringLiteral("ksplashrc"));
0560     // the splashscreen and progress indicator
0561     KConfigGroup ksplashCfg = cfg.group("KSplash");
0562     if (ksplashCfg.readEntry("Engine", QStringLiteral("KSplashQML")) == QLatin1String("KSplashQML")) {
0563         auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0564                                                   QStringLiteral("/org/freedesktop/systemd1"),
0565                                                   QStringLiteral("org.freedesktop.systemd1.Manager"),
0566                                                   QStringLiteral("StartUnit"));
0567         msg << QStringLiteral("plasma-ksplash.service") << QStringLiteral("fail");
0568         QDBusReply<QDBusObjectPath> reply = QDBusConnection::sessionBus().call(msg);
0569     }
0570 }
0571 
0572 static void migrateUserScriptsAutostart()
0573 {
0574     QDir configLocation(QStandardPaths::writableLocation(QStandardPaths::GenericConfigLocation));
0575     QDir autostartScriptsLocation(configLocation.filePath(QStringLiteral("autostart-scripts")));
0576     if (!autostartScriptsLocation.exists()) {
0577         return;
0578     }
0579     const QDir autostartScriptsMovedLocation(configLocation.filePath(QStringLiteral("old-autostart-scripts")));
0580     const auto entries = autostartScriptsLocation.entryInfoList(QDir::Files);
0581     for (const auto &info : entries) {
0582         const auto scriptName = info.fileName();
0583         const auto scriptPath = info.absoluteFilePath();
0584         const auto scriptMovedPath = autostartScriptsMovedLocation.filePath(scriptName);
0585 
0586         // Don't migrate backup files
0587         if (scriptName.endsWith(QLatin1Char('~')) || scriptName.endsWith(QLatin1String(".bak"))
0588             || (scriptName[0] == QLatin1Char('%') && scriptName.endsWith(QLatin1Char('%')))
0589             || (scriptName[0] == QLatin1Char('#') && scriptName.endsWith(QLatin1Char('#')))) {
0590             qCDebug(PLASMA_STARTUP) << "Not migrating backup autostart script" << scriptName;
0591             continue;
0592         }
0593 
0594         // Migrate autostart script to a standard .desktop autostart file
0595         AutostartScriptDesktopFile desktopFile(scriptName, info.isSymLink() ? info.symLinkTarget() : scriptMovedPath);
0596         qCInfo(PLASMA_STARTUP) << "Migrated legacy autostart script" << scriptPath << "to" << desktopFile.fileName();
0597 
0598         if (info.isSymLink() && QFile::remove(scriptPath)) {
0599             qCInfo(PLASMA_STARTUP) << "Removed legacy autostart script" << scriptPath << "that pointed to" << info.symLinkTarget();
0600         }
0601     }
0602     // Delete or rename autostart-scripts to old-autostart-scripts to avoid running the migration again
0603     if (autostartScriptsLocation.entryInfoList(QDir::Files).empty()) {
0604         autostartScriptsLocation.removeRecursively();
0605     } else {
0606         configLocation.rename(autostartScriptsLocation.dirName(), autostartScriptsMovedLocation.dirName());
0607     }
0608     // Reload systemd so that the XDG autostart generator is run again to pick up the new .desktop files
0609     QDBusMessage message = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0610                                                           QStringLiteral("/org/freedesktop/systemd1"),
0611                                                           QStringLiteral("org.freedesktop.systemd1.Manager"),
0612                                                           QStringLiteral("Reload"));
0613     QDBusConnection::sessionBus().call(message);
0614 }
0615 
0616 bool startPlasmaSession(bool wayland)
0617 {
0618     resetSystemdFailedUnits();
0619     reloadSystemd();
0620     OrgKdeKSplashInterface iface(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QDBusConnection::sessionBus());
0621     iface.setStage(QStringLiteral("startPlasma"));
0622     // finally, give the session control to the session manager
0623     // see kdebase/ksmserver for the description of the rest of the startup sequence
0624     // if the KDEWM environment variable has been set, then it will be used as KDE's
0625     // window manager instead of kwin.
0626     // if KDEWM is not set, ksmserver will ensure kwin is started.
0627     // kwrapper5 is used to reduce startup time and memory usage
0628     // kwrapper5 does not return useful error codes such as the exit code of ksmserver.
0629     // We only check for 255 which means that the ksmserver process could not be
0630     // started, any problems thereafter, e.g. ksmserver failing to initialize,
0631     // will remain undetected.
0632     // If the session should be locked from the start (locked autologin),
0633     // lock now and do the rest of the KDE startup underneath the locker.
0634 
0635     bool rc = true;
0636     QEventLoop e;
0637 
0638     QDBusServiceWatcher serviceWatcher;
0639     serviceWatcher.setConnection(QDBusConnection::sessionBus());
0640 
0641     // We want to exit when both ksmserver and plasma-session-shutdown have finished
0642     // This also closes if ksmserver crashes unexpectedly, as in those cases plasma-shutdown is not running
0643     if (wayland) {
0644         serviceWatcher.addWatchedService(QStringLiteral("org.kde.KWinWrapper"));
0645     } else {
0646         serviceWatcher.addWatchedService(QStringLiteral("org.kde.ksmserver"));
0647     }
0648     serviceWatcher.addWatchedService(QStringLiteral("org.kde.Shutdown"));
0649     serviceWatcher.setWatchMode(QDBusServiceWatcher::WatchForUnregistration);
0650 
0651     QObject::connect(&serviceWatcher, &QDBusServiceWatcher::serviceUnregistered, [&]() {
0652         const QStringList watchedServices = serviceWatcher.watchedServices();
0653         bool plasmaSessionRunning = std::any_of(watchedServices.constBegin(), watchedServices.constEnd(), [](const QString &service) {
0654             return QDBusConnection::sessionBus().interface()->isServiceRegistered(service);
0655         });
0656         if (!plasmaSessionRunning) {
0657             e.quit();
0658         }
0659     });
0660 
0661     // Create .desktop files for the scripts in .config/autostart-scripts
0662     migrateUserScriptsAutostart();
0663 
0664     std::unique_ptr<QProcess, KillBeforeDeleter> startPlasmaSession;
0665     if (!useSystemdBoot()) {
0666         startPlasmaSession.reset(new QProcess);
0667         qCDebug(PLASMA_STARTUP) << "Using classic boot";
0668 
0669         QStringList plasmaSessionOptions;
0670         if (wayland) {
0671             plasmaSessionOptions << QStringLiteral("--no-lockscreen");
0672         } else {
0673             if (desktopLockedAtStart) {
0674                 plasmaSessionOptions << QStringLiteral("--lockscreen");
0675             }
0676         }
0677 
0678         startPlasmaSession->setProcessChannelMode(QProcess::ForwardedChannels);
0679         QObject::connect(startPlasmaSession.get(), &QProcess::finished, &e, [&rc](int exitCode, QProcess::ExitStatus) {
0680             if (exitCode == 255) {
0681                 // Startup error
0682                 messageBox(QStringLiteral("startkde: Could not start plasma_session. Check your installation.\n"));
0683                 rc = false;
0684             }
0685         });
0686 
0687         startPlasmaSession->start(QStringLiteral(CMAKE_INSTALL_FULL_BINDIR "/plasma_session"), plasmaSessionOptions);
0688     } else {
0689         qCDebug(PLASMA_STARTUP) << "Using systemd boot";
0690         const QString platform = wayland ? QStringLiteral("wayland") : QStringLiteral("x11");
0691 
0692         auto msg = QDBusMessage::createMethodCall(QStringLiteral("org.freedesktop.systemd1"),
0693                                                   QStringLiteral("/org/freedesktop/systemd1"),
0694                                                   QStringLiteral("org.freedesktop.systemd1.Manager"),
0695                                                   QStringLiteral("StartUnit"));
0696         msg << QStringLiteral("plasma-workspace-%1.target").arg(platform) << QStringLiteral("fail");
0697         QDBusReply<QDBusObjectPath> reply = QDBusConnection::sessionBus().call(msg);
0698         if (!reply.isValid()) {
0699             qCWarning(PLASMA_STARTUP) << "Could not start systemd managed Plasma session:" << reply.error().name() << reply.error().message();
0700             messageBox(QStringLiteral("startkde: Could not start Plasma session.\n"));
0701             rc = false;
0702         } else {
0703             playStartupSound();
0704         }
0705         if (wayland) {
0706             startKSplashViaSystemd();
0707         }
0708     }
0709     if (rc) {
0710         QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, &e, &QEventLoop::quit);
0711         e.exec();
0712     }
0713     return rc;
0714 }
0715 
0716 void waitForKonqi()
0717 {
0718     const KConfig cfg(QStringLiteral("startkderc"));
0719     const KConfigGroup grp = cfg.group("WaitForDrKonqi");
0720     bool wait_drkonqi = grp.readEntry("Enabled", true);
0721     if (wait_drkonqi) {
0722         // wait for remaining drkonqi instances with timeout (in seconds)
0723         const int wait_drkonqi_timeout = grp.readEntry("Timeout", 900) * 1000;
0724         QElapsedTimer wait_drkonqi_counter;
0725         wait_drkonqi_counter.start();
0726         QStringList services = allServices(QLatin1String("org.kde.drkonqi-"));
0727         while (!services.isEmpty()) {
0728             sleep(5);
0729             services = allServices(QLatin1String("org.kde.drkonqi-"));
0730             if (wait_drkonqi_counter.elapsed() >= wait_drkonqi_timeout) {
0731                 // ask remaining drkonqis to die in a graceful way
0732                 for (const auto &service : qAsConst(services)) {
0733                     QDBusInterface iface(service, QStringLiteral("/MainApplication"));
0734                     iface.call(QStringLiteral("quit"));
0735                 }
0736                 break;
0737             }
0738         }
0739     }
0740 }
0741 
0742 void playStartupSound()
0743 {
0744     KNotifyConfig notifyConfig(QStringLiteral("plasma_workspace"), QList<QPair<QString, QString>>(), QStringLiteral("startkde"));
0745     const QString action = notifyConfig.readEntry(QStringLiteral("Action"));
0746     if (action.isEmpty() || !action.split(QLatin1Char('|')).contains(QLatin1String("Sound"))) {
0747         // no startup sound configured
0748         return;
0749     }
0750     Phonon::AudioOutput *audioOutput = new Phonon::AudioOutput(Phonon::NotificationCategory);
0751 
0752     QString soundFilename = notifyConfig.readEntry(QStringLiteral("Sound"));
0753     if (soundFilename.isEmpty()) {
0754         qCWarning(PLASMA_STARTUP) << "Audio notification requested, but no sound file provided in notifyrc file, aborting audio notification";
0755         audioOutput->deleteLater();
0756         return;
0757     }
0758 
0759     QUrl soundURL;
0760     const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation);
0761     for (const QString &dataLocation : dataLocations) {
0762         soundURL = QUrl::fromUserInput(soundFilename, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile);
0763         if (soundURL.isLocalFile() && QFile::exists(soundURL.toLocalFile())) {
0764             break;
0765         } else if (!soundURL.isLocalFile() && soundURL.isValid()) {
0766             break;
0767         }
0768         soundURL.clear();
0769     }
0770     if (soundURL.isEmpty()) {
0771         qCWarning(PLASMA_STARTUP) << "Audio notification requested, but sound file from notifyrc file was not found, aborting audio notification";
0772         audioOutput->deleteLater();
0773         return;
0774     }
0775 
0776     Phonon::MediaObject *mediaObject = new Phonon::MediaObject();
0777     Phonon::createPath(mediaObject, audioOutput);
0778     QObject::connect(mediaObject, &Phonon::MediaObject::finished, audioOutput, &QObject::deleteLater);
0779     QObject::connect(mediaObject, &Phonon::MediaObject::finished, mediaObject, &QObject::deleteLater);
0780 
0781     mediaObject->setCurrentSource(soundURL);
0782     mediaObject->play();
0783 }