diff options
Diffstat (limited to 'qt/src/qdbusconnection.cpp')
-rw-r--r-- | qt/src/qdbusconnection.cpp | 753 |
1 files changed, 753 insertions, 0 deletions
diff --git a/qt/src/qdbusconnection.cpp b/qt/src/qdbusconnection.cpp new file mode 100644 index 0000000..6cd733c --- /dev/null +++ b/qt/src/qdbusconnection.cpp @@ -0,0 +1,753 @@ +/* qdbusconnection.cpp + * + * Copyright (C) 2005 Harald Fernengel <harry@kdevelop.org> + * Copyright (C) 2006 Trolltech AS. All rights reserved. + * Author: Thiago Macieira <thiago.macieira@trolltech.com> + * + * Licensed under the Academic Free License version 2.1 + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ + +#include <qdebug.h> +#include <qcoreapplication.h> +#include <qstringlist.h> + +#include "qdbusbus.h" +#include "qdbusconnection.h" +#include "qdbuserror.h" +#include "qdbusmessage.h" +#include "qdbusconnection_p.h" +#include "qdbusinterface_p.h" +#include "qdbusutil.h" + +class QDBusConnectionManager +{ +public: + QDBusConnectionManager() {} + ~QDBusConnectionManager(); + void bindToApplication(); + QDBusConnectionPrivate *connection(const QString &name) const; + void removeConnection(const QString &name); + void setConnection(const QString &name, QDBusConnectionPrivate *c); + +private: + mutable QMutex mutex; + QHash<QString, QDBusConnectionPrivate *> connectionHash; +}; + +Q_GLOBAL_STATIC(QDBusConnectionManager, manager) + +QDBusConnectionPrivate *QDBusConnectionManager::connection(const QString &name) const +{ + QMutexLocker locker(&mutex); + return connectionHash.value(name, 0); +} + +void QDBusConnectionManager::removeConnection(const QString &name) +{ + QMutexLocker locker(&mutex); + + QDBusConnectionPrivate *d = 0; + d = connectionHash.take(name); + if (d && !d->ref.deref()) + delete d; +} + +QDBusConnectionManager::~QDBusConnectionManager() +{ + for (QHash<QString, QDBusConnectionPrivate *>::const_iterator it = connectionHash.constBegin(); + it != connectionHash.constEnd(); ++it) { + delete it.value(); + } + connectionHash.clear(); +} + +void QDBusConnectionManager::bindToApplication() +{ + QMutexLocker locker(&mutex); + for (QHash<QString, QDBusConnectionPrivate *>::const_iterator it = connectionHash.constBegin(); + it != connectionHash.constEnd(); ++it) { + (*it)->bindToApplication(); + } +} + +QDBUS_EXPORT void qDBusBindToApplication(); +void qDBusBindToApplication() +{ + manager()->bindToApplication(); +} + +void QDBusConnectionManager::setConnection(const QString &name, QDBusConnectionPrivate *c) +{ + connectionHash[name] = c; + c->name = name; +} + +/*! + \fn QDBusConnection QDBus::sessionBus() + \relates QDBusConnection + + Returns a QDBusConnection object opened with the session bus. The object reference returned + by this function is valid until the QCoreApplication's destructor is run, when the + connection will be closed and the object, deleted. +*/ +/*! + \fn QDBusConnection QDBus::systemBus() + \relates QDBusConnection + + Returns a QDBusConnection object opened with the system bus. The object reference returned + by this function is valid until the QCoreApplication's destructor is run, when the + connection will be closed and the object, deleted. +*/ + +/*! + \class QDBusConnection + \brief A connection to the D-Bus bus daemon. + + This class is the initial point in a D-Bus session. Using it, you can get access to remote + objects, interfaces; connect remote signals to your object's slots; register objects, etc. + + D-Bus connections are created using the QDBusConnection::addConnection() function, which opens a + connection to the server daemon and does the initial handshaking, associating that connection + with a name. Further attempts to connect using the same name will return the same + connection. + + The connection is then torn down using the QDBusConnection::closeConnection() function. + + As a convenience for the two most common connection types, the QDBus::sessionBus() and + QDBus::systemBus() functions return open connections to the session server daemon and the system + server daemon, respectively. Those connections are opened when first used and are closed when + the QCoreApplication destructor is run. + + D-Bus also supports peer-to-peer connections, without the need for a bus server daemon. Using + this facility, two applications can talk to each other and exchange messages. This can be + achieved by passing an address to QDBusConnection::addConnection() + function, which was opened by another D-Bus application using QDBusServer. +*/ + +/*! + \enum QDBusConnection::BusType + Specifies the type of the bus connection. The valid bus types are: + + \value SessionBus the session bus, associated with the running desktop session + \value SystemBus the system bus, used to communicate with system-wide processes + \value ActivationBus the activation bus, whose purpose I have no idea... + + On the Session Bus, one can find other applications by the same user that are sharing the same + desktop session (hence the name). On the System Bus, however, processes shared for the whole + system are usually found. +*/ + +/*! + \enum QDBusConnection::WaitMode + Specifies the call waiting mode. + + \value UseEventLoop use the Qt Event Loop to wait for the reply + \value NoUseEventLoop don't use the event loop + + The \c UseEventLoop option allows for the application to continue to update its UI while the + call is performed, but it also opens up the possibility for reentrancy: socket notifiers may + fire, signals may be delivered and other D-Bus calls may be processed. The \c NoUseEventLoop + does not use the event loop, thus being safe from those problems, but it may block the + application for a noticeable period of time, in case the remote application fails to respond. + + Also note that calls that go back to the local application can only be placed in \c UseEventLoop + mode. +*/ + +/*! + \enum QDBusConnection::RegisterOption + Specifies the options for registering objects with the connection. The possible values are: + + \value ExportAdaptors export the contents of adaptors found in this object + + \value ExportSlots export this object's scriptable slots + \value ExportSignals export this object's scriptable signals + \value ExportProperties export this object's scriptable properties + \value ExportContents shorthand form for ExportSlots | ExportSignals | + ExportProperties + + \value ExportAllSlots export all of this object's slots, including + non-scriptable ones + \value ExportAllSignals export all of this object's signals, including + non-scriptable ones + \value ExportAllProperties export all of this object's properties, including + non-scriptable ones + \value ExportAllContents export all of this object's slots, signals and + properties, including non-scriptable ones + + \value ExportChildObjects export this object's child objects + + \warning It is currently not possible to export signals from objects. If you pass the flag + ExportSignals or ExportAllSignals, the registerObject() function will print a warning. + + \sa registerObject(), QDBusAbstractAdaptor, {usingadaptors.html}{Using adaptors} +*/ + +/*! + \enum QDBusConnection::UnregisterMode + The mode for unregistering an object path: + + \value UnregisterNode unregister this node only: do not unregister child objects + \value UnregisterTree unregister this node and all its sub-tree + + Note, however, if this object was registered with the ExportChildObjects option, UnregisterNode + will unregister the child objects too. +*/ + +/*! + Creates a QDBusConnection object attached to the connection with name \a name. + + This does not open the connection. You have to call QDBusConnection::addConnection to open it. +*/ +QDBusConnection::QDBusConnection(const QString &name) +{ + d = manager()->connection(name); + if (d) + d->ref.ref(); +} + +/*! + Creates a copy of the \a other connection. +*/ +QDBusConnection::QDBusConnection(const QDBusConnection &other) +{ + d = other.d; + if (d) + d->ref.ref(); +} + +/*! + Disposes of this object. This does not close the connection: you have to call + QDBusConnection::closeConnection to do that. +*/ +QDBusConnection::~QDBusConnection() +{ + if (d && !d->ref.deref()) + delete d; +} + +/*! + Creates a copy of the connection \a other in this object. The connection this object referenced + before the copy is not spontaneously disconnected. See QDBusConnection::closeConnection for more + information. +*/ +QDBusConnection &QDBusConnection::operator=(const QDBusConnection &other) +{ + if (other.d) + other.d->ref.ref(); + QDBusConnectionPrivate *old = static_cast<QDBusConnectionPrivate *>( + q_atomic_set_ptr(&d, other.d)); + if (old && !old->ref.deref()) + delete old; + + return *this; +} + +/*! + Opens a connection of type \a type to one of the known busses and associate with it the + connection name \a name. Returns a QDBusConnection object associated with that connection. +*/ +QDBusConnection QDBusConnection::addConnection(BusType type, const QString &name) +{ +// Q_ASSERT_X(QCoreApplication::instance(), "QDBusConnection::addConnection", +// "Cannot create connection without a Q[Core]Application instance"); + + QDBusConnectionPrivate *d = manager()->connection(name); + if (d || name.isEmpty()) + return QDBusConnection(name); + + d = new QDBusConnectionPrivate; + DBusConnection *c = 0; + switch (type) { + case SystemBus: + c = dbus_bus_get_private(DBUS_BUS_SYSTEM, &d->error); + break; + case SessionBus: + c = dbus_bus_get_private(DBUS_BUS_SESSION, &d->error); + break; + case ActivationBus: + c = dbus_bus_get_private(DBUS_BUS_STARTER, &d->error); + break; + } + d->setConnection(c); //setConnection does the error handling for us + + manager()->setConnection(name, d); + + QDBusConnection retval(name); + + // create the bus service + QDBusAbstractInterfacePrivate *p; + p = retval.findInterface_helper(QLatin1String(DBUS_SERVICE_DBUS), + QLatin1String(DBUS_PATH_DBUS), + QLatin1String(DBUS_INTERFACE_DBUS)); + if (p) { + d->busService = new QDBusBusService(p); + d->busService->setParent(d); // auto-deletion + d->ref.deref(); // busService has a increased the refcounting to us + } + + return retval; +} + +/*! + Opens a peer-to-peer connection on address \a address and associate with it the + connection name \a name. Returns a QDBusConnection object associated with that connection. +*/ +QDBusConnection QDBusConnection::addConnection(const QString &address, + const QString &name) +{ +// Q_ASSERT_X(QCoreApplication::instance(), "QDBusConnection::addConnection", +// "Cannot create connection without a Q[Core]Application instance"); + + QDBusConnectionPrivate *d = manager()->connection(name); + if (d || name.isEmpty()) + return QDBusConnection(name); + + d = new QDBusConnectionPrivate; + // setConnection does the error handling for us + d->setConnection(dbus_connection_open(address.toUtf8().constData(), &d->error)); + + manager()->setConnection(name, d); + + QDBusConnection retval(name); + + // create the bus service + QDBusAbstractInterfacePrivate *p; + p = retval.findInterface_helper(QLatin1String(DBUS_SERVICE_DBUS), + QLatin1String(DBUS_PATH_DBUS), + QLatin1String(DBUS_INTERFACE_DBUS)); + if (p) { + d->busService = new QDBusBusService(p); + d->busService->setParent(d); // auto-deletion + d->ref.deref(); // busService has a increased the refcounting to us + } + + return retval; +} + +/*! + Closes the connection of name \a name. + + Note that if there are still QDBusConnection objects associated with the same connection, the + connection will not be closed until all references are dropped. However, no further references + can be created using the QDBusConnection::QDBusConnection constructor. +*/ +void QDBusConnection::closeConnection(const QString &name) +{ + manager()->removeConnection(name); +} + +/*! + Sends the \a message over this connection, without waiting for a reply. This is suitable for + errors, signals, and return values as well as calls whose return values are not necessary. + + Returns true if the message was queued successfully, false otherwise. +*/ +bool QDBusConnection::send(const QDBusMessage &message) const +{ + if (!d || !d->connection) + return false; + return d->send(message) != 0; +} + +/*! + Sends the \a message over this connection and returns immediately after queueing it. When the + reply is received, the slot \a method is called in the object \a receiver. This function is + suitable for method calls only. + + This function guarantees that the slot will be called exactly once with the reply, as long as + the parameter types match. If they don't, the reply cannot be delivered. + + Returns the identification of the message that was sent or 0 if nothing was sent. +*/ +int QDBusConnection::sendWithReplyAsync(const QDBusMessage &message, QObject *receiver, + const char *method) const +{ + if (!d || !d->connection) + return 0; + + return d->sendWithReplyAsync(message, receiver, method); +} + +/*! + Sends the \a message over this connection and blocks, waiting for a reply. This function is + suitable for method calls only. It returns the reply message as its return value, which will be + either of type QDBusMessage::ReplyMessage or QDBusMessage::ErrorMessage. + + See the QDBusInterface::call function for a more friendly way of placing calls. + + \warning If \a mode is \c UseEventLoop, this function will reenter the Qt event loop in order to + wait for the reply. During the wait, it may deliver signals and other method calls to + your application. Therefore, it must be prepared to handle a reentrancy whenever a call + is placed with sendWithReply. +*/ +QDBusMessage QDBusConnection::sendWithReply(const QDBusMessage &message, WaitMode mode) const +{ + if (!d || !d->connection) + return QDBusMessage(); + return d->sendWithReply(message, mode); +} + +/*! + Connects the signal specified by the \a service, \a path, \a interface and \a name parameters to + the slot \a slot in object \a receiver. The arguments \a service and \a path can be empty, + denoting a connection to any signal of the \a interface - \a name pair, from any remote + application. + + Returns true if the connection was successful. + + \warning The signal will only be delivered to the slot if the parameters match. This verification + can be done only when the signal is received, not at connection time. +*/ +bool QDBusConnection::connect(const QString &service, const QString &path, const QString& interface, + const QString &name, QObject *receiver, const char *slot) +{ + return connect(service, path, interface, name, QString(), receiver, slot); +} + +/*! + \overload + Connects the signal to the slot \a slot in object \a receiver. Unlike the other + QDBusConnection::connect overload, this function allows one to specify the parameter signature + to be connected using the \a signature variable. The function will then verify that this + signature can be delivered to the slot specified by \a slot and return false otherwise. +*/ +bool QDBusConnection::connect(const QString &service, const QString &path, const QString& interface, + const QString &name, const QString &signature, + QObject *receiver, const char *slot) +{ + if (!receiver || !slot || !d || !d->connection || !QDBusUtil::isValidInterfaceName(interface)) + return false; + + QString source; + if (!service.isEmpty()) { + source = d->getNameOwner(service); + if (source.isEmpty()) + return false; + } + + // check the slot + QDBusConnectionPrivate::SignalHook hook; + QString key; + hook.signature = signature; + if (!d->prepareHook(hook, key, source, path, interface, name, receiver, slot, 0, false)) + return false; // don't connect + + // avoid duplicating: + QWriteLocker locker(&d->lock); + QDBusConnectionPrivate::SignalHookHash::ConstIterator it = d->signalHooks.find(key); + for ( ; it != d->signalHooks.end() && it.key() == key; ++it) { + const QDBusConnectionPrivate::SignalHook &entry = it.value(); + if (entry.sender == hook.sender && + entry.path == hook.path && + entry.signature == hook.signature && + entry.obj == hook.obj && + entry.midx == hook.midx) { + // no need to compare the parameters if it's the same slot + return true; // already there + } + } + + + d->connectSignal(key, hook); + return true; +} + +/*! + Registers the object \a object at path \a path and returns true if the registration was + successful. The \a options parameter specifies how much of the object \a object will be exposed + through D-Bus. + + This function does not replace existing objects: if there is already an object registered at + path \a path, this function will return false. Use unregisterObject() to unregister it first. + + You cannot register an object as a child object of an object that was registered with + QDBusConnection::ExportChildObjects. +*/ +bool QDBusConnection::registerObject(const QString &path, QObject *object, RegisterOptions options) +{ + if (!d || !d->connection || !object || !options || !QDBusUtil::isValidObjectPath(path)) + return false; + + if (options & ExportSignals) { + qWarning("Cannot export signals from objects. Use an adaptor for that purpose."); + return false; + } + + QStringList pathComponents = path.split(QLatin1Char('/')); + if (pathComponents.last().isEmpty()) + pathComponents.removeLast(); + QWriteLocker locker(&d->lock); + + // lower-bound search for where this object should enter in the tree + QDBusConnectionPrivate::ObjectTreeNode *node = &d->rootNode; + int i = 1; + while (node) { + if (pathComponents.count() == i) { + // this node exists + // consider it free if there's no object here and the user is not trying to + // replace the object sub-tree + if ((options & ExportChildObjects && !node->children.isEmpty()) || node->obj) + return false; + + // we can add the object here + node->obj = object; + node->flags = options; + + d->registerObject(node); + qDebug("REGISTERED FOR %s", path.toLocal8Bit().constData()); + return true; + } + + // find the position where we'd insert the node + QVector<QDBusConnectionPrivate::ObjectTreeNode::Data>::Iterator it = + qLowerBound(node->children.begin(), node->children.end(), pathComponents.at(i)); + if (it != node->children.constEnd() && it->name == pathComponents.at(i)) { + // match: this node exists + node = it->node; + + // are we allowed to go deeper? + if (node->flags & ExportChildObjects) { + // we're not + qDebug("Cannot register object at %s because %s exports its own child objects", + qPrintable(path), qPrintable(pathComponents.at(i))); + return false; + } + } else { + // add entry + QDBusConnectionPrivate::ObjectTreeNode::Data entry; + entry.name = pathComponents.at(i); + entry.node = new QDBusConnectionPrivate::ObjectTreeNode; + node->children.insert(it, entry); + + node = entry.node; + } + + // iterate + ++i; + } + + Q_ASSERT_X(false, "QDBusConnection::registerObject", "The impossible happened"); + return false; +} + +/*! + Unregisters an object that was registered with the registerObject() at the object path given by + \a path and, if \a mode is QDBusConnection::UnregisterTree, all of its sub-objects too. + + Note that you cannot unregister objects that were not registered with registerObject(). +*/ +void QDBusConnection::unregisterObject(const QString &path, UnregisterMode mode) +{ + if (!d || !d->connection || !QDBusUtil::isValidObjectPath(path)) + return; + + QStringList pathComponents = path.split(QLatin1Char('/')); + QWriteLocker locker(&d->lock); + QDBusConnectionPrivate::ObjectTreeNode *node = &d->rootNode; + int i = 1; + + // find the object + while (node) { + if (pathComponents.count() == i) { + // found it + node->obj = 0; + node->flags = 0; + + if (mode == UnregisterTree) { + // clear the sub-tree as well + node->clear(); // can't disconnect the objects because we really don't know if they can + // be found somewhere else in the path too + } + + return; + } + + QVector<QDBusConnectionPrivate::ObjectTreeNode::Data>::ConstIterator it = + qLowerBound(node->children.constBegin(), node->children.constEnd(), pathComponents.at(i)); + if (it == node->children.constEnd() || it->name != pathComponents.at(i)) + break; // node not found + + node = it->node; + ++i; + } +} + +/*! + Returns a dynamic QDBusInterface associated with the interface \a interface on object at path \a + path on service \a service. + + This function creates a new object. It is your resposibility to ensure it is properly deleted + (you can use all normal QObject deletion mechanisms, including the QObject::deleteLater() slot + and QObject::setParent()). + + If the searching for this interface on the remote object failed, this function returns 0. +*/ +QDBusInterface *QDBusConnection::findInterface(const QString& service, const QString& path, + const QString& interface) +{ + if (!d) + return 0; + + QDBusInterfacePrivate *p = d->findInterface(service, path, interface); + QDBusInterface *retval = new QDBusInterface(p); + retval->setParent(d); + return retval; +} + +/*! + \fn QDBusConnection::findInterface(const QString &service, const QString &path) + Returns an interface of type \c Interface associated with the object on path \a path at service + \a service. + + \c Interface must be a class generated by \l {dbusidl2cpp.html}. + + This function creates a new object. It is your resposibility to ensure it is properly deleted + (you can use all normal QObject deletion mechanisms, including the QObject::deleteLater() slot + and QObject::setParent()). +*/ + +/*! + Returns a QDBusBusService object that represents the D-Bus bus service on this connection. + + This function returns 0 for peer-to-peer connections. +*/ +QDBusBusService *QDBusConnection::busService() const +{ + if (!d) + return 0; + return d->busService; +} + +QDBusAbstractInterfacePrivate * +QDBusConnection::findInterface_helper(const QString &service, const QString &path, + const QString &interface) +{ + if (!d) + return 0; + if (!interface.isEmpty() && !QDBusUtil::isValidInterfaceName(interface)) + return 0; + + QString owner; + if (!service.isEmpty()) { + if (!QDBusUtil::isValidObjectPath(path)) + return 0; + + // check if it's there first -- FIXME: add binding mode + owner = d->getNameOwner(service); + if (owner.isEmpty()) + return 0; + } else if (!path.isEmpty()) + return 0; + + return new QDBusAbstractInterfacePrivate(*this, d, owner, path, interface); +} + +/*! + Returns true if this QDBusConnection object is connected. + + If it isn't connected, calling QDBusConnection::addConnection on the same connection name + will not make be connected. You need to call the QDBusConnection constructor again. +*/ +bool QDBusConnection::isConnected( ) const +{ + return d && d->connection && dbus_connection_get_is_connected(d->connection); +} + +/*! + Returns the last error that happened in this connection. + + This function is provided for low-level code. If you're using QDBusInterface::call, error codes are + reported by its return value. + + \sa QDBusInterface, QDBusMessage +*/ +QDBusError QDBusConnection::lastError() const +{ + return d ? d->lastError : QDBusError(); +} + +/*! + Returns the unique connection name for this connection, if this QDBusConnection object is + connected, or an empty QString otherwise. + + A Unique Connection Name is a string in the form ":x.xxx" (where x are decimal digits) that is + assigned by the D-Bus server daemon upon connection. It uniquely identifies this client in the + bus. + + This function returns an empty QString for peer-to-peer connections. +*/ +QString QDBusConnection::baseService() const +{ + return d && d->connection ? + QString::fromUtf8(dbus_bus_get_unique_name(d->connection)) + : QString(); +} + +Q_GLOBAL_STATIC(QMutex, defaultBussesMutex) +static const char sessionBusName[] = "qt_default_session_bus"; +static const char systemBusName[] = "qt_default_system_bus"; +static QDBusConnection *sessionBus = 0; +static QDBusConnection *systemBus = 0; + +static void closeConnections() +{ + QMutexLocker locker(defaultBussesMutex()); + delete sessionBus; + delete systemBus; + QDBusConnection::closeConnection(QLatin1String(sessionBusName)); + QDBusConnection::closeConnection(QLatin1String(systemBusName)); + sessionBus = systemBus = 0; +} + +static QDBusConnection *openConnection(QDBusConnection::BusType type) +{ + QMutexLocker locker(defaultBussesMutex()); + qAddPostRoutine(closeConnections); + + if (type == QDBusConnection::SystemBus) { + if (systemBus) + // maybe it got created before we locked the mutex + return systemBus; + systemBus = new QDBusConnection(QDBusConnection::addConnection(QDBusConnection::SystemBus, + QLatin1String(systemBusName))); + return systemBus; + } else { + if (sessionBus) + // maybe it got created before we locked the mutex + return sessionBus; + sessionBus = new QDBusConnection(QDBusConnection::addConnection(QDBusConnection::SessionBus, + QLatin1String(sessionBusName))); + return sessionBus; + } +} + +namespace QDBus { + QDBusConnection &sessionBus() + { + if (::sessionBus) return *::sessionBus; + return *openConnection(QDBusConnection::SessionBus); + } + + QDBusConnection &systemBus() + { + if (::systemBus) return *::systemBus; + return *openConnection(QDBusConnection::SystemBus); + } +} + |