diff options
author | Evan Nguyen <evan.nguyen@nokia.com> | 2010-07-14 14:12:28 +1000 |
---|---|---|
committer | Evan Nguyen <evan.nguyen@nokia.com> | 2010-07-14 14:29:45 +1000 |
commit | 748905bc110ec05b22edca052d6058fc82b98b0b (patch) | |
tree | d46842a20bb08bcad0ec0a9ea5bef662b4b17428 | |
parent | 0def0c80661a6e8573a43ab6301ab06fe4e67117 (diff) |
DBus backend publish services on DBUS for direct use
(cherry picked from commit c5d5c874b116d2f4d1e5f6268a6b93563e34168d)
-rw-r--r-- | src/serviceframework/ipc/ipc.pri | 20 | ||||
-rw-r--r-- | src/serviceframework/ipc/objectendpoint_dbus.cpp | 710 | ||||
-rw-r--r-- | src/serviceframework/ipc/objectendpoint_dbus_p.h | 101 | ||||
-rw-r--r-- | src/serviceframework/ipc/proxyobject_dbus.cpp | 254 | ||||
-rw-r--r-- | src/serviceframework/ipc/proxyobject_dbus_p.h | 76 | ||||
-rw-r--r-- | src/serviceframework/ipc/qremoteservicecontrol_dbus_p.cpp | 75 | ||||
-rw-r--r-- | src/serviceframework/ipc/qservicepackage.cpp | 3 | ||||
-rw-r--r-- | src/serviceframework/ipc/qservicepackage_p.h | 6 | ||||
-rw-r--r-- | tests/auto/qservicemanager_ipc/client/tst_qservicemanager_ipc.cpp | 18 | ||||
-rw-r--r-- | tests/auto/qservicemanager_ipc/service/main.cpp | 1 |
10 files changed, 1204 insertions, 60 deletions
diff --git a/src/serviceframework/ipc/ipc.pri b/src/serviceframework/ipc/ipc.pri index 57ba7fae5d..911ba503e5 100644 --- a/src/serviceframework/ipc/ipc.pri +++ b/src/serviceframework/ipc/ipc.pri @@ -8,14 +8,22 @@ symbian { contains(QT_CONFIG,dbus) { QT += dbus network - PRIVATE_HEADERS += ipc/qremoteservicecontrol_dbus_p.h - SOURCES += ipc/qremoteservicecontrol_dbus_p.cpp + PRIVATE_HEADERS += ipc/qremoteservicecontrol_dbus_p.h \ + ipc/objectendpoint_dbus_p.h \ + ipc/proxyobject_dbus_p.h + SOURCES += ipc/qremoteservicecontrol_dbus_p.cpp \ + ipc/objectendpoint_dbus.cpp \ + ipc/proxyobject_dbus.cpp } else { QT += network - PRIVATE_HEADERS += ipc/qremoteservicecontrol_p.h - SOURCES += ipc/qremoteservicecontrol_p.cpp + PRIVATE_HEADERS += ipc/qremoteservicecontrol_p.h \ + ipc/objectendpoint_p.h \ + ipc/proxyobject_p.h + SOURCES += ipc/qremoteservicecontrol_p.cpp \ + ipc/objectendpoint.cpp \ + ipc/proxyobject.cpp } } @@ -25,9 +33,7 @@ PRIVATE_HEADERS += \ ipc/qmetaobjectbuilder_p.h \ ipc/instancemanager_p.h \ ipc/qservicepackage_p.h \ - ipc/objectendpoint_p.h \ ipc/ipcendpoint_p.h \ - ipc/proxyobject_p.h SOURCES += \ ipc/qslotinvoker.cpp \ @@ -35,6 +41,4 @@ SOURCES += \ ipc/qmetaobjectbuilder.cpp \ ipc/instancemanager.cpp \ ipc/qservicepackage.cpp \ - ipc/objectendpoint.cpp \ ipc/ipcendpoint.cpp \ - ipc/proxyobject.cpp diff --git a/src/serviceframework/ipc/objectendpoint_dbus.cpp b/src/serviceframework/ipc/objectendpoint_dbus.cpp new file mode 100644 index 0000000000..9a75eb1fe9 --- /dev/null +++ b/src/serviceframework/ipc/objectendpoint_dbus.cpp @@ -0,0 +1,710 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "objectendpoint_dbus_p.h" +#include "instancemanager_p.h" +#include "qmetaobjectbuilder_p.h" +#include "proxyobject_dbus_p.h" +#include "qsignalintercepter_p.h" +#include <QTimer> +#include <QEventLoop> +#include <QVarLengthArray> + +QTM_BEGIN_NAMESPACE + +class Response +{ +public: + Response() : isFinished(false), result(0) + { } + + bool isFinished; + void* result; +}; + +typedef QHash<QUuid, Response*> Replies; +Q_GLOBAL_STATIC(Replies, openRequests); + +class ServiceSignalIntercepter : public QSignalIntercepter +{ + //Do not put Q_OBJECT here +public: + ServiceSignalIntercepter(QObject* sender, const QByteArray& signal, + ObjectEndPoint* parent) + : QSignalIntercepter(sender, signal, parent), endPoint(parent) + { + + } + + void setMetaIndex(int index) + { + metaIndex = index; + } + +protected: + void activated( const QList<QVariant>& args ) + { + endPoint->invokeRemote(metaIndex, args, QMetaType::Void); + } +private: + ObjectEndPoint* endPoint; + int metaIndex; + +}; + +class ObjectEndPointPrivate +{ +public: + ObjectEndPointPrivate() + { + } + + ~ObjectEndPointPrivate() + { + } + + //service side + void setupSignalIntercepters(QObject * service) + { + Q_ASSERT(endPointType == ObjectEndPoint::Service); + + //create a signal intercepter for each signal + //offered by service + //exclude QObject signals + const QMetaObject* mo = service->metaObject(); + while (mo && strcmp(mo->className(), "QObject")) + { + for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) { + const QMetaMethod method = mo->method(i); + if (method.methodType() == QMetaMethod::Signal) { + QByteArray signal = method.signature(); + //add '2' for signal - see QSIGNAL_CODE + ServiceSignalIntercepter* intercept = + new ServiceSignalIntercepter(service, "2"+signal, parent ); + intercept->setMetaIndex(i); + } + } + mo = mo->superClass(); + } + } + + /*! + Activate slots connected to given signal. Unfortunately we can only do this + using the signal index relative to the meta object defining the signal. + */ + int triggerConnectedSlots(QObject* service, const QMetaObject* meta, int id, void **args) + { + Q_ASSERT(endPointType == ObjectEndPoint::Client); + + const QMetaObject* parentMeta = meta->superClass(); + if (parentMeta) + id = triggerConnectedSlots(service, parentMeta, id, args); + + if (id < 0) + return id; + + const int methodsThisType = meta->methodCount() - meta->methodOffset(); + if (id >= 0 && id < methodsThisType) + QMetaObject::activate(service, meta, id, args); + + id -= methodsThisType; + return id; + } + + //used on client and service side + ObjectEndPoint::Type endPointType; + ObjectEndPoint* parent; + + //used on service side + QRemoteServiceIdentifier typeIdent; + QUuid serviceInstanceId; +}; + +//TODO list: +/* + - Why do we need typeIdent and serviceInstanceId on service side. The instance id should be sufficient. + - Consider merging invokeRemoteProperty() and invokeRemote() + - QMetaClassInfo support + +*/ + +ObjectEndPoint::ObjectEndPoint(Type type, QServiceIpcEndPoint* comm, QObject* parent) + : QObject(parent), dispatch(comm), service(0) +{ + Q_ASSERT(dispatch); + d = new ObjectEndPointPrivate; + d->parent = this; + d->endPointType = type; + + dispatch->setParent(this); + connect(dispatch, SIGNAL(readyRead()), this, SLOT(newPackageReady())); + connect(dispatch, SIGNAL(disconnected()), this, SLOT(disconnected())); + if (type == Client) { + return; //we are waiting for conctructProxy() call + } else { + if (dispatch->packageAvailable()) + QTimer::singleShot(0, this, SLOT(newPackageReady())); + } +} + +ObjectEndPoint::~ObjectEndPoint() +{ + if (d->endPointType == Service) { + InstanceManager::instance()->removeObjectInstance(d->typeIdent, d->serviceInstanceId); + } + + delete d; +} + +void ObjectEndPoint::disconnected() +{ + deleteLater(); +} + +/*! + Client requests proxy object. The proxy is owned by calling + code and this object must clean itself up upon destruction of + proxy. +*/ +QObject* ObjectEndPoint::constructProxy(const QRemoteServiceIdentifier& ident) +{ + //client side + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + //ask for serialized meta object + //get proxy based on meta object + //return meta object + QServicePackage p; + p.d = new QServicePackagePrivate(); + p.d->messageId = QUuid::createUuid(); + p.d->typeId = ident; + + Response* response = new Response(); + openRequests()->insert(p.d->messageId, response); + + dispatch->writePackage(p); + waitForResponse(p.d->messageId); + + if (response->isFinished) { + if (response->result == 0) + qWarning() << "Request for remote service failed"; + else + service = reinterpret_cast<QServiceProxy* >(response->result); + } else { + qDebug() << "response passed but not finished"; + } + + openRequests()->take(p.d->messageId); + delete response; + + return service; +} + +void ObjectEndPoint::newPackageReady() +{ + //client and service side + + while(dispatch->packageAvailable()) + { + QServicePackage p = dispatch->nextPackage(); + if (!p.isValid()) + continue; + + switch(p.d->packageType) { + case QServicePackage::ObjectCreation: + objectRequest(p); + break; + case QServicePackage::MethodCall: + methodCall(p); + break; + case QServicePackage::PropertyCall: + propertyCall(p); + break; + default: + qWarning() << "Unknown package type received."; + } + } +} + +void ObjectEndPoint::propertyCall(const QServicePackage& p) +{ + if(p.d->responseType == QServicePackage::NotAResponse) { + //service side + Q_ASSERT(d->endPointType == ObjectEndPoint::Service); + + QByteArray data = p.d->payload.toByteArray(); + QDataStream stream(&data, QIODevice::ReadOnly); + int metaIndex = -1; + QVariant arg; + int callType; + stream >> metaIndex; + stream >> arg; + stream >> callType; + const QMetaObject::Call c = (QMetaObject::Call) callType; + + QVariant result; + QMetaProperty property = service->metaObject()->property(metaIndex); + if (property.isValid()) { + switch(c) { + case QMetaObject::ReadProperty: + result = property.read(service); + break; + case QMetaObject::WriteProperty: + property.write(service, arg); + break; + case QMetaObject::ResetProperty: + property.reset(service); + break; + default: + break; + + } + } + + if (c == QMetaObject::ReadProperty) { + QServicePackage response = p.createResponse(); + if (property.isValid()) { + response.d->responseType = QServicePackage::Success; + response.d->payload = result; + } else { + response.d->responseType = QServicePackage::Failed; + } + dispatch->writePackage(response); + } + } else { + //client side + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + + Response* response = openRequests()->value(p.d->messageId); + response->isFinished = true; + if (p.d->responseType == QServicePackage::Failed) { + response->result = 0; + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + qWarning() << "Service method call failed"; + return; + } + QVariant* variant = new QVariant(p.d->payload); + response->result = reinterpret_cast<void *>(variant); + + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + } +} + +QUuid ObjectEndPoint::myInstanceID() +{ + return d->serviceInstanceId; +} + +void ObjectEndPoint::objectRequest(const QServicePackage& p) +{ + if (p.d->responseType != QServicePackage::NotAResponse ) { + //client side + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + d->serviceInstanceId = p.d->instanceId; + + qDebug() << "MY UUID IS" << d->serviceInstanceId; + + Response* response = openRequests()->value(p.d->messageId); + if (p.d->responseType == QServicePackage::Failed) { + response->result = 0; + response->isFinished = true; + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + qWarning() << "Service instantiation failed"; + return; + } + + //deserialize meta object and create proxy object + QServiceProxy* proxy = new QServiceProxy(p.d->payload.toByteArray(), this); + //QServiceProxy* proxy = new QServiceProxy(p.d->typeId, p.d->payload.toByteArray(), this); + response->result = reinterpret_cast<void *>(proxy); + response->isFinished = true; + + //Create DBUS interface + QString serviceName = "com.nokia.qtmobility.sfw." + p.d->typeId.name; + uint hash = qHash(d->serviceInstanceId.toString()); + QString objPath = "/" + p.d->typeId.interface + "/" + p.d->typeId.version + "/" + QString::number(hash); + objPath.replace(QString("."), QString("/")); + + qDebug() << "CLIENT PATH: " << objPath; + + iface = new QDBusInterface(serviceName, objPath, "", QDBusConnection::sessionBus()); + + //wake up waiting code + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + + } else { + //service side + Q_ASSERT(d->endPointType == ObjectEndPoint::Service); + + QServicePackage response = p.createResponse(); + InstanceManager* m = InstanceManager::instance(); + + //get meta object from type register + const QMetaObject* meta = m->metaObject(p.d->typeId); + if (!meta) { + qDebug() << "Unknown type" << p.d->typeId; + dispatch->writePackage(response); + return; + } + + //serialize meta object + QByteArray data; + QDataStream stream( &data, QIODevice::WriteOnly | QIODevice::Append ); + QMetaObjectBuilder builder(meta); + builder.serialize(stream); + + //instantiate service object from type register + service = m->createObjectInstance(p.d->typeId, d->serviceInstanceId); + if (!service) { + qWarning() << "Cannot instanciate service object"; + dispatch->writePackage(response); + return; + } + d->setupSignalIntercepters(service); + + // DBUSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSSS + QDBusConnection *connection = new QDBusConnection(QDBusConnection::sessionBus()); + if (!connection->isConnected()) { + qWarning() << "Cannot connect to DBus"; + } + + // Register proxy service to DBus + QString serviceName = "com.nokia.qtmobility.sfw." + p.d->typeId.name; + uint hash = qHash(d->serviceInstanceId.toString()); + QString objPath = "/" + p.d->typeId.interface + "/" + p.d->typeId.version + "/" + QString::number(hash); + objPath.replace(QString("."), QString("/")); + + qDebug() << "SERVICE PATH: " << objPath; + + connection->registerObject(objPath, service, QDBusConnection::ExportAllSlots | + QDBusConnection::ExportAllProperties | + QDBusConnection::ExportAllSignals | + QDBusConnection::ExportAllContents); + + iface = new QDBusInterface(serviceName, objPath, "", QDBusConnection::sessionBus()); + //////////////////////////////////////// + + //send meta object + d->typeIdent = p.d->typeId; + response.d->instanceId = d->serviceInstanceId; + response.d->typeId = p.d->typeId; + response.d->responseType = QServicePackage::Success; + response.d->payload = QVariant(data); + dispatch->writePackage(response); + } +} + +void ObjectEndPoint::methodCall(const QServicePackage& p) +{ + if (p.d->responseType == QServicePackage::NotAResponse ) { + //service side if slot invocation + //client side if signal emission (isSignal==true) + + QByteArray data = p.d->payload.toByteArray(); + QDataStream stream(&data, QIODevice::ReadOnly); + int metaIndex = -1; + QVariantList args; + stream >> metaIndex; + stream >> args; + + qDebug() << "METHOD CALL" << metaIndex << args; + + QMetaMethod method = service->metaObject()->method(metaIndex); + const bool isSignal = (method.methodType() == QMetaMethod::Signal); + const int returnType = QMetaType::type(method.typeName()); + + if (isSignal) { + qDebug() << " I AM A SIGNAL "; + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + // Construct the raw argument list. + /* we ignore a possible return type of the signal. The value is + not deterministic and it can actually create memory leaks + in moc generated code. + */ + const int numArgs = args.size(); + QVarLengthArray<void *, 32> a( numArgs+1 ); + a[0] = 0; + + const QList<QByteArray> pTypes = method.parameterTypes(); + for ( int arg = 0; arg < numArgs; ++arg ) { + if (pTypes.at(arg) == "QVariant") + a[arg+1] = (void *)&( args[arg] ); + else + a[arg+1] = (void *)( args[arg].data() ); + } + + d->triggerConnectedSlots(service, service->metaObject(), metaIndex, a.data()); + return; + } + + //service side + Q_ASSERT(d->endPointType == ObjectEndPoint::Service); + + const char* typenames[] = {0,0,0,0,0,0,0,0,0,0}; + const void* param[] = {0,0,0,0,0,0,0,0,0,0}; + + for(int i=0; i<args.size(); i++) { + if (args[i].isValid()) { + typenames[i] = args[i].typeName(); + } else { + if (method.parameterTypes().at(i) == "QVariant") + typenames[i] = "QVariant"; + } + param[i] = args[i].constData(); + } + + QString methodName(method.signature()); + int index = methodName.indexOf("("); + methodName.chop(methodName.size()-index); + + qDebug() << "VOID METHOD CALL: " << methodName; + qDebug() << "VOID METHOD ARGS: " << args; + + bool result = false; + if (returnType == QMetaType::Void && strcmp(method.typeName(), "QVariant")) { + // void method call + QDBusMessage msg = iface->callWithArgumentList(QDBus::Block, methodName, args); + if (msg.type() == QDBusMessage::ErrorMessage) { + // Q_INVOKABLE method + result = method.invoke(service, + QGenericArgument(typenames[0], param[0]), + QGenericArgument(typenames[1], param[1]), + QGenericArgument(typenames[2], param[2]), + QGenericArgument(typenames[3], param[3]), + QGenericArgument(typenames[4], param[4]), + QGenericArgument(typenames[5], param[5]), + QGenericArgument(typenames[6], param[6]), + QGenericArgument(typenames[7], param[7]), + QGenericArgument(typenames[8], param[8]), + QGenericArgument(typenames[9], param[9])); + } else { + result = true; + } + } else { + //result buffer + QVariant returnValue; + //ignore whether QVariant is a declared meta type or not + if (returnType != QVariant::Invalid && strcmp(method.typeName(), "QVariant")) { + returnValue = QVariant(returnType, (const void*) 0); + } + + //QDBusReply<QVariant> reply = iface->callWithArgumentList(QDBus::Block, methodName, args); + QDBusReply<QVariant> reply = iface->callWithArgumentList(QDBus::Block, methodName, args); + qDebug() << reply.isValid(); + if (!reply.isValid()) { + qDebug() << "NOT VALID"; + QGenericReturnArgument ret(method.typeName(), returnValue.data()); + result = method.invoke(service, ret, + QGenericArgument(typenames[0], param[0]), + QGenericArgument(typenames[1], param[1]), + QGenericArgument(typenames[2], param[2]), + QGenericArgument(typenames[3], param[3]), + QGenericArgument(typenames[4], param[4]), + QGenericArgument(typenames[5], param[5]), + QGenericArgument(typenames[6], param[6]), + QGenericArgument(typenames[7], param[7]), + QGenericArgument(typenames[8], param[8]), + QGenericArgument(typenames[9], param[9])); + } else { + qDebug() << "++++++++++++++++++++++++++++++++++++++++++++++++++++++"; + } + + QServicePackage response = p.createResponse(); + + if (result) { + response.d->responseType = QServicePackage::Success; + response.d->payload = returnValue; + } else { + response.d->responseType = QServicePackage::Failed; + } + dispatch->writePackage(response); + + } + if (!result) + qWarning( "%s::%s cannot be called.", service->metaObject()->className(), method.signature()); + } else { + //client side + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + Response* response = openRequests()->value(p.d->messageId); + response->isFinished = true; + if (p.d->responseType == QServicePackage::Failed) { + response->result = 0; + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + return; + } + QVariant* variant = new QVariant(p.d->payload); + response->result = reinterpret_cast<void *>(variant); + + QTimer::singleShot(0, this, SIGNAL(pendingRequestFinished())); + } +} + +/*! + Will block if return value expected + Handles property calls +*/ +QVariant ObjectEndPoint::invokeRemoteProperty(int metaIndex, const QVariant& arg, int /*returnType*/, QMetaObject::Call c ) +{ + //client and service side + Q_ASSERT(d->endPointType == ObjectEndPoint::Client + || d->endPointType == ObjectEndPoint::Service); + + QServicePackage p; + p.d = new QServicePackagePrivate(); + p.d->packageType = QServicePackage::PropertyCall; + p.d->messageId = QUuid::createUuid(); + + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly|QIODevice::Append); + stream << metaIndex << arg << c; + p.d->payload = data; + + if (c == QMetaObject::ReadProperty) { + //create response and block for answer + Response* response = new Response(); + openRequests()->insert(p.d->messageId, response); + + dispatch->writePackage(p); + waitForResponse(p.d->messageId); + + QVariant result; + QVariant* resultPointer; + if (response->isFinished) { + if (response->result == 0) { + qWarning() << "Service property call failed"; + } else { + resultPointer = reinterpret_cast<QVariant* >(response->result); + result = (*resultPointer); + } + } else { + qDebug() << "response passed but not finished"; + } + + openRequests()->take(p.d->messageId); + delete resultPointer; + delete response; + + return result; + } else { + dispatch->writePackage(p); + } + + return QVariant(); +} + +/*! + Will block if return value expected + Handles signal/slots +*/ +QVariant ObjectEndPoint::invokeRemote(int metaIndex, const QVariantList& args, int returnType) +{ + qDebug() << "INVOKE REMOTE" << metaIndex << args; + + //client side + //Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + QServicePackage p; + p.d = new QServicePackagePrivate(); + p.d->packageType = QServicePackage::MethodCall; + p.d->messageId = QUuid::createUuid(); + + QByteArray data; + QDataStream stream(&data, QIODevice::WriteOnly|QIODevice::Append); + stream << metaIndex << args; + p.d->payload = data; + + if (returnType == QMetaType::Void) { + dispatch->writePackage(p); + } else { + //create response and block for answer + Response* response = new Response(); + openRequests()->insert(p.d->messageId, response); + + dispatch->writePackage(p); + waitForResponse(p.d->messageId); + + QVariant result; + QVariant* resultPointer; + if (response->isFinished) { + if (response->result == 0) { + qWarning() << "Remote function call failed"; + } else { + resultPointer = reinterpret_cast<QVariant* >(response->result); + result = (*resultPointer); + } + } else { + qDebug() << "response passed but not finished"; + } + + openRequests()->take(p.d->messageId); + delete resultPointer; + delete response; + + return result; + + } + + return QVariant(); +} + +void ObjectEndPoint::waitForResponse(const QUuid& requestId) +{ + Q_ASSERT(d->endPointType == ObjectEndPoint::Client); + + if (openRequests()->contains(requestId) ) { + Response* response = openRequests()->value(requestId); + QEventLoop* loop = new QEventLoop( this ); + connect(this, SIGNAL(pendingRequestFinished()), loop, SLOT(quit())); + + while(!response->isFinished) { + loop->exec(); + } + + delete loop; + } +} + +#include "moc_objectendpoint_dbus_p.cpp" + +QTM_END_NAMESPACE diff --git a/src/serviceframework/ipc/objectendpoint_dbus_p.h b/src/serviceframework/ipc/objectendpoint_dbus_p.h new file mode 100644 index 0000000000..780b17f221 --- /dev/null +++ b/src/serviceframework/ipc/objectendpoint_dbus_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef OBJECT_ENDPOINT_DBUS_H +#define OBJECT_ENDPOINT_DBUS_H + +#include "qmobilityglobal.h" +#include "ipcendpoint_p.h" +#include "qremoteserviceclassregister.h" +#include "qservice.h" +#include <QPointer> +#include <QHash> +#include <QtDBus> + +QTM_BEGIN_NAMESPACE + +class ObjectEndPointPrivate; +class ObjectEndPoint : public QObject +{ + Q_OBJECT +public: + enum Type { + Service = 0, + Client + }; + + ObjectEndPoint(Type type, QServiceIpcEndPoint* comm, QObject* parent = 0); + ~ObjectEndPoint(); + + QObject* constructProxy(const QRemoteServiceIdentifier& ident); + + // DBUS OVERLOAD + //QObject* constructProxy(); + + void objectRequest(const QServicePackage& p); + void methodCall(const QServicePackage& p); + void propertyCall(const QServicePackage& p); + + QVariant invokeRemote(int metaIndex, const QVariantList& args, int returnType); + QVariant invokeRemoteProperty(int metaIndex, const QVariant& arg, int returnType, QMetaObject::Call c); + + QUuid myInstanceID(); + +Q_SIGNALS: + void pendingRequestFinished(); + +public Q_SLOTS: + void newPackageReady(); + void disconnected(); + +//private: +public: + void waitForResponse(const QUuid& requestId); + + QServiceIpcEndPoint* dispatch; + QPointer<QObject> service; + ObjectEndPointPrivate* d; + QDBusInterface *iface; +}; + +QTM_END_NAMESPACE + +#endif //OBJECT_ENDPOINT_DBUS_H diff --git a/src/serviceframework/ipc/proxyobject_dbus.cpp b/src/serviceframework/ipc/proxyobject_dbus.cpp new file mode 100644 index 0000000000..6c0337b3ef --- /dev/null +++ b/src/serviceframework/ipc/proxyobject_dbus.cpp @@ -0,0 +1,254 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "proxyobject_dbus_p.h" +#include "qmetaobjectbuilder_p.h" + +#include <QDebug> +#include <QtDBus> + +QTM_BEGIN_NAMESPACE + +class QServiceProxyPrivate +{ +public: + //QRemoteServiceIdentifier ident; + QByteArray metadata; + QMetaObject* meta; + ObjectEndPoint* endPoint; +}; + +QServiceProxy::QServiceProxy(const QByteArray& metadata, ObjectEndPoint* endPoint, QObject* parent) + : QObject(parent) +{ + Q_ASSERT(endPoint); + d = new QServiceProxyPrivate(); + //d->ident = ident; + d->metadata = metadata; + d->meta = 0; + d->endPoint = endPoint; + + /* + QDBusConnection *connection = new QDBusConnection(QDBusConnection::sessionBus()); + if (!connection->isConnected()) { + qWarning() << "Cannot connect to DBus"; + } + + QString serviceName = "com.nokia.qtmobility.sfw." + d->ident.name; + uint hash = qHash(endPoint->myInstanceID()); + QString objPath = "/" + d->ident.interface + "/" + d->ident.version + "/" + QString::number(hash); + objPath.replace(QString("."), QString("/")); + + QDBusInterface *iface = new QDBusInterface(serviceName, objPath, "", QDBusConnection::sessionBus()); + */ + + QDataStream stream(d->metadata); + QMetaObjectBuilder builder; + QMap<QByteArray, const QMetaObject*> refs; + + builder.deserialize(stream, refs); + if (stream.status() != QDataStream::Ok) { + qWarning() << "Invalid metaObject for service received"; + } else { + QMetaMethodBuilder b = builder.addSignal("errorUnrecoverableIPCFault(QService::UnrecoverableIPCError)"); + + // After all methods are filled in, otherwise qvector won't be big enough + localSignals.fill(false, builder.methodCount()); + localSignals.replace(b.index(), true); // Call activate locally + + d->meta = builder.toMetaObject(); + + qWarning() << "Proxy object for" << d->meta->className() << "created."; + } + + QMetaObject *fun = d->meta; + QMetaProperty prop = fun->property(1); + prop.write(endPoint->service, "YOHOHO"); + + + qDebug() << "PROP READ" << prop.read(endPoint->service); + + +} + +QServiceProxy::~QServiceProxy() +{ + if (d->meta) + delete d->meta; + delete d; +} + +//provide custom Q_OBJECT implementation +const QMetaObject* QServiceProxy::metaObject() const +{ + return d->meta; +} + +int QServiceProxy::qt_metacall(QMetaObject::Call c, int id, void **a) +{ + id = QObject::qt_metacall(c, id, a); + if (id < 0 || !d->meta) + return id; + + if(localSignals.at(id)){ + QMetaObject::activate(this, d->meta, id, a); + return id; + } + + if (c == QMetaObject::InvokeMetaMethod) { + const int mcount = d->meta->methodCount() - d->meta->methodOffset(); + const int metaIndex = id + d->meta->methodOffset(); + + QMetaMethod method = d->meta->method(metaIndex); + + const int returnType = QMetaType::type(method.typeName()); + + //process arguments + const QList<QByteArray> pTypes = method.parameterTypes(); + const int pTypesCount = pTypes.count(); + QVariantList args ; + if (pTypesCount > 10) { + qWarning() << "Cannot call" << method.signature() << ". More than 10 parameter."; + return id; + } + + + for (int i=0; i < pTypesCount; i++) { + const QByteArray& t = pTypes[i]; + + int variantType = QVariant::nameToType(t); + if (variantType == QVariant::UserType) + variantType = QMetaType::type(t); + + if (t == "QVariant") { //ignore whether QVariant is declared as metatype + args << *reinterpret_cast<const QVariant(*)>(a[i+1]); + } else if ( variantType == 0 ){ + qWarning("%s: argument %s has unknown type. Use qRegisterMetaType to register it.", + method.signature(), t.data()); + return id; + } else { + args << QVariant(variantType, a[i+1]); + } + } + + //QVariant looks the same as Void type. we need to distinguish them + if (returnType == QMetaType::Void && strcmp(method.typeName(),"QVariant") ) { + d->endPoint->invokeRemote(metaIndex, args, returnType); + } else { + //TODO: ugly but works + //add +1 if we have a variant return type to avoid triggering of void + //code path + //invokeRemote() parameter list needs review + QVariant result = d->endPoint->invokeRemote(metaIndex, args, + returnType==0 ? returnType+1: returnType); + if (returnType != 0 && strcmp(method.typeName(),"QVariant")) { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::ReadWrite); + QMetaType::save(stream, returnType, result.constData()); + stream.device()->seek(0); + QMetaType::load(stream, returnType, a[0]); + } else { + if (a[0]) *reinterpret_cast< QVariant*>(a[0]) = result; + } + } + id-=mcount; + } else if ( c == QMetaObject::ReadProperty + || c == QMetaObject::WriteProperty + || c == QMetaObject::ResetProperty ) { + const int pCount = d->meta->propertyCount() - d->meta->propertyOffset(); + const int metaIndex = id + d->meta->propertyOffset(); + QMetaProperty property = d->meta->property(metaIndex); + if (property.isValid()) { + int pType = property.type(); + if (pType == QVariant::UserType) + pType = QMetaType::type(property.typeName()); + + QVariant arg; + if ( c == QMetaObject::WriteProperty ) { + if (pType == QVariant::Invalid && QByteArray(property.typeName()) == "QVariant") + arg = *reinterpret_cast<const QVariant(*)>(a[0]); + else if (pType == 0) { + qWarning("%s: property %s has unkown type", property.name(), property.typeName()); + return id; + } else { + arg = QVariant(pType, a[0]); + } + } + QVariant result; + if (c == QMetaObject::ReadProperty) { + result = d->endPoint->invokeRemoteProperty(metaIndex, arg, pType, c); + //wrap result for client + if (pType != 0) { + QByteArray buffer; + QDataStream stream(&buffer, QIODevice::ReadWrite); + QMetaType::save(stream, pType, result.constData()); + stream.device()->seek(0); + QMetaType::load(stream, pType, a[0]); + } else { + if (a[0]) *reinterpret_cast< QVariant*>(a[0]) = result; + } + } else { + d->endPoint->invokeRemoteProperty(metaIndex, arg, pType, c); + } + } + id-=pCount; + } else if ( c == QMetaObject::QueryPropertyDesignable + || c == QMetaObject::QueryPropertyScriptable + || c == QMetaObject::QueryPropertyStored + || c == QMetaObject::QueryPropertyEditable + || c == QMetaObject::QueryPropertyUser ) + { + //Nothing to do? + //These values are part of the transferred meta object already + } else { + //TODO + qWarning() << "MetaCall type" << c << "not yet handled"; + } + return id; +} + +void *QServiceProxy::qt_metacast(const char* className) +{ + if (!className) return 0; + //this object should not be castable to anything but QObject + return QObject::qt_metacast(className); +} +QTM_END_NAMESPACE diff --git a/src/serviceframework/ipc/proxyobject_dbus_p.h b/src/serviceframework/ipc/proxyobject_dbus_p.h new file mode 100644 index 0000000000..d215a9ccd0 --- /dev/null +++ b/src/serviceframework/ipc/proxyobject_dbus_p.h @@ -0,0 +1,76 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the Qt Mobility Components. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the Technology Preview License Agreement accompanying +** this package. +** +** GNU Lesser General Public License Usage +** Alternatively, 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. +** +** If you have questions regarding the use of this file, please contact +** Nokia at qt-info@nokia.com. +** +** +** +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROXY_OBJECT_H +#define PROXY_OBJECT_H + +#include "qmobilityglobal.h" +#include "objectendpoint_dbus_p.h" +#include <QObject> + +QTM_BEGIN_NAMESPACE + +class QServiceProxyPrivate; +class QServiceProxy : public QObject +{ + //Note: Do not put Q_OBJECT here +public: + QServiceProxy(const QByteArray& metadata, ObjectEndPoint* endpoint, QObject* parent = 0); + virtual ~QServiceProxy(); + + //provide custom Q_OBJECT implementation + virtual const QMetaObject* metaObject() const; + int qt_metacall(QMetaObject::Call c, int id, void **a); + void *qt_metacast(const char* className); +/*protected: + void connectNotify(const char* signal); + void disconnectNotify(const char* signal);*/ + +private: + QServiceProxyPrivate* d; + QVector<bool> localSignals; +}; + + + +QTM_END_NAMESPACE + +#endif //PROXY_OBJECT_H diff --git a/src/serviceframework/ipc/qremoteservicecontrol_dbus_p.cpp b/src/serviceframework/ipc/qremoteservicecontrol_dbus_p.cpp index e6188ed642..e89f6a6661 100644 --- a/src/serviceframework/ipc/qremoteservicecontrol_dbus_p.cpp +++ b/src/serviceframework/ipc/qremoteservicecontrol_dbus_p.cpp @@ -41,10 +41,8 @@ #include "qremoteservicecontrol_dbus_p.h" #include "ipcendpoint_p.h" -#include "objectendpoint_p.h" +#include "objectendpoint_dbus_p.h" -#include <QLocalServer> -#include <QLocalSocket> #include <QDataStream> #include <QTimer> @@ -61,8 +59,8 @@ class DBusEndPoint : public QServiceIpcEndPoint Q_OBJECT public: - DBusEndPoint(QDBusInterface* iface, int type, const QString &id, QObject* parent = 0) - : QServiceIpcEndPoint(parent), interface(iface), endType(type), sessionID(id) + DBusEndPoint(QDBusInterface* iface, int type, QObject* parent = 0) + : QServiceIpcEndPoint(parent), interface(iface), endType(type) { Q_ASSERT(interface); interface->setParent(this); @@ -73,6 +71,10 @@ public: { } + void testThis() + { + qDebug() << "HELLO"; + } protected: void flushPackage(const QServicePackage& package) @@ -81,26 +83,32 @@ protected: if (!connection->isConnected()) { qWarning() << "Cannot connect to DBus"; } - + QByteArray block; QDataStream out(&block, QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_6); out << package; - interface->asyncCall("writePackage", block, endType, sessionID); + packageId = package.d->messageId; + interface->asyncCall("writePackage", block, endType, packageId); } protected slots: void readPackage(const QByteArray &package, int type, const QString &id) { + + // Check the message id + QDataStream data(package); + QServicePackage pack; + data >> pack; + // Check that its of a client-server nature if (endType != type) { // Client to Server if (type != 0) { - sessionID = id; readIncoming(package); - // Server to Client } else { - if (id == sessionID) + // Server to Client + if (id == packageId) readIncoming(package); } } @@ -111,7 +119,7 @@ protected slots: QDataStream data(package); QServicePackage pack; data >> pack; - + incoming.enqueue(pack); emit readyRead(); } @@ -119,7 +127,7 @@ protected slots: private: QDBusInterface* interface; int endType; - QString sessionID; + QString packageId; }; class DBusSession: public QObject @@ -200,45 +208,28 @@ bool QRemoteServiceControlPrivate::createServiceEndPoint(const QString& ident) QList<QRemoteServiceIdentifier> list = iManager->allIdents(); if (list.size() > 0) { - QDBusConnection *connection = new QDBusConnection(QDBusConnection::sessionBus()); if (!connection->isConnected()) { qWarning() << "Cannot connect to DBus"; return 0; } - // Register all services in the InstanceManager registered by - // QRemoteServiceRegister - for (int i=0; i<list.size(); i++) { - QString serviceName = "com.nokia.qtmobility.sfw." + list[i].name; - - // TODO: do we want to always re-register services to dbus? - connection->unregisterService(serviceName); - - connection->registerService(serviceName); - if (!connection->registerService(serviceName)) { - qWarning() << "Cannot register service to DBus"; - return 0; - } - - // Registers all services on the DBUS, to be used for complete DBUS - // implementation - QUuid myID; - myID = QUuid(); - QObject* myObj = iManager->createObjectInstance(list[0], myID); + // MAYBE A FOR-LOOP FOR EACH SERVICENAME (ie DBUSExample, IPCExample) - QString path = "/" + list[i].interface + "/" + list[i].version; - path.replace(QString("."), QString("/")); + // TODO: do we want to always re-register services to dbus? + QString serviceName = "com.nokia.qtmobility.sfw." + list[0].name; + connection->unregisterService(serviceName); - connection->registerObject(path, myObj, QDBusConnection::ExportAllSlots); - + connection->registerService(serviceName); + if (!connection->registerService(serviceName)) { + qWarning() << "Cannot register service to DBus"; + return 0; } // Register our DBusSession server/client DBusSession *session = new DBusSession(); new DBusSessionAdaptor(session); - QString serviceName = "com.nokia.qtmobility.sfw." + list[0].name; QString path = "/" + list[0].interface + "/DBusSession"; path.replace(QString("."), QString("/")); connection->registerObject(path, session); @@ -248,8 +239,8 @@ bool QRemoteServiceControlPrivate::createServiceEndPoint(const QString& ident) qWarning() << "Cannot connect to remote service" << serviceName << path;; return 0; } - - DBusEndPoint* ipcEndPoint = new DBusEndPoint(iface, 0, QUuid::createUuid().toString()); + + DBusEndPoint* ipcEndPoint = new DBusEndPoint(iface, 0); ObjectEndPoint* endpoint = new ObjectEndPoint(ObjectEndPoint::Service, ipcEndPoint, this); return true; @@ -277,10 +268,12 @@ QObject* QRemoteServiceControlPrivate::proxyForService(const QRemoteServiceIdent return 0; } - DBusEndPoint* ipcEndPoint = new DBusEndPoint(inface, 1, QUuid::createUuid().toString()); + DBusEndPoint* ipcEndPoint = new DBusEndPoint(inface, 1); ObjectEndPoint* endPoint = new ObjectEndPoint(ObjectEndPoint::Client, ipcEndPoint); - + + //QObject* proxy QObject *proxy = endPoint->constructProxy(typeIdent); + QObject::connect(proxy, SIGNAL(destroyed()), endPoint, SLOT(deleteLater())); return proxy; } diff --git a/src/serviceframework/ipc/qservicepackage.cpp b/src/serviceframework/ipc/qservicepackage.cpp index fd9faea037..2534e929a8 100644 --- a/src/serviceframework/ipc/qservicepackage.cpp +++ b/src/serviceframework/ipc/qservicepackage.cpp @@ -77,6 +77,7 @@ QServicePackage QServicePackage::createResponse() const response.d = new QServicePackagePrivate(); response.d->packageType = d->packageType; response.d->messageId = d->messageId; + response.d->instanceId = d->instanceId; response.d->responseType = QServicePackage::Failed; return response; @@ -95,6 +96,7 @@ QDataStream &operator<<(QDataStream &out, const QServicePackage& package) out << (qint8) package.d->packageType; out << (qint8) package.d->responseType; out << package.d->messageId; + out << package.d->instanceId; out << package.d->typeId; out << package.d->payload; } @@ -130,6 +132,7 @@ QDataStream &operator>>(QDataStream &in, QServicePackage& package) in >> data; package.d->responseType = (QServicePackage::ResponseType) data; in >> package.d->messageId; + in >> package.d->instanceId; in >> package.d->typeId; in >> package.d->payload; } else { diff --git a/src/serviceframework/ipc/qservicepackage_p.h b/src/serviceframework/ipc/qservicepackage_p.h index a6e1d77a59..c12147ec09 100644 --- a/src/serviceframework/ipc/qservicepackage_p.h +++ b/src/serviceframework/ipc/qservicepackage_p.h @@ -80,7 +80,7 @@ public: QServicePackage createResponse() const; bool isValid() const; - + QExplicitlySharedDataPointer<QServicePackagePrivate> d; #ifndef QT_NO_DATASTREAM @@ -104,7 +104,7 @@ public: QServicePackagePrivate() : packageType(QServicePackage::ObjectCreation), typeId(QRemoteServiceIdentifier()), payload(QVariant()), - messageId(QUuid()), responseType(QServicePackage::NotAResponse) + messageId(QUuid()), instanceId(QUuid()), responseType(QServicePackage::NotAResponse) { } @@ -112,12 +112,14 @@ public: QRemoteServiceIdentifier typeId; QVariant payload; QUuid messageId; + QUuid instanceId; QServicePackage::ResponseType responseType; virtual void clean() { packageType = QServicePackage::ObjectCreation; messageId = QUuid(); + instanceId = QUuid(); payload = QVariant(); typeId = QRemoteServiceIdentifier(); responseType = QServicePackage::NotAResponse; diff --git a/tests/auto/qservicemanager_ipc/client/tst_qservicemanager_ipc.cpp b/tests/auto/qservicemanager_ipc/client/tst_qservicemanager_ipc.cpp index 7e1842fc1b..ac306a28a6 100644 --- a/tests/auto/qservicemanager_ipc/client/tst_qservicemanager_ipc.cpp +++ b/tests/auto/qservicemanager_ipc/client/tst_qservicemanager_ipc.cpp @@ -821,8 +821,8 @@ void tst_QServiceManager_IPC::testInvokableFunctions() #ifdef UNIQUE_TESTS void tst_QServiceManager_IPC::testSignalling() { - QSignalSpy spy(serviceUnique, SIGNAL(signalWithIntParam(int))); - QMetaObject::invokeMethod(serviceUnique, "triggerSignalWithIntParam"); + QSignalSpy spy(serviceShared, SIGNAL(signalWithIntParam(int))); + QMetaObject::invokeMethod(serviceShared, "triggerSignalWithIntParam"); QTRY_VERIFY(spy.count() == 1); QCOMPARE(spy.at(0).at(0).toInt(), 5); @@ -938,13 +938,13 @@ void tst_QServiceManager_IPC::testSlotInvokation() #ifdef UNIQUE_TESTS void tst_QServiceManager_IPC::testIpcFailure() -{ - QMetaObject::invokeMethod(serviceUnique, "testIpcFailure"); - int i = 0; - while (!ipcfailure && i++ < 50) - QTest::qWait(50); - - QVERIFY(ipcfailure); + { + QMetaObject::invokeMethod(serviceUnique, "testIpcFailure"); + int i = 0; + while (!ipcfailure && i++ < 50) + QTest::qWait(50); + + QVERIFY(ipcfailure); // TODO restart the connection // service = manager->loadInterface("com.nokia.qt.ipcunittest"); diff --git a/tests/auto/qservicemanager_ipc/service/main.cpp b/tests/auto/qservicemanager_ipc/service/main.cpp index 7651c90f85..230cef72ab 100644 --- a/tests/auto/qservicemanager_ipc/service/main.cpp +++ b/tests/auto/qservicemanager_ipc/service/main.cpp @@ -114,6 +114,7 @@ public: Q_INVOKABLE void setConfirmationHash(uint hash) { + qDebug() << "I DID IT CUNT"; m_hash = hash; } |