/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/ ** ** This file is part of the plugins of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include #include #include #include #include #include #include "dbusdispatcher.h" namespace Maemo { /*! \class Maemo::DBusDispatcher \brief DBusDispatcher is a class that can send DBUS method call messages and receive unicast signals from DBUS objects. */ class DBusDispatcherPrivate { public: DBusDispatcherPrivate(const QString& service, const QString& path, const QString& interface, const QString& signalPath) : service(service), path(path), interface(interface), signalPath(signalPath), connection(0) { memset(&signal_vtable, 0, sizeof(signal_vtable)); } ~DBusDispatcherPrivate() { foreach(DBusPendingCall *call, pending_calls) { dbus_pending_call_cancel(call); dbus_pending_call_unref(call); } } QString service; QString path; QString interface; QString signalPath; struct DBusConnection *connection; QList pending_calls; struct DBusObjectPathVTable signal_vtable; }; static bool constantVariantList(const QVariantList& variantList) { // Special case, empty list == empty struct if (variantList.isEmpty()) { return false; } else { QVariant::Type type = variantList[0].type(); // Iterate items in the list and check if they are same type foreach(QVariant variant, variantList) { if (variant.type() != type) { return false; } } } return true; } static QString variantToSignature(const QVariant& argument, bool constantList = true) { switch (argument.type()) { case QVariant::Bool: return "b"; case QVariant::ByteArray: return "ay"; case QVariant::Char: return "y"; case QVariant::Int: return "i"; case QVariant::UInt: return "u"; case QVariant::StringList: return "as"; case QVariant::String: return "s"; case QVariant::LongLong: return "x"; case QVariant::ULongLong: return "t"; case QVariant::List: { QString signature; QVariantList variantList = argument.toList(); if (!constantList) { signature += DBUS_STRUCT_BEGIN_CHAR_AS_STRING; foreach(QVariant listItem, variantList) { signature += variantToSignature(listItem); } signature += DBUS_STRUCT_END_CHAR_AS_STRING; } else { if (variantList.isEmpty()) return ""; signature = "a" + variantToSignature(variantList[0]); } return signature; } default: qDebug() << "Unsupported variant type: " << argument.type(); break; } return ""; } static bool appendVariantToDBusMessage(const QVariant& argument, DBusMessageIter *dbus_iter) { int idx = 0; DBusMessageIter array_iter; QStringList str_list; dbus_bool_t bool_data; dbus_int32_t int32_data; dbus_uint32_t uint32_data; dbus_int64_t int64_data; dbus_uint64_t uint64_data; char *str_data; char char_data; switch (argument.type()) { case QVariant::Bool: bool_data = argument.toBool(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_BOOLEAN, &bool_data); break; case QVariant::ByteArray: str_data = argument.toByteArray().data(); dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_ARRAY, DBUS_TYPE_BYTE_AS_STRING, &array_iter); dbus_message_iter_append_fixed_array(&array_iter, DBUS_TYPE_BYTE, &str_data, argument.toByteArray().size()); dbus_message_iter_close_container(dbus_iter, &array_iter); break; case QVariant::Char: char_data = argument.toChar().toAscii(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_BYTE, &char_data); break; case QVariant::Int: int32_data = argument.toInt(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_INT32, &int32_data); break; case QVariant::String: { QByteArray data = argument.toString().toUtf8(); str_data = data.data(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_STRING, &str_data); break; } case QVariant::StringList: str_list = argument.toStringList(); dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_ARRAY, "s", &array_iter); for (idx = 0; idx < str_list.size(); idx++) { QByteArray data = str_list.at(idx).toLatin1(); str_data = data.data(); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_STRING, &str_data); } dbus_message_iter_close_container(dbus_iter, &array_iter); break; case QVariant::UInt: uint32_data = argument.toUInt(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_UINT32, &uint32_data); break; case QVariant::ULongLong: uint64_data = argument.toULongLong(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_UINT64, &uint64_data); break; case QVariant::LongLong: int64_data = argument.toLongLong(); dbus_message_iter_append_basic(dbus_iter, DBUS_TYPE_INT64, &int64_data); break; case QVariant::List: { QVariantList variantList = argument.toList(); bool constantList = constantVariantList(variantList); DBusMessageIter array_iter; // List is mapped either as an DBUS array (all items same type) // DBUS struct (variable types) depending on constantList if (constantList) { // Resolve the signature for the first item QString signature = ""; if (!variantList.isEmpty()) { signature = variantToSignature( variantList[0], constantVariantList(variantList[0].toList())); } // Mapped as DBUS array dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_ARRAY, signature.toAscii(), &array_iter); foreach(QVariant listItem, variantList) { appendVariantToDBusMessage(listItem, &array_iter); } dbus_message_iter_close_container(dbus_iter, &array_iter); } else { // Mapped as DBUS struct dbus_message_iter_open_container(dbus_iter, DBUS_TYPE_STRUCT, NULL, &array_iter); foreach(QVariant listItem, variantList) { appendVariantToDBusMessage(listItem, &array_iter); } dbus_message_iter_close_container(dbus_iter, &array_iter); } break; } default: qDebug() << "Unsupported variant type: " << argument.type(); break; } return true; } static QVariant getVariantFromDBusMessage(DBusMessageIter *iter) { dbus_bool_t bool_data; dbus_int32_t int32_data; dbus_uint32_t uint32_data; dbus_int64_t int64_data; dbus_uint64_t uint64_data; char *str_data; char char_data; int argtype = dbus_message_iter_get_arg_type(iter); switch (argtype) { case DBUS_TYPE_BOOLEAN: { dbus_message_iter_get_basic(iter, &bool_data); QVariant variant((bool)bool_data); return variant; } case DBUS_TYPE_ARRAY: { // Handle all arrays here int elem_type = dbus_message_iter_get_element_type(iter); DBusMessageIter array_iter; dbus_message_iter_recurse(iter, &array_iter); if (elem_type == DBUS_TYPE_BYTE) { QByteArray byte_array; do { dbus_message_iter_get_basic(&array_iter, &char_data); byte_array.append(char_data); } while (dbus_message_iter_next(&array_iter)); QVariant variant(byte_array); return variant; } else if (elem_type == DBUS_TYPE_STRING) { QStringList str_list; do { dbus_message_iter_get_basic(&array_iter, &str_data); str_list.append(str_data); } while (dbus_message_iter_next(&array_iter)); QVariant variant(str_list); return variant; } else { QVariantList variantList; do { variantList << getVariantFromDBusMessage(&array_iter); } while (dbus_message_iter_next(&array_iter)); QVariant variant(variantList); return variant; } break; } case DBUS_TYPE_BYTE: { dbus_message_iter_get_basic(iter, &char_data); QChar ch(char_data); QVariant variant(ch); return variant; } case DBUS_TYPE_INT32: { dbus_message_iter_get_basic(iter, &int32_data); QVariant variant((int)int32_data); return variant; } case DBUS_TYPE_UINT32: { dbus_message_iter_get_basic(iter, &uint32_data); QVariant variant((uint)uint32_data); return variant; } case DBUS_TYPE_STRING: { dbus_message_iter_get_basic(iter, &str_data); QString str(QString::fromUtf8(str_data)); QVariant variant(str); return variant; } case DBUS_TYPE_INT64: { dbus_message_iter_get_basic(iter, &int64_data); QVariant variant((qlonglong)int64_data); return variant; } case DBUS_TYPE_UINT64: { dbus_message_iter_get_basic(iter, &uint64_data); QVariant variant((qulonglong)uint64_data); return variant; } case DBUS_TYPE_STRUCT: { // Handle all structs here DBusMessageIter struct_iter; dbus_message_iter_recurse(iter, &struct_iter); QVariantList variantList; do { variantList << getVariantFromDBusMessage(&struct_iter); } while (dbus_message_iter_next(&struct_iter)); QVariant variant(variantList); return variant; } default: qDebug() << "Unsupported DBUS type: " << argtype; } return QVariant(); } static DBusHandlerResult signalHandler (DBusConnection *connection, DBusMessage *message, void *object_ref) { (void)connection; QString interface; QString signal; DBusDispatcher *dispatcher = (DBusDispatcher *)object_ref; if (dbus_message_get_type(message) == DBUS_MESSAGE_TYPE_SIGNAL) { interface = dbus_message_get_interface(message); signal = dbus_message_get_member(message); QList arglist; DBusMessageIter dbus_iter; if (dbus_message_iter_init(message, &dbus_iter)) { // Read return arguments while (dbus_message_iter_get_arg_type (&dbus_iter) != DBUS_TYPE_INVALID) { arglist << getVariantFromDBusMessage(&dbus_iter); dbus_message_iter_next(&dbus_iter); } } dispatcher->emitSignalReceived(interface, signal, arglist); return DBUS_HANDLER_RESULT_HANDLED; } (void)message; return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } DBusDispatcher::DBusDispatcher(const QString& service, const QString& path, const QString& interface, QObject *parent) : QObject(parent), d_ptr(new DBusDispatcherPrivate(service, path, interface, path)) { setupDBus(); } DBusDispatcher::DBusDispatcher(const QString& service, const QString& path, const QString& interface, const QString& signalPath, QObject *parent) : QObject(parent), d_ptr(new DBusDispatcherPrivate(service, path, interface, signalPath)) { setupDBus(); } DBusDispatcher::~DBusDispatcher() { if (d_ptr->connection) { dbus_connection_close(d_ptr->connection); dbus_connection_unref(d_ptr->connection); } delete d_ptr; } void DBusDispatcher::setupDBus() { d_ptr->connection = dbus_bus_get_private(DBUS_BUS_SYSTEM, NULL); if (d_ptr->connection == NULL) qDebug() << "Unable to get DBUS connection!"; else { d_ptr->signal_vtable.message_function = signalHandler; dbus_connection_set_exit_on_disconnect(d_ptr->connection, FALSE); dbus_connection_setup_with_g_main(d_ptr->connection, g_main_context_get_thread_default()); dbus_connection_register_object_path(d_ptr->connection, d_ptr->signalPath.toLatin1(), &d_ptr->signal_vtable, this); } } static DBusMessage *prepareDBusCall(const QString& service, const QString& path, const QString& interface, const QString& method, const QVariant& arg1 = QVariant(), const QVariant& arg2 = QVariant(), const QVariant& arg3 = QVariant(), const QVariant& arg4 = QVariant(), const QVariant& arg5 = QVariant(), const QVariant& arg6 = QVariant(), const QVariant& arg7 = QVariant(), const QVariant& arg8 = QVariant()) { DBusMessage *message = dbus_message_new_method_call(service.toLatin1(), path.toLatin1(), interface.toLatin1(), method.toLatin1()); DBusMessageIter dbus_iter; // Append variants to DBUS message QList arglist; if (arg1.isValid()) arglist << arg1; if (arg2.isValid()) arglist << arg2; if (arg3.isValid()) arglist << arg3; if (arg4.isValid()) arglist << arg4; if (arg5.isValid()) arglist << arg5; if (arg6.isValid()) arglist << arg6; if (arg7.isValid()) arglist << arg7; if (arg8.isValid()) arglist << arg8; dbus_message_iter_init_append (message, &dbus_iter); while (!arglist.isEmpty()) { QVariant argument = arglist.takeFirst(); appendVariantToDBusMessage(argument, &dbus_iter); } return message; } QList DBusDispatcher::call(const QString& method, const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, const QVariant& arg4, const QVariant& arg5, const QVariant& arg6, const QVariant& arg7, const QVariant& arg8) { DBusMessageIter dbus_iter; DBusMessage *message = prepareDBusCall(d_ptr->service, d_ptr->path, d_ptr->interface, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); DBusMessage *reply = dbus_connection_send_with_reply_and_block( d_ptr->connection, message, -1, NULL); dbus_message_unref(message); QList replylist; if (reply != NULL && dbus_message_iter_init(reply, &dbus_iter)) { // Read return arguments while (dbus_message_iter_get_arg_type (&dbus_iter) != DBUS_TYPE_INVALID) { replylist << getVariantFromDBusMessage(&dbus_iter); dbus_message_iter_next(&dbus_iter); } } if (reply != NULL) dbus_message_unref(reply); return replylist; } class PendingCallInfo { public: QString method; DBusDispatcher *dispatcher; DBusDispatcherPrivate *priv; }; static void freePendingCallInfo(void *memory) { PendingCallInfo *info = (PendingCallInfo *)memory; delete info; } static void pendingCallFunction (DBusPendingCall *pending, void *memory) { PendingCallInfo *info = (PendingCallInfo *)memory; QString errorStr; QList replyList; DBusMessage *reply = dbus_pending_call_steal_reply (pending); Q_ASSERT(reply != NULL); if (dbus_message_get_type(reply) == DBUS_MESSAGE_TYPE_ERROR) { errorStr = dbus_message_get_error_name (reply); } else { DBusMessageIter dbus_iter; dbus_message_iter_init(reply, &dbus_iter); // Read return arguments while (dbus_message_iter_get_arg_type (&dbus_iter) != DBUS_TYPE_INVALID) { replyList << getVariantFromDBusMessage(&dbus_iter); dbus_message_iter_next(&dbus_iter); } } info->priv->pending_calls.removeOne(pending); info->dispatcher->emitCallReply(info->method, replyList, errorStr); dbus_message_unref(reply); dbus_pending_call_unref(pending); } bool DBusDispatcher::callAsynchronous(const QString& method, const QVariant& arg1, const QVariant& arg2, const QVariant& arg3, const QVariant& arg4, const QVariant& arg5, const QVariant& arg6, const QVariant& arg7, const QVariant& arg8) { DBusMessage *message = prepareDBusCall(d_ptr->service, d_ptr->path, d_ptr->interface, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8); DBusPendingCall *call = NULL; dbus_bool_t ret = dbus_connection_send_with_reply(d_ptr->connection, message, &call, -1); PendingCallInfo *info = new PendingCallInfo; info->method = method; info->dispatcher = this; info->priv = d_ptr; dbus_pending_call_set_notify(call, pendingCallFunction, info, freePendingCallInfo); d_ptr->pending_calls.append(call); return (bool)ret; } void DBusDispatcher::emitSignalReceived(const QString& interface, const QString& signal, const QList& args) { emit signalReceived(interface, signal, args); } void DBusDispatcher::emitCallReply(const QString& method, const QList& args, const QString& error) { emit callReply(method, args, error); } void DBusDispatcher::synchronousDispatch(int timeout_ms) { dbus_connection_read_write_dispatch(d_ptr->connection, timeout_ms); } } // Maemo namespace