1/****************************************************************************
2**
3** Copyright (C) 2020 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29#include <qbuffer.h>
30#include <qbytearray.h>
31#include <qdebug.h>
32#include <qfile.h>
33#include <qlist.h>
34#include <qstring.h>
35#include <qvarlengtharray.h>
36
37#include <stdio.h>
38#include <stdlib.h>
39#include <string.h>
40
41#include <qdbusconnection.h> // for the Export* flags
42#include <private/qdbusconnection_p.h> // for the qDBusCheckAsyncTag
43
44// copied from dbus-protocol.h:
45static const char docTypeHeader[] =
46 "<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
47 "\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
48
49#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply"
50#define QCLASSINFO_DBUS_INTERFACE "D-Bus Interface"
51#define QCLASSINFO_DBUS_INTROSPECTION "D-Bus Introspection"
52
53#include <qdbusmetatype.h>
54#include <private/qdbusmetatype_p.h>
55#include <private/qdbusutil_p.h>
56
57#include "moc.h"
58#include "generator.h"
59#include "preprocessor.h"
60
61#define PROGRAMNAME "qdbuscpp2xml"
62#define PROGRAMVERSION "0.2"
63#define PROGRAMCOPYRIGHT "Copyright (C) 2020 The Qt Company Ltd."
64
65static QString outputFile;
66static int flags;
67
68static const char help[] =
69 "Usage: " PROGRAMNAME " [options...] [files...]\n"
70 "Parses the C++ source or header file containing a QObject-derived class and\n"
71 "produces the D-Bus Introspection XML."
72 "\n"
73 "Options:\n"
74 " -p|-s|-m Only parse scriptable Properties, Signals and Methods (slots)\n"
75 " -P|-S|-M Parse all Properties, Signals and Methods (slots)\n"
76 " -a Output all scriptable contents (equivalent to -psm)\n"
77 " -A Output all contents (equivalent to -PSM)\n"
78 " -o <filename> Write the output to file <filename>\n"
79 " -h Show this information\n"
80 " -V Show the program version and quit.\n"
81 "\n";
82
83int qDBusParametersForMethod(const FunctionDef &mm, QList<QMetaType> &metaTypes, QString &errorMsg)
84{
85 QList<QByteArray> parameterTypes;
86 parameterTypes.reserve(mm.arguments.size());
87
88 for (const ArgumentDef &arg : mm.arguments)
89 parameterTypes.append(arg.normalizedType);
90
91 return qDBusParametersForMethod(parameterTypes, metaTypes, errorMsg);
92}
93
94
95static inline QString typeNameToXml(const char *typeName)
96{
97 QString plain = QLatin1String(typeName);
98 return plain.toHtmlEscaped();
99}
100
101static QString addFunction(const FunctionDef &mm, bool isSignal = false) {
102
103 QString xml = QString::asprintf(" <%s name=\"%s\">\n",
104 isSignal ? "signal" : "method", mm.name.constData());
105
106 // check the return type first
107 int typeId = QMetaType::fromName(mm.normalizedType).id();
108 if (typeId != QMetaType::Void) {
109 if (typeId) {
110 const char *typeName = QDBusMetaType::typeToSignature(QMetaType(typeId));
111 if (typeName) {
112 xml += QString::fromLatin1(" <arg type=\"%1\" direction=\"out\"/>\n")
113 .arg(typeNameToXml(typeName));
114
115 // do we need to describe this argument?
116 if (!QDBusMetaType::signatureToMetaType(typeName).isValid())
117 xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
118 .arg(typeNameToXml(mm.normalizedType.constData()));
119 } else {
120 return QString();
121 }
122 } else if (!mm.normalizedType.isEmpty()) {
123 return QString(); // wasn't a valid type
124 }
125 }
126 QList<ArgumentDef> names = mm.arguments;
127 QList<QMetaType> types;
128 QString errorMsg;
129 int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
130 if (inputCount == -1) {
131 qWarning() << qPrintable(errorMsg);
132 return QString(); // invalid form
133 }
134 if (isSignal && inputCount + 1 != types.count())
135 return QString(); // signal with output arguments?
136 if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message())
137 return QString(); // signal with QDBusMessage argument?
138
139 bool isScriptable = mm.isScriptable;
140 for (int j = 1; j < types.count(); ++j) {
141 // input parameter for a slot or output for a signal
142 if (types.at(j) == QDBusMetaTypeId::message()) {
143 isScriptable = true;
144 continue;
145 }
146
147 QString name;
148 if (!names.at(j - 1).name.isEmpty())
149 name = QString::fromLatin1("name=\"%1\" ").arg(QString::fromLatin1(names.at(j - 1).name));
150
151 bool isOutput = isSignal || j > inputCount;
152
153 const char *signature = QDBusMetaType::typeToSignature(QMetaType(types.at(j)));
154 xml += QString::fromLatin1(" <arg %1type=\"%2\" direction=\"%3\"/>\n")
155 .arg(name,
156 QLatin1String(signature),
157 isOutput ? QLatin1String("out") : QLatin1String("in"));
158
159 // do we need to describe this argument?
160 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
161 const char *typeName = QMetaType(types.at(j)).name();
162 xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
163 .arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
164 .arg(isOutput && !isSignal ? j - inputCount : j - 1)
165 .arg(typeNameToXml(typeName));
166 }
167 }
168
169 int wantedMask;
170 if (isScriptable)
171 wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
172 : QDBusConnection::ExportScriptableSlots;
173 else
174 wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
175 : QDBusConnection::ExportNonScriptableSlots;
176 if ((flags & wantedMask) != wantedMask)
177 return QString();
178
179 if (qDBusCheckAsyncTag(mm.tag.constData()))
180 // add the no-reply annotation
181 xml += QLatin1String(" <annotation name=\"" ANNOTATION_NO_WAIT "\""
182 " value=\"true\"/>\n");
183
184 QString retval = xml;
185 retval += QString::fromLatin1(" </%1>\n")
186 .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
187
188 return retval;
189}
190
191
192static QString generateInterfaceXml(const ClassDef *mo)
193{
194 QString retval;
195
196 // start with properties:
197 if (flags & (QDBusConnection::ExportScriptableProperties |
198 QDBusConnection::ExportNonScriptableProperties)) {
199 static const char *accessvalues[] = {nullptr, "read", "write", "readwrite"};
200 for (const PropertyDef &mp : mo->propertyList) {
201 if (!((!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportScriptableProperties)) ||
202 (!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportNonScriptableProperties))))
203 continue;
204
205 int access = 0;
206 if (!mp.read.isEmpty())
207 access |= 1;
208 if (!mp.write.isEmpty())
209 access |= 2;
210
211 int typeId = QMetaType::fromName(mp.type).id();
212 if (!typeId) {
213 fprintf(stderr, PROGRAMNAME ": unregistered type: '%s', ignoring\n",
214 mp.type.constData());
215 continue;
216 }
217 const char *signature = QDBusMetaType::typeToSignature(QMetaType(typeId));
218 if (!signature)
219 continue;
220
221 retval += QString::fromLatin1(" <property name=\"%1\" type=\"%2\" access=\"%3\"")
222 .arg(QLatin1String(mp.name),
223 QLatin1String(signature),
224 QLatin1String(accessvalues[access]));
225
226 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
227 retval += QString::fromLatin1(">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n")
228 .arg(typeNameToXml(mp.type.constData()));
229 } else {
230 retval += QLatin1String("/>\n");
231 }
232 }
233 }
234
235 // now add methods:
236
237 if (flags & (QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) {
238 for (const FunctionDef &mm : mo->signalList) {
239 if (mm.wasCloned)
240 continue;
241 if (!mm.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSignals))
242 continue;
243
244 retval += addFunction(mm, true);
245 }
246 }
247
248 if (flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) {
249 for (const FunctionDef &slot : mo->slotList) {
250 if (!slot.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
251 continue;
252 if (slot.access == FunctionDef::Public)
253 retval += addFunction(slot);
254 }
255 for (const FunctionDef &method : mo->methodList) {
256 if (!method.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
257 continue;
258 if (method.access == FunctionDef::Public)
259 retval += addFunction(method);
260 }
261 }
262 return retval;
263}
264
265QString qDBusInterfaceFromClassDef(const ClassDef *mo)
266{
267 QString interface;
268
269 for (const ClassInfoDef &cid : mo->classInfoList) {
270 if (cid.name == QCLASSINFO_DBUS_INTERFACE)
271 return QString::fromUtf8(cid.value);
272 }
273 interface = QLatin1String(mo->classname);
274 interface.replace(QLatin1String("::"), QLatin1String("."));
275
276 if (interface.startsWith(QLatin1String("QDBus"))) {
277 interface.prepend(QLatin1String("org.qtproject.QtDBus."));
278 } else if (interface.startsWith(QLatin1Char('Q')) &&
279 interface.length() >= 2 && interface.at(1).isUpper()) {
280 // assume it's Qt
281 interface.prepend(QLatin1String("local.org.qtproject.Qt."));
282 } else {
283 interface.prepend(QLatin1String("local."));
284 }
285
286 return interface;
287}
288
289
290QString qDBusGenerateClassDefXml(const ClassDef *cdef)
291{
292 for (const ClassInfoDef &cid : cdef->classInfoList) {
293 if (cid.name == QCLASSINFO_DBUS_INTROSPECTION)
294 return QString::fromUtf8(cid.value);
295 }
296
297 // generate the interface name from the meta object
298 QString interface = qDBusInterfaceFromClassDef(cdef);
299
300 QString xml = generateInterfaceXml(cdef);
301
302 if (xml.isEmpty())
303 return QString(); // don't add an empty interface
304 return QString::fromLatin1(" <interface name=\"%1\">\n%2 </interface>\n")
305 .arg(interface, xml);
306}
307
308static void showHelp()
309{
310 printf("%s", help);
311 exit(0);
312}
313
314static void showVersion()
315{
316 printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION);
317 printf("D-Bus QObject-to-XML converter\n");
318 exit(0);
319}
320
321static void parseCmdLine(QStringList &arguments)
322{
323 flags = 0;
324 for (int i = 0; i < arguments.count(); ++i) {
325 const QString arg = arguments.at(i);
326
327 if (arg == QLatin1String("--help"))
328 showHelp();
329
330 if (!arg.startsWith(QLatin1Char('-')))
331 continue;
332
333 char c = arg.count() == 2 ? arg.at(1).toLatin1() : char(0);
334 switch (c) {
335 case 'P':
336 flags |= QDBusConnection::ExportNonScriptableProperties;
337 Q_FALLTHROUGH();
338 case 'p':
339 flags |= QDBusConnection::ExportScriptableProperties;
340 break;
341
342 case 'S':
343 flags |= QDBusConnection::ExportNonScriptableSignals;
344 Q_FALLTHROUGH();
345 case 's':
346 flags |= QDBusConnection::ExportScriptableSignals;
347 break;
348
349 case 'M':
350 flags |= QDBusConnection::ExportNonScriptableSlots;
351 Q_FALLTHROUGH();
352 case 'm':
353 flags |= QDBusConnection::ExportScriptableSlots;
354 break;
355
356 case 'A':
357 flags |= QDBusConnection::ExportNonScriptableContents;
358 Q_FALLTHROUGH();
359 case 'a':
360 flags |= QDBusConnection::ExportScriptableContents;
361 break;
362
363 case 'o':
364 if (arguments.count() < i + 2 || arguments.at(i + 1).startsWith(QLatin1Char('-'))) {
365 printf("-o expects a filename\n");
366 exit(1);
367 }
368 outputFile = arguments.takeAt(i + 1);
369 break;
370
371 case 'h':
372 case '?':
373 showHelp();
374 break;
375
376 case 'V':
377 showVersion();
378 break;
379
380 default:
381 printf("unknown option: \"%s\"\n", qPrintable(arg));
382 exit(1);
383 }
384 }
385
386 if (flags == 0)
387 flags = QDBusConnection::ExportScriptableContents
388 | QDBusConnection::ExportNonScriptableContents;
389}
390
391int main(int argc, char **argv)
392{
393 QStringList args;
394 args.reserve(argc - 1);
395 for (int n = 1; n < argc; ++n)
396 args.append(QString::fromLocal8Bit(argv[n]));
397 parseCmdLine(args);
398
399 QList<ClassDef> classes;
400
401 for (int i = 0; i < args.count(); ++i) {
402 const QString arg = args.at(i);
403
404 if (arg.startsWith(QLatin1Char('-')))
405 continue;
406
407 QFile f(arg);
408 if (!f.open(QIODevice::ReadOnly|QIODevice::Text)) {
409 fprintf(stderr, PROGRAMNAME ": could not open '%s': %s\n",
410 qPrintable(arg), qPrintable(f.errorString()));
411 return 1;
412 }
413
414 Preprocessor pp;
415 Moc moc;
416 pp.macros["Q_MOC_RUN"];
417 pp.macros["__cplusplus"];
418
419 const QByteArray filename = arg.toLocal8Bit();
420
421 moc.filename = filename;
422 moc.currentFilenames.push(filename);
423
424 moc.symbols = pp.preprocessed(moc.filename, &f);
425 moc.parse();
426
427 if (moc.classList.isEmpty())
428 return 0;
429 classes = moc.classList;
430
431 f.close();
432 }
433
434 QFile output;
435 if (outputFile.isEmpty()) {
436 output.open(stdout, QIODevice::WriteOnly);
437 } else {
438 output.setFileName(outputFile);
439 if (!output.open(QIODevice::WriteOnly)) {
440 fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s",
441 qPrintable(outputFile), qPrintable(output.errorString()));
442 return 1;
443 }
444 }
445
446 output.write(docTypeHeader);
447 output.write("<node>\n");
448 for (const ClassDef &cdef : qAsConst(classes)) {
449 QString xml = qDBusGenerateClassDefXml(&cdef);
450 output.write(std::move(xml).toLocal8Bit());
451 }
452 output.write("</node>\n");
453
454 return 0;
455}
456
457