File indexing completed on 2024-12-22 05:09:19

0001 /*
0002     SPDX-FileCopyrightText: 2014 Martin Gräßlin <mgraesslin@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0005 */
0006 #include "connection_thread.h"
0007 #include "logging.h"
0008 // Qt
0009 #include <QAbstractEventDispatcher>
0010 #include <QDebug>
0011 #include <QDir>
0012 #include <QFileSystemWatcher>
0013 #include <QGuiApplication>
0014 #include <QMutex>
0015 #include <QMutexLocker>
0016 #include <QSocketNotifier>
0017 #include <qpa/qplatformnativeinterface.h>
0018 // Wayland
0019 #include <wayland-client-protocol.h>
0020 
0021 #include <poll.h>
0022 
0023 namespace KWayland
0024 {
0025 namespace Client
0026 {
0027 class Q_DECL_HIDDEN ConnectionThread::Private
0028 {
0029 public:
0030     Private(ConnectionThread *q);
0031     ~Private();
0032     void doInitConnection();
0033     void setupSocketNotifier();
0034     void setupSocketFileWatcher();
0035     void dispatchEvents();
0036 
0037     wl_display *display = nullptr;
0038     int fd = -1;
0039     QString socketName;
0040     QDir runtimeDir;
0041     QScopedPointer<QSocketNotifier> socketNotifier;
0042     QScopedPointer<QFileSystemWatcher> socketWatcher;
0043     bool serverDied = false;
0044     bool foreign = false;
0045     QMetaObject::Connection eventDispatcherConnection;
0046     int error = 0;
0047     static QList<ConnectionThread *> connections;
0048     static QRecursiveMutex mutex;
0049 
0050 private:
0051     ConnectionThread *q;
0052 };
0053 
0054 QList<ConnectionThread *> ConnectionThread::Private::connections = QList<ConnectionThread *>{};
0055 QRecursiveMutex ConnectionThread::Private::mutex;
0056 
0057 ConnectionThread::Private::Private(ConnectionThread *q)
0058     : socketName(QString::fromUtf8(qgetenv("WAYLAND_DISPLAY")))
0059     , runtimeDir(QString::fromUtf8(qgetenv("XDG_RUNTIME_DIR")))
0060     , q(q)
0061 {
0062     if (socketName.isEmpty()) {
0063         socketName = QStringLiteral("wayland-0");
0064     }
0065     {
0066         QMutexLocker lock(&mutex);
0067         connections << q;
0068     }
0069 }
0070 
0071 ConnectionThread::Private::~Private()
0072 {
0073     {
0074         QMutexLocker lock(&mutex);
0075         connections.removeOne(q);
0076     }
0077     if (display && !foreign) {
0078         wl_display_flush(display);
0079         wl_display_disconnect(display);
0080     }
0081 }
0082 
0083 void ConnectionThread::Private::doInitConnection()
0084 {
0085     if (fd != -1) {
0086         display = wl_display_connect_to_fd(fd);
0087     } else {
0088         display = wl_display_connect(socketName.toUtf8().constData());
0089     }
0090     if (!display) {
0091         qCWarning(KWAYLAND_CLIENT) << "Failed connecting to Wayland display";
0092         Q_EMIT q->failed();
0093         return;
0094     }
0095     if (fd != -1) {
0096         qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server over file descriptor:" << fd;
0097     } else {
0098         qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server at:" << socketName;
0099     }
0100 
0101     // setup socket notifier
0102     setupSocketNotifier();
0103     setupSocketFileWatcher();
0104     Q_EMIT q->connected();
0105 }
0106 
0107 void ConnectionThread::Private::setupSocketNotifier()
0108 {
0109     const int fd = wl_display_get_fd(display);
0110     socketNotifier.reset(new QSocketNotifier(fd, QSocketNotifier::Read));
0111     QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, q, [this]() {
0112         dispatchEvents();
0113     });
0114 }
0115 
0116 void ConnectionThread::Private::dispatchEvents()
0117 {
0118     if (!display) {
0119         return;
0120     }
0121     // first dispatch any pending events on the default queue
0122     while (wl_display_prepare_read(display) != 0) {
0123         wl_display_dispatch_pending(display);
0124     }
0125     wl_display_flush(display);
0126     // then check if there are any new events waiting to be read
0127     struct pollfd pfd;
0128     pfd.fd = wl_display_get_fd(display);
0129     pfd.events = POLLIN;
0130     int ret = poll(&pfd, 1, 0);
0131     if (ret > 0) {
0132         // if yes, read them now
0133         wl_display_read_events(display);
0134     } else {
0135         wl_display_cancel_read(display);
0136     }
0137 
0138     // finally, dispatch the default queue and all frame queues
0139     if (wl_display_dispatch_pending(display) == -1) {
0140         error = wl_display_get_error(display);
0141         if (error != 0) {
0142             if (display) {
0143                 free(display);
0144                 display = nullptr;
0145             }
0146             Q_EMIT q->errorOccurred();
0147             return;
0148         }
0149     }
0150     Q_EMIT q->eventsRead();
0151 }
0152 
0153 void ConnectionThread::Private::setupSocketFileWatcher()
0154 {
0155     if (!runtimeDir.exists() || fd != -1) {
0156         return;
0157     }
0158     socketWatcher.reset(new QFileSystemWatcher);
0159     socketWatcher->addPath(runtimeDir.absoluteFilePath(socketName));
0160     QObject::connect(socketWatcher.data(), &QFileSystemWatcher::fileChanged, q, [this](const QString &file) {
0161         if (QFile::exists(file) || serverDied) {
0162             return;
0163         }
0164         qCWarning(KWAYLAND_CLIENT) << "Connection to server went away";
0165         serverDied = true;
0166         if (display) {
0167             free(display);
0168             display = nullptr;
0169         }
0170         socketNotifier.reset();
0171 
0172         // need a new filesystem watcher
0173         socketWatcher.reset(new QFileSystemWatcher);
0174         socketWatcher->addPath(runtimeDir.absolutePath());
0175         QObject::connect(socketWatcher.data(), &QFileSystemWatcher::directoryChanged, q, [this]() {
0176             if (!serverDied) {
0177                 return;
0178             }
0179             if (runtimeDir.exists(socketName)) {
0180                 qCDebug(KWAYLAND_CLIENT) << "Socket reappeared";
0181                 socketWatcher.reset();
0182                 serverDied = false;
0183                 error = 0;
0184                 q->initConnection();
0185             }
0186         });
0187         Q_EMIT q->connectionDied();
0188     });
0189 }
0190 
0191 ConnectionThread::ConnectionThread(QObject *parent)
0192     : QObject(parent)
0193     , d(new Private(this))
0194 {
0195     d->eventDispatcherConnection = connect(
0196         QCoreApplication::eventDispatcher(),
0197         &QAbstractEventDispatcher::aboutToBlock,
0198         this,
0199         [this] {
0200             if (d->display) {
0201                 wl_display_flush(d->display);
0202             }
0203         },
0204         Qt::DirectConnection);
0205 }
0206 
0207 ConnectionThread::ConnectionThread(wl_display *display, QObject *parent)
0208     : QObject(parent)
0209     , d(new Private(this))
0210 {
0211     d->display = display;
0212     d->foreign = true;
0213 }
0214 
0215 ConnectionThread::~ConnectionThread()
0216 {
0217     disconnect(d->eventDispatcherConnection);
0218 }
0219 
0220 ConnectionThread *ConnectionThread::fromApplication(QObject *parent)
0221 {
0222     QPlatformNativeInterface *native = qApp->platformNativeInterface();
0223     if (!native) {
0224         return nullptr;
0225     }
0226     wl_display *display = reinterpret_cast<wl_display *>(native->nativeResourceForIntegration(QByteArrayLiteral("wl_display")));
0227     if (!display) {
0228         return nullptr;
0229     }
0230     ConnectionThread *ct = new ConnectionThread(display, parent);
0231     connect(native, &QObject::destroyed, ct, &ConnectionThread::connectionDied);
0232     return ct;
0233 }
0234 
0235 void ConnectionThread::initConnection()
0236 {
0237     QMetaObject::invokeMethod(this, &ConnectionThread::doInitConnection, Qt::QueuedConnection);
0238 }
0239 
0240 void ConnectionThread::doInitConnection()
0241 {
0242     d->doInitConnection();
0243 }
0244 
0245 void ConnectionThread::setSocketName(const QString &socketName)
0246 {
0247     if (d->display) {
0248         // already initialized
0249         return;
0250     }
0251     d->socketName = socketName;
0252 }
0253 
0254 void ConnectionThread::setSocketFd(int fd)
0255 {
0256     if (d->display) {
0257         // already initialized
0258         return;
0259     }
0260     d->fd = fd;
0261 }
0262 
0263 wl_display *ConnectionThread::display()
0264 {
0265     return d->display;
0266 }
0267 
0268 QString ConnectionThread::socketName() const
0269 {
0270     return d->socketName;
0271 }
0272 
0273 void ConnectionThread::flush()
0274 {
0275     if (!d->display) {
0276         return;
0277     }
0278     wl_display_flush(d->display);
0279 }
0280 
0281 void ConnectionThread::roundtrip()
0282 {
0283     if (!d->display) {
0284         return;
0285     }
0286     if (d->foreign) {
0287         // try to perform roundtrip through the QPA plugin if it's supported
0288         if (QPlatformNativeInterface *native = qApp->platformNativeInterface()) {
0289             // in case the platform provides a dedicated roundtrip function use that install of wl_display_roundtrip
0290             QFunctionPointer roundtripFunction = native->platformFunction(QByteArrayLiteral("roundtrip"));
0291             if (roundtripFunction) {
0292                 roundtripFunction();
0293                 return;
0294             }
0295         }
0296     }
0297     wl_display_roundtrip(d->display);
0298 }
0299 
0300 bool ConnectionThread::hasError() const
0301 {
0302     return d->error != 0;
0303 }
0304 
0305 int ConnectionThread::errorCode() const
0306 {
0307     return d->error;
0308 }
0309 
0310 QList<ConnectionThread *> ConnectionThread::connections()
0311 {
0312     return Private::connections;
0313 }
0314 
0315 }
0316 }
0317 
0318 #include "moc_connection_thread.cpp"