1/*
2 SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
5*/
6#include "connection_thread.h"
7#include "logging.h"
8// Qt
9#include <QAbstractEventDispatcher>
10#include <QDebug>
11#include <QDir>
12#include <QFileSystemWatcher>
13#include <QGuiApplication>
14#include <QMutex>
15#include <QMutexLocker>
16#include <QSocketNotifier>
17#include <qpa/qplatformnativeinterface.h>
18// Wayland
19#include <wayland-client-protocol.h>
20
21#include <poll.h>
22
23namespace KWayland
24{
25namespace Client
26{
27class Q_DECL_HIDDEN ConnectionThread::Private
28{
29public:
30 Private(ConnectionThread *q);
31 ~Private();
32 void doInitConnection();
33 void setupSocketNotifier();
34 void setupSocketFileWatcher();
35 void dispatchEvents();
36
37 wl_display *display = nullptr;
38 int fd = -1;
39 QString socketName;
40 QDir runtimeDir;
41 QScopedPointer<QSocketNotifier> socketNotifier;
42 QScopedPointer<QFileSystemWatcher> socketWatcher;
43 bool serverDied = false;
44 bool foreign = false;
45 QMetaObject::Connection eventDispatcherConnection;
46 int error = 0;
47 static QList<ConnectionThread *> connections;
48 static QRecursiveMutex mutex;
49
50private:
51 ConnectionThread *q;
52};
53
54QList<ConnectionThread *> ConnectionThread::Private::connections = QList<ConnectionThread *>{};
55QRecursiveMutex ConnectionThread::Private::mutex;
56
57ConnectionThread::Private::Private(ConnectionThread *q)
58 : socketName(QString::fromUtf8(ba: qgetenv(varName: "WAYLAND_DISPLAY")))
59 , runtimeDir(QString::fromUtf8(ba: qgetenv(varName: "XDG_RUNTIME_DIR")))
60 , q(q)
61{
62 if (socketName.isEmpty()) {
63 socketName = QStringLiteral("wayland-0");
64 }
65 {
66 QMutexLocker lock(&mutex);
67 connections << q;
68 }
69}
70
71ConnectionThread::Private::~Private()
72{
73 {
74 QMutexLocker lock(&mutex);
75 connections.removeOne(t: q);
76 }
77 if (display && !foreign) {
78 wl_display_flush(display);
79 wl_display_disconnect(display);
80 }
81}
82
83void ConnectionThread::Private::doInitConnection()
84{
85 if (fd != -1) {
86 display = wl_display_connect_to_fd(fd);
87 } else {
88 display = wl_display_connect(name: socketName.toUtf8().constData());
89 }
90 if (!display) {
91 qCWarning(KWAYLAND_CLIENT) << "Failed connecting to Wayland display";
92 Q_EMIT q->failed();
93 return;
94 }
95 if (fd != -1) {
96 qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server over file descriptor:" << fd;
97 } else {
98 qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server at:" << socketName;
99 }
100
101 // setup socket notifier
102 setupSocketNotifier();
103 setupSocketFileWatcher();
104 Q_EMIT q->connected();
105}
106
107void ConnectionThread::Private::setupSocketNotifier()
108{
109 const int fd = wl_display_get_fd(display);
110 socketNotifier.reset(other: new QSocketNotifier(fd, QSocketNotifier::Read));
111 QObject::connect(sender: socketNotifier.data(), signal: &QSocketNotifier::activated, context: q, slot: [this]() {
112 dispatchEvents();
113 });
114}
115
116void ConnectionThread::Private::dispatchEvents()
117{
118 if (!display) {
119 return;
120 }
121 // first dispatch any pending events on the default queue
122 while (wl_display_prepare_read(display) != 0) {
123 wl_display_dispatch_pending(display);
124 }
125 wl_display_flush(display);
126 // then check if there are any new events waiting to be read
127 struct pollfd pfd;
128 pfd.fd = wl_display_get_fd(display);
129 pfd.events = POLLIN;
130 int ret = poll(fds: &pfd, nfds: 1, timeout: 0);
131 if (ret > 0) {
132 // if yes, read them now
133 wl_display_read_events(display);
134 } else {
135 wl_display_cancel_read(display);
136 }
137
138 // finally, dispatch the default queue and all frame queues
139 if (wl_display_dispatch_pending(display) == -1) {
140 error = wl_display_get_error(display);
141 if (error != 0) {
142 if (display) {
143 free(ptr: display);
144 display = nullptr;
145 }
146 Q_EMIT q->errorOccurred();
147 return;
148 }
149 }
150 Q_EMIT q->eventsRead();
151}
152
153void ConnectionThread::Private::setupSocketFileWatcher()
154{
155 if (!runtimeDir.exists() || fd != -1) {
156 return;
157 }
158 socketWatcher.reset(other: new QFileSystemWatcher);
159 socketWatcher->addPath(file: runtimeDir.absoluteFilePath(fileName: socketName));
160 QObject::connect(sender: socketWatcher.data(), signal: &QFileSystemWatcher::fileChanged, context: q, slot: [this](const QString &file) {
161 if (QFile::exists(fileName: file) || serverDied) {
162 return;
163 }
164 qCWarning(KWAYLAND_CLIENT) << "Connection to server went away";
165 serverDied = true;
166 if (display) {
167 free(ptr: display);
168 display = nullptr;
169 }
170 socketNotifier.reset();
171
172 // need a new filesystem watcher
173 socketWatcher.reset(other: new QFileSystemWatcher);
174 socketWatcher->addPath(file: runtimeDir.absolutePath());
175 QObject::connect(sender: socketWatcher.data(), signal: &QFileSystemWatcher::directoryChanged, context: q, slot: [this]() {
176 if (!serverDied) {
177 return;
178 }
179 if (runtimeDir.exists(name: socketName)) {
180 qCDebug(KWAYLAND_CLIENT) << "Socket reappeared";
181 socketWatcher.reset();
182 serverDied = false;
183 error = 0;
184 q->initConnection();
185 }
186 });
187 Q_EMIT q->connectionDied();
188 });
189}
190
191ConnectionThread::ConnectionThread(QObject *parent)
192 : QObject(parent)
193 , d(new Private(this))
194{
195 d->eventDispatcherConnection = connect(
196 sender: QCoreApplication::eventDispatcher(),
197 signal: &QAbstractEventDispatcher::aboutToBlock,
198 context: this,
199 slot: [this] {
200 if (d->display) {
201 wl_display_flush(display: d->display);
202 }
203 },
204 type: Qt::DirectConnection);
205}
206
207ConnectionThread::ConnectionThread(wl_display *display, QObject *parent)
208 : QObject(parent)
209 , d(new Private(this))
210{
211 d->display = display;
212 d->foreign = true;
213}
214
215ConnectionThread::~ConnectionThread()
216{
217 disconnect(d->eventDispatcherConnection);
218}
219
220ConnectionThread *ConnectionThread::fromApplication(QObject *parent)
221{
222 QPlatformNativeInterface *native = qApp->platformNativeInterface();
223 if (!native) {
224 return nullptr;
225 }
226 wl_display *display = reinterpret_cast<wl_display *>(native->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
227 if (!display) {
228 return nullptr;
229 }
230 ConnectionThread *ct = new ConnectionThread(display, parent);
231 connect(sender: native, signal: &QObject::destroyed, context: ct, slot: &ConnectionThread::connectionDied);
232 return ct;
233}
234
235void ConnectionThread::initConnection()
236{
237 QMetaObject::invokeMethod(object: this, function: &ConnectionThread::doInitConnection, type: Qt::QueuedConnection);
238}
239
240void ConnectionThread::doInitConnection()
241{
242 d->doInitConnection();
243}
244
245void ConnectionThread::setSocketName(const QString &socketName)
246{
247 if (d->display) {
248 // already initialized
249 return;
250 }
251 d->socketName = socketName;
252}
253
254void ConnectionThread::setSocketFd(int fd)
255{
256 if (d->display) {
257 // already initialized
258 return;
259 }
260 d->fd = fd;
261}
262
263wl_display *ConnectionThread::display()
264{
265 return d->display;
266}
267
268QString ConnectionThread::socketName() const
269{
270 return d->socketName;
271}
272
273void ConnectionThread::flush()
274{
275 if (!d->display) {
276 return;
277 }
278 wl_display_flush(display: d->display);
279}
280
281void ConnectionThread::roundtrip()
282{
283 if (!d->display) {
284 return;
285 }
286 if (d->foreign) {
287 // try to perform roundtrip through the QPA plugin if it's supported
288 if (QPlatformNativeInterface *native = qApp->platformNativeInterface()) {
289 // in case the platform provides a dedicated roundtrip function use that install of wl_display_roundtrip
290 QFunctionPointer roundtripFunction = native->platformFunction(QByteArrayLiteral("roundtrip"));
291 if (roundtripFunction) {
292 roundtripFunction();
293 return;
294 }
295 }
296 }
297 wl_display_roundtrip(display: d->display);
298}
299
300bool ConnectionThread::hasError() const
301{
302 return d->error != 0;
303}
304
305int ConnectionThread::errorCode() const
306{
307 return d->error;
308}
309
310QList<ConnectionThread *> ConnectionThread::connections()
311{
312 return Private::connections;
313}
314
315}
316}
317
318#include "moc_connection_thread.cpp"
319

source code of kwayland/src/client/connection_thread.cpp