diff options
Diffstat (limited to 'src/dbus/qdbusconnectionmanager.cpp')
-rw-r--r-- | src/dbus/qdbusconnectionmanager.cpp | 334 |
1 files changed, 334 insertions, 0 deletions
diff --git a/src/dbus/qdbusconnectionmanager.cpp b/src/dbus/qdbusconnectionmanager.cpp new file mode 100644 index 0000000000..ce52c9fa63 --- /dev/null +++ b/src/dbus/qdbusconnectionmanager.cpp @@ -0,0 +1,334 @@ +// Copyright (C) 2016 The Qt Company Ltd. +// Copyright (C) 2016 Intel Corporation. +// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only + +#include "qdbusconnectionmanager_p.h" + +#include <qcoreapplication.h> +#include <qthread.h> +#include <qstringlist.h> +#include <QtCore/private/qlocking_p.h> + +#include "qdbuserror.h" +#include "qdbuspendingcall_p.h" +#include "qdbusmetatype_p.h" + +#ifndef QT_NO_DBUS + +QT_BEGIN_NAMESPACE + +#ifdef Q_OS_WIN +static void preventDllUnload(); +#endif + +Q_GLOBAL_STATIC(QDBusConnectionManager, _q_manager) + +QDBusConnectionPrivate *QDBusConnectionManager::busConnection(QDBusConnection::BusType type) +{ + static_assert(int(QDBusConnection::SessionBus) + int(QDBusConnection::SystemBus) == 1); + Q_ASSERT(type == QDBusConnection::SessionBus || type == QDBusConnection::SystemBus); + + if (!qdbus_loadLibDBus()) + return nullptr; + + // we'll start in suspended delivery mode if we're in the main thread + // (the event loop will resume delivery) + bool suspendedDelivery = qApp && qApp->thread() == QThread::currentThread(); + + const auto locker = qt_scoped_lock(defaultBusMutex); + if (defaultBuses[type]) + return defaultBuses[type]; + + QString name = QStringLiteral("qt_default_session_bus"); + if (type == QDBusConnection::SystemBus) + name = QStringLiteral("qt_default_system_bus"); + return defaultBuses[type] = connectToBus(type, name, suspendedDelivery); +} + +QDBusConnectionPrivate *QDBusConnectionManager::connection(const QString &name) const +{ + return connectionHash.value(name, nullptr); +} + +QDBusConnectionPrivate *QDBusConnectionManager::existingConnection(const QString &name) const +{ + const auto locker = qt_scoped_lock(mutex); + auto *conn = connection(name); + if (conn) + conn->ref.ref(); + return conn; +} + +void QDBusConnectionManager::removeConnection(const QString &name) +{ + QDBusConnectionPrivate *d = nullptr; + d = connectionHash.take(name); + if (d && !d->ref.deref()) + d->deleteLater(); + + // Static objects may be keeping the connection open. + // However, it is harmless to have outstanding references to a connection that is + // closing as long as those references will be soon dropped without being used. + + // ### Output a warning if connections are being used after they have been removed. +} + +void QDBusConnectionManager::removeConnections(const QStringList &names) +{ + const auto locker = qt_scoped_lock(mutex); + + for (const auto &name : names) + removeConnection(name); +} + +void QDBusConnectionManager::disconnectFrom(const QString &name, + QDBusConnectionPrivate::ConnectionMode mode) +{ + const auto locker = qt_scoped_lock(mutex); + + QDBusConnectionPrivate *d = connection(name); + if (d && d->mode != mode) + return; + removeConnection(name); +} + +QDBusConnectionManager::QDBusConnectionManager() +{ + // Ensure that the custom metatype registry is created before the instance + // of this class. This will ensure that the registry is not destroyed before + // the connection manager at application exit (see also QTBUG-58732). This + // works with compilers that use mechanism similar to atexit() to call + // destructurs for global statics. + QDBusMetaTypeId::init(); + + moveToThread(this); // ugly, don't do this in other projects + +#ifdef Q_OS_WIN + // prevent the library from being unloaded on Windows. See comments in the function. + preventDllUnload(); +#endif + defaultBuses[0] = defaultBuses[1] = nullptr; + start(); +} + +QDBusConnectionManager::~QDBusConnectionManager() +{ + quit(); + wait(); +} + +QDBusConnectionManager* QDBusConnectionManager::instance() +{ + return _q_manager(); +} + +Q_DBUS_EXPORT void qDBusBindToApplication(); +void qDBusBindToApplication() +{ +} + +void QDBusConnectionManager::setConnection(const QString &name, QDBusConnectionPrivate *c) +{ + connectionHash[name] = c; + c->name = name; +} + +void QDBusConnectionManager::addConnection(const QString &name, QDBusConnectionPrivate *c) +{ + const auto locker = qt_scoped_lock(mutex); + setConnection(name, c); +} + +void QDBusConnectionManager::run() +{ + exec(); + + // cleanup: + const auto locker = qt_scoped_lock(mutex); + for (QDBusConnectionPrivate *d : std::as_const(connectionHash)) { + if (!d->ref.deref()) { + delete d; + } else { + d->closeConnection(); + d->moveToThread(nullptr); // allow it to be deleted in another thread + } + } + connectionHash.clear(); + + // allow deletion from any thread without warning + moveToThread(nullptr); +} + +QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(QDBusConnection::BusType type, const QString &name, + bool suspendedDelivery) +{ + QDBusConnectionPrivate *result = nullptr; + + QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToStandardBus, + Qt::BlockingQueuedConnection, qReturnArg(result), type, name, + suspendedDelivery); + + if (suspendedDelivery && result && result->connection) + result->enableDispatchDelayed(qApp); // qApp was checked in the caller + + return result; +} + +QDBusConnectionPrivate *QDBusConnectionManager::connectToBus(const QString &address, const QString &name) +{ + QDBusConnectionPrivate *result = nullptr; + + QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToBus, + Qt::BlockingQueuedConnection, qReturnArg(result), address, name); + + return result; +} + +QDBusConnectionPrivate *QDBusConnectionManager::connectToPeer(const QString &address, const QString &name) +{ + QDBusConnectionPrivate *result = nullptr; + + QMetaObject::invokeMethod(this, &QDBusConnectionManager::doConnectToPeer, + Qt::BlockingQueuedConnection, qReturnArg(result), address, name); + + return result; +} + +QDBusConnectionPrivate * +QDBusConnectionManager::doConnectToStandardBus(QDBusConnection::BusType type, const QString &name, + bool suspendedDelivery) +{ + const auto locker = qt_scoped_lock(mutex); + + // check if the connection exists by name + QDBusConnectionPrivate *d = connection(name); + if (d || name.isEmpty()) + return d; + + d = new QDBusConnectionPrivate; + DBusConnection *c = nullptr; + QDBusErrorInternal error; + + switch (type) { + case QDBusConnection::SystemBus: + c = q_dbus_bus_get_private(DBUS_BUS_SYSTEM, error); + break; + case QDBusConnection::SessionBus: + c = q_dbus_bus_get_private(DBUS_BUS_SESSION, error); + break; + case QDBusConnection::ActivationBus: + c = q_dbus_bus_get_private(DBUS_BUS_STARTER, error); + break; + } + + setConnection(name, d); + + // create the bus service + // will lock in QDBusConnectionPrivate::connectRelay() + d->setConnection(c, error); + d->createBusService(); + if (c && suspendedDelivery) + d->setDispatchEnabled(false); + + return d; +} + +QDBusConnectionPrivate *QDBusConnectionManager::doConnectToBus(const QString &address, + const QString &name) +{ + const auto locker = qt_scoped_lock(mutex); + + // check if the connection exists by name + QDBusConnectionPrivate *d = connection(name); + if (d || name.isEmpty()) + return d; + + d = new QDBusConnectionPrivate; + QDBusErrorInternal error; + + DBusConnection *c = q_dbus_connection_open_private(address.toUtf8().constData(), error); + if (c) { + // register on the bus + if (!q_dbus_bus_register(c, error)) { + q_dbus_connection_close(c); + q_dbus_connection_unref(c); + c = nullptr; + } + } + + setConnection(name, d); + + // create the bus service + // will lock in QDBusConnectionPrivate::connectRelay() + d->setConnection(c, error); + d->createBusService(); + + return d; +} + +QDBusConnectionPrivate *QDBusConnectionManager::doConnectToPeer(const QString &address, + const QString &name) +{ + const auto locker = qt_scoped_lock(mutex); + + // check if the connection exists by name + QDBusConnectionPrivate *d = connection(name); + if (d || name.isEmpty()) + return d; + + d = new QDBusConnectionPrivate; + QDBusErrorInternal error; + + DBusConnection *c = q_dbus_connection_open_private(address.toUtf8().constData(), error); + + setConnection(name, d); + d->setPeer(c, error); + + return d; +} + +void QDBusConnectionManager::createServer(const QString &address, QDBusServer *server) +{ + QMetaObject::invokeMethod( + this, + [&address, server] { + QDBusErrorInternal error; + QDBusConnectionPrivate *d = new QDBusConnectionPrivate; + d->setServer(server, q_dbus_server_listen(address.toUtf8().constData(), error), + error); + }, + Qt::BlockingQueuedConnection); +} + +QT_END_NAMESPACE + +#include "moc_qdbusconnectionmanager_p.cpp" + +#ifdef Q_OS_WIN +# include <qt_windows.h> + +QT_BEGIN_NAMESPACE +static void preventDllUnload() +{ + // Thread termination is really wacky on Windows. For some reason we don't + // understand, exiting from the thread may try to unload the DLL. Since the + // QDBusConnectionManager thread runs until the DLL is unloaded, we've got + // a deadlock: the main thread is waiting for the manager thread to exit, + // but the manager thread is attempting to acquire a lock to unload the DLL. + // + // We work around the issue by preventing the unload from happening in the + // first place. + // + // For this trick, see the blog post titled "What is the point of FreeLibraryAndExitThread?" + // https://devblogs.microsoft.com/oldnewthing/20131105-00/?p=2733 + + static HMODULE self; + GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | + GET_MODULE_HANDLE_EX_FLAG_PIN, + reinterpret_cast<const wchar_t *>(&self), // any address in this DLL + &self); +} +QT_END_NAMESPACE +#endif + +#endif // QT_NO_DBUS |