diff --git a/src/client/connection_thread.cpp b/src/client/connection_thread.cpp index 50affdf..d6fa5ac 100644 --- a/src/client/connection_thread.cpp +++ b/src/client/connection_thread.cpp @@ -1,306 +1,312 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #include "connection_thread.h" #include "logging_p.h" // Qt #include #include #include #include #include #include #include #include #include // Wayland #include namespace KWayland { namespace Client { class Q_DECL_HIDDEN ConnectionThread::Private { public: Private(ConnectionThread *q); ~Private(); void doInitConnection(); void setupSocketNotifier(); void setupSocketFileWatcher(); wl_display *display = nullptr; int fd = -1; QString socketName; QDir runtimeDir; QScopedPointer socketNotifier; QScopedPointer socketWatcher; bool serverDied = false; bool foreign = false; QMetaObject::Connection eventDispatcherConnection; int error = 0; static QVector connections; static QMutex mutex; private: ConnectionThread *q; }; QVector ConnectionThread::Private::connections = QVector{}; QMutex ConnectionThread::Private::mutex{QMutex::Recursive}; ConnectionThread::Private::Private(ConnectionThread *q) : socketName(QString::fromUtf8(qgetenv("WAYLAND_DISPLAY"))) , runtimeDir(QString::fromUtf8(qgetenv("XDG_RUNTIME_DIR"))) , q(q) { if (socketName.isEmpty()) { socketName = QStringLiteral("wayland-0"); } { QMutexLocker lock(&mutex); connections << q; } } ConnectionThread::Private::~Private() { { QMutexLocker lock(&mutex); connections.removeOne(q); } if (display && !foreign) { wl_display_flush(display); wl_display_disconnect(display); } } void ConnectionThread::Private::doInitConnection() { if (fd != -1) { display = wl_display_connect_to_fd(fd); } else { display = wl_display_connect(socketName.toUtf8().constData()); } if (!display) { qCWarning(KWAYLAND_CLIENT) << "Failed connecting to Wayland display"; emit q->failed(); return; } if (fd != -1) { qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server over file descriptor:" << fd; } else { qCDebug(KWAYLAND_CLIENT) << "Connected to Wayland server at:" << socketName; } // setup socket notifier setupSocketNotifier(); setupSocketFileWatcher(); emit q->connected(); } void ConnectionThread::Private::setupSocketNotifier() { const int fd = wl_display_get_fd(display); socketNotifier.reset(new QSocketNotifier(fd, QSocketNotifier::Read)); QObject::connect(socketNotifier.data(), &QSocketNotifier::activated, q, [this]() { if (!display) { return; } if (wl_display_dispatch(display) == -1) { error = wl_display_get_error(display); if (error != 0) { if (display) { free(display); display = nullptr; } emit q->errorOccurred(); return; } } emit q->eventsRead(); } ); } void ConnectionThread::Private::setupSocketFileWatcher() { if (!runtimeDir.exists() || fd != -1) { return; } socketWatcher.reset(new QFileSystemWatcher); socketWatcher->addPath(runtimeDir.absoluteFilePath(socketName)); QObject::connect(socketWatcher.data(), &QFileSystemWatcher::fileChanged, q, [this] (const QString &file) { if (QFile::exists(file) || serverDied) { return; } qCWarning(KWAYLAND_CLIENT) << "Connection to server went away"; serverDied = true; if (display) { free(display); display = nullptr; } socketNotifier.reset(); // need a new filesystem watcher socketWatcher.reset(new QFileSystemWatcher); socketWatcher->addPath(runtimeDir.absolutePath()); QObject::connect(socketWatcher.data(), &QFileSystemWatcher::directoryChanged, q, [this]() { if (!serverDied) { return; } if (runtimeDir.exists(socketName)) { qCDebug(KWAYLAND_CLIENT) << "Socket reappeared"; socketWatcher.reset(); serverDied = false; error = 0; q->initConnection(); } } ); emit q->connectionDied(); } ); } ConnectionThread::ConnectionThread(QObject *parent) : QObject(parent) , d(new Private(this)) { d->eventDispatcherConnection = connect(QCoreApplication::eventDispatcher(), &QAbstractEventDispatcher::aboutToBlock, this, [this] { if (d->display) { wl_display_flush(d->display); } }, Qt::DirectConnection); } +ConnectionThread::ConnectionThread(wl_display *display, QObject *parent) + : QObject(parent) + , d(new Private(this)) +{ + d->display = display; + d->foreign = true; +} + ConnectionThread::~ConnectionThread() { disconnect(d->eventDispatcherConnection); } ConnectionThread *ConnectionThread::fromApplication(QObject *parent) { if (!QGuiApplication::platformName().contains(QStringLiteral("wayland"), Qt::CaseInsensitive)) { return nullptr; } QPlatformNativeInterface *native = qApp->platformNativeInterface(); if (!native) { return nullptr; } wl_display *display = reinterpret_cast(native->nativeResourceForIntegration(QByteArrayLiteral("wl_display"))); if (!display) { return nullptr; } - ConnectionThread *ct = new ConnectionThread(parent); - ct->d->foreign = true; - ct->d->display = display; + ConnectionThread *ct = new ConnectionThread(display, parent); connect(native, &QObject::destroyed, ct, &ConnectionThread::connectionDied); return ct; } void ConnectionThread::initConnection() { QMetaObject::invokeMethod(this, "doInitConnection", Qt::QueuedConnection); } void ConnectionThread::doInitConnection() { d->doInitConnection(); } void ConnectionThread::setSocketName(const QString &socketName) { if (d->display) { // already initialized return; } d->socketName = socketName; } void ConnectionThread::setSocketFd(int fd) { if (d->display) { // already initialized return; } d->fd = fd; } wl_display *ConnectionThread::display() { return d->display; } QString ConnectionThread::socketName() const { return d->socketName; } void ConnectionThread::flush() { if (!d->display) { return; } wl_display_flush(d->display); } void ConnectionThread::roundtrip() { if (!d->display) { return; } if (d->foreign) { // try to perform roundtrip through the QPA plugin if it's supported if (QPlatformNativeInterface *native = qApp->platformNativeInterface()) { // in case the platform provides a dedicated roundtrip function use that install of wl_display_roundtrip QFunctionPointer roundtripFunction = native->platformFunction(QByteArrayLiteral("roundtrip")); if (roundtripFunction) { roundtripFunction(); return; } } } wl_display_roundtrip(d->display); } bool ConnectionThread::hasError() const { return d->error != 0; } int ConnectionThread::errorCode() const { return d->error; } QVector ConnectionThread::connections() { return Private::connections; } } } diff --git a/src/client/connection_thread.h b/src/client/connection_thread.h index 0c4438a..fe65494 100644 --- a/src/client/connection_thread.h +++ b/src/client/connection_thread.h @@ -1,270 +1,276 @@ /******************************************************************** Copyright 2014 Martin Gräßlin This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) version 3, or any later version accepted by the membership of KDE e.V. (or its successor approved by the membership of KDE e.V.), which shall act as a proxy defined in Section 6 of version 3 of the license. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library. If not, see . *********************************************************************/ #ifndef WAYLAND_CONNECTION_THREAD_H #define WAYLAND_CONNECTION_THREAD_H #include #include #include struct wl_display; namespace KWayland { /** * @short KWayland Client. * * This namespace groups all classes related to the Client module. * * The main entry point into the KWayland::Client API is the ConnectionThread class. * It allows to create a Wayland client connection either in a native way or wrap a * connection created by the QtWayland QPA plugin. * * KWayland::Client provides one the one hand a low-level API to interact with the * Wayland API, on the other hand an easy to use convenience API. Each class directly * relates to a low-level Wayland type and allows direct casting into the type. * * On the convenience side KWayland::Client allows easy creation of objects, signals * emitted for Wayland events and easy conversion from Qt to Wayland types. * * Once one has a ConnectionThread created, it's possible to setup a Registry to * get a listing of all registered globals. For each global the Registry provides a convenience * method to create the resource. * * @see ConnectionThread * @see Registry **/ namespace Client { /** * @short Creates and manages the connection to a Wayland server. * * The purpose of this class is to create the connection to a Wayland server * and to manage it. As the name suggests it's intended to move instances of * this class into a dedicated thread. This also means that this class doesn't * inherit QThread. In order to use it in a threaded way one needs to create a * QThread and move the object there: * * @code * ConnectionThread *connection = new ConnectionThread; * QThread *thread = new QThread; * connection->moveToThread(thread); * thread->start(); * @endcode * * To finalize the initialization of the connection one needs to call @link ::initConnection @endlink. * This starts an asynchronous connection initialization. In case the initialization * succeeds the signal @link ::connected @endlink will be emitted, otherwise @link ::failed @endlink will * be emitted: * * @code * connect(connection, &ConnectionThread::connected, [connection] { * qDebug() << "Successfully connected to Wayland server at socket:" << connection->socketName(); * }); * connect(connection, &ConnectionThread::failed, [connection] { * qDebug() << "Failed to connect to Wayland server at socket:" << connection->socketName(); * }); * connection->initConnection(); * @endcode * * This class is also responsible for dispatching events. Whenever new data is available on * the Wayland socket, it will be dispatched and the signal @link ::eventsRead @endlink is emitted. * This allows further event queues in other threads to also dispatch their events. * * Furthermore this class flushes the Wayland connection whenever the QAbstractEventDispatcher * is about to block. * * To disconnect the connection to the Wayland server one should delete the instance of this * class and quit the dedicated thread: * * @code * connection->deleteLater(); * thread->quit(); * thread->wait(); * @endcode * * In addition the ConnectionThread provides integration with QtWayland QPA plugin. For that * it provides a static factory method: * * @code * auto connection = ConnectionThread::fromApplication(); * @endcode * * The semantics of the ConnectionThread are slightly changed if it's integrated with QtWayland. * The ConnectionThread does not hold the connection, does not emit connected or released signals * (one can safely assume that the connection is valid when integrating with the Qt application), * does not dispatch events. Given that the use case of the ConnectionThread is rather limited to * a convenient API around wl_display to allow easily setup an own Registry in a QtWayland powered * application. Also moving the ConnectionThread to a different thread is not necessarily recommended * in that case as QtWayland holds it's connection in an own thread anyway. * **/ class KWAYLANDCLIENT_EXPORT ConnectionThread : public QObject { Q_OBJECT public: explicit ConnectionThread(QObject *parent = nullptr); virtual ~ConnectionThread(); /** * Creates a ConnectionThread for the used QGuiApplication. * This is an integration feature for QtWayland. On non-wayland platforms this method returns * @c nullptr. * * The returned ConnectionThread will be fully setup, which means it manages a wl_display. * There is no need to initConnection and the connected or failed signals won't be emitted. * When the created ConnectionThread gets destroyed the managed wl_display won't be disconnected * as that's managed by Qt. * * The returned ConnectionThread is not able to detect (protocol) error. The signal * {@link errorOccurred} won't be emitted, {@link hasError} will return @c false, even if the * actual connection held by QtWayland is on error. The behavior of QtWayland is to exit the * application on error. * * @since 5.4 **/ static ConnectionThread *fromApplication(QObject *parent = nullptr); /** * The display this ConnectionThread is connected to. * As long as there is no connection this method returns @c null. * @see initConnection **/ wl_display *display(); /** * @returns the name of the socket it connects to. **/ QString socketName() const; /** * Sets the @p socketName to connect to. * Only applies if called before calling initConnection. * The default socket name is derived from environment variable WAYLAND_DISPLAY * and if not set is hard coded to "wayland-0". * * The socket name will be ignored if a file descriptor has been set through @link setSocketFd @endlink. * * @see setSocketFd **/ void setSocketName(const QString &socketName); /** * Sets the socket @p fd to connect to. * Only applies if called before calling initConnection. * If this method is invoked, the connection will be created on the file descriptor * and not on the socket name passed through @link setSocketName @endlink or through the * default environment variable WAYLAND_DISPLAY. * @see setSocketName **/ void setSocketFd(int fd); /** * Trigger a blocking roundtrip to the Wayland server. Ensures that all events are processed * before returning to the event loop. * * @since 5.4 **/ void roundtrip(); /** * @returns whether the Wayland connection experienced an error * @see errorCode * @see errorOccurred * @since 5.23 **/ bool hasError() const; /** * @returns the error code of the last occurred error or @c 0 if the connection doesn't have an error * @see hasError * @see errorOccurred * @since 5.23 **/ int errorCode() const; /** * @returns all connections created in this application * @since 5.37 **/ static QVector connections(); public Q_SLOTS: /** * Initializes the connection in an asynchronous way. * In case the connection gets established the signal @link ::connected @endlink will be * emitted, on failure the signal @link ::failed @endlink will be emitted. * * @see connected * @see failed **/ void initConnection(); /** * Explicitly flush the Wayland display. * @since 5.3 **/ void flush(); Q_SIGNALS: /** * Emitted once a connection to a Wayland server is established. * Normally emitted after invoking initConnection(), but might also be * emitted after re-connecting to another server. **/ void connected(); /** * Emitted if connecting to a Wayland server failed. **/ void failed(); /** * Emitted whenever new events are ready to be read. **/ void eventsRead(); /** * Emitted if the Wayland server connection dies. * If the socket reappears, it is tried to reconnect. **/ void connectionDied(); /** * The Wayland connection experienced a fatal error. * The ConnectionThread is no longer valid, no requests may be sent. * This has the same effects as {@link connectionDied}. * * @see hasError * @see errorCode * @since 5.23 **/ void errorOccurred(); +protected: + /* + * Creates a connection thread from an existing wl_display object + * @see ConnectionThread::fromApplication + */ + explicit ConnectionThread(wl_display *display, QObject *parent); private Q_SLOTS: /** * @internal **/ void doInitConnection(); private: class Private; QScopedPointer d; }; } } #endif