File indexing completed on 2024-05-12 05:32:36

0001 /*
0002     KWin - the KDE window manager
0003     This file is part of the KDE project.
0004 
0005     SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
0006     SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
0007     SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
0008 
0009     SPDX-License-Identifier: GPL-2.0-or-later
0010 */
0011 #include "xwaylandlauncher.h"
0012 
0013 #include <config-kwin.h>
0014 
0015 #include "xwayland_logging.h"
0016 #include "xwaylandsocket.h"
0017 
0018 #include "options.h"
0019 #include "wayland_server.h"
0020 
0021 #if KWIN_BUILD_NOTIFICATIONS
0022 #include <KLocalizedString>
0023 #include <KNotification>
0024 #endif
0025 
0026 #include <QAbstractEventDispatcher>
0027 #include <QDataStream>
0028 #include <QFile>
0029 #include <QRandomGenerator>
0030 #include <QScopeGuard>
0031 #include <QSocketNotifier>
0032 #include <QTimer>
0033 
0034 // system
0035 #include <cerrno>
0036 #include <cstring>
0037 #include <sys/socket.h>
0038 #include <unistd.h>
0039 
0040 namespace KWin
0041 {
0042 namespace Xwl
0043 {
0044 
0045 XwaylandLauncher::XwaylandLauncher(QObject *parent)
0046     : QObject(parent)
0047 {
0048     m_resetCrashCountTimer = new QTimer(this);
0049     m_resetCrashCountTimer->setSingleShot(true);
0050     connect(m_resetCrashCountTimer, &QTimer::timeout, this, &XwaylandLauncher::resetCrashCount);
0051 }
0052 
0053 XwaylandLauncher::~XwaylandLauncher()
0054 {
0055 }
0056 
0057 void XwaylandLauncher::setListenFDs(const QList<int> &listenFds)
0058 {
0059     m_listenFds = listenFds;
0060 }
0061 
0062 void XwaylandLauncher::setDisplayName(const QString &displayName)
0063 {
0064     m_displayName = displayName;
0065 }
0066 
0067 void XwaylandLauncher::setXauthority(const QString &xauthority)
0068 {
0069     m_xAuthority = xauthority;
0070 }
0071 
0072 void XwaylandLauncher::enable()
0073 {
0074     if (m_enabled) {
0075         return;
0076     }
0077     m_enabled = true;
0078 
0079     if (!m_listenFds.isEmpty()) {
0080         Q_ASSERT(!m_displayName.isEmpty());
0081     } else {
0082         m_socket = std::make_unique<XwaylandSocket>(XwaylandSocket::OperationMode::CloseFdsOnExec);
0083         if (!m_socket->isValid()) {
0084             qFatal("Failed to establish X11 socket");
0085         }
0086         m_displayName = m_socket->name();
0087         m_listenFds = m_socket->fileDescriptors();
0088     }
0089 
0090     for (int socket : std::as_const(m_listenFds)) {
0091         QSocketNotifier *notifier = new QSocketNotifier(socket, QSocketNotifier::Read, this);
0092         connect(notifier, &QSocketNotifier::activated, this, [this]() {
0093             if (!m_xwaylandProcess) {
0094                 start();
0095             }
0096         });
0097         connect(this, &XwaylandLauncher::started, notifier, [notifier]() {
0098             notifier->setEnabled(false);
0099         });
0100         connect(this, &XwaylandLauncher::finished, notifier, [this, notifier]() {
0101             // only reactivate if we've not shut down due to the crash count
0102             notifier->setEnabled(m_enabled);
0103         });
0104     }
0105 }
0106 
0107 void XwaylandLauncher::disable()
0108 {
0109     m_enabled = false;
0110     stop();
0111 }
0112 
0113 bool XwaylandLauncher::start()
0114 {
0115     Q_ASSERT(m_enabled);
0116     if (m_xwaylandProcess) {
0117         return false;
0118     }
0119     QList<int> fdsToClose;
0120     auto cleanup = qScopeGuard([&fdsToClose] {
0121         for (const int fd : std::as_const(fdsToClose)) {
0122             close(fd);
0123         }
0124     });
0125 
0126     int pipeFds[2];
0127     if (pipe(pipeFds) != 0) {
0128         qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
0129         Q_EMIT errorOccurred();
0130         return false;
0131     }
0132     fdsToClose << pipeFds[1];
0133 
0134     int sx[2];
0135     if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
0136         qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
0137         Q_EMIT errorOccurred();
0138         return false;
0139     }
0140     int fd = dup(sx[1]);
0141     if (fd < 0) {
0142         qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
0143         Q_EMIT errorOccurred();
0144         return false;
0145     }
0146 
0147     const int waylandSocket = waylandServer()->createXWaylandConnection();
0148     if (waylandSocket == -1) {
0149         qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
0150         Q_EMIT errorOccurred();
0151         return false;
0152     }
0153     const int wlfd = dup(waylandSocket);
0154     if (wlfd < 0) {
0155         qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
0156         Q_EMIT errorOccurred();
0157         return false;
0158     }
0159 
0160     m_xcbConnectionFd = sx[0];
0161 
0162     QStringList arguments;
0163 
0164     arguments << m_displayName;
0165 
0166     if (!m_listenFds.isEmpty()) {
0167         // xauthority externally set and managed
0168         if (!m_xAuthority.isEmpty()) {
0169             arguments << QStringLiteral("-auth") << m_xAuthority;
0170         }
0171 
0172         for (int socket : std::as_const(m_listenFds)) {
0173             int dupSocket = dup(socket);
0174             fdsToClose << dupSocket;
0175 #if HAVE_XWAYLAND_LISTENFD
0176             arguments << QStringLiteral("-listenfd") << QString::number(dupSocket);
0177 #else
0178             arguments << QStringLiteral("-listen") << QString::number(dupSocket);
0179 #endif
0180         }
0181     }
0182 
0183     arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
0184     arguments << QStringLiteral("-rootless");
0185     arguments << QStringLiteral("-wm") << QString::number(fd);
0186 
0187     m_xwaylandProcess = new QProcess(this);
0188     m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
0189     m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
0190     QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
0191     env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
0192     if (qEnvironmentVariableIntValue("KWIN_XWAYLAND_DEBUG") == 1) {
0193         env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
0194     }
0195     m_xwaylandProcess->setProcessEnvironment(env);
0196     m_xwaylandProcess->setArguments(arguments);
0197     connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &XwaylandLauncher::handleXwaylandError);
0198     connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
0199             this, &XwaylandLauncher::handleXwaylandFinished);
0200 
0201     // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively,
0202     // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off.
0203     m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this);
0204     connect(m_readyNotifier, &QSocketNotifier::activated, this, [this]() {
0205         maybeDestroyReadyNotifier();
0206         Q_EMIT started();
0207     });
0208 
0209     m_xwaylandProcess->start();
0210 
0211     return true;
0212 }
0213 
0214 QString XwaylandLauncher::displayName() const
0215 {
0216     return m_displayName;
0217 }
0218 
0219 QString XwaylandLauncher::xauthority() const
0220 {
0221     return m_xAuthority;
0222 }
0223 
0224 int XwaylandLauncher::xcbConnectionFd() const
0225 {
0226     return m_xcbConnectionFd;
0227 }
0228 
0229 QProcess *XwaylandLauncher::process() const
0230 {
0231     return m_xwaylandProcess;
0232 }
0233 
0234 void XwaylandLauncher::stop()
0235 {
0236     if (!m_xwaylandProcess) {
0237         return;
0238     }
0239     Q_EMIT finished();
0240 
0241     maybeDestroyReadyNotifier();
0242     waylandServer()->destroyXWaylandConnection();
0243 
0244     // When the Xwayland process is finally terminated, the finished() signal will be emitted,
0245     // however we don't actually want to process it anymore. Furthermore, we also don't really
0246     // want to handle any errors that may occur during the teardown.
0247     if (m_xwaylandProcess->state() != QProcess::NotRunning) {
0248         disconnect(m_xwaylandProcess, nullptr, this, nullptr);
0249         m_xwaylandProcess->terminate();
0250         m_xwaylandProcess->waitForFinished(5000);
0251     }
0252     delete m_xwaylandProcess;
0253     m_xwaylandProcess = nullptr;
0254 }
0255 
0256 void XwaylandLauncher::maybeDestroyReadyNotifier()
0257 {
0258     if (m_readyNotifier) {
0259         close(m_readyNotifier->socket());
0260 
0261         delete m_readyNotifier;
0262         m_readyNotifier = nullptr;
0263     }
0264 }
0265 
0266 void XwaylandLauncher::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
0267 {
0268     qCDebug(KWIN_XWL) << "Xwayland process has quit with exit status:" << exitStatus << "exit code:" << exitCode;
0269 
0270 #if KWIN_BUILD_NOTIFICATIONS
0271     KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
0272 #endif
0273     m_resetCrashCountTimer->stop();
0274 
0275     switch (options->xwaylandCrashPolicy()) {
0276     case XwaylandCrashPolicy::Restart:
0277         if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
0278             stop();
0279             m_resetCrashCountTimer->start(std::chrono::minutes(10));
0280         } else {
0281             qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
0282                                 "over the past 10 minutes",
0283                       m_crashCount);
0284             disable();
0285         }
0286         break;
0287     case XwaylandCrashPolicy::Stop:
0288         disable();
0289         break;
0290     }
0291 }
0292 
0293 void XwaylandLauncher::resetCrashCount()
0294 {
0295     qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
0296     m_crashCount = 0;
0297 }
0298 
0299 void XwaylandLauncher::handleXwaylandError(QProcess::ProcessError error)
0300 {
0301     switch (error) {
0302     case QProcess::FailedToStart:
0303         qCWarning(KWIN_XWL) << "Xwayland process failed to start";
0304         return;
0305     case QProcess::Crashed:
0306         qCWarning(KWIN_XWL) << "Xwayland process crashed";
0307         break;
0308     case QProcess::Timedout:
0309         qCWarning(KWIN_XWL) << "Xwayland operation timed out";
0310         break;
0311     case QProcess::WriteError:
0312     case QProcess::ReadError:
0313         qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
0314         break;
0315     case QProcess::UnknownError:
0316         qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
0317         break;
0318     }
0319     Q_EMIT errorOccurred();
0320 }
0321 
0322 }
0323 }
0324 
0325 #include "moc_xwaylandlauncher.cpp"