blob: a402cbaa0a26b720bb1c25910756271bd6fdb1cc [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
** All rights reserved.
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** 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 <QDebug>
#include <QtCore>
#include <poll.h>
#include <dbus/dbus.h>
#include <dbus/dbus-glib-lowlevel.h>
#include <glib.h>
#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<DBusPendingCall *> 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<QVariant> 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<QVariant> 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<QVariant> 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<QVariant> 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<QVariant> 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<QVariant>& args) {
emit signalReceived(interface, signal, args); }
void DBusDispatcher::emitCallReply(const QString& method,
const QList<QVariant>& 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