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 }