1 /*
2 KWin - the KDE window manager
3 This file is part of the KDE project.
4
5 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
6 SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
7 SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8
9 SPDX-License-Identifier: GPL-2.0-or-later
10 */
11 #include "xwayland.h"
12 #include "cursor.h"
13 #include "databridge.h"
14 #include "dnd.h"
15 #include "xwldrophandler.h"
16
17 #include "main_wayland.h"
18 #include "options.h"
19 #include "utils.h"
20 #include "wayland_server.h"
21 #include "xcbutils.h"
22 #include "xwayland_logging.h"
23
24 #include "xwaylandsocket.h"
25
26 #include <KLocalizedString>
27 #include <KNotification>
28 #include <KSelectionOwner>
29
30 #include <QAbstractEventDispatcher>
31 #include <QDataStream>
32 #include <QFile>
33 #include <QHostInfo>
34 #include <QRandomGenerator>
35 #include <QScopeGuard>
36 #include <QTimer>
37 #include <QtConcurrentRun>
38
39 // system
40 #ifdef HAVE_UNISTD_H
41 #include <unistd.h>
42 #endif
43 #if HAVE_SYS_PROCCTL_H
44 #include <unistd.h>
45 #endif
46
47 #include <sys/socket.h>
48 #include <cerrno>
49 #include <cstring>
50
51 namespace KWin
52 {
53 namespace Xwl
54 {
55
Xwayland(ApplicationWaylandAbstract * app,QObject * parent)56 Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent)
57 : XwaylandInterface(parent)
58 , m_app(app)
59 {
60 m_resetCrashCountTimer = new QTimer(this);
61 m_resetCrashCountTimer->setSingleShot(true);
62 connect(m_resetCrashCountTimer, &QTimer::timeout, this, &Xwayland::resetCrashCount);
63 }
64
~Xwayland()65 Xwayland::~Xwayland()
66 {
67 stop();
68 }
69
process() const70 QProcess *Xwayland::process() const
71 {
72 return m_xwaylandProcess;
73 }
74
start()75 void Xwayland::start()
76 {
77 if (m_xwaylandProcess) {
78 return;
79 }
80
81 if (!m_listenFds.isEmpty()) {
82 Q_ASSERT(!m_displayName.isEmpty());
83 } else {
84 m_socket.reset(new XwaylandSocket(XwaylandSocket::OperationMode::CloseFdsOnExec));
85 if (!m_socket->isValid()) {
86 qFatal("Failed to establish X11 socket");
87 }
88 setListenFDs({m_socket->unixFileDescriptor(), m_socket->abstractFileDescriptor()});
89 m_displayName = m_socket->name();
90 }
91
92 startInternal();
93 }
94
setListenFDs(const QVector<int> & listenFds)95 void Xwayland::setListenFDs(const QVector<int> &listenFds)
96 {
97 m_listenFds = listenFds;
98 }
99
setDisplayName(const QString & displayName)100 void Xwayland::setDisplayName(const QString &displayName)
101 {
102 m_displayName = displayName;
103 }
104
setXauthority(const QString & xauthority)105 void Xwayland::setXauthority(const QString &xauthority)
106 {
107 m_xAuthority = xauthority;
108 }
109
startInternal()110 bool Xwayland::startInternal()
111 {
112 Q_ASSERT(!m_xwaylandProcess);
113
114 QVector<int> fdsToClose;
115 auto cleanup = qScopeGuard([&fdsToClose] {
116 for (const int fd : qAsConst(fdsToClose)) {
117 close(fd);
118 }
119 });
120
121 int pipeFds[2];
122 if (pipe(pipeFds) != 0) {
123 qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
124 Q_EMIT errorOccurred();
125 return false;
126 }
127 fdsToClose << pipeFds[1];
128
129 int sx[2];
130 if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
131 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
132 Q_EMIT errorOccurred();
133 return false;
134 }
135 int fd = dup(sx[1]);
136 if (fd < 0) {
137 qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
138 Q_EMIT errorOccurred();
139 return false;
140 }
141
142 const int waylandSocket = waylandServer()->createXWaylandConnection();
143 if (waylandSocket == -1) {
144 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
145 Q_EMIT errorOccurred();
146 return false;
147 }
148 const int wlfd = dup(waylandSocket);
149 if (wlfd < 0) {
150 qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
151 Q_EMIT errorOccurred();
152 return false;
153 }
154
155 m_xcbConnectionFd = sx[0];
156
157 QStringList arguments;
158
159 arguments << m_displayName;
160
161 if (!m_listenFds.isEmpty()) {
162 // xauthority externally set and managed
163 if (!m_xAuthority.isEmpty()) {
164 arguments << QStringLiteral("-auth") << m_xAuthority;
165 }
166
167 for (int socket : qAsConst(m_listenFds)) {
168 int dupSocket = dup(socket);
169 fdsToClose << dupSocket;
170 #if defined(HAVE_XWAYLAND_LISTENFD)
171 arguments << QStringLiteral("-listenfd") << QString::number(dupSocket);
172 #else
173 arguments << QStringLiteral("-listen") << QString::number(dupSocket);
174 #endif
175 }
176 }
177
178 arguments << QStringLiteral("-displayfd") << QString::number(pipeFds[1]);
179 arguments << QStringLiteral("-rootless");
180 arguments << QStringLiteral("-wm") << QString::number(fd);
181
182 m_xwaylandProcess = new Process(this);
183 m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
184 m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
185 QProcessEnvironment env = m_app->processStartupEnvironment();
186 env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
187 env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM"));
188 if (qEnvironmentVariableIsSet("KWIN_XWAYLAND_DEBUG")) {
189 env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
190 }
191 m_xwaylandProcess->setProcessEnvironment(env);
192 m_xwaylandProcess->setArguments(arguments);
193 connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &Xwayland::handleXwaylandError);
194 connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
195 this, &Xwayland::handleXwaylandFinished);
196
197 // When Xwayland starts writing the display name to displayfd, it is ready. Alternatively,
198 // the Xwayland can send us the SIGUSR1 signal, but it's already reserved for VT hand-off.
199 m_readyNotifier = new QSocketNotifier(pipeFds[0], QSocketNotifier::Read, this);
200 connect(m_readyNotifier, &QSocketNotifier::activated, this, &Xwayland::handleXwaylandReady);
201
202 m_xwaylandProcess->start();
203
204 return true;
205 }
206
stop()207 void Xwayland::stop()
208 {
209 if (!m_xwaylandProcess) {
210 return;
211 }
212
213 stopInternal();
214 }
215
stopInternal()216 void Xwayland::stopInternal()
217 {
218 Q_ASSERT(m_xwaylandProcess);
219 m_app->setClosingX11Connection(true);
220
221 // If Xwayland has crashed, we must deactivate the socket notifier and ensure that no X11
222 // events will be dispatched before blocking; otherwise we will simply hang...
223 uninstallSocketNotifier();
224 maybeDestroyReadyNotifier();
225
226 DataBridge::destroy();
227 m_selectionOwner.reset();
228
229 destroyX11Connection();
230
231 // When the Xwayland process is finally terminated, the finished() signal will be emitted,
232 // however we don't actually want to process it anymore. Furthermore, we also don't really
233 // want to handle any errors that may occur during the teardown.
234 if (m_xwaylandProcess->state() != QProcess::NotRunning) {
235 disconnect(m_xwaylandProcess, nullptr, this, nullptr);
236 m_xwaylandProcess->terminate();
237 m_xwaylandProcess->waitForFinished(5000);
238 }
239 delete m_xwaylandProcess;
240 m_xwaylandProcess = nullptr;
241
242 waylandServer()->destroyXWaylandConnection(); // This one must be destroyed last!
243
244 m_app->setClosingX11Connection(false);
245 }
246
restartInternal()247 void Xwayland::restartInternal()
248 {
249 if (m_xwaylandProcess) {
250 stopInternal();
251 }
252 startInternal();
253 }
254
dispatchEvents()255 void Xwayland::dispatchEvents()
256 {
257 xcb_connection_t *connection = kwinApp()->x11Connection();
258 if (!connection) {
259 qCWarning(KWIN_XWL, "Attempting to dispatch X11 events with no connection");
260 return;
261 }
262
263 const int connectionError = xcb_connection_has_error(connection);
264 if (connectionError) {
265 qCWarning(KWIN_XWL, "The X11 connection broke (error %d)", connectionError);
266 stop();
267 return;
268 }
269
270 while (xcb_generic_event_t *event = xcb_poll_for_event(connection)) {
271 long result = 0;
272 QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
273 dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
274 free(event);
275 }
276
277 xcb_flush(connection);
278 }
279
installSocketNotifier()280 void Xwayland::installSocketNotifier()
281 {
282 const int fileDescriptor = xcb_get_file_descriptor(kwinApp()->x11Connection());
283
284 m_socketNotifier = new QSocketNotifier(fileDescriptor, QSocketNotifier::Read, this);
285 connect(m_socketNotifier, &QSocketNotifier::activated, this, &Xwayland::dispatchEvents);
286
287 QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
288 connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
289 connect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);
290 }
291
uninstallSocketNotifier()292 void Xwayland::uninstallSocketNotifier()
293 {
294 QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
295 disconnect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
296 disconnect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);
297
298 delete m_socketNotifier;
299 m_socketNotifier = nullptr;
300 }
301
handleXwaylandFinished(int exitCode,QProcess::ExitStatus exitStatus)302 void Xwayland::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
303 {
304 qCDebug(KWIN_XWL) << "Xwayland process has quit with exit code" << exitCode;
305
306 switch (exitStatus) {
307 case QProcess::NormalExit:
308 stop();
309 break;
310 case QProcess::CrashExit:
311 handleXwaylandCrashed();
312 break;
313 }
314 }
315
handleXwaylandCrashed()316 void Xwayland::handleXwaylandCrashed()
317 {
318 KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
319 m_resetCrashCountTimer->stop();
320
321 switch (options->xwaylandCrashPolicy()) {
322 case XwaylandCrashPolicy::Restart:
323 if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
324 restartInternal();
325 m_resetCrashCountTimer->start(std::chrono::minutes(10));
326 } else {
327 qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
328 "over the past 10 minutes", m_crashCount);
329 stop();
330 }
331 break;
332 case XwaylandCrashPolicy::Stop:
333 stop();
334 break;
335 }
336 }
337
resetCrashCount()338 void Xwayland::resetCrashCount()
339 {
340 qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
341 m_crashCount = 0;
342 }
343
handleXwaylandError(QProcess::ProcessError error)344 void Xwayland::handleXwaylandError(QProcess::ProcessError error)
345 {
346 switch (error) {
347 case QProcess::FailedToStart:
348 qCWarning(KWIN_XWL) << "Xwayland process failed to start";
349 return;
350 case QProcess::Crashed:
351 qCWarning(KWIN_XWL) << "Xwayland process crashed";
352 break;
353 case QProcess::Timedout:
354 qCWarning(KWIN_XWL) << "Xwayland operation timed out";
355 break;
356 case QProcess::WriteError:
357 case QProcess::ReadError:
358 qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
359 break;
360 case QProcess::UnknownError:
361 qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
362 break;
363 }
364 Q_EMIT errorOccurred();
365 }
366
handleXwaylandReady()367 void Xwayland::handleXwaylandReady()
368 {
369 // We don't care what Xwayland writes to the displayfd, we just want to know when it's ready.
370 maybeDestroyReadyNotifier();
371
372 if (!createX11Connection()) {
373 Q_EMIT errorOccurred();
374 return;
375 }
376
377 qCInfo(KWIN_XWL) << "Xwayland server started on display" << m_displayName;
378
379 // create selection owner for WM_S0 - magic X display number expected by XWayland
380 m_selectionOwner.reset(new KSelectionOwner("WM_S0", kwinApp()->x11Connection(), kwinApp()->x11RootWindow()));
381 connect(m_selectionOwner.data(), &KSelectionOwner::lostOwnership,
382 this, &Xwayland::handleSelectionLostOwnership);
383 connect(m_selectionOwner.data(), &KSelectionOwner::claimedOwnership,
384 this, &Xwayland::handleSelectionClaimedOwnership);
385 connect(m_selectionOwner.data(), &KSelectionOwner::failedToClaimOwnership,
386 this, &Xwayland::handleSelectionFailedToClaimOwnership);
387 m_selectionOwner->claim(true);
388
389 Cursor *mouseCursor = Cursors::self()->mouse();
390 if (mouseCursor) {
391 Xcb::defineCursor(kwinApp()->x11RootWindow(), mouseCursor->x11Cursor(Qt::ArrowCursor));
392 }
393
394 DataBridge::create(this);
395
396 auto env = m_app->processStartupEnvironment();
397 env.insert(QStringLiteral("DISPLAY"), m_displayName);
398 env.insert(QStringLiteral("XAUTHORITY"), m_xAuthority);
399 qputenv("DISPLAY", m_displayName.toUtf8());
400 qputenv("XAUTHORITY", m_xAuthority.toUtf8());
401 m_app->setProcessStartupEnvironment(env);
402
403 Xcb::sync(); // Trigger possible errors, there's still a chance to abort
404 }
405
handleSelectionLostOwnership()406 void Xwayland::handleSelectionLostOwnership()
407 {
408 qCWarning(KWIN_XWL) << "Somebody else claimed ownership of WM_S0. This should never happen!";
409 stop();
410 }
411
handleSelectionFailedToClaimOwnership()412 void Xwayland::handleSelectionFailedToClaimOwnership()
413 {
414 qCWarning(KWIN_XWL) << "Failed to claim ownership of WM_S0. This should never happen!";
415 stop();
416 }
417
handleSelectionClaimedOwnership()418 void Xwayland::handleSelectionClaimedOwnership()
419 {
420 Q_EMIT started();
421 }
422
maybeDestroyReadyNotifier()423 void Xwayland::maybeDestroyReadyNotifier()
424 {
425 if (m_readyNotifier) {
426 close(m_readyNotifier->socket());
427
428 delete m_readyNotifier;
429 m_readyNotifier = nullptr;
430 }
431 }
432
createX11Connection()433 bool Xwayland::createX11Connection()
434 {
435 xcb_connection_t *connection = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
436
437 const int errorCode = xcb_connection_has_error(connection);
438 if (errorCode) {
439 qCDebug(KWIN_XWL, "Failed to establish the XCB connection (error %d)", errorCode);
440 return false;
441 }
442
443 xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
444 Q_ASSERT(screen);
445
446 m_app->setX11Connection(connection);
447 m_app->setX11DefaultScreen(screen);
448 m_app->setX11ScreenNumber(0);
449 m_app->setX11RootWindow(screen->root);
450
451 m_app->createAtoms();
452 m_app->installNativeX11EventFilter();
453
454 installSocketNotifier();
455
456 // Note that it's very important to have valid x11RootWindow(), x11ScreenNumber(), and
457 // atoms when the rest of kwin is notified about the new X11 connection.
458 Q_EMIT m_app->x11ConnectionChanged();
459
460 return true;
461 }
462
destroyX11Connection()463 void Xwayland::destroyX11Connection()
464 {
465 if (!m_app->x11Connection()) {
466 return;
467 }
468
469 Q_EMIT m_app->x11ConnectionAboutToBeDestroyed();
470
471 Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
472 m_app->destroyAtoms();
473 m_app->removeNativeX11EventFilter();
474
475 xcb_disconnect(m_app->x11Connection());
476 m_xcbConnectionFd = -1;
477
478 m_app->setX11Connection(nullptr);
479 m_app->setX11DefaultScreen(nullptr);
480 m_app->setX11ScreenNumber(-1);
481 m_app->setX11RootWindow(XCB_WINDOW_NONE);
482
483 Q_EMIT m_app->x11ConnectionChanged();
484 }
485
dragMoveFilter(Toplevel * target,const QPoint & pos)486 DragEventReply Xwayland::dragMoveFilter(Toplevel *target, const QPoint &pos)
487 {
488 DataBridge *bridge = DataBridge::self();
489 if (!bridge) {
490 return DragEventReply::Wayland;
491 }
492 return bridge->dragMoveFilter(target, pos);
493 }
494
xwlDropHandler()495 KWaylandServer::AbstractDropHandler *Xwayland::xwlDropHandler()
496 {
497 DataBridge *bridge = DataBridge::self();
498 if (bridge) {
499 return bridge->dnd()->dropHandler();
500 }
501 return nullptr;
502 }
503
504 } // namespace Xwl
505 } // namespace KWin
506