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 | |
23 | namespace KWayland |
24 | { |
25 | namespace Client |
26 | { |
27 | class Q_DECL_HIDDEN ConnectionThread::Private |
28 | { |
29 | public: |
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 | |
50 | private: |
51 | ConnectionThread *q; |
52 | }; |
53 | |
54 | QList<ConnectionThread *> ConnectionThread::Private::connections = QList<ConnectionThread *>{}; |
55 | QRecursiveMutex ConnectionThread::Private::mutex; |
56 | |
57 | ConnectionThread::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 | |
71 | ConnectionThread::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 | |
83 | void 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 | |
107 | void 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 | |
116 | void 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 | |
153 | void 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 | |
191 | ConnectionThread::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 | |
207 | ConnectionThread::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 | |
215 | ConnectionThread::~ConnectionThread() |
216 | { |
217 | disconnect(d->eventDispatcherConnection); |
218 | } |
219 | |
220 | ConnectionThread *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 | |
235 | void ConnectionThread::initConnection() |
236 | { |
237 | QMetaObject::invokeMethod(object: this, function: &ConnectionThread::doInitConnection, type: Qt::QueuedConnection); |
238 | } |
239 | |
240 | void ConnectionThread::doInitConnection() |
241 | { |
242 | d->doInitConnection(); |
243 | } |
244 | |
245 | void ConnectionThread::setSocketName(const QString &socketName) |
246 | { |
247 | if (d->display) { |
248 | // already initialized |
249 | return; |
250 | } |
251 | d->socketName = socketName; |
252 | } |
253 | |
254 | void ConnectionThread::setSocketFd(int fd) |
255 | { |
256 | if (d->display) { |
257 | // already initialized |
258 | return; |
259 | } |
260 | d->fd = fd; |
261 | } |
262 | |
263 | wl_display *ConnectionThread::display() |
264 | { |
265 | return d->display; |
266 | } |
267 | |
268 | QString ConnectionThread::socketName() const |
269 | { |
270 | return d->socketName; |
271 | } |
272 | |
273 | void ConnectionThread::flush() |
274 | { |
275 | if (!d->display) { |
276 | return; |
277 | } |
278 | wl_display_flush(display: d->display); |
279 | } |
280 | |
281 | void 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 | |
300 | bool ConnectionThread::hasError() const |
301 | { |
302 | return d->error != 0; |
303 | } |
304 | |
305 | int ConnectionThread::errorCode() const |
306 | { |
307 | return d->error; |
308 | } |
309 | |
310 | QList<ConnectionThread *> ConnectionThread::connections() |
311 | { |
312 | return Private::connections; |
313 | } |
314 | |
315 | } |
316 | } |
317 | |
318 | #include "moc_connection_thread.cpp" |
319 | |