/* qdbusintegrator.cpp QDBusConnection private implementation * * Copyright (C) 2005 Harald Fernengel * Copyright (C) 2006 Trolltech AS. All rights reserved. * Author: Thiago Macieira * * 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 #include #include #include #include #include #include #include #include "qdbusconnection_p.h" #include "qdbusinterface_p.h" #include "qdbusmessage.h" #include "qdbusabstractadaptor.h" #include "qdbusabstractadaptor_p.h" #include "qdbustypehelper_p.h" #include "qdbusutil.h" #include "qdbustype_p.h" #ifndef USE_OUTSIDE_DISPATCH # define USE_OUTSIDE_DISPATCH 0 #endif int QDBusConnectionPrivate::messageMetaType = 0; typedef void (*QDBusSpyHook)(const QDBusMessage&); typedef QVarLengthArray QDBusSpyHookList; Q_GLOBAL_STATIC(QDBusSpyHookList, qDBusSpyHookList) struct QDBusPendingCall { QPointer receiver; QList metaTypes; int methodIdx; DBusPendingCall *pending; const QDBusConnectionPrivate *connection; }; class CallDeliveryEvent: public QEvent { public: CallDeliveryEvent() : QEvent(QEvent::User), object(0), flags(0), slotIdx(-1) { } const QDBusConnectionPrivate *conn; QPointer object; QDBusMessage message; QList metaTypes; int flags; int slotIdx; }; static dbus_bool_t qDBusAddTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); // qDebug("addTimeout %d", dbus_timeout_get_interval(timeout)); QDBusConnectionPrivate *d = static_cast(data); if (!dbus_timeout_get_enabled(timeout)) return true; if (!QCoreApplication::instance()) { d->pendingTimeouts.append(timeout); return true; } int timerId = d->startTimer(dbus_timeout_get_interval(timeout)); if (!timerId) return false; d->timeouts[timerId] = timeout; return true; } static void qDBusRemoveTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); // qDebug("removeTimeout"); QDBusConnectionPrivate *d = static_cast(data); d->pendingTimeouts.removeAll(timeout); QDBusConnectionPrivate::TimeoutHash::iterator it = d->timeouts.begin(); while (it != d->timeouts.end()) { if (it.value() == timeout) { d->killTimer(it.key()); it = d->timeouts.erase(it); } else { ++it; } } } static void qDBusToggleTimeout(DBusTimeout *timeout, void *data) { Q_ASSERT(timeout); Q_ASSERT(data); //qDebug("ToggleTimeout"); qDBusRemoveTimeout(timeout, data); qDBusAddTimeout(timeout, data); } static dbus_bool_t qDBusAddWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); QDBusConnectionPrivate *d = static_cast(data); int flags = dbus_watch_get_flags(watch); int fd = dbus_watch_get_fd(watch); QDBusConnectionPrivate::Watcher watcher; if (flags & DBUS_WATCH_READABLE) { //qDebug("addReadWatch %d", fd); watcher.watch = watch; if (QCoreApplication::instance()) { watcher.read = new QSocketNotifier(fd, QSocketNotifier::Read, d); watcher.read->setEnabled(dbus_watch_get_enabled(watch)); d->connect(watcher.read, SIGNAL(activated(int)), SLOT(socketRead(int))); } } if (flags & DBUS_WATCH_WRITABLE) { //qDebug("addWriteWatch %d", fd); watcher.watch = watch; if (QCoreApplication::instance()) { watcher.write = new QSocketNotifier(fd, QSocketNotifier::Write, d); watcher.write->setEnabled(dbus_watch_get_enabled(watch)); d->connect(watcher.write, SIGNAL(activated(int)), SLOT(socketWrite(int))); } } d->watchers.insertMulti(fd, watcher); return true; } static void qDBusRemoveWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); //qDebug("remove watch"); QDBusConnectionPrivate *d = static_cast(data); int fd = dbus_watch_get_fd(watch); QDBusConnectionPrivate::WatcherHash::iterator i = d->watchers.find(fd); while (i != d->watchers.end() && i.key() == fd) { if (i.value().watch == watch) { delete i.value().read; delete i.value().write; d->watchers.erase(i); return; } ++i; } } static void qDBusToggleWatch(DBusWatch *watch, void *data) { Q_ASSERT(watch); Q_ASSERT(data); QDBusConnectionPrivate *d = static_cast(data); int fd = dbus_watch_get_fd(watch); QDBusConnectionPrivate::WatcherHash::iterator i = d->watchers.find(fd); while (i != d->watchers.end() && i.key() == fd) { if (i.value().watch == watch) { bool enabled = dbus_watch_get_enabled(watch); int flags = dbus_watch_get_flags(watch); //qDebug("toggle watch %d to %d (write: %d, read: %d)", dbus_watch_get_fd(watch), enabled, flags & DBUS_WATCH_WRITABLE, flags & DBUS_WATCH_READABLE); if (flags & DBUS_WATCH_READABLE && i.value().read) i.value().read->setEnabled(enabled); if (flags & DBUS_WATCH_WRITABLE && i.value().write) i.value().write->setEnabled(enabled); return; } ++i; } } static void qDBusNewConnection(DBusServer *server, DBusConnection *c, void *data) { Q_ASSERT(data); Q_ASSERT(server); Q_ASSERT(c); Q_UNUSED(data); Q_UNUSED(server); Q_UNUSED(c); qDebug("SERVER: GOT A NEW CONNECTION"); // TODO } extern QDBUS_EXPORT void qDBusAddSpyHook(QDBusSpyHook); void qDBusAddSpyHook(QDBusSpyHook hook) { qDBusSpyHookList()->append(hook); } #if USE_OUTSIDE_DISPATCH # define HANDLED DBUS_HANDLER_RESULT_HANDLED_OUTSIDE_DISPATCH static DBusHandlerResult qDBusSignalFilterOutside(DBusConnection *connection, DBusMessage *message, void *data) { Q_ASSERT(data); Q_UNUSED(connection); Q_UNUSED(message); QDBusConnectionPrivate *d = static_cast(data); if (d->mode == QDBusConnectionPrivate::InvalidMode) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; // internal error, actually CallDeliveryEvent *e = d->postedCallDeliveryEvent(); d->deliverCall(*e); delete e; return DBUS_HANDLER_RESULT_HANDLED; } #else # define HANDLED DBUS_HANDLER_RESULT_HANDLED #endif extern "C" { static DBusHandlerResult qDBusSignalFilter(DBusConnection *connection, DBusMessage *message, void *data) { return QDBusConnectionPrivate::messageFilter(connection, message, data); } } DBusHandlerResult QDBusConnectionPrivate::messageFilter(DBusConnection *connection, DBusMessage *message, void *data) { Q_ASSERT(data); Q_UNUSED(connection); QDBusConnectionPrivate *d = static_cast(data); if (d->mode == QDBusConnectionPrivate::InvalidMode) return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; QDBusMessage amsg = QDBusMessage::fromDBusMessage(message, QDBusConnection(d->name)); qDebug() << "got message:" << amsg; const QDBusSpyHookList *list = qDBusSpyHookList(); for (int i = 0; i < list->size(); ++i) { qDebug() << "calling the message spy hook"; (*(*list)[i])(amsg); } bool handled = false; int msgType = dbus_message_get_type(message); if (msgType == DBUS_MESSAGE_TYPE_SIGNAL) { handled = d->handleSignal(amsg); } else if (msgType == DBUS_MESSAGE_TYPE_METHOD_CALL) { handled = d->handleObjectCall(amsg); } return handled ? HANDLED : DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } static void huntAndDestroy(QObject *needle, QDBusConnectionPrivate::ObjectTreeNode *haystack) { foreach (const QDBusConnectionPrivate::ObjectTreeNode::Data &entry, haystack->children) huntAndDestroy(needle, entry.node); if (needle == haystack->obj) { haystack->obj = 0; haystack->flags = 0; } } static void huntAndEmit(DBusConnection *connection, DBusMessage *msg, QObject *needle, QDBusConnectionPrivate::ObjectTreeNode *haystack, const QString &path = QString()) { foreach (const QDBusConnectionPrivate::ObjectTreeNode::Data &entry, haystack->children) huntAndEmit(connection, msg, needle, entry.node, path + QLatin1String("/") + entry.name); if (needle == haystack->obj && haystack->flags & QDBusConnection::ExportAdaptors) { QByteArray p = path.toLatin1(); if (p.isEmpty()) p = "/"; //qDebug() << p; DBusMessage *msg2 = dbus_message_copy(msg); dbus_message_set_path(msg2, p); dbus_connection_send(connection, msg2, 0); dbus_message_unref(msg2); } } static bool typesMatch(int metaId, int variantType) { if (metaId == int(variantType)) return true; if (variantType == QVariant::Int && metaId == QMetaType::Short) return true; if (variantType == QVariant::UInt && (metaId == QMetaType::UShort || metaId == QMetaType::UChar)) return true; if (variantType == QVariant::List) { if (metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId() || metaId == QDBusTypeHelper::listId()) return true; } return false; // no match } static int findSlot(const QMetaObject *mo, const QByteArray &name, int flags, const QDBusTypeList &types, QList& metaTypes) { // find the first slot const QMetaObject *super = mo; while (super != &QObject::staticMetaObject && super != &QDBusAbstractAdaptor::staticMetaObject) super = super->superClass(); int attributeMask = (flags & QDBusConnection::ExportAllSlots) ? 0 : QMetaMethod::Scriptable; for (int idx = super->methodCount() ; idx <= mo->methodCount(); ++idx) { QMetaMethod mm = mo->method(idx); // check access: if (mm.access() != QMetaMethod::Public) continue; // check type: // unnecessary, since slots are never public: //if (mm.methodType() != QMetaMethod::Slot) // continue; // check name: QByteArray sig = QMetaObject::normalizedSignature(mm.signature()); int paren = sig.indexOf('('); if (paren != name.length() || !sig.startsWith( name )) continue; int returnType = qDBusNameToTypeId(mm.typeName()); bool isAsync = qDBusCheckAsyncTag(mm.tag()); // consistency check: if (isAsync && returnType != QMetaType::Void) continue; int inputCount = qDBusParametersForMethod(mm, metaTypes); if (inputCount == -1) continue; // problem parsing metaTypes[0] = returnType; bool hasMessage = false; if (inputCount > 0 && metaTypes.at(inputCount) == QDBusConnectionPrivate::messageMetaType) { // "no input parameters" is allowed as long as the message meta type is there hasMessage = true; --inputCount; } // try to match the parameters if (inputCount != types.count()) continue; // not enough parameters bool matches = true; int i; for (i = 0; i < types.count(); ++i) if ( !typesMatch(metaTypes.at(i + 1), types.at(i).qvariantType()) ) { matches = false; break; } if (!matches) continue; // we didn't match them all // consistency check: if (isAsync && metaTypes.count() > i + 1) continue; if (hasMessage && (mm.attributes() & attributeMask) != attributeMask) continue; // not exported // if we got here, this slot matched return idx; } // no slot matched return -1; } static CallDeliveryEvent* prepareReply(QObject *object, int idx, const QList &metaTypes, const QDBusMessage &msg) { Q_ASSERT(object); int n = metaTypes.count() - 1; if (metaTypes[n] == QDBusConnectionPrivate::messageMetaType) --n; // check that types match for (int i = 0; i < n; ++i) if (!typesMatch(metaTypes.at(i + 1), msg.at(i).type())) return 0; // no match // we can deliver // prepare for the call CallDeliveryEvent *data = new CallDeliveryEvent; data->object = object; data->flags = 0; data->message = msg; data->metaTypes = metaTypes; data->slotIdx = idx; return data; } bool QDBusConnectionPrivate::activateSignal(const QDBusConnectionPrivate::SignalHook& hook, const QDBusMessage &msg) { // This is called by QDBusConnectionPrivate::handleSignal to deliver a signal // that was received from D-Bus // // Signals are delivered to slots if the parameters match // Slots can have less parameters than there are on the message // Slots can optionally have one final parameter that is a QDBusMessage // Slots receive read-only copies of the message (i.e., pass by value or by const-ref) CallDeliveryEvent *call = prepareReply(hook.obj, hook.midx, hook.params, msg); if (call) { postCallDeliveryEvent(call); return true; } return false; } bool QDBusConnectionPrivate::activateCall(QObject* object, int flags, const QDBusMessage &msg) { // This is called by QDBusConnectionPrivate::handleObjectCall to place a call // to a slot on the object. // // The call is delivered to the first slot that matches the following conditions: // - has the same name as the message's target name // - ALL of the message's types are found in slot's parameter list // - optionally has one more parameter of type QDBusMessage // If none match, then the slot of the same name as the message target and with // the first type of QDBusMessage is delivered. // // Because the marshalling of D-Bus data into QVariant loses the information on // the original types, the message signature is used to determine the original type. // Aside from that, the "int" and "unsigned" types will be tried as well. // // The D-Bus specification requires that all MethodCall messages be replied to, unless the // caller specifically waived this requirement. This means that we inspect if the user slot // generated a reply and, if it didn't, we will. Obviously, if the user slot doesn't take a // QDBusMessage parameter, it cannot generate a reply. // // When a return message is generated, the slot's return type, if any, will be placed // in the message's first position. If there are non-const reference parameters to the // slot, they must appear at the end and will be placed in the subsequent message // positions. if (!object) return false; QList metaTypes; int idx; { const QMetaObject *mo = object->metaObject(); QDBusTypeList typeList(msg.signature().toUtf8()); QByteArray name = msg.name().toUtf8(); // find a slot that matches according to the rules above idx = ::findSlot(mo, name, flags, typeList, metaTypes); if (idx == -1) { // try with no parameters, but with a QDBusMessage idx = ::findSlot(mo, name, flags, QDBusTypeList(), metaTypes); if (metaTypes.count() != 2 || metaTypes.at(1) != messageMetaType) return false; } } // found the slot to be called // prepare for the call: CallDeliveryEvent *call = new CallDeliveryEvent; // parameters: call->object = object; call->flags = flags; call->message = msg; // save our state: call->metaTypes = metaTypes; call->slotIdx = idx; postCallDeliveryEvent(call); // ready return true; } void QDBusConnectionPrivate::postCallDeliveryEvent(CallDeliveryEvent *data) { Q_ASSERT(data); data->conn = this; #if USE_OUTSIDE_DISPATCH callDeliveryMutex.lock(); callDeliveryState = data; #else QCoreApplication::postEvent( this, data ); #endif } CallDeliveryEvent *QDBusConnectionPrivate::postedCallDeliveryEvent() { CallDeliveryEvent *e = callDeliveryState; Q_ASSERT(e && e->conn == this); // release it: callDeliveryState = 0; callDeliveryMutex.unlock(); return e; } void QDBusConnectionPrivate::deliverCall(const CallDeliveryEvent& data) const { // resume state: const QList& metaTypes = data.metaTypes; const QDBusMessage& msg = data.message; QVarLengthArray params; params.reserve(metaTypes.count()); QVariantList auxParameters; // let's create the parameter list // first one is the return type -- add it below params.append(0); // add the input parameters int i; for (i = 1; i <= msg.count(); ++i) { int id = metaTypes[i]; if (id == QDBusConnectionPrivate::messageMetaType) break; if (id == int(msg.at(i - 1).userType())) // no conversion needed params.append(const_cast( msg.at(i - 1).constData() )); else { // convert to what the function expects auxParameters.append(QVariant()); const QVariant &in = msg.at(i - 1); QVariant &out = auxParameters[auxParameters.count() - 1]; bool error = false; if (id == QVariant::List) { int mid = in.userType(); // the only conversion possible here is from a specialised QList to QVariantList if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else if (mid == QDBusTypeHelper::listId()) out = qVariantFromValue(QDBusTypeHelper::toVariantList(in)); else error = true; } else if (in.type() == QVariant::UInt) { if (id == QMetaType::UChar) { uchar uc = in.toUInt(); out = qVariantFromValue(uc); } else if (id == QMetaType::UShort) { ushort us = in.toUInt(); out = qVariantFromValue(us); } else { error = true; } } else if (in.type() == QVariant::Int) { if (id == QMetaType::Short) { short s = in.toInt(); out = qVariantFromValue(s); } else { error = true; } } else { error = true; } if (error) qFatal("Internal error: got invalid meta type %d when trying to convert to meta type %d", in.userType(), id); params.append( const_cast(out.constData()) ); } } bool takesMessage = false; if (metaTypes.count() > i && metaTypes[i] == QDBusConnectionPrivate::messageMetaType) { params.append(const_cast(static_cast(&msg))); takesMessage = true; ++i; } // output arguments QVariantList outputArgs; void *null = 0; if (metaTypes[0] != QMetaType::Void) { QVariant arg(metaTypes[0], null); outputArgs.append( arg ); params[0] = const_cast(outputArgs.at( outputArgs.count() - 1 ).constData()); } for ( ; i < metaTypes.count(); ++i) { QVariant arg(metaTypes[i], null); outputArgs.append( arg ); params.append( const_cast(outputArgs.at( outputArgs.count() - 1 ).constData()) ); } // make call: bool fail; if (data.object.isNull()) fail = true; else fail = data.object->qt_metacall(QMetaObject::InvokeMetaMethod, data.slotIdx, params.data()) >= 0; // do we create a reply? Only if the caller is waiting for a reply and one hasn't been sent // yet. if (!msg.noReply() && !msg.wasRepliedTo()) { if (!fail) { // normal reply QDBusMessage reply = QDBusMessage::methodReply(msg); reply += outputArgs; qDebug() << "Automatically sending reply:" << reply; send(reply); } else { // generate internal error QDBusMessage reply = QDBusMessage::error(msg, QDBusError(QDBusError::InternalError, QLatin1String("Failed to deliver message"))); qWarning("Internal error: Failed to deliver message"); send(reply); } } return; } void QDBusConnectionPrivate::customEvent(QEvent *event) { // nothing else should be sending custom events at us CallDeliveryEvent* call = static_cast(event); // self check: Q_ASSERT(call->conn == this); deliverCall(*call); } QDBusConnectionPrivate::QDBusConnectionPrivate(QObject *parent) : QObject(parent), ref(1), mode(InvalidMode), connection(0), server(0), busService(0) { extern bool qDBusInitThreads(); static const int msgType = registerMessageMetaType(); static const bool threads = qDBusInitThreads(); static const bool metatypes = QDBusMetaTypeId::innerInitialize(); Q_UNUSED(msgType); Q_UNUSED(threads); Q_UNUSED(metatypes); dbus_error_init(&error); rootNode.flags = 0; } QDBusConnectionPrivate::~QDBusConnectionPrivate() { if (dbus_error_is_set(&error)) dbus_error_free(&error); closeConnection(); rootNode.clear(); // free resources qDeleteAll(cachedMetaObjects); } void QDBusConnectionPrivate::closeConnection() { QWriteLocker locker(&lock); ConnectionMode oldMode = mode; mode = InvalidMode; // prevent reentrancy if (oldMode == ServerMode) { if (server) { dbus_server_disconnect(server); dbus_server_unref(server); server = 0; } } else if (oldMode == ClientMode) { if (connection) { dbus_connection_close(connection); // send the "close" message while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS) ; dbus_connection_unref(connection); connection = 0; } } } bool QDBusConnectionPrivate::handleError() { lastError = QDBusError(&error); if (dbus_error_is_set(&error)) dbus_error_free(&error); return lastError.isValid(); } void QDBusConnectionPrivate::bindToApplication() { // Yay, now that we have an application we are in business Q_ASSERT_X(QCoreApplication::instance(), "QDBusConnection", "qDBusBindToApplication called without an application"); moveToThread(QCoreApplication::instance()->thread()); // Re-add all watchers WatcherHash oldWatchers = watchers; watchers.clear(); QHashIterator it(oldWatchers); while (it.hasNext()) { it.next(); if (!it.value().read && !it.value().write) { qDBusAddWatch(it.value().watch, this); } else { watchers.insertMulti(it.key(), it.value()); } } // Re-add all timeouts while (!pendingTimeouts.isEmpty()) qDBusAddTimeout(pendingTimeouts.takeFirst(), this); } void QDBusConnectionPrivate::timerEvent(QTimerEvent *e) { DBusTimeout *timeout = timeouts.value(e->timerId(), 0); dbus_timeout_handle(timeout); } void QDBusConnectionPrivate::doDispatch() { if (mode == ClientMode) while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS); } void QDBusConnectionPrivate::socketRead(int fd) { QHashIterator it(watchers); while (it.hasNext()) { it.next(); if (it.key() == fd && it.value().read && it.value().read->isEnabled()) { if (!dbus_watch_handle(it.value().watch, DBUS_WATCH_READABLE)) qDebug("OUT OF MEM"); } } doDispatch(); } void QDBusConnectionPrivate::socketWrite(int fd) { QHashIterator it(watchers); while (it.hasNext()) { it.next(); if (it.key() == fd && it.value().write && it.value().write->isEnabled()) { if (!dbus_watch_handle(it.value().watch, DBUS_WATCH_WRITABLE)) qDebug("OUT OF MEM"); } } } void QDBusConnectionPrivate::objectDestroyed(QObject *obj) { QWriteLocker locker(&lock); huntAndDestroy(obj, &rootNode); SignalHookHash::iterator sit = signalHooks.begin(); while (sit != signalHooks.end()) { if (static_cast(sit.value().obj) == obj) sit = signalHooks.erase(sit); else ++sit; } obj->disconnect(this); } void QDBusConnectionPrivate::relaySignal(QObject *obj, const char *interface, const char *name, const QVariantList &args) { QReadLocker locker(&lock); QDBusMessage message = QDBusMessage::signal(QLatin1String("/"), QLatin1String(interface), QLatin1String(name)); message += args; DBusMessage *msg = message.toDBusMessage(); if (!msg) { qWarning("Could not emit signal %s.%s", interface, name); return; } //qDebug() << "Emitting signal" << message; //qDebug() << "for paths:"; dbus_message_set_no_reply(msg, true); // the reply would not be delivered to anything huntAndEmit(connection, msg, obj, &rootNode); dbus_message_unref(msg); } int QDBusConnectionPrivate::registerMessageMetaType() { int tp = messageMetaType = qRegisterMetaType("QDBusMessage"); return tp; } int QDBusConnectionPrivate::findSlot(QObject* obj, const QByteArray &normalizedName, QList ¶ms) { int midx = obj->metaObject()->indexOfMethod(normalizedName); if (midx == -1) { qWarning("No such slot '%s' while connecting D-Bus", normalizedName.constData()); return -1; } int inputCount = qDBusParametersForMethod(obj->metaObject()->method(midx), params); if ( inputCount == -1 || inputCount + 1 != params.count() ) return -1; // failed to parse or invalid arguments or output arguments return midx; } bool QDBusConnectionPrivate::prepareHook(QDBusConnectionPrivate::SignalHook &hook, QString &key, const QString &service, const QString &path, const QString &interface, const QString &name, QObject *receiver, const char *signal, int minMIdx, bool buildSignature) { QByteArray normalizedName = QMetaObject::normalizedSignature(signal + 1); hook.midx = findSlot(receiver, normalizedName, hook.params); if (hook.midx < minMIdx) return false; hook.sender = service; hook.path = path; hook.obj = receiver; // build the D-Bus signal name and signature QString mname = name; if (mname.isEmpty()) { normalizedName.truncate(normalizedName.indexOf('(')); mname = QString::fromUtf8(normalizedName); } key = mname; key.reserve(interface.length() + 1 + mname.length()); key += ':'; key += interface; if (buildSignature) { hook.signature.clear(); for (int i = 1; i < hook.params.count(); ++i) if (hook.params.at(i) != messageMetaType) hook.signature += QLatin1String( QDBusType::dbusSignature( QVariant::Type(hook.params.at(i)) ) ); } return true; // connect to this signal } bool QDBusConnectionPrivate::activateInternalFilters(const ObjectTreeNode *node, const QDBusMessage &msg) { // object may be null if (msg.interface().isEmpty() || msg.interface() == QLatin1String(DBUS_INTERFACE_INTROSPECTABLE)) { if (msg.method() == QLatin1String("Introspect") && msg.signature().isEmpty()) qDBusIntrospectObject(node, msg); if (msg.interface() == QLatin1String(DBUS_INTERFACE_INTROSPECTABLE)) return true; } if (node->obj && (msg.interface().isEmpty() || msg.interface() == QLatin1String(DBUS_INTERFACE_PROPERTIES))) { if (msg.method() == QLatin1String("Get") && msg.signature() == QLatin1String("ss")) qDBusPropertyGet(node, msg); else if (msg.method() == QLatin1String("Set") && msg.signature() == QLatin1String("ssv")) qDBusPropertySet(node, msg); if (msg.interface() == QLatin1String(DBUS_INTERFACE_PROPERTIES)) return true; } return false; } bool QDBusConnectionPrivate::activateObject(const ObjectTreeNode *node, const QDBusMessage &msg) { // This is called by QDBusConnectionPrivate::handleObjectCall to place a call to a slot // on the object. // // The call is routed through the adaptor sub-objects if we have any // object may be null QDBusAdaptorConnector *connector; if (node->flags & QDBusConnection::ExportAdaptors && (connector = qDBusFindAdaptorConnector(node->obj))) { int newflags = node->flags | QDBusConnection::ExportAllSlots; if (msg.interface().isEmpty()) { // place the call in all interfaces // let the first one that handles it to work foreach (const QDBusAdaptorConnector::AdaptorData &entry, connector->adaptors) if (activateCall(entry.adaptor, newflags, msg)) return true; } else { // check if we have an interface matching the name that was asked: QDBusAdaptorConnector::AdaptorMap::ConstIterator it; it = qLowerBound(connector->adaptors.constBegin(), connector->adaptors.constEnd(), msg.interface()); if (it != connector->adaptors.end() && it->interface == msg.interface()) if (activateCall(it->adaptor, newflags, msg)) return true; } } // no adaptors matched // try our standard filters if (activateInternalFilters(node, msg)) return true; // try the object itself: if (node->flags & QDBusConnection::ExportSlots && activateCall(node->obj, node->flags, msg)) return true; #if 0 // nothing matched qDebug("Call failed: no match for %s%s%s at %s", qPrintable(msg.interface()), msg.interface().isEmpty() ? "" : ".", qPrintable(msg.name()), qPrintable(msg.path())); #endif return false; } template static bool applyForObject(QDBusConnectionPrivate::ObjectTreeNode *root, const QString &fullpath, Func& functor) { // walk the object tree QStringList path = fullpath.split(QLatin1Char('/')); if (path.last().isEmpty()) path.removeLast(); // happens if path is "/" int i = 1; QDBusConnectionPrivate::ObjectTreeNode *node = root; // try our own tree first while (node && !(node->flags & QDBusConnection::ExportChildObjects) ) { if (i == path.count()) { // found our object functor(node); return true; } QVector::ConstIterator it = qLowerBound(node->children.constBegin(), node->children.constEnd(), path.at(i)); if (it != node->children.constEnd() && it->name == path.at(i)) // match node = it->node; else node = 0; ++i; } // any object in the tree can tell us to switch to its own object tree: if (node && node->flags & QDBusConnection::ExportChildObjects) { QObject *obj = node->obj; while (obj) { if (i == path.count()) { // we're at the correct level QDBusConnectionPrivate::ObjectTreeNode fakenode(*node); fakenode.obj = obj; functor(&fakenode); return true; } const QObjectList children = obj->children(); // find a child with the proper name QObject *next = 0; foreach (QObject *child, children) if (child->objectName() == path.at(i)) { next = child; break; } if (!next) break; ++i; obj = next; } } // object not found return false; } struct qdbus_activateObject { QDBusConnectionPrivate *self; const QDBusMessage &msg; bool returnVal; inline qdbus_activateObject(QDBusConnectionPrivate *s, const QDBusMessage &m) : self(s), msg(m) { } inline void operator()(QDBusConnectionPrivate::ObjectTreeNode *node) { returnVal = self->activateObject(node, msg); } }; bool QDBusConnectionPrivate::handleObjectCall(const QDBusMessage &msg) { QReadLocker locker(&lock); qdbus_activateObject apply(this, msg); if (applyForObject(&rootNode, msg.path(), apply)) return apply.returnVal; qDebug("Call failed: no object found at %s", qPrintable(msg.path())); return false; } bool QDBusConnectionPrivate::handleSignal(const QString &key, const QDBusMessage& msg) { bool result = false; SignalHookHash::const_iterator it = signalHooks.find(key); //qDebug("looking for: %s", path.toLocal8Bit().constData()); //qDebug() << signalHooks.keys(); for ( ; it != signalHooks.constEnd() && it.key() == key; ++it) { const SignalHook &hook = it.value(); if ( !hook.sender.isEmpty() && hook.sender != msg.sender() ) continue; if ( !hook.path.isEmpty() && hook.path != msg.path() ) continue; if ( !hook.signature.isEmpty() && hook.signature != msg.signature() ) continue; if ( hook.signature.isEmpty() && !hook.signature.isNull() && !msg.signature().isEmpty()) continue; // yes, |= result |= activateSignal(hook, msg); } return result; } bool QDBusConnectionPrivate::handleSignal(const QDBusMessage& msg) { QString key = msg.member(); key.reserve(key.length() + 1 + msg.interface().length()); key += ':'; key += msg.interface(); QReadLocker locker(&lock); bool result = handleSignal(key, msg); // one try key.truncate(msg.member().length() + 1); // keep the ':' result |= handleSignal(key, msg); // second try return result; } static dbus_int32_t server_slot = -1; void QDBusConnectionPrivate::setServer(DBusServer *s) { if (!server) { handleError(); return; } server = s; mode = ServerMode; dbus_server_allocate_data_slot(&server_slot); if (server_slot < 0) return; dbus_server_set_watch_functions(server, qDBusAddWatch, qDBusRemoveWatch, qDBusToggleWatch, this, 0); // ### check return type? dbus_server_set_timeout_functions(server, qDBusAddTimeout, qDBusRemoveTimeout, qDBusToggleTimeout, this, 0); dbus_server_set_new_connection_function(server, qDBusNewConnection, this, 0); dbus_server_set_data(server, server_slot, this, 0); } void QDBusConnectionPrivate::setConnection(DBusConnection *dbc) { if (!dbc) { handleError(); return; } connection = dbc; mode = ClientMode; dbus_connection_set_exit_on_disconnect(connection, false); dbus_connection_set_watch_functions(connection, qDBusAddWatch, qDBusRemoveWatch, qDBusToggleWatch, this, 0); dbus_connection_set_timeout_functions(connection, qDBusAddTimeout, qDBusRemoveTimeout, qDBusToggleTimeout, this, 0); // dbus_bus_add_match(connection, "type='signal',interface='com.trolltech.dbus.Signal'", &error); // dbus_bus_add_match(connection, "type='signal'", &error); dbus_bus_add_match(connection, "type='signal'", &error); if (handleError()) { closeConnection(); return; } const char *service = dbus_bus_get_unique_name(connection); if (service) { QVarLengthArray filter; filter.append("destination='", 13); filter.append(service, qstrlen(service)); filter.append("\'\0", 2); dbus_bus_add_match(connection, filter.constData(), &error); if (handleError()) { closeConnection(); return; } } else { qWarning("QDBusConnectionPrivate::SetConnection: Unable to get base service"); } #if USE_OUTSIDE_DISPATCH dbus_connection_add_filter_outside(connection, qDBusSignalFilter, qDBusSignalFilterOutside, this, 0); #else dbus_connection_add_filter(connection, qDBusSignalFilter, this, 0); #endif //qDebug("base service: %s", service); // schedule a dispatch: QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); } extern "C"{ static void qDBusResultReceived(DBusPendingCall *pending, void *user_data) { QDBusConnectionPrivate::messageResultReceived(pending, user_data); } } void QDBusConnectionPrivate::messageResultReceived(DBusPendingCall *pending, void *user_data) { QDBusPendingCall *call = reinterpret_cast(user_data); QDBusConnectionPrivate *connection = const_cast(call->connection); Q_ASSERT(call->pending == pending); if (!call->receiver.isNull() && call->methodIdx != -1) { DBusMessage *reply = dbus_pending_call_steal_reply(pending); // Deliver the return values of a remote function call. // // There is only one connection and it is specified by idx // The slot must have the same parameter types that the message does // The slot may have less parameters than the message // The slot may optionally have one final parameter that is QDBusMessage // The slot receives read-only copies of the message (i.e., pass by value or by const-ref) QDBusMessage msg = QDBusMessage::fromDBusMessage(reply, QDBusConnection(connection->name)); qDebug() << "got message: " << msg; CallDeliveryEvent *e = prepareReply(call->receiver, call->methodIdx, call->metaTypes, msg); if (e) connection->postCallDeliveryEvent(e); else qDebug() << "Deliver failed!"; } dbus_pending_call_unref(pending); delete call; } int QDBusConnectionPrivate::send(const QDBusMessage& message) const { DBusMessage *msg = message.toDBusMessage(); if (!msg) return 0; dbus_message_set_no_reply(msg, true); // the reply would not be delivered to anything qDebug() << "sending message:" << message; bool isOk = dbus_connection_send(connection, msg, 0); int serial = 0; if (isOk) serial = dbus_message_get_serial(msg); dbus_message_unref(msg); return serial; } QDBusMessage QDBusConnectionPrivate::sendWithReply(const QDBusMessage &message, int mode) { if (!QCoreApplication::instance() || mode == QDBusConnection::NoUseEventLoop) { DBusMessage *msg = message.toDBusMessage(); if (!msg) return QDBusMessage(); qDebug() << "sending message:" << message; DBusMessage *reply = dbus_connection_send_with_reply_and_block(connection, msg, -1, &error); handleError(); dbus_message_unref(msg); if (lastError.isValid()) return QDBusMessage::fromError(lastError); QDBusMessage amsg = QDBusMessage::fromDBusMessage(reply, QDBusConnection(name)); qDebug() << "got message:" << amsg; if (dbus_connection_get_dispatch_status(connection) == DBUS_DISPATCH_DATA_REMAINS) QMetaObject::invokeMethod(this, "doDispatch", Qt::QueuedConnection); return amsg; } else { // use the event loop QDBusReplyWaiter waiter; if (sendWithReplyAsync(message, &waiter, SLOT(reply(const QDBusMessage&))) > 0) { // enter the event loop and wait for a reply waiter.exec(QEventLoop::ExcludeUserInputEvents | QEventLoop::WaitForMoreEvents); lastError = waiter.replyMsg; // set or clear error return waiter.replyMsg; } return QDBusMessage(); } } int QDBusConnectionPrivate::sendWithReplyAsync(const QDBusMessage &message, QObject *receiver, const char *method) { if (!receiver || !method || !*method) // would not be able to deliver a reply return send(message); int slotIdx = -1; QList metaTypes; QByteArray normalizedName = QMetaObject::normalizedSignature(method + 1); slotIdx = findSlot(receiver, normalizedName, metaTypes); if (slotIdx == -1) // would not be able to deliver a reply return send(message); DBusMessage *msg = message.toDBusMessage(); if (!msg) return 0; qDebug() << "sending message:" << message; DBusPendingCall *pending = 0; if (dbus_connection_send_with_reply(connection, msg, &pending, message.timeout())) { if (slotIdx != -1) { QDBusPendingCall *pcall = new QDBusPendingCall; pcall->receiver = receiver; pcall->metaTypes = metaTypes; pcall->methodIdx = slotIdx; pcall->connection = this; pcall->pending = dbus_pending_call_ref(pending); dbus_pending_call_set_notify(pending, qDBusResultReceived, pcall, 0); } dbus_pending_call_unref(pending); return dbus_message_get_serial(msg); } return 0; } void QDBusConnectionPrivate::connectSignal(const QString &key, const SignalHook &hook) { signalHooks.insertMulti(key, hook); connect(hook.obj, SIGNAL(destroyed(QObject*)), SLOT(objectDestroyed(QObject*))); } void QDBusConnectionPrivate::registerObject(const ObjectTreeNode *node) { connect(node->obj, SIGNAL(destroyed(QObject*)), SLOT(objectDestroyed(QObject*))); if (node->flags & QDBusConnection::ExportAdaptors) { QDBusAdaptorConnector *connector = qDBusCreateAdaptorConnector(node->obj); // disconnect and reconnect to avoid duplicates connector->disconnect(SIGNAL(relaySignal(QObject*,const char*,const char*,QVariantList)), this, SLOT(relaySignal(QObject*,const char*,const char*,QVariantList))); connect(connector, SIGNAL(relaySignal(QObject*,const char*,const char*,QVariantList)), SLOT(relaySignal(QObject*,const char*,const char*,QVariantList))); } } void QDBusConnectionPrivate::connectRelay(const QString &service, const QString &path, const QString &interface, QDBusAbstractInterface *receiver, const char *signal) { // this function is called by QDBusAbstractInterface when one of its signals is connected // we set up a relay from D-Bus into it SignalHook hook; QString key; if (!prepareHook(hook, key, service, path, interface, QString(), receiver, signal, QDBusAbstractInterface::staticMetaObject.methodCount(), true)) return; // don't connect // add it to our list: QWriteLocker locker(&lock); SignalHookHash::ConstIterator it = signalHooks.find(key); SignalHookHash::ConstIterator end = signalHooks.end(); for ( ; it != end && it.key() == key; ++it) { const 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) return; // already there, no need to re-add } connectSignal(key, hook); } void QDBusConnectionPrivate::disconnectRelay(const QString &service, const QString &path, const QString &interface, QDBusAbstractInterface *receiver, const char *signal) { // this function is called by QDBusAbstractInterface when one of its signals is disconnected // we remove relay from D-Bus into it SignalHook hook; QString key; if (!prepareHook(hook, key, service, path, interface, QString(), receiver, signal, QDBusAbstractInterface::staticMetaObject.methodCount(), true)) return; // don't connect // remove it from our list: QWriteLocker locker(&lock); SignalHookHash::Iterator it = signalHooks.find(key); SignalHookHash::Iterator end = signalHooks.end(); for ( ; it != end && it.key() == key; ++it) { const 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) { // found it signalHooks.erase(it); return; } } qWarning("QDBusConnectionPrivate::disconnectRelay called for a signal that was not found"); } QString QDBusConnectionPrivate::getNameOwner(const QString& name) { if (QDBusUtil::isValidUniqueConnectionName(name)) return name; if (!connection || !QDBusUtil::isValidBusName(name)) return QString(); QDBusMessage msg = QDBusMessage::methodCall(QLatin1String(DBUS_SERVICE_DBUS), QLatin1String(DBUS_PATH_DBUS), QLatin1String(DBUS_INTERFACE_DBUS), QLatin1String("GetNameOwner")); msg << name; QDBusMessage reply = sendWithReply(msg, QDBusConnection::NoUseEventLoop); if (!lastError.isValid() && reply.type() == QDBusMessage::ReplyMessage) return reply.first().toString(); return QString(); } QDBusInterfacePrivate * QDBusConnectionPrivate::findInterface(const QString &service, const QString &path, const QString &interface) { // check if it's there first -- FIXME: add binding mode QDBusMetaObject *mo = 0; QString owner = getNameOwner(service); if (connection && !owner.isEmpty() && QDBusUtil::isValidObjectPath(path) && (interface.isEmpty() || QDBusUtil::isValidInterfaceName(interface))) // always call here with the unique connection name mo = findMetaObject(owner, path, interface); QDBusInterfacePrivate *p = new QDBusInterfacePrivate(QDBusConnection(name), this, owner, path, interface, mo); if (!mo) { // invalid object p->isValid = false; p->lastError = lastError; if (!lastError.isValid()) { // try to determine why we couldn't get the data if (!connection) p->lastError = QDBusError(QDBusError::Disconnected, QLatin1String("Not connected to D-Bus server")); else if (owner.isEmpty()) p->lastError = QDBusError(QDBusError::ServiceUnknown, QString(QLatin1String("Service %1 is unknown")).arg(service)); else if (!QDBusUtil::isValidObjectPath(path)) p->lastError = QDBusError(QDBusError::InvalidArgs, QString(QLatin1String("Object path %1 is invalid")).arg(path)); else if (!interface.isEmpty() && !QDBusUtil::isValidInterfaceName(interface)) p->lastError = QDBusError(QDBusError::InvalidArgs, QString(QLatin1String("Interface %1 is invalid")).arg(interface)); else p->lastError = QDBusError(QDBusError::Other, QLatin1String("Unknown error")); } } return p; } struct qdbus_Introspect { QString xml; inline void operator()(QDBusConnectionPrivate::ObjectTreeNode *node) { xml = qDBusIntrospectObject(node); } }; QDBusMetaObject * QDBusConnectionPrivate::findMetaObject(const QString &service, const QString &path, const QString &interface) { // service must be a unique connection name if (!interface.isEmpty()) { QReadLocker locker(&lock); QDBusMetaObject *mo = cachedMetaObjects.value(interface, 0); if (mo) return mo; } if (service == QString::fromUtf8(dbus_bus_get_unique_name(connection))) { // it's one of our own QWriteLocker locker(&lock); QDBusMetaObject *mo = 0; if (!interface.isEmpty()) mo = cachedMetaObjects.value(interface, 0); if (mo) // maybe it got created when we switched from read to write lock return mo; qdbus_Introspect apply; if (!applyForObject(&rootNode, path, apply)) { lastError = QDBusError(QDBusError::InvalidArgs, QString(QLatin1String("No object at %1")).arg(path)); return 0; // no object at path } // release the lock and return return QDBusMetaObject::createMetaObject(interface, apply.xml, cachedMetaObjects, lastError); } // not local: introspect the target object: QDBusMessage msg = QDBusMessage::methodCall(service, path, QLatin1String(DBUS_INTERFACE_INTROSPECTABLE), QLatin1String("Introspect")); QDBusMessage reply = sendWithReply(msg, QDBusConnection::NoUseEventLoop); // it doesn't exist yet, we have to create it QWriteLocker locker(&lock); QDBusMetaObject *mo = 0; if (!interface.isEmpty()) mo = cachedMetaObjects.value(interface, 0); if (mo) // maybe it got created when we switched from read to write lock return mo; QString xml; if (reply.type() == QDBusMessage::ReplyMessage) // fetch the XML description xml = reply.first().toString(); else { lastError = reply; if (reply.type() != QDBusMessage::ErrorMessage || lastError != QDBusError::UnknownMethod) return 0; // error } // release the lock and return return QDBusMetaObject::createMetaObject(interface, xml, cachedMetaObjects, lastError); } void QDBusReplyWaiter::reply(const QDBusMessage &msg) { replyMsg = msg; QTimer::singleShot(0, this, SLOT(quit())); } #include "qdbusconnection_p.moc"