File indexing completed on 2025-03-16 11:21:58
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 <QHostInfo> 0030 #include <QRandomGenerator> 0031 #include <QScopeGuard> 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 QVector<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::start() 0073 { 0074 if (m_xwaylandProcess) { 0075 return; 0076 } 0077 0078 if (!m_listenFds.isEmpty()) { 0079 Q_ASSERT(!m_displayName.isEmpty()); 0080 } else { 0081 m_socket.reset(new XwaylandSocket(XwaylandSocket::OperationMode::CloseFdsOnExec)); 0082 if (!m_socket->isValid()) { 0083 qFatal("Failed to establish X11 socket"); 0084 } 0085 m_displayName = m_socket->name(); 0086 m_listenFds = m_socket->fileDescriptors(); 0087 } 0088 0089 startInternal(); 0090 } 0091 0092 bool XwaylandLauncher::startInternal() 0093 { 0094 Q_ASSERT(!m_xwaylandProcess); 0095 0096 QVector<int> fdsToClose; 0097 auto cleanup = qScopeGuard([&fdsToClose] { 0098 for (const int fd : std::as_const(fdsToClose)) { 0099 close(fd); 0100 } 0101 }); 0102 0103 int pipeFds[2]; 0104 if (pipe(pipeFds) != 0) { 0105 qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno)); 0106 Q_EMIT errorOccurred(); 0107 return false; 0108 } 0109 fdsToClose << pipeFds[1]; 0110 0111 int sx[2]; 0112 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) { 0113 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno)); 0114 Q_EMIT errorOccurred(); 0115 return false; 0116 } 0117 int fd = dup(sx[1]); 0118 if (fd < 0) { 0119 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno)); 0120 Q_EMIT errorOccurred(); 0121 return false; 0122 } 0123 0124 const int waylandSocket = waylandServer()->createXWaylandConnection(); 0125 if (waylandSocket == -1) { 0126 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno)); 0127 Q_EMIT errorOccurred(); 0128 return false; 0129 } 0130 const int wlfd = dup(waylandSocket); 0131 if (wlfd < 0) { 0132 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno)); 0133 Q_EMIT errorOccurred(); 0134 return false; 0135 } 0136 0137 m_xcbConnectionFd = sx[0]; 0138 0139 QStringList arguments; 0140 0141 arguments << m_displayName; 0142 0143 if (!m_listenFds.isEmpty()) { 0144 // xauthority externally set and managed 0145 if (!m_xAuthority.isEmpty()) { 0146 arguments << QStringLiteral("-auth") << m_xAuthority; 0147 } 0148 0149 for (int socket : std::as_const(m_listenFds)) { 0150 int dupSocket = dup(socket); 0151 fdsToClose << dupSocket; 0152 #if HAVE_XWAYLAND_LISTENFD 0153 arguments << QStringLiteral("-listenfd") << QString::number(dupSocket); 0154 #else 0155 arguments << QStringLiteral("-listen") << QString::number(dupSocket); 0156 #endif 0157 } 0158 } 0159 0160 arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]); 0161 arguments << QStringLiteral("-rootless"); 0162 arguments << QStringLiteral("-wm") << QString::number(fd); 0163 0164 m_xwaylandProcess = new QProcess(this); 0165 m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel); 0166 m_xwaylandProcess->setProgram(QStringLiteral("Xwayland")); 0167 QProcessEnvironment env = QProcessEnvironment::systemEnvironment(); 0168 env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd)); 0169 if (qEnvironmentVariableIsSet("KWIN_XWAYLAND_DEBUG")) { 0170 env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1")); 0171 } 0172 m_xwaylandProcess->setProcessEnvironment(env); 0173 m_xwaylandProcess->setArguments(arguments); 0174 connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &XwaylandLauncher::handleXwaylandError); 0175 connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished), 0176 this, &XwaylandLauncher::handleXwaylandFinished); 0177 0178 // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively, 0179 // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off. 0180 m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this); 0181 connect(m_readyNotifier, &QSocketNotifier::activated, this, [this]() { 0182 maybeDestroyReadyNotifier(); 0183 Q_EMIT started(); 0184 }); 0185 0186 m_xwaylandProcess->start(); 0187 0188 return true; 0189 } 0190 0191 void XwaylandLauncher::stop() 0192 { 0193 if (!m_xwaylandProcess) { 0194 return; 0195 } 0196 0197 stopInternal(); 0198 } 0199 0200 QString XwaylandLauncher::displayName() const 0201 { 0202 return m_displayName; 0203 } 0204 0205 QString XwaylandLauncher::xauthority() const 0206 { 0207 return m_xAuthority; 0208 } 0209 0210 int XwaylandLauncher::xcbConnectionFd() const 0211 { 0212 return m_xcbConnectionFd; 0213 } 0214 0215 QProcess *XwaylandLauncher::process() const 0216 { 0217 return m_xwaylandProcess; 0218 } 0219 0220 void XwaylandLauncher::stopInternal() 0221 { 0222 Q_EMIT finished(); 0223 0224 maybeDestroyReadyNotifier(); 0225 waylandServer()->destroyXWaylandConnection(); 0226 0227 // When the Xwayland process is finally terminated, the finished() signal will be emitted, 0228 // however we don't actually want to process it anymore. Furthermore, we also don't really 0229 // want to handle any errors that may occur during the teardown. 0230 if (m_xwaylandProcess->state() != QProcess::NotRunning) { 0231 disconnect(m_xwaylandProcess, nullptr, this, nullptr); 0232 m_xwaylandProcess->terminate(); 0233 m_xwaylandProcess->waitForFinished(5000); 0234 } 0235 delete m_xwaylandProcess; 0236 m_xwaylandProcess = nullptr; 0237 } 0238 0239 void XwaylandLauncher::restartInternal() 0240 { 0241 if (m_xwaylandProcess) { 0242 stopInternal(); 0243 } 0244 startInternal(); 0245 } 0246 0247 void XwaylandLauncher::maybeDestroyReadyNotifier() 0248 { 0249 if (m_readyNotifier) { 0250 close(m_readyNotifier->socket()); 0251 0252 delete m_readyNotifier; 0253 m_readyNotifier = nullptr; 0254 } 0255 } 0256 0257 void XwaylandLauncher::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus) 0258 { 0259 qCDebug(KWIN_XWL) << "Xwayland process has quit with exit status:" << exitStatus << "exit code:" << exitCode; 0260 0261 #if KWIN_BUILD_NOTIFICATIONS 0262 KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed")); 0263 #endif 0264 m_resetCrashCountTimer->stop(); 0265 0266 switch (options->xwaylandCrashPolicy()) { 0267 case XwaylandCrashPolicy::Restart: 0268 if (++m_crashCount <= options->xwaylandMaxCrashCount()) { 0269 restartInternal(); 0270 m_resetCrashCountTimer->start(std::chrono::minutes(10)); 0271 } else { 0272 qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times " 0273 "over the past 10 minutes", 0274 m_crashCount); 0275 stop(); 0276 } 0277 break; 0278 case XwaylandCrashPolicy::Stop: 0279 stop(); 0280 break; 0281 } 0282 } 0283 0284 void XwaylandLauncher::resetCrashCount() 0285 { 0286 qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount; 0287 m_crashCount = 0; 0288 } 0289 0290 void XwaylandLauncher::handleXwaylandError(QProcess::ProcessError error) 0291 { 0292 switch (error) { 0293 case QProcess::FailedToStart: 0294 qCWarning(KWIN_XWL) << "Xwayland process failed to start"; 0295 return; 0296 case QProcess::Crashed: 0297 qCWarning(KWIN_XWL) << "Xwayland process crashed"; 0298 break; 0299 case QProcess::Timedout: 0300 qCWarning(KWIN_XWL) << "Xwayland operation timed out"; 0301 break; 0302 case QProcess::WriteError: 0303 case QProcess::ReadError: 0304 qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland"; 0305 break; 0306 case QProcess::UnknownError: 0307 qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland"; 0308 break; 0309 } 0310 Q_EMIT errorOccurred(); 0311 } 0312 0313 } 0314 }