/* KWin - the KDE window manager This file is part of the KDE project. SPDX-FileCopyrightText: 2019 Roman Gilg SPDX-FileCopyrightText: 2020 Vlad Zahorodnii SPDX-FileCopyrightText: 2022 David Edmundson SPDX-License-Identifier: GPL-2.0-or-later */ #include "xwaylandlauncher.h" #include #include "xwayland_logging.h" #include "xwaylandsocket.h" #include "options.h" #include "wayland_server.h" #if KWIN_BUILD_NOTIFICATIONS #include #include #endif #include #include #include #include #include #include #include #include // system #include #include #include #include namespace KWin { namespace Xwl { XwaylandLauncher::XwaylandLauncher(QObject *parent) : QObject(parent) { m_resetCrashCountTimer = new QTimer(this); m_resetCrashCountTimer->setSingleShot(true); connect(m_resetCrashCountTimer, &QTimer::timeout, this, &XwaylandLauncher::resetCrashCount); } XwaylandLauncher::~XwaylandLauncher() { } void XwaylandLauncher::setListenFDs(const QVector &listenFds) { m_listenFds = listenFds; } void XwaylandLauncher::setDisplayName(const QString &displayName) { m_displayName = displayName; } void XwaylandLauncher::setXauthority(const QString &xauthority) { m_xAuthority = xauthority; } void XwaylandLauncher::enable() { if (m_enabled) { return; } m_enabled = true; if (!m_listenFds.isEmpty()) { Q_ASSERT(!m_displayName.isEmpty()); } else { m_socket = std::make_unique(XwaylandSocket::OperationMode::CloseFdsOnExec); if (!m_socket->isValid()) { qFatal("Failed to establish X11 socket"); } m_displayName = m_socket->name(); m_listenFds = m_socket->fileDescriptors(); } for (int socket : qAsConst(m_listenFds)) { QSocketNotifier *notifier = new QSocketNotifier(socket, QSocketNotifier::Read, this); connect(notifier, &QSocketNotifier::activated, this, [this]() { if (!m_xwaylandProcess) { start(); } }); connect(this, &XwaylandLauncher::started, notifier, [notifier]() { notifier->setEnabled(false); }); connect(this, &XwaylandLauncher::finished, notifier, [this, notifier]() { // only reactivate if we've not shut down due to the crash count notifier->setEnabled(m_enabled); }); } } void XwaylandLauncher::disable() { m_enabled = false; stop(); } bool XwaylandLauncher::start() { Q_ASSERT(m_enabled); if (m_xwaylandProcess) { return false; } QVector fdsToClose; auto cleanup = qScopeGuard([&fdsToClose] { for (const int fd : std::as_const(fdsToClose)) { close(fd); } }); int pipeFds[2]; if (pipe(pipeFds) != 0) { qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno)); Q_EMIT errorOccurred(); return false; } fdsToClose << pipeFds[1]; int sx[2]; if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno)); Q_EMIT errorOccurred(); return false; } int fd = dup(sx[1]); if (fd < 0) { qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno)); Q_EMIT errorOccurred(); return false; } const int waylandSocket = waylandServer()->createXWaylandConnection(); if (waylandSocket == -1) { qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno)); Q_EMIT errorOccurred(); return false; } const int wlfd = dup(waylandSocket); if (wlfd < 0) { qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno)); Q_EMIT errorOccurred(); return false; } m_xcbConnectionFd = sx[0]; QStringList arguments; arguments << m_displayName; if (!m_listenFds.isEmpty()) { // xauthority externally set and managed if (!m_xAuthority.isEmpty()) { arguments << QStringLiteral("-auth") << m_xAuthority; } for (int socket : std::as_const(m_listenFds)) { int dupSocket = dup(socket); fdsToClose << dupSocket; #if HAVE_XWAYLAND_LISTENFD arguments << QStringLiteral("-listenfd") << QString::number(dupSocket); #else arguments << QStringLiteral("-listen") << QString::number(dupSocket); #endif } } arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]); arguments << QStringLiteral("-rootless"); arguments << QStringLiteral("-wm") << QString::number(fd); m_xwaylandProcess = new QProcess(this); m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); if (qEnvironmentVariableIsSet("KWIN_XWAYLAND_DEBUG")) { env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1")); } m_xwaylandProcess->setProcessEnvironment(env); m_xwaylandProcess->setArguments(arguments); connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &XwaylandLauncher::handleXwaylandError); connect(m_xwaylandProcess, QOverload::of(&QProcess::finished), this, &XwaylandLauncher::handleXwaylandFinished); // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively, // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off. m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this); connect(m_readyNotifier, &QSocketNotifier::activated, this, [this]() { maybeDestroyReadyNotifier(); Q_EMIT started(); }); m_xwaylandProcess->start(); return true; } QString XwaylandLauncher::displayName() const { return m_displayName; } QString XwaylandLauncher::xauthority() const { return m_xAuthority; } int XwaylandLauncher::xcbConnectionFd() const { return m_xcbConnectionFd; } QProcess *XwaylandLauncher::process() const { return m_xwaylandProcess; } void XwaylandLauncher::stop() { if (!m_xwaylandProcess) { return; } Q_EMIT finished(); maybeDestroyReadyNotifier(); waylandServer()->destroyXWaylandConnection(); // When the Xwayland process is finally terminated, the finished() signal will be emitted, // however we don't actually want to process it anymore. Furthermore, we also don't really // want to handle any errors that may occur during the teardown. if (m_xwaylandProcess->state() != QProcess::NotRunning) { disconnect(m_xwaylandProcess, nullptr, this, nullptr); m_xwaylandProcess->terminate(); m_xwaylandProcess->waitForFinished(5000); } delete m_xwaylandProcess; m_xwaylandProcess = nullptr; } void XwaylandLauncher::maybeDestroyReadyNotifier() { if (m_readyNotifier) { close(m_readyNotifier->socket()); delete m_readyNotifier; m_readyNotifier = nullptr; } } void XwaylandLauncher::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KWIN_XWL) << "Xwayland process has quit with exit status:" << exitStatus << "exit code:" << exitCode; #if KWIN_BUILD_NOTIFICATIONS KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed")); #endif m_resetCrashCountTimer->stop(); switch (options->xwaylandCrashPolicy()) { case XwaylandCrashPolicy::Restart: if (++m_crashCount <= options->xwaylandMaxCrashCount()) { stop(); m_resetCrashCountTimer->start(std::chrono::minutes(10)); } else { qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times " "over the past 10 minutes", m_crashCount); disable(); } break; case XwaylandCrashPolicy::Stop: disable(); break; } } void XwaylandLauncher::resetCrashCount() { qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount; m_crashCount = 0; } void XwaylandLauncher::handleXwaylandError(QProcess::ProcessError error) { switch (error) { case QProcess::FailedToStart: qCWarning(KWIN_XWL) << "Xwayland process failed to start"; return; case QProcess::Crashed: qCWarning(KWIN_XWL) << "Xwayland process crashed"; break; case QProcess::Timedout: qCWarning(KWIN_XWL) << "Xwayland operation timed out"; break; case QProcess::WriteError: case QProcess::ReadError: qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland"; break; case QProcess::UnknownError: qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland"; break; } Q_EMIT errorOccurred(); } } } #include "moc_xwaylandlauncher.cpp"