Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qdbuscpp2xml.cpp
Go to the documentation of this file.
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(mm.arguments.size());
66
67 for (const ArgumentDef &arg : mm.arguments)
68 parameterTypes.append(arg.normalizedType);
69
70 return qDBusParametersForMethod(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(" <%s name=\"%s\">\n",
83 isSignal ? "signal" : "method", mm.name.constData());
84
85 // check the return type first
86 int typeId = QMetaType::fromName(mm.normalizedType).id();
87 const bool hasVoidReturn = typeId == QMetaType::Void;
88 if (!hasVoidReturn) {
89 if (typeId) {
90 const char *typeName = QDBusMetaType::typeToSignature(QMetaType(typeId));
91 if (typeName) {
92 xml += QString::fromLatin1(" <arg type=\"%1\" direction=\"out\"/>\n")
93 .arg(typeNameToXml(typeName));
94
95 // do we need to describe this argument?
96 if (!QDBusMetaType::signatureToMetaType(typeName).isValid())
97 xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
98 .arg(typeNameToXml(mm.normalizedType.constData()));
99 } else {
100 return QString();
101 }
102 } else if (!mm.normalizedType.isEmpty()) {
103 qWarning() << "Unregistered return type:" << mm.normalizedType.constData();
104 return QString();
105 }
106 }
107 QList<ArgumentDef> names = mm.arguments;
108 QList<QMetaType> types;
109 QString errorMsg;
110 int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
111 const int outputArgsStart = hasVoidReturn ? 0 : 1;
112 if (inputCount == -1) {
113 qWarning() << qPrintable(errorMsg);
114 return QString(); // invalid form
115 }
116 if (isSignal && inputCount + 1 != types.size())
117 return QString(); // signal with output arguments?
118 if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message())
119 return QString(); // signal with QDBusMessage argument?
120
121 bool isScriptable = mm.isScriptable;
122 for (qsizetype j = 1; j < types.size(); ++j) {
123 // input parameter for a slot or output for a signal
124 if (types.at(j) == QDBusMetaTypeId::message()) {
125 isScriptable = true;
126 continue;
127 }
128
129 QString name;
130 if (!names.at(j - 1).name.isEmpty())
131 name = QString::fromLatin1("name=\"%1\" ").arg(QString::fromLatin1(names.at(j - 1).name));
132
133 bool isOutput = isSignal || j > inputCount;
134
135 const char *signature = QDBusMetaType::typeToSignature(QMetaType(types.at(j)));
136 xml += QString::fromLatin1(" <arg %1type=\"%2\" direction=\"%3\"/>\n")
137 .arg(name,
138 QLatin1StringView(signature),
139 isOutput ? "out"_L1 : "in"_L1);
140
141 // do we need to describe this argument?
142 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
143 const char *typeName = QMetaType(types.at(j)).name();
144 xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
145 .arg(isOutput ? "Out"_L1 : "In"_L1)
146 .arg(isOutput && !isSignal ? j - 1 - inputCount + outputArgsStart : j - 1)
147 .arg(typeNameToXml(typeName));
148 }
149 }
150
151 int wantedMask;
152 if (isScriptable)
153 wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
154 : QDBusConnection::ExportScriptableSlots;
155 else
156 wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
157 : QDBusConnection::ExportNonScriptableSlots;
158 if ((flags & wantedMask) != wantedMask)
159 return QString();
160
161 if (qDBusCheckAsyncTag(mm.tag.constData()))
162 // add the no-reply annotation
163 xml += " <annotation name=\"" ANNOTATION_NO_WAIT "\" value=\"true\"/>\n"_L1;
164
165 QString retval = xml;
166 retval += QString::fromLatin1(" </%1>\n").arg(isSignal ? "signal"_L1 : "method"_L1);
167
168 return retval;
169}
170
171
172static QString generateInterfaceXml(const ClassDef *mo)
173{
174 QString retval;
175
176 // start with properties:
177 if (flags & (QDBusConnection::ExportScriptableProperties |
178 QDBusConnection::ExportNonScriptableProperties)) {
179 static const char *accessvalues[] = {nullptr, "read", "write", "readwrite"};
180 for (const PropertyDef &mp : mo->propertyList) {
181 if (!((!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportScriptableProperties)) ||
182 (!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportNonScriptableProperties))))
183 continue;
184
185 int access = 0;
186 if (!mp.read.isEmpty())
187 access |= 1;
188 if (!mp.write.isEmpty())
189 access |= 2;
190 if (!mp.member.isEmpty())
191 access |= 3;
192
193 int typeId = QMetaType::fromName(mp.type).id();
194 if (!typeId) {
195 fprintf(stderr, PROGRAMNAME ": unregistered type: '%s', ignoring\n",
196 mp.type.constData());
197 continue;
198 }
199 const char *signature = QDBusMetaType::typeToSignature(QMetaType(typeId));
200 if (!signature)
201 continue;
202
203 retval += QString::fromLatin1(" <property name=\"%1\" type=\"%2\" access=\"%3\"")
204 .arg(QLatin1StringView(mp.name),
205 QLatin1StringView(signature),
206 QLatin1StringView(accessvalues[access]));
207
208 if (!QDBusMetaType::signatureToMetaType(signature).isValid()) {
209 retval += QString::fromLatin1(">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n")
210 .arg(typeNameToXml(mp.type.constData()));
211 } else {
212 retval += "/>\n"_L1;
213 }
214 }
215 }
216
217 // now add methods:
218
219 if (flags & (QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) {
220 for (const FunctionDef &mm : mo->signalList) {
221 if (mm.wasCloned)
222 continue;
223 if (!mm.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSignals))
224 continue;
225
226 retval += addFunction(mm, true);
227 }
228 }
229
230 if (flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) {
231 for (const FunctionDef &slot : mo->slotList) {
232 if (!slot.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
233 continue;
234 if (slot.access == FunctionDef::Public)
235 retval += addFunction(slot);
236 }
237 for (const FunctionDef &method : mo->methodList) {
238 if (!method.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
239 continue;
240 if (method.access == FunctionDef::Public)
241 retval += addFunction(method);
242 }
243 }
244 return retval;
245}
246
248{
249 QString interface;
250
251 for (const ClassInfoDef &cid : mo->classInfoList) {
252 if (cid.name == QCLASSINFO_DBUS_INTERFACE)
253 return QString::fromUtf8(cid.value);
254 }
255 interface = QLatin1StringView(mo->classname);
256 interface.replace("::"_L1, "."_L1);
257
258 if (interface.startsWith("QDBus"_L1)) {
259 interface.prepend("org.qtproject.QtDBus."_L1);
260 } else if (interface.startsWith(u'Q') &&
261 interface.size() >= 2 && interface.at(1).isUpper()) {
262 // assume it's Qt
263 interface.prepend("local.org.qtproject.Qt."_L1);
264 } else {
265 interface.prepend("local."_L1);
266 }
267
268 return interface;
269}
270
271
273{
274 for (const ClassInfoDef &cid : cdef->classInfoList) {
275 if (cid.name == QCLASSINFO_DBUS_INTROSPECTION)
276 return QString::fromUtf8(cid.value);
277 }
278
279 // generate the interface name from the meta object
280 QString interface = qDBusInterfaceFromClassDef(cdef);
281
282 QString xml = generateInterfaceXml(cdef);
283
284 if (xml.isEmpty())
285 return QString(); // don't add an empty interface
286 return QString::fromLatin1(" <interface name=\"%1\">\n%2 </interface>\n")
287 .arg(interface, xml);
288}
289
290static void showHelp()
291{
292 printf("%s", help);
293 exit(0);
294}
295
296static void showVersion()
297{
298 printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION);
299 printf("D-Bus QObject-to-XML converter\n");
300 exit(0);
301}
302
303class CustomType {
304public:
305 CustomType(const QByteArray &typeName)
307 {
308 metaTypeImpl.name = typeName.constData();
309 }
310 QMetaType metaType() const { return QMetaType(&metaTypeImpl); }
311
312private:
313 // not copiable and not movable because of QBasicAtomicInt
314 QtPrivate::QMetaTypeInterface metaTypeImpl =
315 { /*.revision=*/ 0,
316 /*.alignment=*/ 0,
317 /*.size=*/ 0,
318 /*.flags=*/ 0,
319 /*.typeId=*/ 0,
320 /*.metaObjectFn=*/ 0,
321 /*.name=*/ nullptr, // set by the constructor
322 /*.defaultCtr=*/ nullptr,
323 /*.copyCtr=*/ nullptr,
324 /*.moveCtr=*/ nullptr,
325 /*.dtor=*/ nullptr,
326 /*.equals=*/ nullptr,
327 /*.lessThan=*/ nullptr,
328 /*.debugStream=*/ nullptr,
329 /*.dataStreamOut=*/ nullptr,
330 /*.dataStreamIn=*/ nullptr,
331 /*.legacyRegisterOp=*/ nullptr
332 };
333 QByteArray typeName;
334};
335// Unlike std::vector, std::deque works with non-copiable non-movable types
337
338static void parseCmdLine(QStringList &arguments)
339{
340 flags = 0;
341 for (qsizetype i = 0; i < arguments.size(); ++i) {
342 const QString arg = arguments.at(i);
343
344 if (arg == "--help"_L1)
346
347 if (!arg.startsWith(u'-'))
348 continue;
349
350 char c = arg.size() == 2 ? arg.at(1).toLatin1() : char(0);
351 switch (c) {
352 case 'P':
353 flags |= QDBusConnection::ExportNonScriptableProperties;
354 Q_FALLTHROUGH();
355 case 'p':
356 flags |= QDBusConnection::ExportScriptableProperties;
357 break;
358
359 case 'S':
360 flags |= QDBusConnection::ExportNonScriptableSignals;
361 Q_FALLTHROUGH();
362 case 's':
363 flags |= QDBusConnection::ExportScriptableSignals;
364 break;
365
366 case 'M':
367 flags |= QDBusConnection::ExportNonScriptableSlots;
368 Q_FALLTHROUGH();
369 case 'm':
370 flags |= QDBusConnection::ExportScriptableSlots;
371 break;
372
373 case 'A':
374 flags |= QDBusConnection::ExportNonScriptableContents;
375 Q_FALLTHROUGH();
376 case 'a':
377 flags |= QDBusConnection::ExportScriptableContents;
378 break;
379
380 case 't':
381 if (arguments.size() < i + 2) {
382 printf("-t expects a type=dbustype argument\n");
383 exit(1);
384 } else {
385 const QByteArray arg = arguments.takeAt(i + 1).toUtf8();
386 // lastIndexOf because the C++ type could contain '=' while the DBus type can't
387 const qsizetype separator = arg.lastIndexOf('=');
388 if (separator == -1) {
389 printf("-t expects a type=dbustype argument, but no '=' was found\n");
390 exit(1);
391 }
392 const QByteArray type = arg.left(separator);
393 const QByteArray dbustype = arg.mid(separator+1);
394
395 s_customTypes.emplace_back(type);
396 QMetaType metaType = s_customTypes.back().metaType();
397 QDBusMetaType::registerCustomType(metaType, dbustype);
398 }
399 break;
400
401 case 'o':
402 if (arguments.size() < i + 2 || arguments.at(i + 1).startsWith(u'-')) {
403 printf("-o expects a filename\n");
404 exit(1);
405 }
406 outputFile = arguments.takeAt(i + 1);
407 break;
408
409 case 'h':
410 case '?':
412 break;
413
414 case 'V':
416 break;
417
418 default:
419 printf("unknown option: \"%s\"\n", qPrintable(arg));
420 exit(1);
421 }
422 }
423
424 if (flags == 0)
425 flags = QDBusConnection::ExportScriptableContents
426 | QDBusConnection::ExportNonScriptableContents;
427}
428
429int main(int argc, char **argv)
430{
431 QStringList args;
432 args.reserve(argc - 1);
433 for (int n = 1; n < argc; ++n)
434 args.append(QString::fromLocal8Bit(argv[n]));
435 parseCmdLine(args);
436
437 QDBusMetaTypeId::init();
438
439 QList<ClassDef> classes;
440
441 if (args.isEmpty())
442 args << u"-"_s;
443 for (const auto &arg: std::as_const(args)) {
444 if (arg.startsWith(u'-') && arg.size() > 1)
445 continue;
446
447 QFile f;
448 bool fileIsOpen;
449 QString fileName;
450 if (arg == u'-') {
451 fileName = "stdin"_L1;
452 fileIsOpen = f.open(stdin, QIODevice::ReadOnly | QIODevice::Text);
453 } else {
454 fileName = arg;
455 f.setFileName(arg);
456 fileIsOpen = f.open(QIODevice::ReadOnly | QIODevice::Text);
457 }
458 if (!fileIsOpen) {
459 fprintf(stderr, PROGRAMNAME ": could not open '%s': %s\n",
460 qPrintable(fileName), qPrintable(f.errorString()));
461 return 1;
462 }
463
464 Preprocessor pp;
465 Moc moc;
466 pp.macros["Q_MOC_RUN"];
467 pp.macros["__cplusplus"];
468
469 const QByteArray filename = arg.toLocal8Bit();
470
471 moc.filename = filename;
472 moc.currentFilenames.push(filename);
473
474 moc.symbols = pp.preprocessed(moc.filename, &f);
475 moc.parse();
476
477 if (moc.classList.isEmpty())
478 return 0;
479 classes = moc.classList;
480
481 f.close();
482 }
483
484 QFile output;
485 if (outputFile.isEmpty()) {
486 if (!output.open(stdout, QIODevice::WriteOnly)) {
487 fprintf(stderr, PROGRAMNAME ": could not open standard output: %s\n",
488 qPrintable(output.errorString()));
489 return 1;
490 }
491 } else {
492 output.setFileName(outputFile);
493 if (!output.open(QIODevice::WriteOnly)) {
494 fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s\n",
495 qPrintable(outputFile), qPrintable(output.errorString()));
496 return 1;
497 }
498 }
499
500 output.write(docTypeHeader);
501 output.write("<node>\n");
502 for (const ClassDef &cdef : std::as_const(classes)) {
503 QString xml = qDBusGenerateClassDefXml(&cdef);
504 output.write(std::move(xml).toLocal8Bit());
505 }
506 output.write("</node>\n");
507
508 return 0;
509}
QMetaType metaType() const
CustomType(const QByteArray &typeName)
Definition qlist.h:81
#define QCLASSINFO_DBUS_INTERFACE
#define QCLASSINFO_DBUS_INTROSPECTION
#define ANNOTATION_NO_WAIT
int qDBusParametersForMethod(const FunctionDef &mm, QList< QMetaType > &metaTypes, QString &errorMsg)
#define PROGRAMNAME
static QString addFunction(const FunctionDef &mm, bool isSignal=false)
static const char help[]
QString qDBusGenerateClassDefXml(const ClassDef *cdef)
static void showVersion()
static void parseCmdLine(QStringList &arguments)
QString qDBusInterfaceFromClassDef(const ClassDef *mo)
static QString typeNameToXml(const char *typeName)
#define PROGRAMVERSION
static int flags
static void showHelp()
static QString generateInterfaceXml(const ClassDef *mo)
static std::deque< CustomType > s_customTypes
static const char docTypeHeader[]
#define qPrintable(string)
Definition qstring.h:1683
int main(int argc, char *argv[])
[ctor_close]
bool isScriptable
Definition moc.h:100