1 /***************************************************************************
2 * Copyright (C) 2005-07 by the Quassel IRC Team *
3 * devel@quassel-irc.org *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
15 * You should have received a copy of the GNU General Public License *
16 * along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
19 ***************************************************************************
20 * SignalProxy has been inspired by QxtRPCPeer, part of libqxt, *
21 * the Qt eXTension Library <http://www.libqxt.org>. We would like to *
22 * thank Arvid "aep" Picciani and Adam "ahigerd" Higerd for providing *
23 * QxtRPCPeer, valuable input and the genius idea to (ab)use Qt's *
24 * Meta Object System for transmitting signals over the network. *
26 * To make contribution back into libqxt possible, redistribution and *
27 * modification of this file is additionally allowed under the terms of *
28 * the Common Public License, version 1.0, as published by IBM. *
29 ***************************************************************************/
31 #include "signalproxy.h"
34 #include <QAbstractSocket>
40 #include <QMetaMethod>
42 class SignalRelay: public QObject {
44 /* Q_OBJECT is not necessary or even allowed, because we implement
45 qt_metacall ourselves (and don't use any other features of the meta
49 SignalRelay(SignalProxy* parent, QObject* source);
50 int qt_metacall(QMetaObject::Call _c, int _id, void **_a);
52 void attachSignal(int methodId, const QByteArray &func);
59 QMultiHash<int, QByteArray> sigNames;
62 SignalRelay::SignalRelay(SignalProxy* parent, QObject* source)
67 QObject::connect(source, SIGNAL(destroyed()), parent, SLOT(detachSender()));
70 int SignalRelay::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
71 _id = QObject::qt_metacall(_c, _id, _a);
74 if(_c == QMetaObject::InvokeMetaMethod) {
75 if(sigNames.contains(_id)) {
76 const QList<int> &argTypes = proxy->argTypes(caller, _id);
78 int n = argTypes.size();
79 for(int i=0; i<n; i++)
80 params.append(QVariant(argTypes[i], _a[i+1]));
81 QMultiHash<int, QByteArray>::const_iterator funcIter = sigNames.constFind(_id);
82 while(funcIter != sigNames.constEnd() && funcIter.key() == _id) {
83 proxy->call(funcIter.value(), params);
92 int SignalRelay::sigCount() const {
93 // only for debuging purpose
94 return sigNames.count();
97 void SignalRelay::attachSignal(int methodId, const QByteArray &func) {
98 // we ride without safetybelts here... all checking for valid method etc pp has to be done by the caller
99 // all connected methodIds are offset by the standard methodCount of QObject
100 if(!sigNames.contains(methodId))
101 QMetaObject::connect(caller, methodId, this, QObject::staticMetaObject.methodCount() + methodId);
104 if(!func.isEmpty()) {
105 fn = QMetaObject::normalizedSignature(func);
107 fn = QByteArray("2") + caller->metaObject()->method(methodId).signature();
109 sigNames.insert(methodId, fn);
111 // ====================
113 // ====================
116 // ====================
118 // ====================
119 SignalProxy::SignalProxy(QObject* parent)
125 SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
131 SignalProxy::SignalProxy(ProxyMode mode, QIODevice* device, QObject* parent)
138 SignalProxy::~SignalProxy() {
139 QList<QObject*> senders = _relayHash.keys();
140 foreach(QObject* sender, senders)
141 detachObject(sender);
144 void SignalProxy::setProxyMode(ProxyMode mode) {
145 foreach(QIODevice* peer, _peerByteCount.keys()) {
147 qWarning() << "SignalProxy: Cannot change proxy mode while connected";
155 SignalProxy::ProxyMode SignalProxy::proxyMode() const {
159 bool SignalProxy::addPeer(QIODevice* iodev) {
163 if(_peerByteCount.contains(iodev))
166 if(proxyMode() == Client && !_peerByteCount.isEmpty()) {
167 qWarning("SignalProxy: only one peer allowed in client mode!");
172 qWarning("SignalProxy::the device you passed is not open!");
174 connect(iodev, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
175 connect(iodev, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
177 QAbstractSocket* sock = qobject_cast<QAbstractSocket*>(iodev);
179 connect(sock, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
182 _peerByteCount[iodev] = 0;
184 if(_peerByteCount.count() == 1)
190 void SignalProxy::removePeerBySender() {
191 // OK we're brutal here... but since it's a private slot we know what we've got connected to it...
192 QIODevice *ioDev = (QIODevice *)(sender());
196 void SignalProxy::removePeer(QIODevice* iodev) {
197 if(_peerByteCount.isEmpty()) {
198 qWarning() << "SignalProxy: No peers in use!";
202 if(proxyMode() == Server && !iodev) {
204 QList<QIODevice *> peers = _peerByteCount.keys();
205 foreach(QIODevice *peer, peers)
209 if(proxyMode() != Server && !iodev)
210 iodev = _peerByteCount.keys().first();
214 if(!_peerByteCount.contains(iodev)) {
215 qWarning() << "SignalProxy: unknown QIODevice" << iodev;
222 if(readDataFromDevice(iodev, _peerByteCount[iodev], var))
223 receivePeerSignal(var);
227 _peerByteCount.remove(iodev);
229 disconnect(iodev, 0, this, 0);
230 emit peerRemoved(iodev);
232 if(_peerByteCount.isEmpty())
236 void SignalProxy::setArgTypes(QObject* obj, int methodId) {
237 QList<QByteArray> p = obj->metaObject()->method(methodId).parameterTypes();
240 for(int i=0; i<ct; i++)
241 argTypes.append(QMetaType::type(p.value(i)));
243 const QByteArray &className(obj->metaObject()->className());
244 Q_ASSERT(_classInfo.contains(className));
245 Q_ASSERT(!_classInfo[className]->argTypes.contains(methodId));
246 _classInfo[className]->argTypes[methodId] = argTypes;
249 const QList<int> &SignalProxy::argTypes(QObject *obj, int methodId) {
250 const QByteArray &className(obj->metaObject()->className());
251 Q_ASSERT(_classInfo.contains(className));
252 if(!_classInfo[className]->argTypes.contains(methodId))
253 setArgTypes(obj, methodId);
254 return _classInfo[className]->argTypes[methodId];
257 void SignalProxy::setMethodName(QObject *obj, int methodId) {
258 const QByteArray &className(obj->metaObject()->className());
259 QByteArray method = obj->metaObject()->method(methodId).signature();
260 method = method.left(method.indexOf('('));
262 Q_ASSERT(_classInfo.contains(className));
263 Q_ASSERT(!_classInfo[className]->methodNames.contains(methodId));
264 _classInfo[className]->methodNames[methodId] = method;
267 const QByteArray &SignalProxy::methodName(QObject *obj, int methodId) {
268 QByteArray className(obj->metaObject()->className());
269 Q_ASSERT(_classInfo.contains(className));
270 if(!_classInfo[className]->methodNames.contains(methodId))
271 setMethodName(obj, methodId);
272 return _classInfo[className]->methodNames[methodId];
276 void SignalProxy::createClassInfo(QObject *obj) {
277 QByteArray className(obj->metaObject()->className());
278 if(!_classInfo.contains(className))
279 _classInfo[className] = new ClassInfo();
282 bool SignalProxy::attachSignal(QObject* sender, const char* signal, const QByteArray& sigName) {
283 const QMetaObject* meta = sender->metaObject();
284 QByteArray sig(meta->normalizedSignature(signal).mid(1));
285 int methodId = meta->indexOfMethod(sig.constData());
286 if(methodId == -1 || meta->method(methodId).methodType() != QMetaMethod::Signal) {
287 qWarning() << "SignalProxy::attachSignal(): No such signal" << signal;
291 createClassInfo(sender);
294 if(_relayHash.contains(sender))
295 relay = _relayHash[sender];
297 relay = _relayHash[sender] = new SignalRelay(this, sender);
299 relay->attachSignal(methodId, sigName);
305 bool SignalProxy::attachSlot(const QByteArray& sigName, QObject* recv, const char* slot) {
306 const QMetaObject* meta = recv->metaObject();
307 int methodId = meta->indexOfMethod(meta->normalizedSignature(slot).mid(1));
308 if(methodId == -1 || meta->method(methodId).methodType() == QMetaMethod::Method) {
309 qWarning() << "SignalProxy::attachSlot(): No such slot" << slot;
313 createClassInfo(recv);
315 QByteArray funcName = QMetaObject::normalizedSignature(sigName.constData());
316 _attachedSlots.insert(funcName, qMakePair(recv, methodId));
318 QObject::disconnect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
319 QObject::connect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
324 void SignalProxy::detachSender() {
325 // this is a slot so we can bypass the QueuedConnection
326 // and if someone is forcing direct connection in a multithreaded environment he's just nuts...
327 _detachSignals(sender());
328 _detachSlots(sender());
331 // detachObject/Signals/Slots() can be called as a result of an incoming call
332 // this might destroy our the iterator used for delivery
333 // thus we wrap the actual disconnection by using QueuedConnections
334 void SignalProxy::detachObject(QObject* obj) {
339 void SignalProxy::detachSignals(QObject* sender) {
340 QMetaObject::invokeMethod(this, "_detachSignals",
341 Qt::QueuedConnection,
342 Q_ARG(QObject*, sender));
345 void SignalProxy::detachSlots(QObject* receiver) {
346 QMetaObject::invokeMethod(this, "_detachSlots",
347 Qt::QueuedConnection,
348 Q_ARG(QObject*, receiver));
351 void SignalProxy::_detachSignals(QObject* sender) {
352 if(!_relayHash.contains(sender))
354 _relayHash.take(sender)->deleteLater();
357 void SignalProxy::_detachSlots(QObject* receiver) {
358 SlotHash::iterator slotIter = _attachedSlots.begin();
359 while(slotIter != _attachedSlots.end()) {
360 if(slotIter.value().first == receiver) {
361 slotIter = _attachedSlots.erase(slotIter);
367 void SignalProxy::call(const char* signal, QVariant p1, QVariant p2, QVariant p3, QVariant p4, QVariant p5, QVariant p6, QVariant p7, QVariant p8, QVariant p9) {
368 QByteArray func = QMetaObject::normalizedSignature(signal);
370 params << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9;
374 void SignalProxy::call(const QByteArray &funcName, const QVariantList ¶ms) {
375 QVariantList packedFunc;
376 packedFunc << funcName;
377 packedFunc << params;
378 foreach(QIODevice* dev, _peerByteCount.keys())
379 writeDataToDevice(dev, QVariant(packedFunc));
382 void SignalProxy::receivePeerSignal(const QVariant &packedFunc) {
383 QVariantList params(packedFunc.toList());
384 QByteArray funcName = params.takeFirst().toByteArray();
385 int numParams, methodId;
388 SlotHash::const_iterator slot = _attachedSlots.constFind(funcName);
389 while(slot != _attachedSlots.constEnd() && slot.key() == funcName) {
390 receiver = (*slot).first;
391 methodId = (*slot).second;
392 numParams = argTypes(receiver, methodId).count();
393 QGenericArgument args[9];
394 for(int i = 0; i < numParams; i++)
395 args[i] = QGenericArgument(params[i].typeName(), params[i].constData());
396 if(!QMetaObject::invokeMethod(receiver, methodName(receiver, methodId),
397 args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])) {
398 qWarning("SignalProxy::receivePeerSignal(): invokeMethod for \"%s\" failed ", methodName(receiver, methodId).constData());
404 void SignalProxy::dataAvailable() {
405 // yet again. it's a private slot. no need for checks.
406 QIODevice* ioDev = qobject_cast<QIODevice* >(sender());
408 while(readDataFromDevice(ioDev, _peerByteCount[ioDev], var))
409 receivePeerSignal(var);
412 void SignalProxy::writeDataToDevice(QIODevice *dev, const QVariant &item) {
413 QAbstractSocket* sock = qobject_cast<QAbstractSocket*>(dev);
414 if(!dev->isOpen() || (sock && sock->state()!=QAbstractSocket::ConnectedState)) {
415 qWarning("SignalProxy: Can't call on a closed device");
419 QDataStream out(&block, QIODevice::WriteOnly);
420 out.setVersion(QDataStream::Qt_4_2);
421 out << (quint32)0 << item;
422 out.device()->seek(0);
423 out << (quint32)(block.size() - sizeof(quint32));
427 bool SignalProxy::readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) {
429 in.setVersion(QDataStream::Qt_4_2);
432 if(dev->bytesAvailable() < (int)sizeof(quint32)) return false;
436 if(dev->bytesAvailable() < blockSize)
443 void SignalProxy::dumpProxyStats() {
445 if(proxyMode() == Server)
451 foreach(SignalRelay *relay, _relayHash.values())
452 sigCount += relay->sigCount();
455 qDebug() << " Proxy Mode:" << mode;
456 qDebug() << "attached sending Objects:" << _relayHash.count();
457 qDebug() << " Number of Signals:" << sigCount;
458 qDebug() << " attached Slots:" << _attachedSlots.count();
459 qDebug() << "number of Classes cached:" << _classInfo.count();