| 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 | |