File indexing completed on 2024-04-28 05:30:24

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich <ettrich@kde.org>
0006     SPDX-FileCopyrightText: 2003 Lubos Lunak <l.lunak@kde.org>
0007     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "main_x11.h"
0012 
0013 #include <config-kwin.h>
0014 
0015 #include "backends/x11/standalone/x11_standalone_backend.h"
0016 #include "compositor_x11.h"
0017 #include "core/outputbackend.h"
0018 #include "core/session.h"
0019 #include "cursor.h"
0020 #include "effect/effecthandler.h"
0021 #include "outline.h"
0022 #include "screenedge.h"
0023 #include "sm.h"
0024 #include "tabletmodemanager.h"
0025 #include "utils/xcbutils.h"
0026 #include "workspace.h"
0027 
0028 #include <KConfigGroup>
0029 #include <KCrash>
0030 #include <KGlobalAccel>
0031 #include <KLocalizedString>
0032 #include <KSelectionOwner>
0033 #include <KSignalHandler>
0034 
0035 #include <QAction>
0036 #include <QComboBox>
0037 #include <QCommandLineParser>
0038 #include <QDialog>
0039 #include <QDialogButtonBox>
0040 #include <QFile>
0041 #include <QLabel>
0042 #include <QPushButton>
0043 #include <QSurfaceFormat>
0044 #include <QVBoxLayout>
0045 #include <qplatformdefs.h>
0046 #include <private/qtx11extras_p.h>
0047 #include <QtDBus>
0048 
0049 // system
0050 #include <iostream>
0051 #include <unistd.h>
0052 
0053 Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtWarningMsg)
0054 
0055 namespace KWin
0056 {
0057 
0058 class AlternativeWMDialog : public QDialog
0059 {
0060 public:
0061     AlternativeWMDialog()
0062         : QDialog()
0063     {
0064         QWidget *mainWidget = new QWidget(this);
0065         QVBoxLayout *layout = new QVBoxLayout(mainWidget);
0066         QString text = i18n("KWin is unstable.\n"
0067                             "It seems to have crashed several times in a row.\n"
0068                             "You can select another window manager to run:");
0069         QLabel *textLabel = new QLabel(text, mainWidget);
0070         layout->addWidget(textLabel);
0071         wmList = new QComboBox(mainWidget);
0072         wmList->setEditable(true);
0073         layout->addWidget(wmList);
0074 
0075         addWM(QStringLiteral("metacity"));
0076         addWM(QStringLiteral("openbox"));
0077         addWM(QStringLiteral("fvwm2"));
0078         addWM(QStringLiteral("kwin_x11"));
0079 
0080         QVBoxLayout *mainLayout = new QVBoxLayout(this);
0081         mainLayout->addWidget(mainWidget);
0082         QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
0083         buttons->button(QDialogButtonBox::Ok)->setDefault(true);
0084         connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
0085         connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
0086         mainLayout->addWidget(buttons);
0087 
0088         raise();
0089     }
0090 
0091     void addWM(const QString &wm)
0092     {
0093         // TODO: Check if WM is installed
0094         if (!QStandardPaths::findExecutable(wm).isEmpty()) {
0095             wmList->addItem(wm);
0096         }
0097     }
0098     QString selectedWM() const
0099     {
0100         return wmList->currentText();
0101     }
0102 
0103 private:
0104     QComboBox *wmList;
0105 };
0106 
0107 class KWinSelectionOwner : public KSelectionOwner
0108 {
0109     Q_OBJECT
0110 public:
0111     explicit KWinSelectionOwner()
0112         : KSelectionOwner(make_selection_atom())
0113     {
0114     }
0115 
0116 private:
0117     bool genericReply(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) override
0118     {
0119         if (target_P == xa_version) {
0120             int32_t version[] = {2, 0};
0121             xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_REPLACE, requestor_P,
0122                                 property_P, XCB_ATOM_INTEGER, 32, 2, version);
0123         } else {
0124             return KSelectionOwner::genericReply(target_P, property_P, requestor_P);
0125         }
0126         return true;
0127     }
0128 
0129     void replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) override
0130     {
0131         KSelectionOwner::replyTargets(property_P, requestor_P);
0132         xcb_atom_t atoms[1] = {xa_version};
0133         // PropModeAppend !
0134         xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_APPEND, requestor_P,
0135                             property_P, XCB_ATOM_ATOM, 32, 1, atoms);
0136     }
0137 
0138     void getAtoms() override
0139     {
0140         KSelectionOwner::getAtoms();
0141         if (xa_version == XCB_ATOM_NONE) {
0142             constexpr QByteArrayView name{"VERSION"};
0143             UniqueCPtr<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(
0144                 kwinApp()->x11Connection(),
0145                 xcb_intern_atom_unchecked(kwinApp()->x11Connection(), false, name.length(), name.constData()),
0146                 nullptr));
0147             if (atom) {
0148                 xa_version = atom->atom;
0149             }
0150         }
0151     }
0152 
0153     xcb_atom_t make_selection_atom()
0154     {
0155         constexpr QByteArrayView screen{"WM_S0"};
0156         UniqueCPtr<xcb_intern_atom_reply_t> atom(xcb_intern_atom_reply(
0157             kwinApp()->x11Connection(),
0158             xcb_intern_atom_unchecked(kwinApp()->x11Connection(), false, screen.length(), screen.constData()),
0159             nullptr));
0160         if (!atom) {
0161             return XCB_ATOM_NONE;
0162         }
0163         return atom->atom;
0164     }
0165     static xcb_atom_t xa_version;
0166 };
0167 xcb_atom_t KWinSelectionOwner::xa_version = XCB_ATOM_NONE;
0168 
0169 //************************************
0170 // ApplicationX11
0171 //************************************
0172 
0173 ApplicationX11::ApplicationX11(int &argc, char **argv)
0174     : Application(OperationModeX11, argc, argv)
0175     , owner()
0176     , m_replace(false)
0177 {
0178     setX11Connection(QX11Info::connection());
0179     setX11RootWindow(QX11Info::appRootWindow());
0180 }
0181 
0182 ApplicationX11::~ApplicationX11()
0183 {
0184     setTerminating();
0185     // need to unload all effects before destroying Workspace, as effects might call into Workspace
0186     if (effects) {
0187         effects->unloadAllEffects();
0188     }
0189     destroyPlugins();
0190     destroyColorManager();
0191     destroyWorkspace();
0192     destroyCompositor();
0193     // If there was no --replace (no new WM)
0194     if (owner != nullptr && owner->ownerWindow() != XCB_WINDOW_NONE) {
0195         Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
0196     }
0197 }
0198 
0199 void ApplicationX11::setReplace(bool replace)
0200 {
0201     m_replace = replace;
0202 }
0203 
0204 std::unique_ptr<Edge> ApplicationX11::createScreenEdge(ScreenEdges *parent)
0205 {
0206     return static_cast<X11StandaloneBackend *>(outputBackend())->createScreenEdge(parent);
0207 }
0208 
0209 std::unique_ptr<Cursor> ApplicationX11::createPlatformCursor()
0210 {
0211     return static_cast<X11StandaloneBackend *>(outputBackend())->createPlatformCursor();
0212 }
0213 
0214 std::unique_ptr<OutlineVisual> ApplicationX11::createOutline(Outline *outline)
0215 {
0216     // first try composited Outline
0217     if (auto outlineVisual = Application::createOutline(outline)) {
0218         return outlineVisual;
0219     }
0220     return static_cast<X11StandaloneBackend *>(outputBackend())->createOutline(outline);
0221 }
0222 
0223 void ApplicationX11::createEffectsHandler(Compositor *compositor, WorkspaceScene *scene)
0224 {
0225     static_cast<X11StandaloneBackend *>(outputBackend())->createEffectsHandler(compositor, scene);
0226 }
0227 
0228 void ApplicationX11::startInteractiveWindowSelection(std::function<void(KWin::Window *)> callback, const QByteArray &cursorName)
0229 {
0230     static_cast<X11StandaloneBackend *>(outputBackend())->startInteractiveWindowSelection(callback, cursorName);
0231 }
0232 
0233 void ApplicationX11::startInteractivePositionSelection(std::function<void(const QPointF &)> callback)
0234 {
0235     static_cast<X11StandaloneBackend *>(outputBackend())->startInteractivePositionSelection(callback);
0236 }
0237 
0238 PlatformCursorImage ApplicationX11::cursorImage() const
0239 {
0240     return static_cast<X11StandaloneBackend *>(outputBackend())->cursorImage();
0241 }
0242 
0243 void ApplicationX11::lostSelection()
0244 {
0245     sendPostedEvents();
0246     // need to unload all effects before destroying Workspace, as effects might call into Workspace
0247     if (effects) {
0248         effects->unloadAllEffects();
0249     }
0250     destroyPlugins();
0251     destroyColorManager();
0252     destroyWorkspace();
0253     destroyCompositor();
0254     // Remove windowmanager privileges
0255     Xcb::selectInput(kwinApp()->x11RootWindow(), XCB_EVENT_MASK_PROPERTY_CHANGE);
0256     removeNativeX11EventFilter();
0257     quit();
0258 }
0259 
0260 void ApplicationX11::performStartup()
0261 {
0262     crashChecking();
0263 
0264     owner = std::make_unique<KWinSelectionOwner>();
0265     connect(owner.get(), &KSelectionOwner::failedToClaimOwnership, [] {
0266         fputs(i18n("kwin: unable to claim manager selection, another wm running? (try using --replace)\n").toLocal8Bit().constData(), stderr);
0267         ::exit(1);
0268     });
0269     connect(owner.get(), &KSelectionOwner::lostOwnership, this, &ApplicationX11::lostSelection);
0270     connect(owner.get(), &KSelectionOwner::claimedOwnership, this, [this] {
0271         installNativeX11EventFilter();
0272         createOptions();
0273 
0274         if (!outputBackend()->initialize()) {
0275             std::exit(1);
0276         }
0277 
0278         // Check  whether another windowmanager is running
0279         const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT};
0280         UniqueCPtr<xcb_generic_error_t> redirectCheck(xcb_request_check(kwinApp()->x11Connection(),
0281                                                                         xcb_change_window_attributes_checked(kwinApp()->x11Connection(),
0282                                                                                                              kwinApp()->x11RootWindow(),
0283                                                                                                              XCB_CW_EVENT_MASK,
0284                                                                                                              maskValues)));
0285         if (redirectCheck) {
0286             fputs(i18n("kwin: another window manager is running (try using --replace)\n").toLocal8Bit().constData(), stderr);
0287             if (!wasCrash()) { // if this is a crash-restart, DrKonqi may have stopped the process w/o killing the connection
0288                 ::exit(1);
0289             }
0290         }
0291 
0292         // Update the timestamp if a global shortcut is pressed or released. Needed
0293         // to ensure that kwin can grab the keyboard.
0294         connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutActiveChanged, this, [this](QAction *triggeredAction) {
0295             QVariant timestamp = triggeredAction->property("org.kde.kglobalaccel.activationTimestamp");
0296             bool ok = false;
0297             const quint32 t = timestamp.toULongLong(&ok);
0298             if (ok) {
0299                 setX11Time(t);
0300             }
0301         });
0302 
0303         createInput();
0304         X11Compositor::create(this);
0305         createWorkspace();
0306         createColorManager();
0307         createPlugins();
0308 
0309         Xcb::sync(); // Trigger possible errors, there's still a chance to abort
0310 
0311         notifyKSplash();
0312         notifyStarted();
0313     });
0314     // we need to do an XSync here, otherwise the QPA might crash us later on
0315     Xcb::sync();
0316     owner->claim(m_replace || wasCrash(), true);
0317 
0318     createAtoms();
0319 
0320     createTabletModeManager();
0321 }
0322 
0323 void ApplicationX11::setupCrashHandler()
0324 {
0325     KCrash::setEmergencySaveFunction(ApplicationX11::crashHandler);
0326 }
0327 
0328 void ApplicationX11::crashChecking()
0329 {
0330     setupCrashHandler();
0331     if (crashes >= 4) {
0332         // Something has gone seriously wrong
0333         AlternativeWMDialog dialog;
0334         QString cmd = QStringLiteral("kwin_x11");
0335         if (dialog.exec() == QDialog::Accepted) {
0336             cmd = dialog.selectedWM();
0337         } else {
0338             ::exit(1);
0339         }
0340         if (cmd.length() > 500) {
0341             qCDebug(KWIN_CORE) << "Command is too long, truncating";
0342             cmd = cmd.left(500);
0343         }
0344         qCDebug(KWIN_CORE) << "Starting" << cmd << "and exiting";
0345         char buf[1024];
0346         sprintf(buf, "%s &", cmd.toLatin1().data());
0347         system(buf);
0348         ::exit(1);
0349     }
0350     // Reset crashes count if we stay up for more that 15 seconds
0351     QTimer::singleShot(15 * 1000, this, &Application::resetCrashesCount);
0352 }
0353 
0354 void ApplicationX11::notifyKSplash()
0355 {
0356     // Tell KSplash that KWin has started
0357     QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"),
0358                                                                          QStringLiteral("/KSplash"),
0359                                                                          QStringLiteral("org.kde.KSplash"),
0360                                                                          QStringLiteral("setStage"));
0361     ksplashProgressMessage.setArguments(QList<QVariant>() << QStringLiteral("wm"));
0362     QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage);
0363 }
0364 
0365 void ApplicationX11::crashHandler(int signal)
0366 {
0367     crashes++;
0368 
0369     fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes);
0370     char cmd[1024];
0371     sprintf(cmd, "%s --crashes %d &",
0372             QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes);
0373 
0374     sleep(1);
0375     system(cmd);
0376 }
0377 
0378 } // namespace
0379 
0380 int main(int argc, char *argv[])
0381 {
0382     KWin::Application::setupMalloc();
0383     KWin::Application::setupLocalizedString();
0384 
0385     signal(SIGPIPE, SIG_IGN);
0386 
0387     // enforce xcb plugin, unfortunately command line switch has precedence
0388     setenv("QT_QPA_PLATFORM", "xcb", true);
0389 
0390     // disable highdpi scaling
0391     setenv("QT_ENABLE_HIGHDPI_SCALING", "0", true);
0392 
0393     qunsetenv("QT_SCALE_FACTOR");
0394     qunsetenv("QT_SCREEN_SCALE_FACTORS");
0395 
0396     // KSMServer talks to us directly on DBus.
0397     QCoreApplication::setAttribute(Qt::AA_DisableSessionManager);
0398     // For sharing thumbnails between our scene graph and qtquick.
0399     QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts);
0400 
0401     QSurfaceFormat format = QSurfaceFormat::defaultFormat();
0402     // shared opengl contexts must have the same reset notification policy
0403     format.setOptions(QSurfaceFormat::ResetNotification);
0404     // disables vsync for any QtQuick windows we create (BUG 406180)
0405     format.setSwapInterval(0);
0406     QSurfaceFormat::setDefaultFormat(format);
0407 
0408     KWin::ApplicationX11 a(argc, argv);
0409 
0410     // reset QT_QPA_PLATFORM so we don't propagate it to our children (e.g. apps launched from the overview effect)
0411     qunsetenv("QT_QPA_PLATFORM");
0412     qunsetenv("QT_ENABLE_HIGHDPI_SCALING");
0413 
0414     a.setProcessStartupEnvironment(QProcessEnvironment(QProcessEnvironment::InheritFromParent));
0415 
0416     KSignalHandler::self()->watchSignal(SIGTERM);
0417     KSignalHandler::self()->watchSignal(SIGINT);
0418     KSignalHandler::self()->watchSignal(SIGHUP);
0419     QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived,
0420                      &a, &QCoreApplication::exit);
0421 
0422     KWin::Application::createAboutData();
0423 
0424     QCommandLineOption replaceOption(QStringLiteral("replace"), i18n("Replace already-running ICCCM2.0-compliant window manager"));
0425 
0426     QCommandLineParser parser;
0427     a.setupCommandLine(&parser);
0428     parser.addOption(replaceOption);
0429 #if KWIN_BUILD_ACTIVITIES
0430     QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"),
0431                                           i18n("Disable KActivities integration."));
0432     parser.addOption(noActivitiesOption);
0433 #endif
0434 
0435     parser.process(a);
0436     a.processCommandLine(&parser);
0437     a.setReplace(parser.isSet(replaceOption));
0438 #if KWIN_BUILD_ACTIVITIES
0439     if (parser.isSet(noActivitiesOption)) {
0440         a.setUseKActivities(false);
0441     }
0442 #endif
0443 
0444     // perform sanity checks
0445     if (a.platformName().toLower() != QStringLiteral("xcb")) {
0446         fprintf(stderr, "%s: FATAL ERROR expecting platform xcb but got platform %s\n",
0447                 argv[0], qPrintable(a.platformName()));
0448         exit(1);
0449     }
0450     if (!QX11Info::display()) {
0451         fprintf(stderr, "%s: FATAL ERROR KWin requires Xlib support in the xcb plugin. Do not configure Qt with -no-xcb-xlib\n",
0452                 argv[0]);
0453         exit(1);
0454     }
0455 
0456     a.setSession(KWin::Session::create(KWin::Session::Type::Noop));
0457     a.setOutputBackend(std::make_unique<KWin::X11StandaloneBackend>());
0458     a.start();
0459 
0460     return a.exec();
0461 }
0462 
0463 #include "main_x11.moc"
0464 
0465 #include "moc_main_x11.cpp"