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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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