KWin
Loading...
Searching...
No Matches
xwaylandlauncher.cpp
Go to the documentation of this file.
1/*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
6 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
7 SPDX-FileCopyrightText: 2022 David Edmundson <davidedmundson@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10*/
11#include "xwaylandlauncher.h"
12
13#include <config-kwin.h>
14
15#include "xwayland_logging.h"
16#include "xwaylandsocket.h"
17
18#include "options.h"
19#include "wayland_server.h"
20
21#if KWIN_BUILD_NOTIFICATIONS
22#include <KLocalizedString>
23#include <KNotification>
24#endif
25
26#include <QAbstractEventDispatcher>
27#include <QDataStream>
28#include <QFile>
29#include <QRandomGenerator>
30#include <QScopeGuard>
31#include <QSocketNotifier>
32#include <QTimer>
33
34// system
35#include <cerrno>
36#include <cstring>
37#include <sys/socket.h>
38#include <unistd.h>
39
40namespace KWin
41{
42namespace Xwl
43{
44
46 : QObject(parent)
47{
48 m_resetCrashCountTimer = new QTimer(this);
49 m_resetCrashCountTimer->setSingleShot(true);
50 connect(m_resetCrashCountTimer, &QTimer::timeout, this, &XwaylandLauncher::resetCrashCount);
51}
52
56
57void XwaylandLauncher::setListenFDs(const QList<int> &listenFds)
58{
59 m_listenFds = listenFds;
60}
61
62void XwaylandLauncher::setDisplayName(const QString &displayName)
63{
64 m_displayName = displayName;
65}
66
67void XwaylandLauncher::setXauthority(const QString &xauthority)
68{
69 m_xAuthority = xauthority;
70}
71
73{
74 if (m_enabled) {
75 return;
76 }
77 m_enabled = true;
78
79 if (!m_listenFds.isEmpty()) {
80 Q_ASSERT(!m_displayName.isEmpty());
81 } else {
82 m_socket = std::make_unique<XwaylandSocket>(XwaylandSocket::OperationMode::CloseFdsOnExec);
83 if (!m_socket->isValid()) {
84 qFatal("Failed to establish X11 socket");
85 }
86 m_displayName = m_socket->name();
87 m_listenFds = m_socket->fileDescriptors();
88 }
89
90 for (int socket : std::as_const(m_listenFds)) {
91 QSocketNotifier *notifier = new QSocketNotifier(socket, QSocketNotifier::Read, this);
92 connect(notifier, &QSocketNotifier::activated, this, [this]() {
93 if (!m_xwaylandProcess) {
94 start();
95 }
96 });
97 connect(this, &XwaylandLauncher::started, notifier, [notifier]() {
98 notifier->setEnabled(false);
99 });
100 connect(this, &XwaylandLauncher::finished, notifier, [this, notifier]() {
101 // only reactivate if we've not shut down due to the crash count
102 notifier->setEnabled(m_enabled);
103 });
104 }
105}
106
108{
109 m_enabled = false;
110 stop();
111}
112
114{
115 Q_ASSERT(m_enabled);
116 if (m_xwaylandProcess) {
117 return false;
118 }
119 QList<int> fdsToClose;
120 auto cleanup = qScopeGuard([&fdsToClose] {
121 for (const int fd : std::as_const(fdsToClose)) {
122 close(fd);
123 }
124 });
125
126 int pipeFds[2];
127 if (pipe(pipeFds) != 0) {
128 qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
129 Q_EMIT errorOccurred();
130 return false;
131 }
132 fdsToClose << pipeFds[1];
133
134 int sx[2];
135 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
136 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
137 Q_EMIT errorOccurred();
138 return false;
139 }
140 int fd = dup(sx[1]);
141 if (fd < 0) {
142 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
143 Q_EMIT errorOccurred();
144 return false;
145 }
146
147 const int waylandSocket = waylandServer()->createXWaylandConnection();
148 if (waylandSocket == -1) {
149 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
150 Q_EMIT errorOccurred();
151 return false;
152 }
153 const int wlfd = dup(waylandSocket);
154 if (wlfd < 0) {
155 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
156 Q_EMIT errorOccurred();
157 return false;
158 }
159
160 m_xcbConnectionFd = sx[0];
161
162 QStringList arguments;
163
164 arguments << m_displayName;
165
166 if (!m_listenFds.isEmpty()) {
167 // xauthority externally set and managed
168 if (!m_xAuthority.isEmpty()) {
169 arguments << QStringLiteral("-auth") << m_xAuthority;
170 }
171
172 for (int socket : std::as_const(m_listenFds)) {
173 int dupSocket = dup(socket);
174 fdsToClose << dupSocket;
175#if HAVE_XWAYLAND_LISTENFD
176 arguments << QStringLiteral("-listenfd") << QString::number(dupSocket);
177#else
178 arguments << QStringLiteral("-listen") << QString::number(dupSocket);
179#endif
180 }
181 }
182
183 arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
184 arguments << QStringLiteral("-rootless");
185 arguments << QStringLiteral("-wm") << QString::number(fd);
186
187 m_xwaylandProcess = new QProcess(this);
188 m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
189 m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
190 QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
191 env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
192 if (qEnvironmentVariableIntValue("KWIN_XWAYLAND_DEBUG") == 1) {
193 env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
194 }
195 m_xwaylandProcess->setProcessEnvironment(env);
196 m_xwaylandProcess->setArguments(arguments);
197 connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &XwaylandLauncher::handleXwaylandError);
198 connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
199 this, &XwaylandLauncher::handleXwaylandFinished);
200
201 // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively,
202 // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off.
203 m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this);
204 connect(m_readyNotifier, &QSocketNotifier::activated, this, [this]() {
205 maybeDestroyReadyNotifier();
206 Q_EMIT started();
207 });
208
209 m_xwaylandProcess->start();
210
211 return true;
212}
213
215{
216 return m_displayName;
217}
218
220{
221 return m_xAuthority;
222}
223
225{
226 return m_xcbConnectionFd;
227}
228
230{
231 return m_xwaylandProcess;
232}
233
235{
236 if (!m_xwaylandProcess) {
237 return;
238 }
239 Q_EMIT finished();
240
241 maybeDestroyReadyNotifier();
243
244 // When the Xwayland process is finally terminated, the finished() signal will be emitted,
245 // however we don't actually want to process it anymore. Furthermore, we also don't really
246 // want to handle any errors that may occur during the teardown.
247 if (m_xwaylandProcess->state() != QProcess::NotRunning) {
248 disconnect(m_xwaylandProcess, nullptr, this, nullptr);
249 m_xwaylandProcess->terminate();
250 m_xwaylandProcess->waitForFinished(5000);
251 }
252 delete m_xwaylandProcess;
253 m_xwaylandProcess = nullptr;
254}
255
256void XwaylandLauncher::maybeDestroyReadyNotifier()
257{
258 if (m_readyNotifier) {
259 close(m_readyNotifier->socket());
260
261 delete m_readyNotifier;
262 m_readyNotifier = nullptr;
263 }
264}
265
266void XwaylandLauncher::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
267{
268 qCDebug(KWIN_XWL) << "Xwayland process has quit with exit status:" << exitStatus << "exit code:" << exitCode;
269
270#if KWIN_BUILD_NOTIFICATIONS
271 KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
272#endif
273 m_resetCrashCountTimer->stop();
274
275 switch (options->xwaylandCrashPolicy()) {
277 if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
278 stop();
279 m_resetCrashCountTimer->start(std::chrono::minutes(10));
280 } else {
281 qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
282 "over the past 10 minutes",
283 m_crashCount);
284 disable();
285 }
286 break;
288 disable();
289 break;
290 }
291}
292
293void XwaylandLauncher::resetCrashCount()
294{
295 qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
296 m_crashCount = 0;
297}
298
299void XwaylandLauncher::handleXwaylandError(QProcess::ProcessError error)
300{
301 switch (error) {
302 case QProcess::FailedToStart:
303 qCWarning(KWIN_XWL) << "Xwayland process failed to start";
304 return;
305 case QProcess::Crashed:
306 qCWarning(KWIN_XWL) << "Xwayland process crashed";
307 break;
308 case QProcess::Timedout:
309 qCWarning(KWIN_XWL) << "Xwayland operation timed out";
310 break;
311 case QProcess::WriteError:
312 case QProcess::ReadError:
313 qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
314 break;
315 case QProcess::UnknownError:
316 qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
317 break;
318 }
319 Q_EMIT errorOccurred();
320}
321
322}
323}
324
325#include "moc_xwaylandlauncher.cpp"
XwaylandCrashPolicy xwaylandCrashPolicy
Definition options.h:78
void setXauthority(const QString &xauthority)
void setDisplayName(const QString &displayName)
void setListenFDs(const QList< int > &listenFds)
WaylandServer * waylandServer()
Options * options
Definition main.cpp:73
@ Stop
Definition options.h:47
@ Restart
Definition options.h:48