/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include <QtCore/qbytearray.h> | |
#include <QtCore/qcoreapplication.h> | |
#include <QtCore/qdatetime.h> | |
#include <QtCore/qdebug.h> | |
#include <QtCore/qfile.h> | |
#include <QtCore/qstring.h> | |
#include <QtCore/qstringlist.h> | |
#include <QtCore/qtextstream.h> | |
#include <QtCore/qset.h> | |
#include <QtDBus/QtDBus> | |
#include "private/qdbusmetaobject_p.h" | |
#include "private/qdbusintrospection_p.h" | |
#include <stdio.h> | |
#include <stdlib.h> | |
#define PROGRAMNAME "qdbusxml2cpp" | |
#define PROGRAMVERSION "0.7" | |
#define PROGRAMCOPYRIGHT "Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies)." | |
#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply" | |
static QString globalClassName; | |
static QString parentClassName; | |
static QString proxyFile; | |
static QString adaptorFile; | |
static QString inputFile; | |
static bool skipNamespaces; | |
static bool verbose; | |
static bool includeMocs; | |
static QString commandLine; | |
static QStringList includes; | |
static QStringList wantedInterfaces; | |
static const char help[] = | |
"Usage: " PROGRAMNAME " [options...] [xml-or-xml-file] [interfaces...]\n" | |
"Produces the C++ code to implement the interfaces defined in the input file.\n" | |
"\n" | |
"Options:\n" | |
" -a <filename> Write the adaptor code to <filename>\n" | |
" -c <classname> Use <classname> as the class name for the generated classes\n" | |
" -h Show this information\n" | |
" -i <filename> Add #include to the output\n" | |
" -l <classname> When generating an adaptor, use <classname> as the parent class\n" | |
" -m Generate #include \"filename.moc\" statements in the .cpp files\n" | |
" -N Don't use namespaces\n" | |
" -p <filename> Write the proxy code to <filename>\n" | |
" -v Be verbose.\n" | |
" -V Show the program version and quit.\n" | |
"\n" | |
"If the file name given to the options -a and -p does not end in .cpp or .h, the\n" | |
"program will automatically append the suffixes and produce both files.\n" | |
"You can also use a colon (:) to separate the header name from the source file\n" | |
"name, as in '-a filename_p.h:filename.cpp'.\n" | |
"\n" | |
"If you pass a dash (-) as the argument to either -p or -a, the output is written\n" | |
"to the standard output\n"; | |
static const char includeList[] = | |
"#include <QtCore/QByteArray>\n" | |
"#include <QtCore/QList>\n" | |
"#include <QtCore/QMap>\n" | |
"#include <QtCore/QString>\n" | |
"#include <QtCore/QStringList>\n" | |
"#include <QtCore/QVariant>\n"; | |
static const char forwardDeclarations[] = | |
"class QByteArray;\n" | |
"template<class T> class QList;\n" | |
"template<class Key, class Value> class QMap;\n" | |
"class QString;\n" | |
"class QStringList;\n" | |
"class QVariant;\n"; | |
static void showHelp() | |
{ | |
printf("%s", help); | |
exit(0); | |
} | |
static void showVersion() | |
{ | |
printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION); | |
printf("D-Bus binding tool for Qt\n"); | |
exit(0); | |
} | |
static QString nextArg(QStringList &args, int i, char opt) | |
{ | |
QString arg = args.value(i); | |
if (arg.isEmpty()) { | |
printf("-%c needs at least one argument\n", opt); | |
exit(1); | |
} | |
return args.takeAt(i); | |
} | |
static void parseCmdLine(QStringList args) | |
{ | |
args.takeFirst(); | |
commandLine = QLatin1String(PROGRAMNAME " "); | |
commandLine += args.join(QLatin1String(" ")); | |
int i = 0; | |
while (i < args.count()) { | |
if (!args.at(i).startsWith(QLatin1Char('-'))) { | |
++i; | |
continue; | |
} | |
QString arg = args.takeAt(i); | |
char c = '\0'; | |
if (arg.length() == 2) | |
c = arg.at(1).toLatin1(); | |
else if (arg == QLatin1String("--help")) | |
c = 'h'; | |
switch (c) { | |
case 'a': | |
adaptorFile = nextArg(args, i, 'a'); | |
break; | |
case 'c': | |
globalClassName = nextArg(args, i, 'c'); | |
break; | |
case 'v': | |
verbose = true; | |
break; | |
case 'i': | |
includes << nextArg(args, i, 'i'); | |
break; | |
case 'l': | |
parentClassName = nextArg(args, i, 'l'); | |
break; | |
case 'm': | |
includeMocs = true; | |
break; | |
case 'N': | |
skipNamespaces = true; | |
break; | |
case '?': | |
case 'h': | |
showHelp(); | |
break; | |
case 'V': | |
showVersion(); | |
break; | |
case 'p': | |
proxyFile = nextArg(args, i, 'p'); | |
break; | |
default: | |
printf("unknown option: '%s'\n", qPrintable(arg)); | |
exit(1); | |
} | |
} | |
if (!args.isEmpty()) | |
inputFile = args.takeFirst(); | |
wantedInterfaces << args; | |
} | |
static QDBusIntrospection::Interfaces readInput() | |
{ | |
QFile input(inputFile); | |
if (inputFile.isEmpty() || inputFile == QLatin1String("-")) | |
input.open(stdin, QIODevice::ReadOnly); | |
else | |
input.open(QIODevice::ReadOnly); | |
QByteArray data = input.readAll(); | |
// check if the input is already XML | |
data = data.trimmed(); | |
if (data.startsWith("<!DOCTYPE ") || data.startsWith("<?xml") || | |
data.startsWith("<node") || data.startsWith("<interface")) | |
// already XML | |
return QDBusIntrospection::parseInterfaces(QString::fromUtf8(data)); | |
fprintf(stderr, "Cannot process input: '%s'. Stop.\n", qPrintable(inputFile)); | |
exit(1); | |
} | |
static void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces) | |
{ | |
if (!wantedInterfaces.isEmpty()) { | |
QDBusIntrospection::Interfaces::Iterator it = interfaces.begin(); | |
while (it != interfaces.end()) | |
if (!wantedInterfaces.contains(it.key())) | |
it = interfaces.erase(it); | |
else | |
++it; | |
} | |
} | |
// produce a header name from the file name | |
static QString header(const QString &name) | |
{ | |
QStringList parts = name.split(QLatin1Char(':')); | |
QString retval = parts.first(); | |
if (retval.isEmpty() || retval == QLatin1String("-")) | |
return retval; | |
if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) && | |
!retval.endsWith(QLatin1String(".cc"))) | |
retval.append(QLatin1String(".h")); | |
return retval; | |
} | |
// produce a cpp name from the file name | |
static QString cpp(const QString &name) | |
{ | |
QStringList parts = name.split(QLatin1Char(':')); | |
QString retval = parts.last(); | |
if (retval.isEmpty() || retval == QLatin1String("-")) | |
return retval; | |
if (!retval.endsWith(QLatin1String(".h")) && !retval.endsWith(QLatin1String(".cpp")) && | |
!retval.endsWith(QLatin1String(".cc"))) | |
retval.append(QLatin1String(".cpp")); | |
return retval; | |
} | |
// produce a moc name from the file name | |
static QString moc(const QString &name) | |
{ | |
QString retval = header(name); | |
if (retval.isEmpty()) | |
return retval; | |
retval.truncate(retval.length() - 1); // drop the h in .h | |
retval += QLatin1String("moc"); | |
return retval; | |
} | |
static QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost) | |
{ | |
ts << "/*" << endl | |
<< " * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION << endl | |
<< " * Command line was: " << commandLine << endl | |
<< " *" << endl | |
<< " * " PROGRAMNAME " is " PROGRAMCOPYRIGHT << endl | |
<< " *" << endl | |
<< " * This is an auto-generated file." << endl; | |
if (changesWillBeLost) | |
ts << " * Do not edit! All changes made to it will be lost." << endl; | |
else | |
ts << " * This file may have been hand-edited. Look for HAND-EDIT comments" << endl | |
<< " * before re-generating it." << endl; | |
ts << " */" << endl | |
<< endl; | |
return ts; | |
} | |
enum ClassType { Proxy, Adaptor }; | |
static QString classNameForInterface(const QString &interface, ClassType classType) | |
{ | |
if (!globalClassName.isEmpty()) | |
return globalClassName; | |
QStringList parts = interface.split(QLatin1Char('.')); | |
QString retval; | |
if (classType == Proxy) | |
foreach (QString part, parts) { | |
part[0] = part[0].toUpper(); | |
retval += part; | |
} | |
else { | |
retval = parts.last(); | |
retval[0] = retval[0].toUpper(); | |
} | |
if (classType == Proxy) | |
retval += QLatin1String("Interface"); | |
else | |
retval += QLatin1String("Adaptor"); | |
return retval; | |
} | |
static QByteArray qtTypeName(const QString &signature, const QDBusIntrospection::Annotations &annotations, int paramId = -1, const char *direction = "Out") | |
{ | |
int type = QDBusMetaType::signatureToType(signature.toLatin1()); | |
if (type == QVariant::Invalid) { | |
QString annotationName = QString::fromLatin1("com.trolltech.QtDBus.QtTypeName"); | |
if (paramId >= 0) | |
annotationName += QString::fromLatin1(".%1%2").arg(QLatin1String(direction)).arg(paramId); | |
QString qttype = annotations.value(annotationName); | |
if (!qttype.isEmpty()) | |
return qttype.toLatin1(); | |
fprintf(stderr, "Got unknown type `%s'\n", qPrintable(signature)); | |
fprintf(stderr, "You should add <annotation name=\"%s\" value=\"<type>\"/> to the XML description\n", | |
qPrintable(annotationName)); | |
exit(1); | |
} | |
return QVariant::typeToName(QVariant::Type(type)); | |
} | |
static QString nonConstRefArg(const QByteArray &arg) | |
{ | |
return QLatin1String(arg + " &"); | |
} | |
static QString templateArg(const QByteArray &arg) | |
{ | |
if (!arg.endsWith('>')) | |
return QLatin1String(arg); | |
return QLatin1String(arg + ' '); | |
} | |
static QString constRefArg(const QByteArray &arg) | |
{ | |
if (!arg.startsWith('Q')) | |
return QLatin1String(arg + ' '); | |
else | |
return QString( QLatin1String("const %1 &") ).arg( QLatin1String(arg) ); | |
} | |
static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs, | |
const QDBusIntrospection::Arguments &outputArgs = | |
QDBusIntrospection::Arguments()) | |
{ | |
QStringList retval; | |
for (int i = 0; i < inputArgs.count(); ++i) { | |
const QDBusIntrospection::Argument &arg = inputArgs.at(i); | |
QString name = arg.name; | |
if (name.isEmpty()) | |
name = QString( QLatin1String("in%1") ).arg(i); | |
while (retval.contains(name)) | |
name += QLatin1String("_"); | |
retval << name; | |
} | |
for (int i = 0; i < outputArgs.count(); ++i) { | |
const QDBusIntrospection::Argument &arg = outputArgs.at(i); | |
QString name = arg.name; | |
if (name.isEmpty()) | |
name = QString( QLatin1String("out%1") ).arg(i); | |
while (retval.contains(name)) | |
name += QLatin1String("_"); | |
retval << name; | |
} | |
return retval; | |
} | |
static void writeArgList(QTextStream &ts, const QStringList &argNames, | |
const QDBusIntrospection::Annotations &annotations, | |
const QDBusIntrospection::Arguments &inputArgs, | |
const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments()) | |
{ | |
// input args: | |
bool first = true; | |
int argPos = 0; | |
for (int i = 0; i < inputArgs.count(); ++i) { | |
const QDBusIntrospection::Argument &arg = inputArgs.at(i); | |
QString type = constRefArg(qtTypeName(arg.type, annotations, i, "In")); | |
if (!first) | |
ts << ", "; | |
ts << type << argNames.at(argPos++); | |
first = false; | |
} | |
argPos++; | |
// output args | |
// yes, starting from 1 | |
for (int i = 1; i < outputArgs.count(); ++i) { | |
const QDBusIntrospection::Argument &arg = outputArgs.at(i); | |
QString name = arg.name; | |
if (!first) | |
ts << ", "; | |
ts << nonConstRefArg(qtTypeName(arg.type, annotations, i, "Out")) | |
<< argNames.at(argPos++); | |
first = false; | |
} | |
} | |
static QString propertyGetter(const QDBusIntrospection::Property &property) | |
{ | |
QString getter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertyGetter")); | |
if (getter.isEmpty()) { | |
getter = property.name; | |
getter[0] = getter[0].toLower(); | |
} | |
return getter; | |
} | |
static QString propertySetter(const QDBusIntrospection::Property &property) | |
{ | |
QString setter = property.annotations.value(QLatin1String("com.trolltech.QtDBus.propertySetter")); | |
if (setter.isEmpty()) { | |
setter = QLatin1String("set") + property.name; | |
setter[3] = setter[3].toUpper(); | |
} | |
return setter; | |
} | |
static QString stringify(const QString &data) | |
{ | |
QString retval; | |
int i; | |
for (i = 0; i < data.length(); ++i) { | |
retval += QLatin1Char('\"'); | |
for ( ; i < data.length() && data[i] != QLatin1Char('\n'); ++i) | |
if (data[i] == QLatin1Char('\"')) | |
retval += QLatin1String("\\\""); | |
else | |
retval += data[i]; | |
retval += QLatin1String("\\n\"\n"); | |
} | |
return retval; | |
} | |
static void openFile(const QString &fileName, QFile &file) | |
{ | |
if (fileName.isEmpty()) | |
return; | |
bool isOk = false; | |
if (fileName == QLatin1String("-")) { | |
isOk = file.open(stdout, QIODevice::WriteOnly | QIODevice::Text); | |
} else { | |
file.setFileName(fileName); | |
isOk = file.open(QIODevice::WriteOnly | QIODevice::Truncate | QIODevice::Text); | |
} | |
if (!isOk) | |
fprintf(stderr, "Unable to open '%s': %s\n", qPrintable(fileName), | |
qPrintable(file.errorString())); | |
} | |
static void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces) | |
{ | |
// open the file | |
QString headerName = header(filename); | |
QByteArray headerData; | |
QTextStream hs(&headerData); | |
QString cppName = cpp(filename); | |
QByteArray cppData; | |
QTextStream cs(&cppData); | |
// write the header: | |
writeHeader(hs, true); | |
if (cppName != headerName) | |
writeHeader(cs, false); | |
// include guards: | |
QString includeGuard; | |
if (!headerName.isEmpty() && headerName != QLatin1String("-")) { | |
includeGuard = headerName.toUpper().replace(QLatin1Char('.'), QLatin1Char('_')); | |
int pos = includeGuard.lastIndexOf(QLatin1Char('/')); | |
if (pos != -1) | |
includeGuard = includeGuard.mid(pos + 1); | |
} else { | |
includeGuard = QLatin1String("QDBUSXML2CPP_PROXY"); | |
} | |
includeGuard = QString(QLatin1String("%1_%2")) | |
.arg(includeGuard) | |
.arg(QDateTime::currentDateTime().toTime_t()); | |
hs << "#ifndef " << includeGuard << endl | |
<< "#define " << includeGuard << endl | |
<< endl; | |
// include our stuff: | |
hs << "#include <QtCore/QObject>" << endl | |
<< includeList | |
<< "#include <QtDBus/QtDBus>" << endl; | |
foreach (QString include, includes) { | |
hs << "#include \"" << include << "\"" << endl; | |
if (headerName.isEmpty()) | |
cs << "#include \"" << include << "\"" << endl; | |
} | |
hs << endl; | |
if (cppName != headerName) { | |
if (!headerName.isEmpty() && headerName != QLatin1String("-")) | |
cs << "#include \"" << headerName << "\"" << endl << endl; | |
} | |
foreach (const QDBusIntrospection::Interface *interface, interfaces) { | |
QString className = classNameForInterface(interface->name, Proxy); | |
// comment: | |
hs << "/*" << endl | |
<< " * Proxy class for interface " << interface->name << endl | |
<< " */" << endl; | |
cs << "/*" << endl | |
<< " * Implementation of interface class " << className << endl | |
<< " */" << endl | |
<< endl; | |
// class header: | |
hs << "class " << className << ": public QDBusAbstractInterface" << endl | |
<< "{" << endl | |
<< " Q_OBJECT" << endl; | |
// the interface name | |
hs << "public:" << endl | |
<< " static inline const char *staticInterfaceName()" << endl | |
<< " { return \"" << interface->name << "\"; }" << endl | |
<< endl; | |
// constructors/destructors: | |
hs << "public:" << endl | |
<< " " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = 0);" << endl | |
<< endl | |
<< " ~" << className << "();" << endl | |
<< endl; | |
cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)" << endl | |
<< " : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)" << endl | |
<< "{" << endl | |
<< "}" << endl | |
<< endl | |
<< className << "::~" << className << "()" << endl | |
<< "{" << endl | |
<< "}" << endl | |
<< endl; | |
// properties: | |
foreach (const QDBusIntrospection::Property &property, interface->properties) { | |
QByteArray type = qtTypeName(property.type, property.annotations); | |
QString templateType = templateArg(type); | |
QString constRefType = constRefArg(type); | |
QString getter = propertyGetter(property); | |
QString setter = propertySetter(property); | |
hs << " Q_PROPERTY(" << type << " " << property.name; | |
// getter: | |
if (property.access != QDBusIntrospection::Property::Write) | |
// it's readble | |
hs << " READ " << getter; | |
// setter | |
if (property.access != QDBusIntrospection::Property::Read) | |
// it's writeable | |
hs << " WRITE " << setter; | |
hs << ")" << endl; | |
// getter: | |
if (property.access != QDBusIntrospection::Property::Write) { | |
hs << " inline " << type << " " << getter << "() const" << endl | |
<< " { return qvariant_cast< " << type << " >(property(\"" | |
<< property.name << "\")); }" << endl; | |
} | |
// setter: | |
if (property.access != QDBusIntrospection::Property::Read) { | |
hs << " inline void " << setter << "(" << constRefArg(type) << "value)" << endl | |
<< " { setProperty(\"" << property.name | |
<< "\", qVariantFromValue(value)); }" << endl; | |
} | |
hs << endl; | |
} | |
// methods: | |
hs << "public Q_SLOTS: // METHODS" << endl; | |
foreach (const QDBusIntrospection::Method &method, interface->methods) { | |
bool isDeprecated = method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == QLatin1String("true"); | |
bool isNoReply = | |
method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true"); | |
if (isNoReply && !method.outputArgs.isEmpty()) { | |
fprintf(stderr, "warning: method %s in interface %s is marked 'no-reply' but has output arguments.\n", | |
qPrintable(method.name), qPrintable(interface->name)); | |
continue; | |
} | |
hs << " inline " | |
<< (isDeprecated ? "Q_DECL_DEPRECATED " : ""); | |
if (isNoReply) { | |
hs << "Q_NOREPLY void "; | |
} else { | |
hs << "QDBusPendingReply<"; | |
for (int i = 0; i < method.outputArgs.count(); ++i) | |
hs << (i > 0 ? ", " : "") | |
<< templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out")); | |
hs << "> "; | |
} | |
hs << method.name << "("; | |
QStringList argNames = makeArgNames(method.inputArgs); | |
writeArgList(hs, argNames, method.annotations, method.inputArgs); | |
hs << ")" << endl | |
<< " {" << endl | |
<< " QList<QVariant> argumentList;" << endl; | |
if (!method.inputArgs.isEmpty()) { | |
hs << " argumentList"; | |
for (int argPos = 0; argPos < method.inputArgs.count(); ++argPos) | |
hs << " << qVariantFromValue(" << argNames.at(argPos) << ')'; | |
hs << ";" << endl; | |
} | |
if (isNoReply) | |
hs << " callWithArgumentList(QDBus::NoBlock, " | |
<< "QLatin1String(\"" << method.name << "\"), argumentList);" << endl; | |
else | |
hs << " return asyncCallWithArgumentList(QLatin1String(\"" | |
<< method.name << "\"), argumentList);" << endl; | |
// close the function: | |
hs << " }" << endl; | |
if (method.outputArgs.count() > 1) { | |
// generate the old-form QDBusReply methods with multiple incoming parameters | |
hs << " inline " | |
<< (isDeprecated ? "Q_DECL_DEPRECATED " : "") | |
<< "QDBusReply<" | |
<< templateArg(qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out")) << "> "; | |
hs << method.name << "("; | |
QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs); | |
writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs); | |
hs << ")" << endl | |
<< " {" << endl | |
<< " QList<QVariant> argumentList;" << endl; | |
int argPos = 0; | |
if (!method.inputArgs.isEmpty()) { | |
hs << " argumentList"; | |
for (argPos = 0; argPos < method.inputArgs.count(); ++argPos) | |
hs << " << qVariantFromValue(" << argNames.at(argPos) << ')'; | |
hs << ";" << endl; | |
} | |
hs << " QDBusMessage reply = callWithArgumentList(QDBus::Block, " | |
<< "QLatin1String(\"" << method.name << "\"), argumentList);" << endl; | |
argPos++; | |
hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == " | |
<< method.outputArgs.count() << ") {" << endl; | |
// yes, starting from 1 | |
for (int i = 1; i < method.outputArgs.count(); ++i) | |
hs << " " << argNames.at(argPos++) << " = qdbus_cast<" | |
<< templateArg(qtTypeName(method.outputArgs.at(i).type, method.annotations, i, "Out")) | |
<< ">(reply.arguments().at(" << i << "));" << endl; | |
hs << " }" << endl | |
<< " return reply;" << endl | |
<< " }" << endl; | |
} | |
hs << endl; | |
} | |
hs << "Q_SIGNALS: // SIGNALS" << endl; | |
foreach (const QDBusIntrospection::Signal &signal, interface->signals_) { | |
hs << " "; | |
if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == | |
QLatin1String("true")) | |
hs << "Q_DECL_DEPRECATED "; | |
hs << "void " << signal.name << "("; | |
QStringList argNames = makeArgNames(signal.outputArgs); | |
writeArgList(hs, argNames, signal.annotations, signal.outputArgs); | |
hs << ");" << endl; // finished for header | |
} | |
// close the class: | |
hs << "};" << endl | |
<< endl; | |
} | |
if (!skipNamespaces) { | |
QStringList last; | |
QDBusIntrospection::Interfaces::ConstIterator it = interfaces.constBegin(); | |
do | |
{ | |
QStringList current; | |
QString name; | |
if (it != interfaces.constEnd()) { | |
current = it->constData()->name.split(QLatin1Char('.')); | |
name = current.takeLast(); | |
} | |
int i = 0; | |
while (i < current.count() && i < last.count() && current.at(i) == last.at(i)) | |
++i; | |
// i parts matched | |
// close last.arguments().count() - i namespaces: | |
for (int j = i; j < last.count(); ++j) | |
hs << QString((last.count() - j - 1 + i) * 2, QLatin1Char(' ')) << "}" << endl; | |
// open current.arguments().count() - i namespaces | |
for (int j = i; j < current.count(); ++j) | |
hs << QString(j * 2, QLatin1Char(' ')) << "namespace " << current.at(j) << " {" << endl; | |
// add this class: | |
if (!name.isEmpty()) { | |
hs << QString(current.count() * 2, QLatin1Char(' ')) | |
<< "typedef ::" << classNameForInterface(it->constData()->name, Proxy) | |
<< " " << name << ";" << endl; | |
} | |
if (it == interfaces.constEnd()) | |
break; | |
++it; | |
last = current; | |
} while (true); | |
} | |
// close the include guard | |
hs << "#endif" << endl; | |
QString mocName = moc(filename); | |
if (includeMocs && !mocName.isEmpty()) | |
cs << endl | |
<< "#include \"" << mocName << "\"" << endl; | |
cs.flush(); | |
hs.flush(); | |
QFile file; | |
openFile(headerName, file); | |
file.write(headerData); | |
if (headerName == cppName) { | |
file.write(cppData); | |
} else { | |
QFile cppFile; | |
openFile(cppName, cppFile); | |
cppFile.write(cppData); | |
} | |
} | |
static void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces) | |
{ | |
// open the file | |
QString headerName = header(filename); | |
QByteArray headerData; | |
QTextStream hs(&headerData); | |
QString cppName = cpp(filename); | |
QByteArray cppData; | |
QTextStream cs(&cppData); | |
// write the headers | |
writeHeader(hs, false); | |
if (cppName != headerName) | |
writeHeader(cs, true); | |
// include guards: | |
QString includeGuard; | |
if (!headerName.isEmpty() && headerName != QLatin1String("-")) { | |
includeGuard = headerName.toUpper().replace(QLatin1Char('.'), QLatin1Char('_')); | |
int pos = includeGuard.lastIndexOf(QLatin1Char('/')); | |
if (pos != -1) | |
includeGuard = includeGuard.mid(pos + 1); | |
} else { | |
includeGuard = QLatin1String("QDBUSXML2CPP_ADAPTOR"); | |
} | |
includeGuard = QString(QLatin1String("%1_%2")) | |
.arg(includeGuard) | |
.arg(QDateTime::currentDateTime().toTime_t()); | |
hs << "#ifndef " << includeGuard << endl | |
<< "#define " << includeGuard << endl | |
<< endl; | |
// include our stuff: | |
hs << "#include <QtCore/QObject>" << endl; | |
if (cppName == headerName) | |
hs << "#include <QtCore/QMetaObject>" << endl | |
<< "#include <QtCore/QVariant>" << endl; | |
hs << "#include <QtDBus/QtDBus>" << endl; | |
foreach (QString include, includes) { | |
hs << "#include \"" << include << "\"" << endl; | |
if (headerName.isEmpty()) | |
cs << "#include \"" << include << "\"" << endl; | |
} | |
if (cppName != headerName) { | |
if (!headerName.isEmpty() && headerName != QLatin1String("-")) | |
cs << "#include \"" << headerName << "\"" << endl; | |
cs << "#include <QtCore/QMetaObject>" << endl | |
<< includeList | |
<< endl; | |
hs << forwardDeclarations; | |
} else { | |
hs << includeList; | |
} | |
hs << endl; | |
QString parent = parentClassName; | |
if (parentClassName.isEmpty()) | |
parent = QLatin1String("QObject"); | |
foreach (const QDBusIntrospection::Interface *interface, interfaces) { | |
QString className = classNameForInterface(interface->name, Adaptor); | |
// comment: | |
hs << "/*" << endl | |
<< " * Adaptor class for interface " << interface->name << endl | |
<< " */" << endl; | |
cs << "/*" << endl | |
<< " * Implementation of adaptor class " << className << endl | |
<< " */" << endl | |
<< endl; | |
// class header: | |
hs << "class " << className << ": public QDBusAbstractAdaptor" << endl | |
<< "{" << endl | |
<< " Q_OBJECT" << endl | |
<< " Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")" << endl | |
<< " Q_CLASSINFO(\"D-Bus Introspection\", \"\"" << endl | |
<< stringify(interface->introspection) | |
<< " \"\")" << endl | |
<< "public:" << endl | |
<< " " << className << "(" << parent << " *parent);" << endl | |
<< " virtual ~" << className << "();" << endl | |
<< endl; | |
if (!parentClassName.isEmpty()) | |
hs << " inline " << parent << " *parent() const" << endl | |
<< " { return static_cast<" << parent << " *>(QObject::parent()); }" << endl | |
<< endl; | |
// constructor/destructor | |
cs << className << "::" << className << "(" << parent << " *parent)" << endl | |
<< " : QDBusAbstractAdaptor(parent)" << endl | |
<< "{" << endl | |
<< " // constructor" << endl | |
<< " setAutoRelaySignals(true);" << endl | |
<< "}" << endl | |
<< endl | |
<< className << "::~" << className << "()" << endl | |
<< "{" << endl | |
<< " // destructor" << endl | |
<< "}" << endl | |
<< endl; | |
hs << "public: // PROPERTIES" << endl; | |
foreach (const QDBusIntrospection::Property &property, interface->properties) { | |
QByteArray type = qtTypeName(property.type, property.annotations); | |
QString constRefType = constRefArg(type); | |
QString getter = propertyGetter(property); | |
QString setter = propertySetter(property); | |
hs << " Q_PROPERTY(" << type << " " << property.name; | |
if (property.access != QDBusIntrospection::Property::Write) | |
hs << " READ " << getter; | |
if (property.access != QDBusIntrospection::Property::Read) | |
hs << " WRITE " << setter; | |
hs << ")" << endl; | |
// getter: | |
if (property.access != QDBusIntrospection::Property::Write) { | |
hs << " " << type << " " << getter << "() const;" << endl; | |
cs << type << " " | |
<< className << "::" << getter << "() const" << endl | |
<< "{" << endl | |
<< " // get the value of property " << property.name << endl | |
<< " return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));" << endl | |
<< "}" << endl | |
<< endl; | |
} | |
// setter | |
if (property.access != QDBusIntrospection::Property::Read) { | |
hs << " void " << setter << "(" << constRefType << "value);" << endl; | |
cs << "void " << className << "::" << setter << "(" << constRefType << "value)" << endl | |
<< "{" << endl | |
<< " // set the value of property " << property.name << endl | |
<< " parent()->setProperty(\"" << property.name << "\", qVariantFromValue(value"; | |
if (constRefType.contains(QLatin1String("QDBusVariant"))) | |
cs << ".variant()"; | |
cs << "));" << endl | |
<< "}" << endl | |
<< endl; | |
} | |
hs << endl; | |
} | |
hs << "public Q_SLOTS: // METHODS" << endl; | |
foreach (const QDBusIntrospection::Method &method, interface->methods) { | |
bool isNoReply = | |
method.annotations.value(QLatin1String(ANNOTATION_NO_WAIT)) == QLatin1String("true"); | |
if (isNoReply && !method.outputArgs.isEmpty()) { | |
fprintf(stderr, "warning: method %s in interface %s is marked 'no-reply' but has output arguments.\n", | |
qPrintable(method.name), qPrintable(interface->name)); | |
continue; | |
} | |
hs << " "; | |
if (method.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == | |
QLatin1String("true")) | |
hs << "Q_DECL_DEPRECATED "; | |
QByteArray returnType; | |
if (isNoReply) { | |
hs << "Q_NOREPLY void "; | |
cs << "void "; | |
} else if (method.outputArgs.isEmpty()) { | |
hs << "void "; | |
cs << "void "; | |
} else { | |
returnType = qtTypeName(method.outputArgs.first().type, method.annotations, 0, "Out"); | |
hs << returnType << " "; | |
cs << returnType << " "; | |
} | |
QString name = method.name; | |
hs << name << "("; | |
cs << className << "::" << name << "("; | |
QStringList argNames = makeArgNames(method.inputArgs, method.outputArgs); | |
writeArgList(hs, argNames, method.annotations, method.inputArgs, method.outputArgs); | |
writeArgList(cs, argNames, method.annotations, method.inputArgs, method.outputArgs); | |
hs << ");" << endl; // finished for header | |
cs << ")" << endl | |
<< "{" << endl | |
<< " // handle method call " << interface->name << "." << method.name << endl; | |
// make the call | |
bool usingInvokeMethod = false; | |
if (parentClassName.isEmpty() && method.inputArgs.count() <= 10 | |
&& method.outputArgs.count() <= 1) | |
usingInvokeMethod = true; | |
if (usingInvokeMethod) { | |
// we are using QMetaObject::invokeMethod | |
if (!returnType.isEmpty()) | |
cs << " " << returnType << " " << argNames.at(method.inputArgs.count()) | |
<< ";" << endl; | |
static const char invoke[] = " QMetaObject::invokeMethod(parent(), \""; | |
cs << invoke << name << "\""; | |
if (!method.outputArgs.isEmpty()) | |
cs << ", Q_RETURN_ARG(" | |
<< qtTypeName(method.outputArgs.at(0).type, method.annotations, | |
0, "Out") | |
<< ", " | |
<< argNames.at(method.inputArgs.count()) | |
<< ")"; | |
for (int i = 0; i < method.inputArgs.count(); ++i) | |
cs << ", Q_ARG(" | |
<< qtTypeName(method.inputArgs.at(i).type, method.annotations, | |
i, "In") | |
<< ", " | |
<< argNames.at(i) | |
<< ")"; | |
cs << ");" << endl; | |
if (!returnType.isEmpty()) | |
cs << " return " << argNames.at(method.inputArgs.count()) << ";" << endl; | |
} else { | |
if (parentClassName.isEmpty()) | |
cs << " //"; | |
else | |
cs << " "; | |
if (!method.outputArgs.isEmpty()) | |
cs << "return "; | |
if (parentClassName.isEmpty()) | |
cs << "static_cast<YourObjectType *>(parent())->"; | |
else | |
cs << "parent()->"; | |
cs << name << "("; | |
int argPos = 0; | |
bool first = true; | |
for (int i = 0; i < method.inputArgs.count(); ++i) { | |
cs << (first ? "" : ", ") << argNames.at(argPos++); | |
first = false; | |
} | |
++argPos; // skip retval, if any | |
for (int i = 1; i < method.outputArgs.count(); ++i) { | |
cs << (first ? "" : ", ") << argNames.at(argPos++); | |
first = false; | |
} | |
cs << ");" << endl; | |
} | |
cs << "}" << endl | |
<< endl; | |
} | |
hs << "Q_SIGNALS: // SIGNALS" << endl; | |
foreach (const QDBusIntrospection::Signal &signal, interface->signals_) { | |
hs << " "; | |
if (signal.annotations.value(QLatin1String("org.freedesktop.DBus.Deprecated")) == | |
QLatin1String("true")) | |
hs << "Q_DECL_DEPRECATED "; | |
hs << "void " << signal.name << "("; | |
QStringList argNames = makeArgNames(signal.outputArgs); | |
writeArgList(hs, argNames, signal.annotations, signal.outputArgs); | |
hs << ");" << endl; // finished for header | |
} | |
// close the class: | |
hs << "};" << endl | |
<< endl; | |
} | |
// close the include guard | |
hs << "#endif" << endl; | |
QString mocName = moc(filename); | |
if (includeMocs && !mocName.isEmpty()) | |
cs << endl | |
<< "#include \"" << mocName << "\"" << endl; | |
cs.flush(); | |
hs.flush(); | |
QFile file; | |
openFile(headerName, file); | |
file.write(headerData); | |
if (headerName == cppName) { | |
file.write(cppData); | |
} else { | |
QFile cppFile; | |
openFile(cppName, cppFile); | |
cppFile.write(cppData); | |
} | |
} | |
int main(int argc, char **argv) | |
{ | |
QCoreApplication app(argc, argv); | |
parseCmdLine(app.arguments()); | |
QDBusIntrospection::Interfaces interfaces = readInput(); | |
cleanInterfaces(interfaces); | |
if (!proxyFile.isEmpty() || adaptorFile.isEmpty()) | |
writeProxy(proxyFile, interfaces); | |
if (!adaptorFile.isEmpty()) | |
writeAdaptor(adaptorFile, interfaces); | |
return 0; | |
} | |
/*! | |
\page qdbusxml2cpp.html | |
\title QtDBus XML compiler (qdbusxml2cpp) | |
\keyword qdbusxml2cpp | |
The QtDBus XML compiler is a tool that can be used to parse interface descriptions and produce | |
static code representing those interfaces, which can then be used to make calls to remote | |
objects or implement said interfaces. | |
\c qdbusxml2cpp has two modes of operation, that correspond to the two possible outputs it can | |
produce: the interface (proxy) class or the adaptor class. The latter consists of both a C++ | |
header and a source file, which are meant to be edited and adapted to your needs. | |
The \c qdbusxml2cpp tool is not meant to be run every time you compile your | |
application. Instead, it's meant to be used when developing the code or when the interface | |
changes. | |
The adaptor classes generated by \c qdbusxml2cpp are just a skeleton that must be completed. It | |
generates, by default, calls to slots with the same name on the object the adaptor is attached | |
to. However, you may modify those slots or the property accessor functions to suit your needs. | |
*/ |