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