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