File indexing completed on 2024-04-28 05:35:58

0001 /*
0002     SPDX-FileCopyrightText: 2012 Marco Martin <mart@kde.org>
0003     SPDX-FileCopyrightText: 2013 Sebastian Kügler <sebas@kde.org>
0004     SPDX-FileCopyrightText: 2015 David Edmundson <davidedmundson@kde.org>
0005     SPDX-FileCopyrightText: 2023 Harald Sitter <sitter@kde.org>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <csignal>
0011 
0012 #include <QApplication>
0013 #include <QCommandLineParser>
0014 #include <QDBusConnection>
0015 #include <QDBusMessage>
0016 #include <QDebug>
0017 #include <QLoggingCategory>
0018 #include <QMessageBox>
0019 #include <QProcess>
0020 #include <QQmlDebuggingEnabler>
0021 #include <QQuickWindow>
0022 #include <QSessionManager>
0023 #include <QSurfaceFormat>
0024 
0025 #include <KAboutData>
0026 #include <KSignalHandler>
0027 
0028 #ifdef WITH_KUSERFEEDBACKCORE
0029 #include "userfeedback.h"
0030 #endif
0031 
0032 #include <kcrash.h>
0033 #include <kdbusservice.h>
0034 #include <klocalizedstring.h>
0035 
0036 #include "coronatesthelper.h"
0037 #include "debug.h"
0038 #include "shellcorona.h"
0039 #include "softwarerendernotifier.h"
0040 
0041 #include <QDBusConnectionInterface>
0042 #include <QDir>
0043 
0044 int main(int argc, char *argv[])
0045 {
0046 #if QT_CONFIG(qml_debug)
0047     if (qEnvironmentVariableIsSet("PLASMA_ENABLE_QML_DEBUG")) {
0048         QQmlDebuggingEnabler debugger;
0049     }
0050 #endif
0051 
0052     auto format = QSurfaceFormat::defaultFormat();
0053     format.setOption(QSurfaceFormat::ResetNotification);
0054     QSurfaceFormat::setDefaultFormat(format);
0055 
0056     QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
0057 
0058     QQuickWindow::setDefaultAlphaBuffer(true);
0059 
0060     // this works around a bug of Qt and the plasmashell protocol
0061     // consider disabling when layer-shell lands
0062     qputenv("QT_WAYLAND_DISABLE_FIXED_POSITIONS", {});
0063     // this variable controls whether to reconnect or exit if the compositor dies, given plasmashell does a lot of
0064     // bespoke wayland code disable for now. consider disabling when layer-shell lands
0065     qunsetenv("QT_WAYLAND_RECONNECT");
0066     QApplication app(argc, argv);
0067 
0068     qunsetenv("QT_WAYLAND_DISABLE_FIXED_POSITIONS");
0069     qputenv("QT_WAYLAND_RECONNECT", "1");
0070 
0071     KCrash::initialize();
0072 
0073     // Quit on SIGTERM to properly save state. See systemd.kill(5).
0074     // https://bugs.kde.org/show_bug.cgi?id=470604
0075     KSignalHandler::self()->watchSignal(SIGTERM);
0076     QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &app, [&app](int signal) {
0077         if (signal == SIGTERM) {
0078             app.quit();
0079         }
0080     });
0081 
0082     KLocalizedString::setApplicationDomain(QByteArrayLiteral("plasmashell"));
0083 
0084     // The executable's path is added to the library/plugin paths.
0085     // This does not make much sense for plasmashell.
0086     app.removeLibraryPath(QCoreApplication::applicationDirPath());
0087 
0088     KAboutData aboutData(QStringLiteral("plasmashell"), QString(), QStringLiteral(PROJECT_VERSION), i18n("Plasma Shell"), KAboutLicense::GPL);
0089 
0090     KAboutData::setApplicationData(aboutData);
0091 
0092     app.setQuitOnLastWindowClosed(false);
0093 
0094     bool replace = false;
0095 
0096     ShellCorona *corona = nullptr;
0097     {
0098         QCommandLineParser cliOptions;
0099 
0100         QCommandLineOption dbgOption(QStringList() << QStringLiteral("d") << QStringLiteral("qmljsdebugger"), i18n("Enable QML Javascript debugger"));
0101 
0102         QCommandLineOption noRespawnOption(QStringList() << QStringLiteral("n") << QStringLiteral("no-respawn"),
0103                                            i18n("Do not restart plasma-shell automatically after a crash"));
0104 
0105         QCommandLineOption shellPluginOption(QStringList() << QStringLiteral("p") << QStringLiteral("shell-plugin"),
0106                                              i18n("Force loading the given shell plugin"),
0107                                              QStringLiteral("plugin"),
0108                                              ShellCorona::defaultShell());
0109 
0110         QCommandLineOption replaceOption({QStringLiteral("replace")}, i18n("Replace an existing instance"));
0111 
0112         QCommandLineOption testOption(QStringList() << QStringLiteral("test"),
0113                                       i18n("Enables test mode and specifies the layout javascript file to set up the testing environment"),
0114                                       i18n("file"),
0115                                       QStringLiteral("layout.js"));
0116 
0117 #ifdef WITH_KUSERFEEDBACKCORE
0118         QCommandLineOption feedbackOption(QStringList() << QStringLiteral("feedback"), i18n("Lists the available options for user feedback"));
0119 #endif
0120         cliOptions.addOption(dbgOption);
0121         cliOptions.addOption(noRespawnOption);
0122         cliOptions.addOption(shellPluginOption);
0123         cliOptions.addOption(testOption);
0124         cliOptions.addOption(replaceOption);
0125 #ifdef WITH_KUSERFEEDBACKCORE
0126         cliOptions.addOption(feedbackOption);
0127 #endif
0128 
0129         aboutData.setupCommandLine(&cliOptions);
0130         cliOptions.process(app);
0131         aboutData.processCommandLine(&cliOptions);
0132 
0133         // don't let the first KJob terminate us
0134         QCoreApplication::setQuitLockEnabled(false);
0135 
0136         auto disableSessionManagement = [](QSessionManager &sm) {
0137             sm.setRestartHint(QSessionManager::RestartNever);
0138         };
0139         QObject::connect(&app, &QGuiApplication::commitDataRequest, disableSessionManagement);
0140         QObject::connect(&app, &QGuiApplication::saveStateRequest, disableSessionManagement);
0141 
0142         corona = new ShellCorona(&app);
0143         corona->setShell(cliOptions.value(shellPluginOption));
0144         if (!corona->kPackage().isValid()) {
0145             qCritical() << "starting invalid corona" << corona->shell();
0146             return 1;
0147         }
0148 
0149 #ifdef WITH_KUSERFEEDBACKCORE
0150         auto userFeedback = new UserFeedback(corona, &app);
0151         if (cliOptions.isSet(feedbackOption)) {
0152             QTextStream(stdout) << userFeedback->describeDataSources();
0153             return 0;
0154         }
0155 #endif
0156 
0157         QObject::connect(QCoreApplication::instance(), &QCoreApplication::aboutToQuit, corona, &QObject::deleteLater);
0158 
0159         if (!cliOptions.isSet(noRespawnOption) && !cliOptions.isSet(testOption)) {
0160             KCrash::setFlags(KCrash::AutoRestart);
0161         }
0162 
0163         if (cliOptions.isSet(testOption)) {
0164             const QUrl layoutUrl = QUrl::fromUserInput(cliOptions.value(testOption), {}, QUrl::AssumeLocalFile);
0165             if (!layoutUrl.isLocalFile()) {
0166                 qCWarning(PLASMASHELL) << "ensure the layout file is local" << layoutUrl;
0167                 cliOptions.showHelp(1);
0168             }
0169 
0170             QStandardPaths::setTestModeEnabled(true);
0171             QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).removeRecursively();
0172             corona->setTestModeLayout(layoutUrl.toLocalFile());
0173 
0174             qApp->setProperty("org.kde.KActivities.core.disableAutostart", true);
0175 
0176             new CoronaTestHelper(corona);
0177         }
0178 
0179         // Tells libnotificationmanager that we're the only true application that may own notification and job progress services
0180         qApp->setProperty("_plasma_dbus_master", true);
0181 
0182         QObject::connect(corona, &ShellCorona::glInitializationFailed, &app, [&app]() {
0183             // scene graphs errors come from a thread
0184             // even though we process them in the main thread, app.exit could still process these events
0185             static bool s_multipleInvokations = false;
0186             if (s_multipleInvokations) {
0187                 return;
0188             }
0189             s_multipleInvokations = true;
0190 
0191             qCritical("Open GL context could not be created");
0192             auto configGroup = KSharedConfig::openConfig()->group(QStringLiteral("QtQuickRendererSettings"));
0193             if (configGroup.readEntry("SceneGraphBackend") != QLatin1String("software")) {
0194                 configGroup.writeEntry("SceneGraphBackend", "software", KConfigBase::Global | KConfigBase::Persistent);
0195                 configGroup.sync();
0196                 QProcess::startDetached(QStringLiteral("plasmashell"), app.arguments());
0197             } else {
0198                 QCoreApplication::setAttribute(Qt::AA_ForceRasterWidgets);
0199                 QMessageBox::critical(nullptr,
0200                                       i18n("Plasma Failed To Start"),
0201                                       i18n("Plasma is unable to start as it could not correctly use OpenGL 2 or software fallback\nPlease check that your "
0202                                            "graphic drivers are set up correctly."));
0203             }
0204             app.exit(-1);
0205         });
0206         replace = cliOptions.isSet(replaceOption);
0207     }
0208 
0209     KDBusService service(KDBusService::Unique | KDBusService::StartupOption(replace ? KDBusService::Replace : 0));
0210 
0211     corona->init();
0212     SoftwareRendererNotifier::notifyIfRelevant();
0213 
0214     return app.exec();
0215 }