1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 Intel Corporation.
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtDBus module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41#include "qdbusabstractadaptor.h"
42#include "qdbusabstractadaptor_p.h"
43
44#include <QtCore/qcoreapplication.h>
45#include <QtCore/qmetaobject.h>
46#include <QtCore/qset.h>
47#include <QtCore/qtimer.h>
48#include <QtCore/qthread.h>
49
50#include "qdbusconnection.h"
51
52#include "qdbusconnection_p.h" // for qDBusParametersForMethod
53#include "qdbusmetatype_p.h"
54
55#include <algorithm>
56
57#ifndef QT_NO_DBUS
58
59QT_BEGIN_NAMESPACE
60
61static int cachedRelaySlotMethodIndex = -1;
62
63int QDBusAdaptorConnector::relaySlotMethodIndex()
64{
65 if (cachedRelaySlotMethodIndex == -1) {
66 cachedRelaySlotMethodIndex = staticMetaObject.indexOfMethod("relaySlot()");
67 Q_ASSERT(cachedRelaySlotMethodIndex != -1);
68 }
69 return cachedRelaySlotMethodIndex;
70}
71
72QDBusAdaptorConnector *qDBusFindAdaptorConnector(QObject *obj)
73{
74 if (!obj)
75 return nullptr;
76 const QObjectList &children = obj->children();
77 QObjectList::ConstIterator it = children.constBegin();
78 QObjectList::ConstIterator end = children.constEnd();
79 for ( ; it != end; ++it) {
80 QDBusAdaptorConnector *connector = qobject_cast<QDBusAdaptorConnector *>(*it);
81 if (connector) {
82 connector->polish();
83 return connector;
84 }
85 }
86 return nullptr;
87}
88
89QDBusAdaptorConnector *qDBusFindAdaptorConnector(QDBusAbstractAdaptor *adaptor)
90{
91 return qDBusFindAdaptorConnector(adaptor->parent());
92}
93
94QDBusAdaptorConnector *qDBusCreateAdaptorConnector(QObject *obj)
95{
96 QDBusAdaptorConnector *connector = qDBusFindAdaptorConnector(obj);
97 if (connector)
98 return connector;
99 return new QDBusAdaptorConnector(obj);
100}
101
102QString QDBusAbstractAdaptorPrivate::retrieveIntrospectionXml(QDBusAbstractAdaptor *adaptor)
103{
104 return adaptor->d_func()->xml;
105}
106
107void QDBusAbstractAdaptorPrivate::saveIntrospectionXml(QDBusAbstractAdaptor *adaptor,
108 const QString &xml)
109{
110 adaptor->d_func()->xml = xml;
111}
112
113/*!
114 \class QDBusAbstractAdaptor
115 \inmodule QtDBus
116 \since 4.2
117
118 \brief The QDBusAbstractAdaptor class is the base class of D-Bus adaptor classes.
119
120 The QDBusAbstractAdaptor class is the starting point for all objects intending to provide
121 interfaces to the external world using D-Bus. This is accomplished by attaching a one or more
122 classes derived from QDBusAbstractAdaptor to a normal QObject and then registering that QObject
123 with QDBusConnection::registerObject. QDBusAbstractAdaptor objects are intended to be
124 light-weight wrappers, mostly just relaying calls into the real object (its parent) and the
125 signals from it.
126
127 Each QDBusAbstractAdaptor-derived class should define the D-Bus interface it is implementing
128 using the Q_CLASSINFO macro in the class definition. Note that only one interface can be
129 exposed in this way.
130
131 QDBusAbstractAdaptor uses the standard QObject mechanism of signals, slots and properties to
132 determine what signals, methods and properties to export to the bus. Any signal emitted by
133 QDBusAbstractAdaptor-derived classes will be automatically be relayed through any D-Bus
134 connections the object is registered on.
135
136 Classes derived from QDBusAbstractAdaptor must be created on the heap using the \a new operator
137 and must not be deleted by the user (they will be deleted automatically when the object they are
138 connected to is also deleted).
139
140 \sa {usingadaptors.html}{Using adaptors}, QDBusConnection
141*/
142
143/*!
144 Constructs a QDBusAbstractAdaptor with \a obj as the parent object.
145*/
146QDBusAbstractAdaptor::QDBusAbstractAdaptor(QObject* obj)
147 : QObject(*new QDBusAbstractAdaptorPrivate, obj)
148{
149 QDBusAdaptorConnector *connector = qDBusCreateAdaptorConnector(obj);
150
151 connector->waitingForPolish = true;
152 QMetaObject::invokeMethod(connector, "polish", Qt::QueuedConnection);
153}
154
155/*!
156 Destroys the adaptor.
157
158 \warning Adaptors are destroyed automatically when the real object they refer to is
159 destroyed. Do not delete the adaptors yourself.
160*/
161QDBusAbstractAdaptor::~QDBusAbstractAdaptor()
162{
163}
164
165/*!
166 Toggles automatic signal relaying from the real object (see object()).
167
168 Automatic signal relaying consists of signal-to-signal connection of the signals on the parent
169 that have the exact same method signatue in both classes.
170
171 If \a enable is set to true, connect the signals; if set to false, disconnect all signals.
172*/
173void QDBusAbstractAdaptor::setAutoRelaySignals(bool enable)
174{
175 const QMetaObject *us = metaObject();
176 const QMetaObject *them = parent()->metaObject();
177 bool connected = false;
178 for (int idx = staticMetaObject.methodCount(); idx < us->methodCount(); ++idx) {
179 QMetaMethod mm = us->method(idx);
180
181 if (mm.methodType() != QMetaMethod::Signal)
182 continue;
183
184 // try to connect/disconnect to a signal on the parent that has the same method signature
185 QByteArray sig = QMetaObject::normalizedSignature(mm.methodSignature().constData());
186 if (them->indexOfSignal(sig) == -1)
187 continue;
188 sig.prepend(QSIGNAL_CODE + '0');
189 parent()->disconnect(sig, this, sig);
190 if (enable)
191 connected = connect(parent(), sig, sig) || connected;
192 }
193 d_func()->autoRelaySignals = connected;
194}
195
196/*!
197 Returns \c true if automatic signal relaying from the real object (see object()) is enabled,
198 otherwiser returns \c false.
199
200 \sa setAutoRelaySignals()
201*/
202bool QDBusAbstractAdaptor::autoRelaySignals() const
203{
204 return d_func()->autoRelaySignals;
205}
206
207QDBusAdaptorConnector::QDBusAdaptorConnector(QObject *obj)
208 : QObject(obj), waitingForPolish(false)
209{
210}
211
212QDBusAdaptorConnector::~QDBusAdaptorConnector()
213{
214}
215
216void QDBusAdaptorConnector::addAdaptor(QDBusAbstractAdaptor *adaptor)
217{
218 // find the interface name
219 const QMetaObject *mo = adaptor->metaObject();
220 int ciid = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTERFACE);
221 if (ciid != -1) {
222 QMetaClassInfo mci = mo->classInfo(ciid);
223 if (*mci.value()) {
224 // find out if this interface exists first
225 const char *interface = mci.value();
226 AdaptorMap::Iterator it = std::lower_bound(adaptors.begin(), adaptors.end(),
227 QByteArray(interface));
228 if (it != adaptors.end() && qstrcmp(interface, it->interface) == 0) {
229 // exists. Replace it (though it's probably the same)
230 if (it->adaptor != adaptor) {
231 // reconnect the signals
232 disconnectAllSignals(it->adaptor);
233 connectAllSignals(adaptor);
234 }
235 it->adaptor = adaptor;
236 } else {
237 // create a new one
238 AdaptorData entry;
239 entry.interface = interface;
240 entry.adaptor = adaptor;
241 adaptors << entry;
242
243 // connect the adaptor's signals to our relaySlot slot
244 connectAllSignals(adaptor);
245 }
246 }
247 }
248}
249
250void QDBusAdaptorConnector::disconnectAllSignals(QObject *obj)
251{
252 QMetaObject::disconnect(obj, -1, this, relaySlotMethodIndex());
253}
254
255void QDBusAdaptorConnector::connectAllSignals(QObject *obj)
256{
257 QMetaObject::connect(obj, -1, this, relaySlotMethodIndex(), Qt::DirectConnection);
258}
259
260void QDBusAdaptorConnector::polish()
261{
262 if (!waitingForPolish)
263 return; // avoid working multiple times if multiple adaptors were added
264
265 waitingForPolish = false;
266 const QObjectList &objs = parent()->children();
267 QObjectList::ConstIterator it = objs.constBegin();
268 QObjectList::ConstIterator end = objs.constEnd();
269 for ( ; it != end; ++it) {
270 QDBusAbstractAdaptor *adaptor = qobject_cast<QDBusAbstractAdaptor *>(*it);
271 if (adaptor)
272 addAdaptor(adaptor);
273 }
274
275 // sort the adaptor list
276 std::sort(adaptors.begin(), adaptors.end());
277}
278
279void QDBusAdaptorConnector::relaySlot(QMethodRawArguments argv)
280{
281 QObject *sndr = sender();
282 if (Q_LIKELY(sndr)) {
283 relay(sndr, senderSignalIndex(), argv.arguments);
284 } else {
285 qWarning("QtDBus: cannot relay signals from parent %s(%p \"%s\") unless they are emitted in the object's thread %s(%p \"%s\"). "
286 "Current thread is %s(%p \"%s\").",
287 parent()->metaObject()->className(), parent(), qPrintable(parent()->objectName()),
288 parent()->thread()->metaObject()->className(), parent()->thread(), qPrintable(parent()->thread()->objectName()),
289 QThread::currentThread()->metaObject()->className(), QThread::currentThread(), qPrintable(QThread::currentThread()->objectName()));
290 }
291}
292
293void QDBusAdaptorConnector::relay(QObject *senderObj, int lastSignalIdx, void **argv)
294{
295 if (lastSignalIdx < QObject::staticMetaObject.methodCount())
296 // QObject signal (destroyed(QObject *)) -- ignore
297 return;
298
299 const QMetaObject *senderMetaObject = senderObj->metaObject();
300 QMetaMethod mm = senderMetaObject->method(lastSignalIdx);
301
302 QObject *realObject = senderObj;
303 if (qobject_cast<QDBusAbstractAdaptor *>(senderObj))
304 // it's an adaptor, so the real object is in fact its parent
305 realObject = realObject->parent();
306
307 // break down the parameter list
308 QList<QMetaType> types;
309 QString errorMsg;
310 int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
311 if (inputCount == -1) {
312 // invalid signal signature
313 qWarning("QDBusAbstractAdaptor: Cannot relay signal %s::%s: %s",
314 senderMetaObject->className(), mm.methodSignature().constData(),
315 qPrintable(errorMsg));
316 return;
317 }
318 if (inputCount + 1 != types.count() ||
319 types.at(inputCount) == QDBusMetaTypeId::message()) {
320 // invalid signal signature
321 qWarning("QDBusAbstractAdaptor: Cannot relay signal %s::%s",
322 senderMetaObject->className(), mm.methodSignature().constData());
323 return;
324 }
325
326 QVariantList args;
327 const int numTypes = types.count();
328 args.reserve(numTypes - 1);
329 for (int i = 1; i < numTypes; ++i)
330 args << QVariant(QMetaType(types.at(i)), argv[i]);
331
332 // now emit the signal with all the information
333 emit relaySignal(realObject, senderMetaObject, lastSignalIdx, args);
334}
335
336QT_END_NAMESPACE
337
338#endif // QT_NO_DBUS
339