335 lines
10 KiB
C++
335 lines
10 KiB
C++
// 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
|