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

source code of qtbase/src/tools/qdbuscpp2xml/qdbuscpp2xml.cpp