/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 1999, 2000 Matthias Ettrich SPDX-FileCopyrightText: 2003 Lubos Lunak SPDX-FileCopyrightText: 2014 Martin Gräßlin SPDX-License-Identifier: GPL-2.0-or-later */ #include "main_x11.h" #include #include "backends/x11/standalone/x11_standalone_backend.h" #include "core/outputbackend.h" #include "core/session.h" #include "outline.h" #include "screenedge.h" #include "sm.h" #include "tabletmodemanager.h" #include "utils/xcbutils.h" #include "workspace.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // system #include #include Q_LOGGING_CATEGORY(KWIN_CORE, "kwin_core", QtWarningMsg) namespace KWin { class AlternativeWMDialog : public QDialog { public: AlternativeWMDialog() : QDialog() { QWidget *mainWidget = new QWidget(this); QVBoxLayout *layout = new QVBoxLayout(mainWidget); QString text = i18n("KWin is unstable.\n" "It seems to have crashed several times in a row.\n" "You can select another window manager to run:"); QLabel *textLabel = new QLabel(text, mainWidget); layout->addWidget(textLabel); wmList = new QComboBox(mainWidget); wmList->setEditable(true); layout->addWidget(wmList); addWM(QStringLiteral("metacity")); addWM(QStringLiteral("openbox")); addWM(QStringLiteral("fvwm2")); addWM(QStringLiteral("kwin_x11")); QVBoxLayout *mainLayout = new QVBoxLayout(this); mainLayout->addWidget(mainWidget); QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); buttons->button(QDialogButtonBox::Ok)->setDefault(true); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); mainLayout->addWidget(buttons); raise(); } void addWM(const QString &wm) { // TODO: Check if WM is installed if (!QStandardPaths::findExecutable(wm).isEmpty()) { wmList->addItem(wm); } } QString selectedWM() const { return wmList->currentText(); } private: QComboBox *wmList; }; class KWinSelectionOwner : public KSelectionOwner { Q_OBJECT public: explicit KWinSelectionOwner() : KSelectionOwner(make_selection_atom()) { } private: bool genericReply(xcb_atom_t target_P, xcb_atom_t property_P, xcb_window_t requestor_P) override { if (target_P == xa_version) { int32_t version[] = {2, 0}; xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_REPLACE, requestor_P, property_P, XCB_ATOM_INTEGER, 32, 2, version); } else { return KSelectionOwner::genericReply(target_P, property_P, requestor_P); } return true; } void replyTargets(xcb_atom_t property_P, xcb_window_t requestor_P) override { KSelectionOwner::replyTargets(property_P, requestor_P); xcb_atom_t atoms[1] = {xa_version}; // PropModeAppend ! xcb_change_property(kwinApp()->x11Connection(), XCB_PROP_MODE_APPEND, requestor_P, property_P, XCB_ATOM_ATOM, 32, 1, atoms); } void getAtoms() override { KSelectionOwner::getAtoms(); if (xa_version == XCB_ATOM_NONE) { const QByteArray name(QByteArrayLiteral("VERSION")); UniqueCPtr atom(xcb_intern_atom_reply( kwinApp()->x11Connection(), xcb_intern_atom_unchecked(kwinApp()->x11Connection(), false, name.length(), name.constData()), nullptr)); if (atom) { xa_version = atom->atom; } } } xcb_atom_t make_selection_atom() { QByteArray screen(QByteArrayLiteral("WM_S0")); UniqueCPtr atom(xcb_intern_atom_reply( kwinApp()->x11Connection(), xcb_intern_atom_unchecked(kwinApp()->x11Connection(), false, screen.length(), screen.constData()), nullptr)); if (!atom) { return XCB_ATOM_NONE; } return atom->atom; } static xcb_atom_t xa_version; }; xcb_atom_t KWinSelectionOwner::xa_version = XCB_ATOM_NONE; //************************************ // ApplicationX11 //************************************ ApplicationX11::ApplicationX11(int &argc, char **argv) : Application(OperationModeX11, argc, argv) , owner() , m_replace(false) { setX11Connection(QX11Info::connection()); setX11RootWindow(QX11Info::appRootWindow()); } ApplicationX11::~ApplicationX11() { setTerminating(); destroyPlugins(); destroyCompositor(); destroyColorManager(); destroyWorkspace(); // If there was no --replace (no new WM) if (owner != nullptr && owner->ownerWindow() != XCB_WINDOW_NONE) { Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT); } } void ApplicationX11::setReplace(bool replace) { m_replace = replace; } std::unique_ptr ApplicationX11::createScreenEdge(ScreenEdges *parent) { return static_cast(outputBackend())->createScreenEdge(parent); } void ApplicationX11::createPlatformCursor(QObject *parent) { static_cast(outputBackend())->createPlatformCursor(parent); } std::unique_ptr ApplicationX11::createOutline(Outline *outline) { // first try composited Outline if (auto outlineVisual = Application::createOutline(outline)) { return outlineVisual; } return static_cast(outputBackend())->createOutline(outline); } void ApplicationX11::createEffectsHandler(Compositor *compositor, WorkspaceScene *scene) { static_cast(outputBackend())->createEffectsHandler(compositor, scene); } void ApplicationX11::startInteractiveWindowSelection(std::function callback, const QByteArray &cursorName) { static_cast(outputBackend())->startInteractiveWindowSelection(callback, cursorName); } void ApplicationX11::startInteractivePositionSelection(std::function callback) { static_cast(outputBackend())->startInteractivePositionSelection(callback); } PlatformCursorImage ApplicationX11::cursorImage() const { return static_cast(outputBackend())->cursorImage(); } void ApplicationX11::lostSelection() { sendPostedEvents(); destroyPlugins(); destroyCompositor(); destroyColorManager(); destroyWorkspace(); // Remove windowmanager privileges Xcb::selectInput(kwinApp()->x11RootWindow(), XCB_EVENT_MASK_PROPERTY_CHANGE); removeNativeX11EventFilter(); quit(); } void ApplicationX11::performStartup() { crashChecking(); owner.reset(new KWinSelectionOwner()); connect(owner.get(), &KSelectionOwner::failedToClaimOwnership, [] { fputs(i18n("kwin: unable to claim manager selection, another wm running? (try using --replace)\n").toLocal8Bit().constData(), stderr); ::exit(1); }); connect(owner.get(), &KSelectionOwner::lostOwnership, this, &ApplicationX11::lostSelection); connect(owner.get(), &KSelectionOwner::claimedOwnership, this, [this] { installNativeX11EventFilter(); // first load options - done internally by a different thread createOptions(); if (!outputBackend()->initialize()) { std::exit(1); } // Check whether another windowmanager is running const uint32_t maskValues[] = {XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT}; UniqueCPtr redirectCheck(xcb_request_check(kwinApp()->x11Connection(), xcb_change_window_attributes_checked(kwinApp()->x11Connection(), kwinApp()->x11RootWindow(), XCB_CW_EVENT_MASK, maskValues))); if (redirectCheck) { fputs(i18n("kwin: another window manager is running (try using --replace)\n").toLocal8Bit().constData(), stderr); if (!wasCrash()) { // if this is a crash-restart, DrKonqi may have stopped the process w/o killing the connection ::exit(1); } } // Update the timestamp if a global shortcut is pressed or released. Needed // to ensure that kwin can grab the keyboard. connect(KGlobalAccel::self(), &KGlobalAccel::globalShortcutActiveChanged, this, [this](QAction *triggeredAction) { QVariant timestamp = triggeredAction->property("org.kde.kglobalaccel.activationTimestamp"); bool ok = false; const quint32 t = timestamp.toULongLong(&ok); if (ok) { setX11Time(t); } }); createInput(); createWorkspace(); createColorManager(); createPlugins(); Xcb::sync(); // Trigger possible errors, there's still a chance to abort notifyKSplash(); notifyStarted(); }); // we need to do an XSync here, otherwise the QPA might crash us later on Xcb::sync(); owner->claim(m_replace || wasCrash(), true); createAtoms(); createTabletModeManager(); } void ApplicationX11::setupCrashHandler() { KCrash::setEmergencySaveFunction(ApplicationX11::crashHandler); } void ApplicationX11::crashChecking() { setupCrashHandler(); if (crashes >= 4) { // Something has gone seriously wrong AlternativeWMDialog dialog; QString cmd = QStringLiteral("kwin_x11"); if (dialog.exec() == QDialog::Accepted) { cmd = dialog.selectedWM(); } else { ::exit(1); } if (cmd.length() > 500) { qCDebug(KWIN_CORE) << "Command is too long, truncating"; cmd = cmd.left(500); } qCDebug(KWIN_CORE) << "Starting" << cmd << "and exiting"; char buf[1024]; sprintf(buf, "%s &", cmd.toLatin1().data()); system(buf); ::exit(1); } // Reset crashes count if we stay up for more that 15 seconds QTimer::singleShot(15 * 1000, this, &Application::resetCrashesCount); } void ApplicationX11::notifyKSplash() { // Tell KSplash that KWin has started QDBusMessage ksplashProgressMessage = QDBusMessage::createMethodCall(QStringLiteral("org.kde.KSplash"), QStringLiteral("/KSplash"), QStringLiteral("org.kde.KSplash"), QStringLiteral("setStage")); ksplashProgressMessage.setArguments(QList() << QStringLiteral("wm")); QDBusConnection::sessionBus().asyncCall(ksplashProgressMessage); } void ApplicationX11::crashHandler(int signal) { crashes++; fprintf(stderr, "Application::crashHandler() called with signal %d; recent crashes: %d\n", signal, crashes); char cmd[1024]; sprintf(cmd, "%s --crashes %d &", QFile::encodeName(QCoreApplication::applicationFilePath()).constData(), crashes); sleep(1); system(cmd); } } // namespace int main(int argc, char *argv[]) { KWin::Application::setupMalloc(); KWin::Application::setupLocalizedString(); signal(SIGPIPE, SIG_IGN); // enforce xcb plugin, unfortunately command line switch has precedence setenv("QT_QPA_PLATFORM", "xcb", true); // disable highdpi scaling setenv("QT_ENABLE_HIGHDPI_SCALING", "0", true); qunsetenv("QT_DEVICE_PIXEL_RATIO"); qunsetenv("QT_SCALE_FACTOR"); qunsetenv("QT_SCREEN_SCALE_FACTORS"); // KSMServer talks to us directly on DBus. QCoreApplication::setAttribute(Qt::AA_DisableSessionManager); // For sharing thumbnails between our scene graph and qtquick. QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QSurfaceFormat format = QSurfaceFormat::defaultFormat(); // shared opengl contexts must have the same reset notification policy format.setOptions(QSurfaceFormat::ResetNotification); // disables vsync for any QtQuick windows we create (BUG 406180) format.setSwapInterval(0); QSurfaceFormat::setDefaultFormat(format); KWin::ApplicationX11 a(argc, argv); // reset QT_QPA_PLATFORM so we don't propagate it to our children (e.g. apps launched from the overview effect) qunsetenv("QT_QPA_PLATFORM"); qunsetenv("QT_ENABLE_HIGHDPI_SCALING"); KSignalHandler::self()->watchSignal(SIGTERM); KSignalHandler::self()->watchSignal(SIGINT); KSignalHandler::self()->watchSignal(SIGHUP); QObject::connect(KSignalHandler::self(), &KSignalHandler::signalReceived, &a, &QCoreApplication::exit); KWin::Application::createAboutData(); QCommandLineOption replaceOption(QStringLiteral("replace"), i18n("Replace already-running ICCCM2.0-compliant window manager")); QCommandLineParser parser; a.setupCommandLine(&parser); parser.addOption(replaceOption); #if KWIN_BUILD_ACTIVITIES QCommandLineOption noActivitiesOption(QStringLiteral("no-kactivities"), i18n("Disable KActivities integration.")); parser.addOption(noActivitiesOption); #endif parser.process(a); a.processCommandLine(&parser); a.setReplace(parser.isSet(replaceOption)); #if KWIN_BUILD_ACTIVITIES if (parser.isSet(noActivitiesOption)) { a.setUseKActivities(false); } #endif // perform sanity checks if (a.platformName().toLower() != QStringLiteral("xcb")) { fprintf(stderr, "%s: FATAL ERROR expecting platform xcb but got platform %s\n", argv[0], qPrintable(a.platformName())); exit(1); } if (!QX11Info::display()) { fprintf(stderr, "%s: FATAL ERROR KWin requires Xlib support in the xcb plugin. Do not configure Qt with -no-xcb-xlib\n", argv[0]); exit(1); } a.setSession(KWin::Session::create(KWin::Session::Type::Noop)); a.setOutputBackend(std::make_unique()); a.start(); return a.exec(); } #include "main_x11.moc"