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 <qbytearray.h>
5#include <qcommandlineparser.h>
6#include <qcoreapplication.h>
7#include <qdebug.h>
8#include <qfile.h>
9#include <qfileinfo.h>
10#include <qloggingcategory.h>
11#include <qstring.h>
12#include <qstringlist.h>
13#include <qtextstream.h>
14#include <qset.h>
15
16#include <qdbusmetatype.h>
17#include <private/qdbusintrospection_p.h>
18
19#include <stdio.h>
20#include <stdlib.h>
21
22#define PROGRAMNAME "qdbusxml2cpp"
23#define PROGRAMVERSION "0.8"
24#define PROGRAMCOPYRIGHT QT_COPYRIGHT
25
26#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply"
27
28using namespace Qt::StringLiterals;
29
30static QString globalClassName;
31static QString parentClassName;
32static QString proxyFile;
33static QString adaptorFile;
34static QString inputFile;
35static bool skipNamespaces;
36static bool verbose;
37static bool includeMocs;
38static QString commandLine;
39static QStringList includes;
40static QStringList globalIncludes;
41static QStringList wantedInterfaces;
42
43static const char includeList[] =
44 "#include <QtCore/QByteArray>\n"
45 "#include <QtCore/QList>\n"
46 "#include <QtCore/QMap>\n"
47 "#include <QtCore/QString>\n"
48 "#include <QtCore/QStringList>\n"
49 "#include <QtCore/QVariant>\n";
50
51static const char forwardDeclarations[] =
52 "#include <QtCore/qcontainerfwd.h>\n";
53
54static QDBusIntrospection::Interfaces readInput()
55{
56 QFile input(inputFile);
57 if (inputFile.isEmpty() || inputFile == "-"_L1)
58 input.open(stdin, ioFlags: QIODevice::ReadOnly);
59 else
60 input.open(flags: QIODevice::ReadOnly);
61
62 QByteArray data = input.readAll();
63
64 // check if the input is already XML
65 data = data.trimmed();
66 if (data.startsWith(bv: "<!DOCTYPE ") || data.startsWith(bv: "<?xml") ||
67 data.startsWith(bv: "<node") || data.startsWith(bv: "<interface"))
68 // already XML
69 return QDBusIntrospection::parseInterfaces(xml: QString::fromUtf8(ba: data));
70
71 fprintf(stderr, format: "%s: Cannot process input: '%s'. Stop.\n",
72 PROGRAMNAME, qPrintable(inputFile));
73 exit(status: 1);
74}
75
76static void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces)
77{
78 if (!wantedInterfaces.isEmpty()) {
79 QDBusIntrospection::Interfaces::Iterator it = interfaces.begin();
80 while (it != interfaces.end())
81 if (!wantedInterfaces.contains(str: it.key()))
82 it = interfaces.erase(it);
83 else
84 ++it;
85 }
86}
87
88static bool isSupportedSuffix(QStringView suffix)
89{
90 const QLatin1StringView candidates[] = {
91 "h"_L1,
92 "cpp"_L1,
93 "cc"_L1
94 };
95
96 for (auto candidate : candidates)
97 if (suffix == candidate)
98 return true;
99
100 return false;
101}
102
103// produce a header name from the file name
104static QString header(const QString &name)
105{
106 QStringList parts = name.split(sep: u':');
107 QString retval = parts.front();
108
109 if (retval.isEmpty() || retval == "-"_L1)
110 return retval;
111
112 QFileInfo header{retval};
113 if (!isSupportedSuffix(suffix: header.suffix()))
114 retval.append(s: ".h"_L1);
115
116 return retval;
117}
118
119// produce a cpp name from the file name
120static QString cpp(const QString &name)
121{
122 QStringList parts = name.split(sep: u':');
123 QString retval = parts.back();
124
125 if (retval.isEmpty() || retval == "-"_L1)
126 return retval;
127
128 QFileInfo source{retval};
129 if (!isSupportedSuffix(suffix: source.suffix()))
130 retval.append(s: ".cpp"_L1);
131
132 return retval;
133}
134
135// produce a moc name from the file name
136static QString moc(const QString &name)
137{
138 QString retval;
139 const QStringList fileNames = name.split(sep: u':');
140
141 if (fileNames.size() == 1) {
142 QFileInfo fi{fileNames.front()};
143 if (isSupportedSuffix(suffix: fi.suffix())) {
144 // Generates a file that contains the header and the implementation: include "filename.moc"
145 retval += fi.completeBaseName();
146 retval += ".moc"_L1;
147 } else {
148 // Separate source and header files are generated: include "moc_filename.cpp"
149 retval += "moc_"_L1;
150 retval += fi.fileName();
151 retval += ".cpp"_L1;
152 }
153 } else {
154 QString headerName = fileNames.front();
155 QString sourceName = fileNames.back();
156
157 if (sourceName.isEmpty() || sourceName == "-"_L1) {
158 // If only a header is generated, don't include anything
159 } else if (headerName.isEmpty() || headerName == "-"_L1) {
160 // If only source file is generated: include "moc_sourcename.cpp"
161 QFileInfo source{sourceName};
162
163 retval += "moc_"_L1;
164 retval += source.completeBaseName();
165 retval += ".cpp"_L1;
166
167 fprintf(stderr, format: "warning: no header name is provided, assuming it to be \"%s\"\n",
168 qPrintable(source.completeBaseName() + ".h"_L1));
169 } else {
170 // Both source and header generated: include "moc_headername.cpp"
171 QFileInfo header{headerName};
172
173 retval += "moc_"_L1;
174 retval += header.completeBaseName();
175 retval += ".cpp"_L1;
176 }
177 }
178
179 return retval;
180}
181
182static QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost)
183{
184 ts << "/*\n"
185 " * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION "\n"
186 " * Command line was: " << commandLine << "\n"
187 " *\n"
188 " * " PROGRAMNAME " is " PROGRAMCOPYRIGHT "\n"
189 " *\n"
190 " * This is an auto-generated file.\n";
191
192 if (changesWillBeLost)
193 ts << " * Do not edit! All changes made to it will be lost.\n";
194 else
195 ts << " * This file may have been hand-edited. Look for HAND-EDIT comments\n"
196 " * before re-generating it.\n";
197
198 ts << " */\n\n";
199
200 return ts;
201}
202
203enum ClassType { Proxy, Adaptor };
204static QString classNameForInterface(const QString &interface, ClassType classType)
205{
206 if (!globalClassName.isEmpty())
207 return globalClassName;
208
209 const auto parts = QStringView{interface}.split(sep: u'.');
210
211 QString retval;
212 if (classType == Proxy) {
213 for (const auto &part : parts) {
214 retval += part[0].toUpper();
215 retval += part.mid(pos: 1);
216 }
217 } else {
218 retval += parts.last()[0].toUpper() + parts.last().mid(pos: 1);
219 }
220
221 if (classType == Proxy)
222 retval += "Interface"_L1;
223 else
224 retval += "Adaptor"_L1;
225
226 return retval;
227}
228
229static QByteArray qtTypeName(const QString &where, const QString &signature,
230 const QDBusIntrospection::Annotations &annotations, qsizetype paramId = -1,
231 const char *direction = "Out")
232{
233 int type = QDBusMetaType::signatureToMetaType(signature: signature.toLatin1()).id();
234 if (type == QMetaType::UnknownType) {
235 QString annotationName = u"org.qtproject.QtDBus.QtTypeName"_s;
236 if (paramId >= 0)
237 annotationName += ".%1%2"_L1.arg(args: QLatin1StringView(direction)).arg(a: paramId);
238 QString qttype = annotations.value(key: annotationName);
239 if (!qttype.isEmpty())
240 return std::move(qttype).toLatin1();
241
242 QString oldAnnotationName = u"com.trolltech.QtDBus.QtTypeName"_s;
243 if (paramId >= 0)
244 oldAnnotationName += ".%1%2"_L1.arg(args: QLatin1StringView(direction)).arg(a: paramId);
245 qttype = annotations.value(key: oldAnnotationName);
246
247 if (qttype.isEmpty()) {
248 fprintf(stderr, format: "%s: Got unknown type `%s' processing '%s'\n",
249 PROGRAMNAME, qPrintable(signature), qPrintable(inputFile));
250 fprintf(stderr,
251 format: "You should add <annotation name=\"%s\" value=\"<type>\"/> to the XML "
252 "description for '%s'\n",
253 qPrintable(annotationName), qPrintable(where));
254
255 exit(status: 1);
256 }
257
258 fprintf(stderr, format: "%s: Warning: deprecated annotation '%s' found while processing '%s'; "
259 "suggest updating to '%s'\n",
260 PROGRAMNAME, qPrintable(oldAnnotationName), qPrintable(inputFile),
261 qPrintable(annotationName));
262 return std::move(qttype).toLatin1();
263 }
264
265 return QMetaType(type).name();
266}
267
268static QString nonConstRefArg(const QByteArray &arg)
269{
270 return QLatin1StringView(arg) + " &"_L1;
271}
272
273static QString templateArg(const QByteArray &arg)
274{
275 if (!arg.endsWith(c: '>'))
276 return QLatin1StringView(arg);
277
278 return QLatin1StringView(arg) + " "_L1;
279}
280
281static QString constRefArg(const QByteArray &arg)
282{
283 if (!arg.startsWith(c: 'Q'))
284 return QLatin1StringView(arg) + " "_L1;
285 else
286 return "const %1 &"_L1.arg(args: QLatin1StringView(arg));
287}
288
289static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs,
290 const QDBusIntrospection::Arguments &outputArgs =
291 QDBusIntrospection::Arguments())
292{
293 QStringList retval;
294 const qsizetype numInputArgs = inputArgs.size();
295 const qsizetype numOutputArgs = outputArgs.size();
296 retval.reserve(asize: numInputArgs + numOutputArgs);
297 for (qsizetype i = 0; i < numInputArgs; ++i) {
298 const QDBusIntrospection::Argument &arg = inputArgs.at(i);
299 QString name = arg.name;
300 if (name.isEmpty())
301 name = u"in%1"_s.arg(a: i);
302 else
303 name.replace(before: u'-', after: u'_');
304 while (retval.contains(str: name))
305 name += "_"_L1;
306 retval << name;
307 }
308 for (qsizetype i = 0; i < numOutputArgs; ++i) {
309 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
310 QString name = arg.name;
311 if (name.isEmpty())
312 name = u"out%1"_s.arg(a: i);
313 else
314 name.replace(before: u'-', after: u'_');
315 while (retval.contains(str: name))
316 name += "_"_L1;
317 retval << name;
318 }
319 return retval;
320}
321
322static void writeArgList(QTextStream &ts, const QStringList &argNames,
323 const QDBusIntrospection::Annotations &annotations,
324 const QDBusIntrospection::Arguments &inputArgs,
325 const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments())
326{
327 // input args:
328 bool first = true;
329 qsizetype argPos = 0;
330 for (qsizetype i = 0; i < inputArgs.size(); ++i) {
331 const QDBusIntrospection::Argument &arg = inputArgs.at(i);
332 QString type = constRefArg(arg: qtTypeName(where: arg.name, signature: arg.type, annotations, paramId: i, direction: "In"));
333
334 if (!first)
335 ts << ", ";
336 ts << type << argNames.at(i: argPos++);
337 first = false;
338 }
339
340 argPos++;
341
342 // output args
343 // yes, starting from 1
344 for (qsizetype i = 1; i < outputArgs.size(); ++i) {
345 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
346
347 if (!first)
348 ts << ", ";
349 ts << nonConstRefArg(arg: qtTypeName(where: arg.name, signature: arg.type, annotations, paramId: i, direction: "Out"))
350 << argNames.at(i: argPos++);
351 first = false;
352 }
353}
354
355static void writeSignalArgList(QTextStream &ts, const QStringList &argNames,
356 const QDBusIntrospection::Annotations &annotations,
357 const QDBusIntrospection::Arguments &outputArgs)
358{
359 bool first = true;
360 qsizetype argPos = 0;
361 for (qsizetype i = 0; i < outputArgs.size(); ++i) {
362 const QDBusIntrospection::Argument &arg = outputArgs.at(i);
363 QString type = constRefArg(
364 arg: qtTypeName(where: arg.name, signature: arg.type, annotations, paramId: i, direction: "Out"));
365
366 if (!first)
367 ts << ", ";
368 ts << type << argNames.at(i: argPos++);
369 first = false;
370 }
371}
372
373static QString propertyGetter(const QDBusIntrospection::Property &property)
374{
375 QString getter = property.annotations.value(key: "org.qtproject.QtDBus.PropertyGetter"_L1);
376 if (!getter.isEmpty())
377 return getter;
378
379 getter = property.annotations.value(key: "com.trolltech.QtDBus.propertyGetter"_L1);
380 if (!getter.isEmpty()) {
381 fprintf(stderr, format: "%s: Warning: deprecated annotation 'com.trolltech.QtDBus.propertyGetter' found"
382 " while processing '%s';"
383 " suggest updating to 'org.qtproject.QtDBus.PropertyGetter'\n",
384 PROGRAMNAME, qPrintable(inputFile));
385 return getter;
386 }
387
388 getter = property.name;
389 getter[0] = getter[0].toLower();
390 return getter;
391}
392
393static QString propertySetter(const QDBusIntrospection::Property &property)
394{
395 QString setter = property.annotations.value(key: "org.qtproject.QtDBus.PropertySetter"_L1);
396 if (!setter.isEmpty())
397 return setter;
398
399 setter = property.annotations.value(key: "com.trolltech.QtDBus.propertySetter"_L1);
400 if (!setter.isEmpty()) {
401 fprintf(stderr, format: "%s: Warning: deprecated annotation 'com.trolltech.QtDBus.propertySetter' found"
402 " while processing '%s';"
403 " suggest updating to 'org.qtproject.QtDBus.PropertySetter'\n",
404 PROGRAMNAME, qPrintable(inputFile));
405 return setter;
406 }
407
408 setter = "set"_L1 + property.name;
409 setter[3] = setter[3].toUpper();
410 return setter;
411}
412
413static QString methodName(const QDBusIntrospection::Method &method)
414{
415 QString name = method.annotations.value(key: u"org.qtproject.QtDBus.MethodName"_s);
416 if (!name.isEmpty())
417 return name;
418
419 return method.name;
420}
421
422static QString stringify(const QString &data)
423{
424 QString retval;
425 qsizetype i;
426 for (i = 0; i < data.size(); ++i) {
427 retval += u'\"';
428 for ( ; i < data.size() && data[i] != u'\n' && data[i] != u'\r'; ++i)
429 if (data[i] == u'\"')
430 retval += "\\\""_L1;
431 else
432 retval += data[i];
433 if (i+1 < data.size() && data[i] == u'\r' && data[i+1] == u'\n')
434 i++;
435 retval += "\\n\"\n"_L1;
436 }
437 return retval;
438}
439
440static bool openFile(const QString &fileName, QFile &file)
441{
442 if (fileName.isEmpty())
443 return false;
444
445 bool isOk = false;
446 if (fileName == "-"_L1) {
447 isOk = file.open(stdout, ioFlags: QIODevice::WriteOnly | QIODevice::Text);
448 } else {
449 file.setFileName(fileName);
450 isOk = file.open(flags: QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text);
451 }
452
453 if (!isOk)
454 fprintf(stderr, format: "%s: Unable to open '%s': %s\n",
455 PROGRAMNAME, qPrintable(fileName), qPrintable(file.errorString()));
456 return isOk;
457}
458
459static void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces)
460{
461 // open the file
462 QString headerName = header(name: filename);
463 QByteArray headerData;
464 QTextStream hs(&headerData);
465
466 QString cppName = cpp(name: filename);
467 QByteArray cppData;
468 QTextStream cs(&cppData);
469
470 // write the header:
471 writeHeader(ts&: hs, changesWillBeLost: true);
472 if (cppName != headerName)
473 writeHeader(ts&: cs, changesWillBeLost: false);
474
475 // include guards:
476 QString includeGuard;
477 if (!headerName.isEmpty() && headerName != "-"_L1) {
478 includeGuard = headerName.toUpper().replace(before: u'.', after: u'_');
479 qsizetype pos = includeGuard.lastIndexOf(c: u'/');
480 if (pos != -1)
481 includeGuard = includeGuard.mid(position: pos + 1);
482 } else {
483 includeGuard = u"QDBUSXML2CPP_PROXY"_s;
484 }
485
486 hs << "#ifndef " << includeGuard << "\n"
487 "#define " << includeGuard << "\n\n";
488
489 // include our stuff:
490 hs << "#include <QtCore/QObject>\n"
491 << includeList;
492#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
493 hs << "#include <QtDBus/QtDBus>\n";
494#else
495 hs << "#include <QtDBus/QDBusAbstractInterface>\n"
496 "#include <QtDBus/QDBusPendingReply>\n";
497#endif
498
499 for (const QString &include : std::as_const(t&: includes)) {
500 hs << "#include \"" << include << "\"\n";
501 if (headerName.isEmpty())
502 cs << "#include \"" << include << "\"\n";
503 }
504
505 for (const QString &include : std::as_const(t&: globalIncludes)) {
506 hs << "#include <" << include << ">\n";
507 if (headerName.isEmpty())
508 cs << "#include <" << include << ">\n";
509 }
510
511 hs << "\n";
512
513 if (cppName != headerName) {
514 if (!headerName.isEmpty() && headerName != "-"_L1)
515 cs << "#include \"" << headerName << "\"\n\n";
516 }
517
518 for (const QDBusIntrospection::Interface *interface : interfaces) {
519 QString className = classNameForInterface(interface: interface->name, classType: Proxy);
520
521 // comment:
522 hs << "/*\n"
523 " * Proxy class for interface " << interface->name << "\n"
524 " */\n";
525 cs << "/*\n"
526 " * Implementation of interface class " << className << "\n"
527 " */\n\n";
528
529 // class header:
530 hs << "class " << className << ": public QDBusAbstractInterface\n"
531 "{\n"
532 " Q_OBJECT\n";
533
534 // the interface name
535 hs << "public:\n"
536 " static inline const char *staticInterfaceName()\n"
537 " { return \"" << interface->name << "\"; }\n\n";
538
539 // constructors/destructors:
540 hs << "public:\n"
541 " " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);\n\n"
542 " ~" << className << "();\n\n";
543 cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)\n"
544 " : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)\n"
545 "{\n"
546 "}\n\n"
547 << className << "::~" << className << "()\n"
548 "{\n"
549 "}\n\n";
550
551 // properties:
552 for (const QDBusIntrospection::Property &property : interface->properties) {
553 QByteArray type = qtTypeName(where: property.name, signature: property.type, annotations: property.annotations);
554 QString getter = propertyGetter(property);
555 QString setter = propertySetter(property);
556
557 hs << " Q_PROPERTY(" << type << " " << property.name;
558
559 // getter:
560 if (property.access != QDBusIntrospection::Property::Write)
561 // it's readable
562 hs << " READ " << getter;
563
564 // setter
565 if (property.access != QDBusIntrospection::Property::Read)
566 // it's writeable
567 hs << " WRITE " << setter;
568
569 hs << ")\n";
570
571 // getter:
572 if (property.access != QDBusIntrospection::Property::Write) {
573 hs << " inline " << type << " " << getter << "() const\n"
574 " { return qvariant_cast< " << type << " >(property(\""
575 << property.name << "\")); }\n";
576 }
577
578 // setter:
579 if (property.access != QDBusIntrospection::Property::Read) {
580 hs << " inline void " << setter << "(" << constRefArg(arg: type) << "value)\n"
581 " { setProperty(\"" << property.name
582 << "\", QVariant::fromValue(value)); }\n";
583 }
584
585 hs << "\n";
586 }
587
588 // methods:
589 hs << "public Q_SLOTS: // METHODS\n";
590 for (const QDBusIntrospection::Method &method : interface->methods) {
591 bool isDeprecated = method.annotations.value(key: "org.freedesktop.DBus.Deprecated"_L1) == "true"_L1;
592 bool isNoReply =
593 method.annotations.value(ANNOTATION_NO_WAIT ""_L1) == "true"_L1;
594 if (isNoReply && !method.outputArgs.isEmpty()) {
595 fprintf(stderr, format: "%s: warning while processing '%s': method %s in interface %s is marked 'no-reply' but has output arguments.\n",
596 PROGRAMNAME, qPrintable(inputFile), qPrintable(method.name),
597 qPrintable(interface->name));
598 continue;
599 }
600
601 if (isDeprecated)
602 hs << " Q_DECL_DEPRECATED ";
603 else
604 hs << " ";
605
606 if (isNoReply) {
607 hs << "Q_NOREPLY inline void ";
608 } else {
609 hs << "inline QDBusPendingReply<";
610 for (qsizetype i = 0; i < method.outputArgs.size(); ++i)
611 hs << (i > 0 ? ", " : "")
612 << templateArg(arg: qtTypeName(where: method.outputArgs.at(i).name, signature: method.outputArgs.at(i).type,
613 annotations: method.annotations, paramId: i, direction: "Out"));
614 hs << "> ";
615 }
616
617 hs << methodName(method) << "(";
618
619 QStringList argNames = makeArgNames(inputArgs: method.inputArgs);
620 writeArgList(ts&: hs, argNames, annotations: method.annotations, inputArgs: method.inputArgs);
621
622 hs << ")\n"
623 " {\n"
624 " QList<QVariant> argumentList;\n";
625
626 if (!method.inputArgs.isEmpty()) {
627 hs << " argumentList";
628 for (qsizetype argPos = 0; argPos < method.inputArgs.size(); ++argPos)
629 hs << " << QVariant::fromValue(" << argNames.at(i: argPos) << ')';
630 hs << ";\n";
631 }
632
633 if (isNoReply)
634 hs << " callWithArgumentList(QDBus::NoBlock, "
635 "QStringLiteral(\"" << method.name << "\"), argumentList);\n";
636 else
637 hs << " return asyncCallWithArgumentList(QStringLiteral(\""
638 << method.name << "\"), argumentList);\n";
639
640 // close the function:
641 hs << " }\n";
642
643 if (method.outputArgs.size() > 1) {
644 // generate the old-form QDBusReply methods with multiple incoming parameters
645 hs << (isDeprecated ? " Q_DECL_DEPRECATED " : " ") << "inline QDBusReply<"
646 << templateArg(arg: qtTypeName(where: method.outputArgs.first().name, signature: method.outputArgs.first().type,
647 annotations: method.annotations, paramId: 0, direction: "Out"))
648 << "> ";
649 hs << method.name << "(";
650
651 QStringList argNames = makeArgNames(inputArgs: method.inputArgs, outputArgs: method.outputArgs);
652 writeArgList(ts&: hs, argNames, annotations: method.annotations, inputArgs: method.inputArgs, outputArgs: method.outputArgs);
653
654 hs << ")\n"
655 " {\n"
656 " QList<QVariant> argumentList;\n";
657
658 qsizetype argPos = 0;
659 if (!method.inputArgs.isEmpty()) {
660 hs << " argumentList";
661 for (argPos = 0; argPos < method.inputArgs.size(); ++argPos)
662 hs << " << QVariant::fromValue(" << argNames.at(i: argPos) << ')';
663 hs << ";\n";
664 }
665
666 hs << " QDBusMessage reply = callWithArgumentList(QDBus::Block, "
667 "QStringLiteral(\"" << method.name << "\"), argumentList);\n";
668
669 argPos++;
670 hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == "
671 << method.outputArgs.size() << ") {\n";
672
673 // yes, starting from 1
674 for (qsizetype i = 1; i < method.outputArgs.size(); ++i)
675 hs << " " << argNames.at(i: argPos++) << " = qdbus_cast<"
676 << templateArg(arg: qtTypeName(where: method.outputArgs.at(i).name, signature: method.outputArgs.at(i).type,
677 annotations: method.annotations, paramId: i, direction: "Out"))
678 << ">(reply.arguments().at(" << i << "));\n";
679 hs << " }\n"
680 " return reply;\n"
681 " }\n";
682 }
683
684 hs << "\n";
685 }
686
687 hs << "Q_SIGNALS: // SIGNALS\n";
688 for (const QDBusIntrospection::Signal &signal : interface->signals_) {
689 hs << " ";
690 if (signal.annotations.value(key: "org.freedesktop.DBus.Deprecated"_L1) == "true"_L1)
691 hs << "Q_DECL_DEPRECATED ";
692
693 hs << "void " << signal.name << "(";
694
695 QStringList argNames = makeArgNames(inputArgs: signal.outputArgs);
696 writeSignalArgList(ts&: hs, argNames, annotations: signal.annotations, outputArgs: signal.outputArgs);
697
698 hs << ");\n"; // finished for header
699 }
700
701 // close the class:
702 hs << "};\n\n";
703 }
704
705 if (!skipNamespaces) {
706 QStringList last;
707 QDBusIntrospection::Interfaces::ConstIterator it = interfaces.constBegin();
708 do
709 {
710 QStringList current;
711 QString name;
712 if (it != interfaces.constEnd()) {
713 current = it->constData()->name.split(sep: u'.');
714 name = current.takeLast();
715 }
716
717 qsizetype i = 0;
718 while (i < current.size() && i < last.size() && current.at(i) == last.at(i))
719 ++i;
720
721 // i parts matched
722 // close last.arguments().size() - i namespaces:
723 for (qsizetype j = i; j < last.size(); ++j)
724 hs << QString((last.size() - j - 1 + i) * 2, u' ') << "}\n";
725
726 // open current.arguments().size() - i namespaces
727 for (qsizetype j = i; j < current.size(); ++j)
728 hs << QString(j * 2, u' ') << "namespace " << current.at(i: j) << " {\n";
729
730 // add this class:
731 if (!name.isEmpty()) {
732 hs << QString(current.size() * 2, u' ')
733 << "using " << name << " = ::" << classNameForInterface(interface: it->constData()->name, classType: Proxy)
734 << ";\n";
735 }
736
737 if (it == interfaces.constEnd())
738 break;
739 ++it;
740 last = current;
741 } while (true);
742 }
743
744 // close the include guard
745 hs << "#endif\n";
746
747 QString mocName = moc(name: filename);
748 if (includeMocs && !mocName.isEmpty())
749 cs << "\n"
750 "#include \"" << mocName << "\"\n";
751
752 cs.flush();
753 hs.flush();
754
755 QFile file;
756 const bool headerOpen = openFile(fileName: headerName, file);
757 if (headerOpen)
758 file.write(data: headerData);
759
760 if (headerName == cppName) {
761 if (headerOpen)
762 file.write(data: cppData);
763 } else {
764 QFile cppFile;
765 if (openFile(fileName: cppName, file&: cppFile))
766 cppFile.write(data: cppData);
767 }
768}
769
770static void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces)
771{
772 // open the file
773 QString headerName = header(name: filename);
774 QByteArray headerData;
775 QTextStream hs(&headerData);
776
777 QString cppName = cpp(name: filename);
778 QByteArray cppData;
779 QTextStream cs(&cppData);
780
781 // write the headers
782 writeHeader(ts&: hs, changesWillBeLost: false);
783 if (cppName != headerName)
784 writeHeader(ts&: cs, changesWillBeLost: true);
785
786 // include guards:
787 QString includeGuard;
788 if (!headerName.isEmpty() && headerName != "-"_L1) {
789 includeGuard = headerName.toUpper().replace(before: u'.', after: u'_');
790 qsizetype pos = includeGuard.lastIndexOf(c: u'/');
791 if (pos != -1)
792 includeGuard = includeGuard.mid(position: pos + 1);
793 } else {
794 includeGuard = u"QDBUSXML2CPP_ADAPTOR"_s;
795 }
796
797 hs << "#ifndef " << includeGuard << "\n"
798 "#define " << includeGuard << "\n\n";
799
800 // include our stuff:
801 hs << "#include <QtCore/QObject>\n";
802 if (cppName == headerName)
803 hs << "#include <QtCore/QMetaObject>\n"
804 "#include <QtCore/QVariant>\n";
805#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
806 hs << "#include <QtDBus/QtDBus>\n";
807#else
808 hs << "#include <QtDBus/QDBusAbstractAdaptor>\n"
809 "#include <QtDBus/QDBusObjectPath>\n";
810#endif
811
812 for (const QString &include : std::as_const(t&: includes)) {
813 hs << "#include \"" << include << "\"\n";
814 if (headerName.isEmpty())
815 cs << "#include \"" << include << "\"\n";
816 }
817
818 for (const QString &include : std::as_const(t&: globalIncludes)) {
819 hs << "#include <" << include << ">\n";
820 if (headerName.isEmpty())
821 cs << "#include <" << include << ">\n";
822 }
823
824 if (cppName != headerName) {
825 if (!headerName.isEmpty() && headerName != "-"_L1)
826 cs << "#include \"" << headerName << "\"\n";
827
828 cs << "#include <QtCore/QMetaObject>\n"
829 << includeList
830 << "\n";
831 hs << forwardDeclarations;
832 } else {
833 hs << includeList;
834 }
835
836 hs << "\n";
837
838 QString parent = parentClassName;
839 if (parentClassName.isEmpty())
840 parent = u"QObject"_s;
841
842 for (const QDBusIntrospection::Interface *interface : interfaces) {
843 QString className = classNameForInterface(interface: interface->name, classType: Adaptor);
844
845 // comment:
846 hs << "/*\n"
847 " * Adaptor class for interface " << interface->name << "\n"
848 " */\n";
849 cs << "/*\n"
850 " * Implementation of adaptor class " << className << "\n"
851 " */\n\n";
852
853 // class header:
854 hs << "class " << className << ": public QDBusAbstractAdaptor\n"
855 "{\n"
856 " Q_OBJECT\n"
857 " Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")\n"
858 " Q_CLASSINFO(\"D-Bus Introspection\", \"\"\n"
859 << stringify(data: interface->introspection)
860 << " \"\")\n"
861 "public:\n"
862 " " << className << "(" << parent << " *parent);\n"
863 " virtual ~" << className << "();\n\n";
864
865 if (!parentClassName.isEmpty())
866 hs << " inline " << parent << " *parent() const\n"
867 " { return static_cast<" << parent << " *>(QObject::parent()); }\n\n";
868
869 // constructor/destructor
870 cs << className << "::" << className << "(" << parent << " *parent)\n"
871 " : QDBusAbstractAdaptor(parent)\n"
872 "{\n"
873 " // constructor\n"
874 " setAutoRelaySignals(true);\n"
875 "}\n\n"
876 << className << "::~" << className << "()\n"
877 "{\n"
878 " // destructor\n"
879 "}\n\n";
880
881 hs << "public: // PROPERTIES\n";
882 for (const QDBusIntrospection::Property &property : interface->properties) {
883 QByteArray type = qtTypeName(where: property.name, signature: property.type, annotations: property.annotations);
884 QString constRefType = constRefArg(arg: type);
885 QString getter = propertyGetter(property);
886 QString setter = propertySetter(property);
887
888 hs << " Q_PROPERTY(" << type << " " << property.name;
889 if (property.access != QDBusIntrospection::Property::Write)
890 hs << " READ " << getter;
891 if (property.access != QDBusIntrospection::Property::Read)
892 hs << " WRITE " << setter;
893 hs << ")\n";
894
895 // getter:
896 if (property.access != QDBusIntrospection::Property::Write) {
897 hs << " " << type << " " << getter << "() const;\n";
898 cs << type << " "
899 << className << "::" << getter << "() const\n"
900 "{\n"
901 " // get the value of property " << property.name << "\n"
902 " return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));\n"
903 "}\n\n";
904 }
905
906 // setter
907 if (property.access != QDBusIntrospection::Property::Read) {
908 hs << " void " << setter << "(" << constRefType << "value);\n";
909 cs << "void " << className << "::" << setter << "(" << constRefType << "value)\n"
910 "{\n"
911 " // set the value of property " << property.name << "\n"
912 " parent()->setProperty(\"" << property.name << "\", QVariant::fromValue(value";
913 if (constRefType.contains(s: "QDBusVariant"_L1))
914 cs << ".variant()";
915 cs << "));\n"
916 "}\n\n";
917 }
918
919 hs << "\n";
920 }
921
922 hs << "public Q_SLOTS: // METHODS\n";
923 for (const QDBusIntrospection::Method &method : interface->methods) {
924 bool isNoReply =
925 method.annotations.value(ANNOTATION_NO_WAIT ""_L1) == "true"_L1;
926 if (isNoReply && !method.outputArgs.isEmpty()) {
927 fprintf(stderr, format: "%s: warning while processing '%s': method %s in interface %s is marked 'no-reply' but has output arguments.\n",
928 PROGRAMNAME, qPrintable(inputFile), qPrintable(method.name), qPrintable(interface->name));
929 continue;
930 }
931
932 hs << " ";
933 QByteArray returnType;
934 if (isNoReply) {
935 hs << "Q_NOREPLY void ";
936 cs << "void ";
937 } else if (method.outputArgs.isEmpty()) {
938 hs << "void ";
939 cs << "void ";
940 } else {
941 returnType = qtTypeName(where: method.outputArgs.first().name, signature: method.outputArgs.first().type,
942 annotations: method.annotations, paramId: 0, direction: "Out");
943 hs << returnType << " ";
944 cs << returnType << " ";
945 }
946
947 QString name = methodName(method);
948 hs << name << "(";
949 cs << className << "::" << name << "(";
950
951 QStringList argNames = makeArgNames(inputArgs: method.inputArgs, outputArgs: method.outputArgs);
952 writeArgList(ts&: hs, argNames, annotations: method.annotations, inputArgs: method.inputArgs, outputArgs: method.outputArgs);
953 writeArgList(ts&: cs, argNames, annotations: method.annotations, inputArgs: method.inputArgs, outputArgs: method.outputArgs);
954
955 hs << ");\n"; // finished for header
956 cs << ")\n"
957 "{\n"
958 " // handle method call " << interface->name << "." << methodName(method) << "\n";
959
960 // make the call
961 bool usingInvokeMethod = false;
962 if (parentClassName.isEmpty() && method.inputArgs.size() <= 10
963 && method.outputArgs.size() <= 1)
964 usingInvokeMethod = true;
965
966 if (usingInvokeMethod) {
967 // we are using QMetaObject::invokeMethod
968 if (!returnType.isEmpty())
969 cs << " " << returnType << " " << argNames.at(i: method.inputArgs.size())
970 << ";\n";
971
972 static const char invoke[] = " QMetaObject::invokeMethod(parent(), \"";
973 cs << invoke << name << "\"";
974
975 if (!method.outputArgs.isEmpty())
976 cs << ", Q_RETURN_ARG("
977 << qtTypeName(where: method.outputArgs.at(i: 0).name, signature: method.outputArgs.at(i: 0).type, annotations: method.annotations,
978 paramId: 0, direction: "Out")
979 << ", " << argNames.at(i: method.inputArgs.size()) << ")";
980
981 for (qsizetype i = 0; i < method.inputArgs.size(); ++i)
982 cs << ", Q_ARG("
983 << qtTypeName(where: method.inputArgs.at(i).name, signature: method.inputArgs.at(i).type, annotations: method.annotations,
984 paramId: i, direction: "In")
985 << ", " << argNames.at(i) << ")";
986
987 cs << ");\n";
988
989 if (!returnType.isEmpty())
990 cs << " return " << argNames.at(i: method.inputArgs.size()) << ";\n";
991 } else {
992 if (parentClassName.isEmpty())
993 cs << " //";
994 else
995 cs << " ";
996
997 if (!method.outputArgs.isEmpty())
998 cs << "return ";
999
1000 if (parentClassName.isEmpty())
1001 cs << "static_cast<YourObjectType *>(parent())->";
1002 else
1003 cs << "parent()->";
1004 cs << name << "(";
1005
1006 qsizetype argPos = 0;
1007 bool first = true;
1008 for (qsizetype i = 0; i < method.inputArgs.size(); ++i) {
1009 cs << (first ? "" : ", ") << argNames.at(i: argPos++);
1010 first = false;
1011 }
1012 ++argPos; // skip retval, if any
1013 for (qsizetype i = 1; i < method.outputArgs.size(); ++i) {
1014 cs << (first ? "" : ", ") << argNames.at(i: argPos++);
1015 first = false;
1016 }
1017
1018 cs << ");\n";
1019 }
1020 cs << "}\n\n";
1021 }
1022
1023 hs << "Q_SIGNALS: // SIGNALS\n";
1024 for (const QDBusIntrospection::Signal &signal : interface->signals_) {
1025 hs << " void " << signal.name << "(";
1026
1027 QStringList argNames = makeArgNames(inputArgs: signal.outputArgs);
1028 writeSignalArgList(ts&: hs, argNames, annotations: signal.annotations, outputArgs: signal.outputArgs);
1029
1030 hs << ");\n"; // finished for header
1031 }
1032
1033 // close the class:
1034 hs << "};\n\n";
1035 }
1036
1037 // close the include guard
1038 hs << "#endif\n";
1039
1040 QString mocName = moc(name: filename);
1041 if (includeMocs && !mocName.isEmpty())
1042 cs << "\n"
1043 "#include \"" << mocName << "\"\n";
1044
1045 cs.flush();
1046 hs.flush();
1047
1048 QFile file;
1049 const bool headerOpen = openFile(fileName: headerName, file);
1050 if (headerOpen)
1051 file.write(data: headerData);
1052
1053 if (headerName == cppName) {
1054 if (headerOpen)
1055 file.write(data: cppData);
1056 } else {
1057 QFile cppFile;
1058 if (openFile(fileName: cppName, file&: cppFile))
1059 cppFile.write(data: cppData);
1060 }
1061}
1062
1063int main(int argc, char **argv)
1064{
1065 QCoreApplication app(argc, argv);
1066 QCoreApplication::setApplicationName(QStringLiteral(PROGRAMNAME));
1067 QCoreApplication::setApplicationVersion(QStringLiteral(PROGRAMVERSION));
1068
1069 QCommandLineParser parser;
1070 parser.setApplicationDescription(
1071 "Produces the C++ code to implement the interfaces defined in the input file.\n\n"
1072 "If the file name given to the options -a and -p does not end in .cpp or .h, the\n"
1073 "program will automatically append the suffixes and produce both files.\n"
1074 "You can also use a colon (:) to separate the header name from the source file\n"
1075 "name, as in '-a filename_p.h:filename.cpp'.\n\n"
1076 "If you pass a dash (-) as the argument to either -p or -a, the output is written\n"
1077 "to the standard output."_L1);
1078
1079 parser.addHelpOption();
1080 parser.addVersionOption();
1081 parser.addPositionalArgument(name: u"xml-or-xml-file"_s, description: u"XML file to use."_s);
1082 parser.addPositionalArgument(name: u"interfaces"_s, description: u"List of interfaces to use."_s,
1083 syntax: u"[interfaces ...]"_s);
1084
1085 QCommandLineOption adapterCodeOption(QStringList{u"a"_s, u"adaptor"_s},
1086 u"Write the adaptor code to <filename>"_s, u"filename"_s);
1087 parser.addOption(commandLineOption: adapterCodeOption);
1088
1089 QCommandLineOption classNameOption(QStringList{u"c"_s, u"classname"_s},
1090 u"Use <classname> as the class name for the generated classes. "
1091 u"This option can only be used when processing a single interface."_s,
1092 u"classname"_s);
1093 parser.addOption(commandLineOption: classNameOption);
1094
1095 QCommandLineOption addIncludeOption(QStringList{u"i"_s, u"include"_s},
1096 u"Add #include \"filename\" to the output"_s, u"filename"_s);
1097 parser.addOption(commandLineOption: addIncludeOption);
1098
1099 QCommandLineOption addGlobalIncludeOption(QStringList{u"I"_s, u"global-include"_s},
1100 u"Add #include <filename> to the output"_s, u"filename"_s);
1101 parser.addOption(commandLineOption: addGlobalIncludeOption);
1102
1103 QCommandLineOption adapterParentOption(u"l"_s,
1104 u"When generating an adaptor, use <classname> as the parent class"_s, u"classname"_s);
1105 parser.addOption(commandLineOption: adapterParentOption);
1106
1107 QCommandLineOption mocIncludeOption(QStringList{u"m"_s, u"moc"_s},
1108 u"Generate #include \"filename.moc\" statements in the .cpp files"_s);
1109 parser.addOption(commandLineOption: mocIncludeOption);
1110
1111 QCommandLineOption noNamespaceOption(QStringList{u"N"_s, u"no-namespaces"_s},
1112 u"Don't use namespaces"_s);
1113 parser.addOption(commandLineOption: noNamespaceOption);
1114
1115 QCommandLineOption proxyCodeOption(QStringList{u"p"_s, u"proxy"_s},
1116 u"Write the proxy code to <filename>"_s, u"filename"_s);
1117 parser.addOption(commandLineOption: proxyCodeOption);
1118
1119 QCommandLineOption verboseOption(QStringList{u"V"_s, u"verbose"_s},
1120 u"Be verbose."_s);
1121 parser.addOption(commandLineOption: verboseOption);
1122
1123 parser.process(app);
1124
1125 adaptorFile = parser.value(option: adapterCodeOption);
1126 globalClassName = parser.value(option: classNameOption);
1127 includes = parser.values(option: addIncludeOption);
1128 globalIncludes = parser.values(option: addGlobalIncludeOption);
1129 parentClassName = parser.value(option: adapterParentOption);
1130 includeMocs = parser.isSet(option: mocIncludeOption);
1131 skipNamespaces = parser.isSet(option: noNamespaceOption);
1132 proxyFile = parser.value(option: proxyCodeOption);
1133 verbose = parser.isSet(option: verboseOption);
1134
1135 wantedInterfaces = parser.positionalArguments();
1136 if (!wantedInterfaces.isEmpty()) {
1137 inputFile = wantedInterfaces.takeFirst();
1138
1139 QFileInfo inputInfo(inputFile);
1140 if (!inputInfo.exists() || !inputInfo.isFile() || !inputInfo.isReadable()) {
1141 qCritical(msg: "Error: Input %s is not a file or cannot be accessed\n", qPrintable(inputFile));
1142 return 1;
1143 }
1144 }
1145
1146 if (verbose)
1147 QLoggingCategory::setFilterRules(u"dbus.parser.debug=true"_s);
1148
1149 QDBusIntrospection::Interfaces interfaces = readInput();
1150 cleanInterfaces(interfaces);
1151
1152 if (!globalClassName.isEmpty() && interfaces.count() != 1) {
1153 qCritical(msg: "Option -c/--classname can only be used with a single interface.\n");
1154 return 1;
1155 }
1156
1157 QStringList args = app.arguments();
1158 args.removeFirst();
1159 commandLine = PROGRAMNAME " "_L1 + args.join(sep: u' ');
1160
1161 if (!proxyFile.isEmpty() || adaptorFile.isEmpty())
1162 writeProxy(filename: proxyFile, interfaces);
1163
1164 if (!adaptorFile.isEmpty())
1165 writeAdaptor(filename: adaptorFile, interfaces);
1166
1167 return 0;
1168}
1169
1170

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