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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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