File indexing completed on 2024-11-10 04:57:40
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"