diff options
Diffstat (limited to 'qt/qdbusintegrator.cpp')
-rw-r--r-- | qt/qdbusintegrator.cpp | 621 |
1 files changed, 621 insertions, 0 deletions
diff --git a/qt/qdbusintegrator.cpp b/qt/qdbusintegrator.cpp new file mode 100644 index 00000000..4a19d33e --- /dev/null +++ b/qt/qdbusintegrator.cpp @@ -0,0 +1,621 @@ +/* qdbusintegrator.cpp QDBusConnection private implementation + * + * Copyright (C) 2005 Harald Fernengel <harry@kdevelop.org> + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +#include <QtCore/qcoreapplication.h> +#include <QtCore/qcoreevent.h> +#include <QtCore/qdebug.h> +#include <QtCore/qmetaobject.h> +#include <QtCore/qsocketnotifier.h> + +#include "qdbusconnection_p.h" +#include "qdbusmessage.h" + +int QDBusConnectionPrivate::messageMetaType = 0; + +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<QDBusConnectionPrivate *>(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<QDBusConnectionPrivate *>(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<QDBusConnectionPrivate *>(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); + 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); + 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<QDBusConnectionPrivate *>(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<QDBusConnectionPrivate *>(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); + + qDebug("SERVER: GOT A NEW CONNECTION"); // TODO +} + +static DBusHandlerResult qDBusSignalFilter(DBusConnection *connection, + DBusMessage *message, void *data) +{ + Q_ASSERT(data); + Q_UNUSED(connection); + + QDBusConnectionPrivate *d = static_cast<QDBusConnectionPrivate *>(data); + if (d->mode == QDBusConnectionPrivate::InvalidMode) + return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; + + int msgType = dbus_message_get_type(message); + bool handled = false; + + QDBusMessage amsg = QDBusMessage::fromDBusMessage(message); + qDebug() << "got message: " << dbus_message_get_type(message) << amsg; + + if (msgType == DBUS_MESSAGE_TYPE_SIGNAL) { + handled = d->handleSignal(message); + } else if (msgType == DBUS_MESSAGE_TYPE_METHOD_CALL) { + handled = d->handleObjectCall(message); + } + + return handled ? DBUS_HANDLER_RESULT_HANDLED : + DBUS_HANDLER_RESULT_NOT_YET_HANDLED; +} + +static bool qInvokeDBusSlot(const QDBusConnectionPrivate::SignalHook& hook, const QDBusMessage &msg) +{ + int count = msg.count(); + if (!(count == hook.params.count() + || (count + 1 == hook.params.count() + && hook.params[count] == QDBusConnectionPrivate::messageMetaType))) + return false; + + QVarLengthArray<void *, 16> params; + params.append(0); // return value + for (int i = 0; i < msg.count(); ++i) { + const QVariant &v = msg.at(i); + if (int(v.type()) != hook.params[i]) { + return false; + } + params.append(const_cast<void *>(v.constData())); + } + if (count + 1 == hook.params.count()) + params.append(const_cast<QDBusMessage *>(&msg)); + return hook.obj->qt_metacall(QMetaObject::InvokeMetaMethod, hook.midx, params.data()) < 0; +} + +static bool qInvokeDBusSlot(QObject *object, int idx, const QDBusMessage &msg) +{ + Q_ASSERT(object); + + const QMetaMethod method = object->metaObject()->method(idx); + if (!method.signature()) + return false; + + QVarLengthArray<void *> params; + params.append(0); // ### return type + + QList<QByteArray> parameterTypes = method.parameterTypes(); + + // check parameters, the slot should have <= parameters than the message + // also allow the QDBusMessage itself as last parameter slot + if ((parameterTypes.count() > msg.count()) + || (parameterTypes.count() + 1 != msg.count()) + && parameterTypes.last() != "QDBusMessage") { + qWarning("Cannot deliver asynchronous reply to object named '%s' because of parameter " + "mismatch. Please check your sendWithReplyAsync() statements.", + object->objectName().toLocal8Bit().constData()); + return false; + } + + int i; + for (i = 0; i < parameterTypes.count(); ++i) { + const QByteArray param = parameterTypes.at(i); + if (param == msg.at(i).typeName()) { + params.append(const_cast<void *>(msg.at(i).constData())); + } else if (i == parameterTypes.count() - 1 && param == "QDBusMessage") { + params.append(const_cast<void *>(static_cast<const void *>(&msg))); + } else { + qWarning("Parameter mismatch while delivering message, expected '%s', got '%s'", + msg.at(i).typeName(), param.constData()); + return false; + } + } + return object->qt_metacall(QMetaObject::InvokeMetaMethod, idx, params.data()) < 0; +} + +static bool qInvokeDBusSlot(QObject *object, QDBusMessage *msg) +{ + Q_ASSERT(object); + Q_ASSERT(msg); + + const QMetaObject *mo = object->metaObject(); + QVarLengthArray<void *> params; + params.append(0); // ### return type + + /* Try to find a slot with all args and the QDBusMessage */ + QByteArray slotName = msg->name().toUtf8(); // QVarLengthArray? + slotName.append("("); + for (int i = 0; i < msg->count(); ++i) { + slotName.append(msg->at(i).typeName()).append(","); + params.append(const_cast<void *>(msg->at(i).constData())); + } + slotName.append("QDBusMessage)"); + + int idx = mo->indexOfSlot(slotName.constData()); + if (idx >= 0) { + params.append(msg); + return object->qt_metacall(QMetaObject::InvokeMetaMethod, idx, params.data()) < 0; + } + + /* Try to find only args, without the QDBusMessage */ + slotName.chop(13); + slotName[slotName.count() - 1] = ')'; + + idx = mo->indexOfSlot(slotName.constData()); + if (idx >= 0 && (mo->method(idx).attributes() & QMetaMethod::Scriptable)) + return object->qt_metacall(QMetaObject::InvokeMetaMethod, idx, params.data()) < 0; + + /* Try to find a slot with only QDBusMessage */ + slotName = msg->name().toUtf8(); + slotName.append("(QDBusMessage)"); + + idx = mo->indexOfSlot(slotName.constData()); + if (idx >= 0) + return QMetaObject::invokeMethod(object, msg->name().toUtf8().constData(), + Q_ARG(QDBusMessage, *msg)); + + return false; +} + +int QDBusConnectionPrivate::registerMessageMetaType() +{ + int tp = messageMetaType = qRegisterMetaType<QDBusMessage>("QDBusMessage"); + return tp; +} + +bool QDBusConnectionPrivate::SignalHook::setSlot(const char *slotName) +{ + Q_ASSERT(static_cast<QObject *>(obj)); Q_ASSERT(slotName); + + QByteArray normalizedName = QMetaObject::normalizedSignature(slotName); + const QMetaObject *mo = obj->metaObject(); + midx = mo->indexOfMethod(normalizedName.constData()); + if (midx < 0) + return false; + + const QList<QByteArray> ptypes = mo->method(midx).parameterTypes(); + for (int i = 0; i < ptypes.count(); ++i) { + int t = QVariant::nameToType(ptypes.at(i).constData()); + if (t == QVariant::UserType) + t = QMetaType::type(ptypes.at(i).constData()); + if (t == QVariant::Invalid) + return false; + params.append(t); + } + + return true; +} + +QDBusConnectionPrivate::QDBusConnectionPrivate(QObject *parent) + : QObject(parent), ref(1), mode(InvalidMode), connection(0), server(0) +{ + static const int msgType = registerMessageMetaType(); + Q_UNUSED(msgType); + + dbus_error_init(&error); +} + +QDBusConnectionPrivate::~QDBusConnectionPrivate() +{ + if (dbus_error_is_set(&error)) + dbus_error_free(&error); + + closeConnection(); +} + +void QDBusConnectionPrivate::closeConnection() +{ + 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 + // Re-add all watchers + WatcherHash oldWatchers = watchers; + watchers.clear(); + QHashIterator<int, QDBusConnectionPrivate::Watcher> it(oldWatchers); + while (it.hasNext()) { + it.next(); + if (!it.value().read && !it.value().write) { + qDBusAddWatch(it.value().watch, this); + } + } + + // Re-add all timeouts + while (!pendingTimeouts.isEmpty()) + qDBusAddTimeout(pendingTimeouts.takeFirst(), this); +} + +void QDBusConnectionPrivate::socketRead(int fd) +{ + QHashIterator<int, QDBusConnectionPrivate::Watcher> 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"); + } + } + if (mode == ClientMode) + while (dbus_connection_dispatch(connection) == DBUS_DISPATCH_DATA_REMAINS); + // ### break out of loop? +} + +void QDBusConnectionPrivate::socketWrite(int fd) +{ + QHashIterator<int, QDBusConnectionPrivate::Watcher> 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) +{ + ObjectHookHash::iterator it = objectHooks.begin(); + while (it != objectHooks.end()) { + if (static_cast<QObject *>(it.value().obj) == obj) + it = objectHooks.erase(it); + else + ++it; + } + SignalHookHash::iterator sit = signalHooks.begin(); + while (sit != signalHooks.end()) { + if (static_cast<QObject *>(sit.value().obj) == obj) + sit = signalHooks.erase(sit); + else + ++sit; + } + obj->disconnect(this); +} + +bool QDBusConnectionPrivate::handleObjectCall(DBusMessage *message) const +{ + QDBusMessage msg = QDBusMessage::fromDBusMessage(message); + + ObjectHook hook; + ObjectHookHash::ConstIterator it = objectHooks.find(msg.path()); + while (it != objectHooks.constEnd() && it.key() == msg.path()) { + if (it.value().interface == msg.interface()) { + hook = it.value(); + break; + } else if (it.value().interface.isEmpty()) { + hook = it.value(); + } + ++it; + } + + if (!hook.obj) { + qDebug("NO OBJECT for %s", msg.path().toLocal8Bit().constData()); + return false; + } + + if (!qInvokeDBusSlot(hook.obj, &msg)) { + qDebug("NO SUCH SLOT: %s(QDBusMessage)", msg.name().toLocal8Bit().constData()); + return false; + } + + return true; +} + +bool QDBusConnectionPrivate::handleSignal(const QString &path, const QDBusMessage &msg) const +{ + SignalHookHash::const_iterator it = signalHooks.find(path); + qDebug("looking for: %s", path.toLocal8Bit().constData()); + qDebug() << signalHooks.keys(); + while (it != signalHooks.constEnd() && it.key() == path) { + const SignalHook &hook = it.value(); + if ((hook.name.isEmpty() || hook.name == msg.name()) + && (hook.interface.isEmpty() || hook.interface == msg.interface())) + qInvokeDBusSlot(hook, msg); + ++it; + } + return true; +} + +bool QDBusConnectionPrivate::handleSignal(DBusMessage *message) const +{ + QDBusMessage msg = QDBusMessage::fromDBusMessage(message); + + // yes, it is a single "|" below... + return handleSignal(QString(), msg) | handleSignal(msg.path(), msg); +} + +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<char, 56> 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"); + } + + dbus_connection_add_filter(connection, qDBusSignalFilter, this, 0); + + qDebug("base service: %s", service); +} + +struct QDBusPendingCall +{ + QPointer<QObject> receiver; + int methodIdx; + DBusPendingCall *pending; +}; + +static void qDBusResultReceived(DBusPendingCall *pending, void *user_data) +{ + QDBusPendingCall *call = reinterpret_cast<QDBusPendingCall *>(user_data); + Q_ASSERT(call->pending == pending); + + if (!call->receiver.isNull() && call->methodIdx != -1) { + DBusMessage *reply = dbus_pending_call_steal_reply(pending); + qInvokeDBusSlot(call->receiver, call->methodIdx, QDBusMessage::fromDBusMessage(reply)); + } + dbus_pending_call_unref(pending); + delete call; +} + +int QDBusConnectionPrivate::sendWithReplyAsync(const QDBusMessage &message, QObject *receiver, + const char *method) const +{ + DBusMessage *msg = message.toDBusMessage(); + if (!msg) + return 0; + + int slotIdx = -1; + if (receiver && method && *method) { + QByteArray normalized = QMetaObject::normalizedSignature(method + 1); + slotIdx = receiver->metaObject()->indexOfMethod(normalized.constData()); + if (slotIdx == -1) + qWarning("QDBusConnection::sendWithReplyAsync: no such method: '%s'", + normalized.constData()); + } + + 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->methodIdx = slotIdx; + pcall->pending = dbus_pending_call_ref(pending); + dbus_pending_call_set_notify(pending, qDBusResultReceived, pcall, 0); + } + return dbus_message_get_serial(msg); + } + + return 0; +} |