1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtDBus module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include <QtCore/qmetaobject.h>
41#include <QtCore/qstringlist.h>
42#include <QtCore/qdebug.h>
43
44#include "qdbusinterface_p.h" // for ANNOTATION_NO_WAIT
45#include "qdbusabstractadaptor_p.h" // for QCLASSINFO_DBUS_*
46#include "qdbusconnection_p.h" // for the flags
47#include "qdbusmetatype_p.h"
48#include "qdbusmetatype.h"
49#include "qdbusutil_p.h"
50
51#ifndef QT_NO_DBUS
52
53QT_BEGIN_NAMESPACE
54
55extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
56 const QMetaObject *base, int flags);
57
58static inline QString typeNameToXml(const char *typeName)
59{
60 // ### copied from qtextdocument.cpp
61 // ### move this into Qt Core at some point
62 const QLatin1String plain(typeName);
63 QString rich;
64 rich.reserve(int(plain.size() * 1.1));
65 for (int i = 0; i < plain.size(); ++i) {
66 if (plain.at(i) == QLatin1Char('<'))
67 rich += QLatin1String("&lt;");
68 else if (plain.at(i) == QLatin1Char('>'))
69 rich += QLatin1String("&gt;");
70 else if (plain.at(i) == QLatin1Char('&'))
71 rich += QLatin1String("&amp;");
72 else
73 rich += plain.at(i);
74 }
75 return rich;
76}
77
78static inline QLatin1String accessAsString(bool read, bool write)
79{
80 if (read)
81 return write ? QLatin1String("readwrite") : QLatin1String("read") ;
82 else
83 return write ? QLatin1String("write") : QLatin1String("") ;
84}
85
86// implement the D-Bus org.freedesktop.DBus.Introspectable interface
87// we do that by analysing the metaObject of all the adaptor interfaces
88
89static QString generateInterfaceXml(const QMetaObject *mo, int flags, int methodOffset, int propOffset)
90{
91 QString retval;
92
93 // start with properties:
94 if (flags & (QDBusConnection::ExportScriptableProperties |
95 QDBusConnection::ExportNonScriptableProperties)) {
96 for (int i = propOffset; i < mo->propertyCount(); ++i) {
97
98 QMetaProperty mp = mo->property(i);
99
100 if (!((mp.isScriptable() && (flags & QDBusConnection::ExportScriptableProperties)) ||
101 (!mp.isScriptable() && (flags & QDBusConnection::ExportNonScriptableProperties))))
102 continue;
103
104 QMetaType type = mp.metaType();
105 if (!type.isValid())
106 continue;
107 const char *signature = QDBusMetaType::typeToSignature(type);
108 if (!signature)
109 continue;
110
111 retval += QLatin1String(" <property name=\"%1\" type=\"%2\" access=\"%3\"")
112 .arg(QLatin1String(mp.name()),
113 QLatin1String(signature),
114 accessAsString(mp.isReadable(), mp.isWritable()));
115
116 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
117 const char *typeName = type.name();
118 retval += QLatin1String(">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n")
119 .arg(typeNameToXml(typeName));
120 } else {
121 retval += QLatin1String("/>\n");
122 }
123 }
124 }
125
126 // now add methods:
127 for (int i = methodOffset; i < mo->methodCount(); ++i) {
128 QMetaMethod mm = mo->method(i);
129
130 bool isSignal;
131 if (mm.methodType() == QMetaMethod::Signal)
132 // adding a signal
133 isSignal = true;
134 else if (mm.access() == QMetaMethod::Public && (mm.methodType() == QMetaMethod::Slot || mm.methodType() == QMetaMethod::Method))
135 isSignal = false;
136 else
137 continue; // neither signal nor public slot
138
139 if (isSignal && !(flags & (QDBusConnection::ExportScriptableSignals |
140 QDBusConnection::ExportNonScriptableSignals)))
141 continue; // we're not exporting any signals
142 if (!isSignal && (!(flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) &&
143 !(flags & (QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportNonScriptableInvokables))))
144 continue; // we're not exporting any slots or invokables
145
146 // we want to skip non-scriptable stuff as early as possible to avoid bogus warning
147 // for methods that are not being exported at all
148 bool isScriptable = mm.attributes() & QMetaMethod::Scriptable;
149 if (!isScriptable && !(flags & (isSignal ? QDBusConnection::ExportNonScriptableSignals : QDBusConnection::ExportNonScriptableInvokables | QDBusConnection::ExportNonScriptableSlots)))
150 continue;
151
152 QString xml = QString::asprintf(" <%s name=\"%s\">\n",
153 isSignal ? "signal" : "method", mm.name().constData());
154
155 // check the return type first
156 QMetaType typeId = mm.returnMetaType();
157 if (typeId.isValid() && typeId.id() != QMetaType::Void) {
158 const char *typeName = QDBusMetaType::typeToSignature(typeId);
159 if (typeName) {
160 xml += QLatin1String(" <arg type=\"%1\" direction=\"out\"/>\n")
161 .arg(typeNameToXml(typeName));
162
163 // do we need to describe this argument?
164 if (!QDBusMetaType::signatureToMetaType(typeName).isValid())
165 xml += QLatin1String(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
166 .arg(typeNameToXml(QMetaType(typeId).name()));
167 } else {
168 qWarning() << "Unsupported return type" << typeId.id() << typeId.name() << "in method" << mm.name();
169 continue;
170 }
171 }
172 else if (!typeId.isValid()) {
173 qWarning() << "Invalid return type in method" << mm.name();
174 continue; // wasn't a valid type
175 }
176
177 QList<QByteArray> names = mm.parameterNames();
178 QList<QMetaType> types;
179 QString errorMsg;
180 int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
181 if (inputCount == -1) {
182 qWarning() << "Skipped method" << mm.name() << ":" << qPrintable(errorMsg);
183 continue; // invalid form
184 }
185 if (isSignal && inputCount + 1 != types.count())
186 continue; // signal with output arguments?
187 if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message())
188 continue; // signal with QDBusMessage argument?
189 if (isSignal && mm.attributes() & QMetaMethod::Cloned)
190 continue; // cloned signal?
191
192 int j;
193 for (j = 1; j < types.count(); ++j) {
194 // input parameter for a slot or output for a signal
195 if (types.at(j) == QDBusMetaTypeId::message()) {
196 isScriptable = true;
197 continue;
198 }
199
200 QString name;
201 if (!names.at(j - 1).isEmpty())
202 name = QLatin1String("name=\"%1\" ").arg(QLatin1String(names.at(j - 1)));
203
204 bool isOutput = isSignal || j > inputCount;
205
206 const char *signature = QDBusMetaType::typeToSignature(types.at(j));
207 xml += QString::asprintf(" <arg %lstype=\"%s\" direction=\"%s\"/>\n",
208 qUtf16Printable(name), signature, isOutput ? "out" : "in");
209
210 // do we need to describe this argument?
211 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
212 const char *typeName = QMetaType(types.at(j)).name();
213 xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
214 .arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
215 .arg(isOutput && !isSignal ? j - inputCount : j - 1)
216 .arg(typeNameToXml(typeName));
217 }
218 }
219
220 int wantedMask;
221 if (isScriptable)
222 wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
223 : QDBusConnection::ExportScriptableSlots;
224 else
225 wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
226 : QDBusConnection::ExportNonScriptableSlots;
227 if ((flags & wantedMask) != wantedMask)
228 continue;
229
230 if (qDBusCheckAsyncTag(mm.tag()))
231 // add the no-reply annotation
232 xml += QLatin1String(" <annotation name=\"" ANNOTATION_NO_WAIT "\""
233 " value=\"true\"/>\n");
234
235 retval += xml;
236 retval += QLatin1String(" </%1>\n")
237 .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
238 }
239
240 return retval;
241}
242
243QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
244 const QMetaObject *base, int flags)
245{
246 if (interface.isEmpty())
247 // generate the interface name from the meta object
248 interface = qDBusInterfaceFromMetaObject(mo);
249
250 QString xml;
251 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
252 if (idx >= mo->classInfoOffset())
253 return QString::fromUtf8(mo->classInfo(idx).value());
254 else
255 xml = generateInterfaceXml(mo, flags, base->methodCount(), base->propertyCount());
256
257 if (xml.isEmpty())
258 return QString(); // don't add an empty interface
259 return QLatin1String(" <interface name=\"%1\">\n%2 </interface>\n")
260 .arg(interface, xml);
261}
262#if 0
263QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, const QMetaObject *base,
264 int flags)
265{
266 if (interface.isEmpty()) {
267 // generate the interface name from the meta object
268 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTERFACE);
269 if (idx >= mo->classInfoOffset()) {
270 interface = QLatin1String(mo->classInfo(idx).value());
271 } else {
272 interface = QLatin1String(mo->className());
273 interface.replace(QLatin1String("::"), QLatin1String("."));
274
275 if (interface.startsWith(QLatin1String("QDBus"))) {
276 interface.prepend(QLatin1String("org.qtproject.QtDBus."));
277 } else if (interface.startsWith(QLatin1Char('Q')) &&
278 interface.length() >= 2 && interface.at(1).isUpper()) {
279 // assume it's Qt
280 interface.prepend(QLatin1String("org.qtproject.Qt."));
281 } else if (!QCoreApplication::instance()||
282 QCoreApplication::instance()->applicationName().isEmpty()) {
283 interface.prepend(QLatin1String("local."));
284 } else {
285 interface.prepend(QLatin1Char('.')).prepend(QCoreApplication::instance()->applicationName());
286 QStringList domainName =
287 QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
288 Qt::SkipEmptyParts);
289 if (domainName.isEmpty())
290 interface.prepend(QLatin1String("local."));
291 else
292 for (int i = 0; i < domainName.count(); ++i)
293 interface.prepend(QLatin1Char('.')).prepend(domainName.at(i));
294 }
295 }
296 }
297
298 QString xml;
299 int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
300 if (idx >= mo->classInfoOffset())
301 return QString::fromUtf8(mo->classInfo(idx).value());
302 else
303 xml = generateInterfaceXml(mo, flags, base->methodCount(), base->propertyCount());
304
305 if (xml.isEmpty())
306 return QString(); // don't add an empty interface
307 return QString::fromLatin1(" <interface name=\"%1\">\n%2 </interface>\n")
308 .arg(interface, xml);
309}
310
311#endif
312
313QT_END_NAMESPACE
314
315#endif // QT_NO_DBUS
316