File indexing completed on 2024-04-28 16:48:55

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