File indexing completed on 2024-05-05 05:38:57

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