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);
57 QMultiHash<int, QByteArray> sigNames;
60 SignalRelay::SignalRelay(SignalProxy* parent, QObject* source)
65 QObject::connect(source, SIGNAL(destroyed()), parent, SLOT(detachSender()));
68 int SignalRelay::qt_metacall(QMetaObject::Call _c, int _id, void **_a) {
69 _id = QObject::qt_metacall(_c, _id, _a);
72 if(_c == QMetaObject::InvokeMetaMethod) {
73 if(sigNames.contains(_id)) {
74 const QList<int> &argTypes = proxy->argTypes(caller, _id);
76 int n = argTypes.size();
77 for(int i=0; i<n; i++)
78 params.append(QVariant(argTypes[i], _a[i+1]));
79 QMultiHash<int, QByteArray>::const_iterator funcIter = sigNames.constFind(_id);
80 while(funcIter != sigNames.constEnd() && funcIter.key() == _id) {
81 proxy->call(funcIter.value(), params);
90 void SignalRelay::attachSignal(int methodId, const QByteArray &func) {
91 // we ride without safetybelts here... all checking for valid method etc pp has to be done by the caller
92 // all connected methodIds are offset by the standard methodCount of QObject
93 if(!sigNames.contains(methodId))
94 QMetaObject::connect(caller, methodId, this, QObject::staticMetaObject.methodCount() + methodId);
98 fn = QMetaObject::normalizedSignature(func);
100 fn = QByteArray("2") + caller->metaObject()->method(methodId).signature();
102 sigNames.insert(methodId, fn);
104 // ====================
106 // ====================
109 // ====================
111 // ====================
112 SignalProxy::SignalProxy(QObject* parent)
119 SignalProxy::SignalProxy(ProxyMode mode, QObject* parent)
126 SignalProxy::SignalProxy(ProxyMode mode, QIODevice* device, QObject* parent)
134 SignalProxy::~SignalProxy() {
135 QList<QObject*> senders = _relayHash.keys();
136 foreach(QObject* sender, senders)
137 detachObject(sender);
140 void SignalProxy::setProxyMode(ProxyMode mode) {
141 foreach(QIODevice* peer, _peerByteCount.keys()) {
143 qWarning() << "SignalProxy: Cannot change proxy mode while connected";
151 SignalProxy::ProxyMode SignalProxy::proxyMode() const {
155 bool SignalProxy::maxPeersReached() {
156 if(_peerByteCount.empty())
158 if(proxyMode() != Server)
160 if(_maxClients == -1)
163 return (_maxClients <= _peerByteCount.count());
166 bool SignalProxy::addPeer(QIODevice* iodev) {
170 if(_peerByteCount.contains(iodev))
173 if(maxPeersReached()) {
174 qWarning("SignalProxy: max peer count reached");
179 qWarning("SignalProxy::the device you passed is not open!");
181 connect(iodev, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
182 connect(iodev, SIGNAL(readyRead()), this, SLOT(dataAvailable()));
184 QAbstractSocket* sock = qobject_cast<QAbstractSocket*>(iodev);
186 connect(sock, SIGNAL(disconnected()), this, SLOT(removePeerBySender()));
189 _peerByteCount[iodev] = 0;
191 if(_peerByteCount.count() == 1)
197 void SignalProxy::removePeerBySender() {
198 // OK we're brutal here... but since it's a private slot we know what we've got connected to it...
199 QIODevice *ioDev = (QIODevice *)(sender());
203 void SignalProxy::removePeer(QIODevice* iodev) {
204 if(_peerByteCount.isEmpty()) {
205 qWarning() << "SignalProxy: No peers in use!";
209 if(proxyMode() == Server && !iodev) {
211 QList<QIODevice *> peers = _peerByteCount.keys();
212 foreach(QIODevice *peer, peers)
216 if(proxyMode() != Server && !iodev)
217 iodev = _peerByteCount.keys().first();
221 if(!_peerByteCount.contains(iodev)) {
222 qWarning() << "SignalProxy: unknown QIODevice" << iodev;
229 if(readDataFromDevice(iodev, _peerByteCount[iodev], var))
230 receivePeerSignal(var);
234 _peerByteCount.remove(iodev);
236 disconnect(iodev, 0, this, 0);
237 emit peerRemoved(iodev);
239 if(_peerByteCount.isEmpty())
243 void SignalProxy::setArgTypes(QObject* obj, int methodId) {
244 QList<QByteArray> p = obj->metaObject()->method(methodId).parameterTypes();
247 for(int i=0; i<ct; i++)
248 argTypes.append(QMetaType::type(p.value(i)));
250 const QByteArray &className(obj->metaObject()->className());
251 Q_ASSERT(_classInfo.contains(className));
252 Q_ASSERT(!_classInfo[className]->argTypes.contains(methodId));
253 _classInfo[className]->argTypes[methodId] = argTypes;
256 const QList<int> &SignalProxy::argTypes(QObject *obj, int methodId) {
257 const QByteArray &className(obj->metaObject()->className());
258 Q_ASSERT(_classInfo.contains(className));
259 if(!_classInfo[className]->argTypes.contains(methodId))
260 setArgTypes(obj, methodId);
261 return _classInfo[className]->argTypes[methodId];
264 void SignalProxy::setMethodName(QObject *obj, int methodId) {
265 const QByteArray &className(obj->metaObject()->className());
266 QByteArray method = obj->metaObject()->method(methodId).signature();
267 method = method.left(method.indexOf('('));
269 Q_ASSERT(_classInfo.contains(className));
270 Q_ASSERT(!_classInfo[className]->methodNames.contains(methodId));
271 _classInfo[className]->methodNames[methodId] = method;
274 const QByteArray &SignalProxy::methodName(QObject *obj, int methodId) {
275 QByteArray className(obj->metaObject()->className());
276 Q_ASSERT(_classInfo.contains(className));
277 if(!_classInfo[className]->methodNames.contains(methodId))
278 setMethodName(obj, methodId);
279 return _classInfo[className]->methodNames[methodId];
283 void SignalProxy::createClassInfo(QObject *obj) {
284 QByteArray className(obj->metaObject()->className());
285 if(!_classInfo.contains(className))
286 _classInfo[className] = new ClassInfo();
289 bool SignalProxy::attachSignal(QObject* sender, const char* signal, const QByteArray& sigName) {
290 const QMetaObject* meta = sender->metaObject();
291 QByteArray sig(meta->normalizedSignature(signal).mid(1));
292 int methodId = meta->indexOfMethod(sig.constData());
293 if(methodId == -1 || meta->method(methodId).methodType() != QMetaMethod::Signal) {
294 qWarning() << "SignalProxy::attachSignal(): No such signal" << signal;
298 createClassInfo(sender);
301 if(_relayHash.contains(sender))
302 relay = _relayHash[sender];
304 relay = _relayHash[sender] = new SignalRelay(this, sender);
306 relay->attachSignal(methodId, sigName);
312 bool SignalProxy::attachSlot(const QByteArray& sigName, QObject* recv, const char* slot) {
313 const QMetaObject* meta = recv->metaObject();
314 int methodId = meta->indexOfMethod(meta->normalizedSignature(slot).mid(1));
315 if(methodId == -1 || meta->method(methodId).methodType() == QMetaMethod::Method) {
316 qWarning() << "SignalProxy::attachSlot(): No such slot" << slot;
320 createClassInfo(recv);
322 QByteArray funcName = QMetaObject::normalizedSignature(sigName.constData());
323 _attachedSlots.insert(funcName, qMakePair(recv, methodId));
325 QObject::disconnect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
326 QObject::connect(recv, SIGNAL(destroyed()), this, SLOT(detachSender()));
331 void SignalProxy::detachSender() {
332 // this is a slot so we can bypass the QueuedConnection
333 _detachSignals(sender());
334 _detachSlots(sender());
337 // detachObject/Signals/Slots() can be called as a result of an incoming call
338 // this might destroy our the iterator used for delivery
339 // thus we wrap the actual disconnection by using QueuedConnections
340 void SignalProxy::detachObject(QObject* obj) {
345 void SignalProxy::detachSignals(QObject* sender) {
346 QMetaObject::invokeMethod(this, "_detachSignals",
347 Qt::QueuedConnection,
348 Q_ARG(QObject*, sender));
351 void SignalProxy::_detachSignals(QObject* sender) {
352 if(!_relayHash.contains(sender))
354 _relayHash.take(sender)->deleteLater();
357 void SignalProxy::detachSlots(QObject* receiver) {
358 QMetaObject::invokeMethod(this, "_detachSlots",
359 Qt::QueuedConnection,
360 Q_ARG(QObject*, receiver));
363 void SignalProxy::_detachSlots(QObject* receiver) {
364 SlotHash::iterator slotIter = _attachedSlots.begin();
365 while(slotIter != _attachedSlots.end()) {
366 if(slotIter.value().first == receiver) {
367 slotIter = _attachedSlots.erase(slotIter);
374 void SignalProxy::call(const char* signal, QVariant p1, QVariant p2, QVariant p3, QVariant p4, QVariant p5, QVariant p6, QVariant p7, QVariant p8, QVariant p9) {
375 QByteArray func = QMetaObject::normalizedSignature(signal);
377 params << p1 << p2 << p3 << p4 << p5 << p6 << p7 << p8 << p9;
381 void SignalProxy::call(const QByteArray &funcName, const QVariantList ¶ms) {
382 QVariantList packedFunc;
383 packedFunc << funcName;
384 packedFunc << params;
385 foreach(QIODevice* dev, _peerByteCount.keys())
386 writeDataToDevice(dev, QVariant(packedFunc));
389 void SignalProxy::receivePeerSignal(const QVariant &packedFunc) {
390 QVariantList params(packedFunc.toList());
391 QByteArray funcName = params.takeFirst().toByteArray();
392 int numParams, methodId;
395 SlotHash::const_iterator slot = _attachedSlots.constFind(funcName);
396 while(slot != _attachedSlots.constEnd() && slot.key() == funcName) {
397 receiver = (*slot).first;
398 methodId = (*slot).second;
399 numParams = argTypes(receiver, methodId).count();
400 QGenericArgument args[9];
401 for(int i = 0; i < numParams; i++)
402 args[i] = QGenericArgument(params[i].typeName(), params[i].constData());
403 if(!QMetaObject::invokeMethod(receiver, methodName(receiver, methodId),
404 args[0], args[1], args[2], args[3], args[4], args[5], args[6], args[7], args[8])) {
405 qWarning("SignalProxy::receivePeerSignal(): invokeMethod for \"%s\" failed ", methodName(receiver, methodId).constData());
411 void SignalProxy::dataAvailable() {
412 QIODevice* ioDev = qobject_cast<QIODevice* >(sender());
414 if(!_peerByteCount.contains(ioDev)) {
415 qWarning() << "SignalProxy: Unrecognized client object connected to dataAvailable";
419 while(readDataFromDevice(ioDev, _peerByteCount[ioDev], var))
420 receivePeerSignal(var);
424 void SignalProxy::writeDataToDevice(QIODevice *dev, const QVariant &item) {
425 QAbstractSocket* sock = qobject_cast<QAbstractSocket*>(dev);
426 if(!dev->isOpen() || (sock && sock->state()!=QAbstractSocket::ConnectedState)) {
427 qWarning("SignalProxy: Can't call on a closed device");
431 QDataStream out(&block, QIODevice::WriteOnly);
432 out.setVersion(QDataStream::Qt_4_2);
433 out << (quint32)0 << item;
434 out.device()->seek(0);
435 out << (quint32)(block.size() - sizeof(quint32));
439 bool SignalProxy::readDataFromDevice(QIODevice *dev, quint32 &blockSize, QVariant &item) {
441 in.setVersion(QDataStream::Qt_4_2);
444 if(dev->bytesAvailable() < (int)sizeof(quint32)) return false;
448 if(dev->bytesAvailable() < blockSize)