xwayland.cpp 11.7 KB
Newer Older
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
1
2
3
/*
    KWin - the KDE window manager
    This file is part of the KDE project.
4

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
5
6
7
    SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
    SPDX-FileCopyrightText: 2019 Roman Gilg <subdiff@gmail.com>
    SPDX-FileCopyrightText: 2020 Vlad Zahorodnii <vlad.zahorodnii@kde.org>
8

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
9
10
    SPDX-License-Identifier: GPL-2.0-or-later
*/
11
#include "xwayland.h"
12
13
#include "databridge.h"

14
#include "main_wayland.h"
15
#include "options.h"
16
#include "utils.h"
17
18
#include "wayland_server.h"
#include "xcbutils.h"
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
19
#include "xwayland_logging.h"
20
21

#include <KLocalizedString>
22
#include <KNotification>
23
#include <KSelectionOwner>
24
25
26

#include <QAbstractEventDispatcher>
#include <QFile>
27
#include <QTimer>
28
#include <QtConcurrentRun>
29
30
31
32
33
34
35
36
37
38

// system
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if HAVE_SYS_PROCCTL_H
#include <unistd.h>
#endif

#include <sys/socket.h>
39
40
#include <cerrno>
#include <cstring>
41

42
static QByteArray readDisplay(int pipe)
43
{
44
    QByteArray displayName;
45
    QFile readPipe;
46

47
    if (!readPipe.open(pipe, QIODevice::ReadOnly)) {
48
        qCWarning(KWIN_XWL) << "Failed to open X11 display name pipe:" << readPipe.errorString();
49
50
51
52
    } else {
        displayName = readPipe.readLine();
        displayName.prepend(QByteArrayLiteral(":"));
        displayName.remove(displayName.size() - 1, 1);
53
54
55
56
    }

    // close our pipe
    close(pipe);
57
    return displayName;
58
59
}

60
61
namespace KWin
{
62
63
64
namespace Xwl
{

65
Xwayland::Xwayland(ApplicationWaylandAbstract *app, QObject *parent)
66
67
    : XwaylandInterface(parent)
    , m_app(app)
68
{
69
70
71
    m_resetCrashCountTimer = new QTimer(this);
    m_resetCrashCountTimer->setSingleShot(true);
    connect(m_resetCrashCountTimer, &QTimer::timeout, this, &Xwayland::resetCrashCount);
72
73
74
75
}

Xwayland::~Xwayland()
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
76
    stop();
77
78
}

79
80
81
82
83
QProcess *Xwayland::process() const
{
    return m_xwaylandProcess;
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
84
void Xwayland::start()
85
{
86
87
88
89
    if (m_xwaylandProcess) {
        return;
    }

90
91
    int pipeFds[2];
    if (pipe(pipeFds) != 0) {
92
        qCWarning(KWIN_XWL, "Failed to create pipe to start Xwayland: %s", strerror(errno));
93
        emit errorOccurred();
94
95
96
97
        return;
    }
    int sx[2];
    if (socketpair(AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0, sx) < 0) {
98
        qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
99
        emit errorOccurred();
100
101
102
103
        return;
    }
    int fd = dup(sx[1]);
    if (fd < 0) {
104
        qCWarning(KWIN_XWL, "Failed to open socket for XCB connection: %s", strerror(errno));
105
        emit errorOccurred();
106
107
108
109
110
        return;
    }

    const int waylandSocket = waylandServer()->createXWaylandConnection();
    if (waylandSocket == -1) {
111
        qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
112
        emit errorOccurred();
113
114
115
116
        return;
    }
    const int wlfd = dup(waylandSocket);
    if (wlfd < 0) {
117
        qCWarning(KWIN_XWL, "Failed to open socket for Xwayland server: %s", strerror(errno));
118
        emit errorOccurred();
119
120
121
122
        return;
    }

    m_xcbConnectionFd = sx[0];
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
123
    m_displayFileDescriptor = pipeFds[0];
124
125
126
127
128
129
130

    m_xwaylandProcess = new Process(this);
    m_xwaylandProcess->setProcessChannelMode(QProcess::ForwardedErrorChannel);
    m_xwaylandProcess->setProgram(QStringLiteral("Xwayland"));
    QProcessEnvironment env = m_app->processStartupEnvironment();
    env.insert("WAYLAND_SOCKET", QByteArray::number(wlfd));
    env.insert("EGL_PLATFORM", QByteArrayLiteral("DRM"));
131
132
133
    if (qEnvironmentVariableIsSet("KWIN_XWAYLAND_DEBUG")) {
        env.insert("WAYLAND_DEBUG", QByteArrayLiteral("1"));
    }
134
135
136
137
138
139
    m_xwaylandProcess->setProcessEnvironment(env);
    m_xwaylandProcess->setArguments({QStringLiteral("-displayfd"),
                           QString::number(pipeFds[1]),
                           QStringLiteral("-rootless"),
                           QStringLiteral("-wm"),
                           QString::number(fd)});
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
140
141
142
143
    connect(m_xwaylandProcess, &QProcess::errorOccurred, this, &Xwayland::handleXwaylandError);
    connect(m_xwaylandProcess, &QProcess::started, this, &Xwayland::handleXwaylandStarted);
    connect(m_xwaylandProcess, QOverload<int, QProcess::ExitStatus>::of(&QProcess::finished),
            this, &Xwayland::handleXwaylandFinished);
144
145
146
147
    m_xwaylandProcess->start();
    close(pipeFds[1]);
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
148
void Xwayland::stop()
149
{
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
150
151
152
153
    if (!m_xwaylandProcess) {
        return;
    }

154
155
    m_app->setClosingX11Connection(true);

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
156
157
158
159
    // If Xwayland has crashed, we must deactivate the socket notifier and ensure that no X11
    // events will be dispatched before blocking; otherwise we will simply hang...
    uninstallSocketNotifier();

160
    DataBridge::destroy();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175

    destroyX11Connection();

    // When the Xwayland process is finally terminated, the finished() signal will be emitted,
    // however we don't actually want to process it anymore. Furthermore, we also don't really
    // want to handle any errors that may occur during the teardown.
    if (m_xwaylandProcess->state() != QProcess::NotRunning) {
        disconnect(m_xwaylandProcess, nullptr, this, nullptr);
        m_xwaylandProcess->terminate();
        m_xwaylandProcess->waitForFinished(5000);
    }
    delete m_xwaylandProcess;
    m_xwaylandProcess = nullptr;

    waylandServer()->destroyXWaylandConnection(); // This one must be destroyed last!
176
177

    m_app->setClosingX11Connection(false);
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
178
179
}

180
181
182
183
184
185
void Xwayland::restart()
{
    stop();
    start();
}

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
186
187
188
189
190
191
192
193
void Xwayland::dispatchEvents()
{
    xcb_connection_t *connection = kwinApp()->x11Connection();
    if (!connection) {
        qCWarning(KWIN_XWL, "Attempting to dispatch X11 events with no connection");
        return;
    }

194
195
196
197
198
199
200
    const int connectionError = xcb_connection_has_error(connection);
    if (connectionError) {
        qCWarning(KWIN_XWL, "The X11 connection broke (error %d)", connectionError);
        stop();
        return;
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
    while (xcb_generic_event_t *event = xcb_poll_for_event(connection)) {
        long result = 0;
        QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
        dispatcher->filterNativeEvent(QByteArrayLiteral("xcb_generic_event_t"), event, &result);
        free(event);
    }

    xcb_flush(connection);
}

void Xwayland::installSocketNotifier()
{
    const int fileDescriptor = xcb_get_file_descriptor(kwinApp()->x11Connection());

    m_socketNotifier = new QSocketNotifier(fileDescriptor, QSocketNotifier::Read, this);
    connect(m_socketNotifier, &QSocketNotifier::activated, this, &Xwayland::dispatchEvents);

    QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
    connect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
    connect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);
}

void Xwayland::uninstallSocketNotifier()
{
    QAbstractEventDispatcher *dispatcher = QCoreApplication::eventDispatcher();
    disconnect(dispatcher, &QAbstractEventDispatcher::aboutToBlock, this, &Xwayland::dispatchEvents);
    disconnect(dispatcher, &QAbstractEventDispatcher::awake, this, &Xwayland::dispatchEvents);

    delete m_socketNotifier;
    m_socketNotifier = nullptr;
}

void Xwayland::handleXwaylandStarted()
{
235
236
237
    m_watcher = new QFutureWatcher<QByteArray>(this);
    connect(m_watcher, &QFutureWatcher<QByteArray>::finished, this, &Xwayland::handleXwaylandReady);
    m_watcher->setFuture(QtConcurrent::run(readDisplay, m_displayFileDescriptor));
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
238
239
}

240
void Xwayland::handleXwaylandFinished(int exitCode, QProcess::ExitStatus exitStatus)
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
241
242
243
{
    qCDebug(KWIN_XWL) << "Xwayland process has quit with exit code" << exitCode;

244
245
246
247
248
249
250
251
252
253
254
255
    switch (exitStatus) {
    case QProcess::NormalExit:
        stop();
        break;
    case QProcess::CrashExit:
        handleXwaylandCrashed();
        break;
    }
}

void Xwayland::handleXwaylandCrashed()
{
256
    KNotification::event(QStringLiteral("xwaylandcrash"), i18n("Xwayland has crashed"));
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
    m_resetCrashCountTimer->stop();

    switch (options->xwaylandCrashPolicy()) {
    case XwaylandCrashPolicy::Restart:
        if (++m_crashCount <= options->xwaylandMaxCrashCount()) {
            restart();
            m_resetCrashCountTimer->start(std::chrono::minutes(10));
        } else {
            qCWarning(KWIN_XWL, "Stopping Xwayland server because it has crashed %d times "
                      "over the past 10 minutes", m_crashCount);
            stop();
        }
        break;
    case XwaylandCrashPolicy::Stop:
        stop();
        break;
    }
}

void Xwayland::resetCrashCount()
{
    qCDebug(KWIN_XWL) << "Resetting the crash counter, its current value is" << m_crashCount;
    m_crashCount = 0;
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
280
281
282
283
284
285
286
287
288
}

void Xwayland::handleXwaylandError(QProcess::ProcessError error)
{
    switch (error) {
    case QProcess::FailedToStart:
        qCWarning(KWIN_XWL) << "Xwayland process failed to start";
        return;
    case QProcess::Crashed:
289
        qCWarning(KWIN_XWL) << "Xwayland process crashed";
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
290
291
292
293
294
295
296
297
298
299
300
301
        break;
    case QProcess::Timedout:
        qCWarning(KWIN_XWL) << "Xwayland operation timed out";
        break;
    case QProcess::WriteError:
    case QProcess::ReadError:
        qCWarning(KWIN_XWL) << "An error occurred while communicating with Xwayland";
        break;
    case QProcess::UnknownError:
        qCWarning(KWIN_XWL) << "An unknown error has occurred in Xwayland";
        break;
    }
302
    emit errorOccurred();
303
304
}

305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
void Xwayland::handleXwaylandReady()
{
    m_displayName = m_watcher->result();

    m_watcher->deleteLater();
    m_watcher = nullptr;

    if (!createX11Connection()) {
        emit errorOccurred();
        return;
    }

    qCInfo(KWIN_XWL) << "Xwayland server started on display" << m_displayName;
    qputenv("DISPLAY", m_displayName);

    // create selection owner for WM_S0 - magic X display number expected by XWayland
    KSelectionOwner owner("WM_S0", kwinApp()->x11Connection(), kwinApp()->x11RootWindow());
    owner.claim(true);

    DataBridge::create(this);

    auto env = m_app->processStartupEnvironment();
    env.insert(QStringLiteral("DISPLAY"), m_displayName);
    m_app->setProcessStartupEnvironment(env);

    emit started();

    Xcb::sync(); // Trigger possible errors, there's still a chance to abort
}

335
bool Xwayland::createX11Connection()
336
{
337
    xcb_connection_t *connection = xcb_connect_to_fd(m_xcbConnectionFd, nullptr);
338
339
340
341
342

    const int errorCode = xcb_connection_has_error(connection);
    if (errorCode) {
        qCDebug(KWIN_XWL, "Failed to establish the XCB connection (error %d)", errorCode);
        return false;
343
344
    }

345
346
    xcb_screen_t *screen = xcb_setup_roots_iterator(xcb_get_setup(connection)).data;
    Q_ASSERT(screen);
347

348
    m_app->setX11Connection(connection);
349
    m_app->setX11DefaultScreen(screen);
350
    m_app->setX11ScreenNumber(0);
351
    m_app->setX11RootWindow(screen->root);
352
353

    m_app->createAtoms();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
354
355
356
    m_app->installNativeX11EventFilter();

    installSocketNotifier();
357
358
359
360

    // Note that it's very important to have valid x11RootWindow(), x11ScreenNumber(), and
    // atoms when the rest of kwin is notified about the new X11 connection.
    emit m_app->x11ConnectionChanged();
361
362

    return true;
363
364
365
366
367
368
369
370
}

void Xwayland::destroyX11Connection()
{
    if (!m_app->x11Connection()) {
        return;
    }

Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
371
372
    emit m_app->x11ConnectionAboutToBeDestroyed();

373
374
    Xcb::setInputFocus(XCB_INPUT_FOCUS_POINTER_ROOT);
    m_app->destroyAtoms();
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
375
    m_app->removeNativeX11EventFilter();
376
377

    xcb_disconnect(m_app->x11Connection());
Vlad Zahorodnii's avatar
Vlad Zahorodnii committed
378
379
    m_xcbConnectionFd = -1;

380
    m_app->setX11Connection(nullptr);
381
    m_app->setX11DefaultScreen(nullptr);
382
383
384
385
    m_app->setX11ScreenNumber(-1);
    m_app->setX11RootWindow(XCB_WINDOW_NONE);

    emit m_app->x11ConnectionChanged();
386
387
}

388
DragEventReply Xwayland::dragMoveFilter(Toplevel *target, const QPoint &pos)
389
{
390
391
    DataBridge *bridge = DataBridge::self();
    if (!bridge) {
392
393
        return DragEventReply::Wayland;
    }
394
    return bridge->dragMoveFilter(target, pos);
395
396
}

397
398
} // namespace Xwl
} // namespace KWin