Revision control

Copy as Markdown

// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <qbytearray.h>
#include <qcommandlineparser.h>
#include <qcoreapplication.h>
#include <qdebug.h>
#include <qfile.h>
#include <qfileinfo.h>
#include <qloggingcategory.h>
#include <qstring.h>
#include <qstringlist.h>
#include <qtextstream.h>
#include <qset.h>
#include <qdbusmetatype.h>
#include <private/qdbusintrospection_p.h>
#include <stdio.h>
#include <stdlib.h>
#define PROGRAMNAME "qdbusxml2cpp"
#define PROGRAMVERSION "0.8"
#define PROGRAMCOPYRIGHT QT_COPYRIGHT
#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply"
using namespace Qt::StringLiterals;
class QDBusXmlToCpp final
{
public:
int run(const QCoreApplication &app);
private:
class DiagnosticsReporter final : public QDBusIntrospection::DiagnosticsReporter
{
public:
void setFileName(const QString &fileName) { m_fileName = fileName; }
bool hadErrors() const { return m_hadErrors; }
void warning(const QDBusIntrospection::SourceLocation &location, const char *msg,
...) override;
void error(const QDBusIntrospection::SourceLocation &location, const char *msg,
...) override;
void note(const QDBusIntrospection::SourceLocation &location, const char *msg, ...)
Q_ATTRIBUTE_FORMAT_PRINTF(3, 4);
private:
QString m_fileName;
bool m_hadErrors = false;
void report(const QDBusIntrospection::SourceLocation &location, const char *msg, va_list ap,
const char *severity);
};
enum ClassType { Proxy, Adaptor };
void writeAdaptor(const QString &filename, const QDBusIntrospection::Interfaces &interfaces);
void writeProxy(const QString &filename, const QDBusIntrospection::Interfaces &interfaces);
QDBusIntrospection::Interfaces readInput();
void cleanInterfaces(QDBusIntrospection::Interfaces &interfaces);
QTextStream &writeHeader(QTextStream &ts, bool changesWillBeLost);
QString classNameForInterface(const QString &interface, ClassType classType);
QByteArray qtTypeName(const QDBusIntrospection::SourceLocation &location,
const QString &signature,
const QDBusIntrospection::Annotations &annotations,
qsizetype paramId = -1, const char *direction = "Out");
void
writeArgList(QTextStream &ts, const QStringList &argNames,
const QDBusIntrospection::Annotations &annotations,
const QDBusIntrospection::Arguments &inputArgs,
const QDBusIntrospection::Arguments &outputArgs = QDBusIntrospection::Arguments());
void writeSignalArgList(QTextStream &ts, const QStringList &argNames,
const QDBusIntrospection::Annotations &annotations,
const QDBusIntrospection::Arguments &outputArgs);
QString propertyGetter(const QDBusIntrospection::Property &property);
QString propertySetter(const QDBusIntrospection::Property &property);
QString globalClassName;
QString parentClassName;
QString customNamespace;
QString inputFile;
bool skipNamespaces = false;
bool includeMocs = false;
QString commandLine;
QStringList includes;
QStringList globalIncludes;
QStringList wantedInterfaces;
DiagnosticsReporter reporter;
};
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[] =
"#include <QtCore/qcontainerfwd.h>\n";
void QDBusXmlToCpp::DiagnosticsReporter::warning(const QDBusIntrospection::SourceLocation &location,
const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
report(location, msg, ap, "warning");
va_end(ap);
}
void QDBusXmlToCpp::DiagnosticsReporter::error(const QDBusIntrospection::SourceLocation &location,
const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
report(location, msg, ap, "error");
va_end(ap);
m_hadErrors = true;
}
void QDBusXmlToCpp::DiagnosticsReporter::note(const QDBusIntrospection::SourceLocation &location,
const char *msg, ...)
{
va_list ap;
va_start(ap, msg);
report(location, msg, ap, "note");
va_end(ap);
m_hadErrors = true;
}
void QDBusXmlToCpp::DiagnosticsReporter::report(const QDBusIntrospection::SourceLocation &location,
const char *msg, va_list ap, const char *severity)
{
fprintf(stderr, "%s:%lld:%lld: %s: ", qPrintable(m_fileName),
(long long int)location.lineNumber, (long long int)location.columnNumber + 1, severity);
vfprintf(stderr, msg, ap);
}
QDBusIntrospection::Interfaces QDBusXmlToCpp::readInput()
{
QFile input(inputFile);
if (inputFile.isEmpty() || inputFile == "-"_L1) {
reporter.setFileName("<standard input>"_L1);
if (!input.open(stdin, QIODevice::ReadOnly)) {
fprintf(stderr, PROGRAMNAME ": could not open standard input: %s\n",
qPrintable(input.errorString()));
exit(1);
}
} else {
reporter.setFileName(inputFile);
if (!input.open(QIODevice::ReadOnly)) {
fprintf(stderr, PROGRAMNAME ": could not open input file '%s': %s\n",
qPrintable(inputFile), qPrintable(input.errorString()));
exit(1);
}
}
QByteArray data = input.readAll();
auto interfaces = QDBusIntrospection::parseInterfaces(QString::fromUtf8(data), &reporter);
if (reporter.hadErrors())
exit(1);
return interfaces;
}
void QDBusXmlToCpp::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;
}
}
static bool isSupportedSuffix(QStringView suffix)
{
const QLatin1StringView candidates[] = {
"h"_L1,
"cpp"_L1,
"cc"_L1
};
for (auto candidate : candidates)
if (suffix == candidate)
return true;
return false;
}
// produce a header name from the file name
static QString header(const QString &name)
{
QStringList parts = name.split(u':');
QString retval = parts.front();
if (retval.isEmpty() || retval == "-"_L1)
return retval;
QFileInfo header{retval};
if (!isSupportedSuffix(header.suffix()))
retval.append(".h"_L1);
return retval;
}
// produce a cpp name from the file name
static QString cpp(const QString &name)
{
QStringList parts = name.split(u':');
QString retval = parts.back();
if (retval.isEmpty() || retval == "-"_L1)
return retval;
QFileInfo source{retval};
if (!isSupportedSuffix(source.suffix()))
retval.append(".cpp"_L1);
return retval;
}
// produce a moc name from the file name
static QString moc(const QString &name)
{
QString retval;
const QStringList fileNames = name.split(u':');
if (fileNames.size() == 1) {
QFileInfo fi{fileNames.front()};
if (isSupportedSuffix(fi.suffix())) {
// Generates a file that contains the header and the implementation: include "filename.moc"
retval += fi.completeBaseName();
retval += ".moc"_L1;
} else {
// Separate source and header files are generated: include "moc_filename.cpp"
retval += "moc_"_L1;
retval += fi.fileName();
retval += ".cpp"_L1;
}
} else {
QString headerName = fileNames.front();
QString sourceName = fileNames.back();
if (sourceName.isEmpty() || sourceName == "-"_L1) {
// If only a header is generated, don't include anything
} else if (headerName.isEmpty() || headerName == "-"_L1) {
// If only source file is generated: include "moc_sourcename.cpp"
QFileInfo source{sourceName};
retval += "moc_"_L1;
retval += source.completeBaseName();
retval += ".cpp"_L1;
fprintf(stderr, "warning: no header name is provided, assuming it to be \"%s\"\n",
qPrintable(source.completeBaseName() + ".h"_L1));
} else {
// Both source and header generated: include "moc_headername.cpp"
QFileInfo header{headerName};
retval += "moc_"_L1;
retval += header.completeBaseName();
retval += ".cpp"_L1;
}
}
return retval;
}
QTextStream &QDBusXmlToCpp::writeHeader(QTextStream &ts, bool changesWillBeLost)
{
ts << "/*\n"
" * This file was generated by " PROGRAMNAME " version " PROGRAMVERSION "\n"
" * Source file was " << QFileInfo(inputFile).fileName() << "\n"
" *\n"
" * " PROGRAMNAME " is " PROGRAMCOPYRIGHT "\n"
" *\n"
" * This is an auto-generated file.\n";
if (changesWillBeLost)
ts << " * Do not edit! All changes made to it will be lost.\n";
else
ts << " * This file may have been hand-edited. Look for HAND-EDIT comments\n"
" * before re-generating it.\n";
ts << " */\n\n";
return ts;
}
QString QDBusXmlToCpp::classNameForInterface(const QString &interface,
QDBusXmlToCpp::ClassType classType)
{
if (!globalClassName.isEmpty())
return globalClassName;
const auto parts = QStringView{interface}.split(u'.');
QString retval;
if (classType == Proxy) {
for (const auto &part : parts) {
retval += part[0].toUpper();
retval += part.mid(1);
}
} else {
retval += parts.last()[0].toUpper() + parts.last().mid(1);
}
if (classType == Proxy)
retval += "Interface"_L1;
else
retval += "Adaptor"_L1;
return retval;
}
QByteArray QDBusXmlToCpp::qtTypeName(const QDBusIntrospection::SourceLocation &location,
const QString &signature,
const QDBusIntrospection::Annotations &annotations,
qsizetype paramId, const char *direction)
{
int type = QDBusMetaType::signatureToMetaType(signature.toLatin1()).id();
if (type == QMetaType::UnknownType) {
QString annotationName = u"org.qtproject.QtDBus.QtTypeName"_s;
if (paramId >= 0)
annotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId);
auto annotation = annotations.value(annotationName);
QString qttype = annotation.value;
if (!qttype.isEmpty())
return std::move(qttype).toLatin1();
QString oldAnnotationName = u"com.trolltech.QtDBus.QtTypeName"_s;
if (paramId >= 0)
oldAnnotationName += ".%1%2"_L1.arg(QLatin1StringView(direction)).arg(paramId);
annotation = annotations.value(oldAnnotationName);
qttype = annotation.value;
if (qttype.isEmpty()) {
reporter.error(location, "unknown type `%s'\n", qPrintable(signature));
reporter.note(location, "you should add <annotation name=\"%s\" value=\"<type>\"/>\n",
qPrintable(annotationName));
exit(1);
}
reporter.warning(annotation.location, "deprecated annotation '%s' found\n",
qPrintable(oldAnnotationName));
reporter.note(annotation.location, "suggest updating to '%s'\n",
qPrintable(annotationName));
return std::move(qttype).toLatin1();
}
return QMetaType(type).name();
}
static QString nonConstRefArg(const QByteArray &arg)
{
return QLatin1StringView(arg) + " &"_L1;
}
static QString templateArg(const QByteArray &arg)
{
if (!arg.endsWith('>'))
return QLatin1StringView(arg);
return QLatin1StringView(arg) + " "_L1;
}
static QString constRefArg(const QByteArray &arg)
{
if (!arg.startsWith('Q'))
return QLatin1StringView(arg) + " "_L1;
else
return "const %1 &"_L1.arg(QLatin1StringView(arg));
}
static QStringList makeArgNames(const QDBusIntrospection::Arguments &inputArgs,
const QDBusIntrospection::Arguments &outputArgs =
QDBusIntrospection::Arguments())
{
QStringList retval;
const qsizetype numInputArgs = inputArgs.size();
const qsizetype numOutputArgs = outputArgs.size();
retval.reserve(numInputArgs + numOutputArgs);
for (qsizetype i = 0; i < numInputArgs; ++i) {
const QDBusIntrospection::Argument &arg = inputArgs.at(i);
QString name = arg.name;
if (name.isEmpty())
name = u"in%1"_s.arg(i);
else
name.replace(u'-', u'_');
while (retval.contains(name))
name += "_"_L1;
retval << name;
}
for (qsizetype i = 0; i < numOutputArgs; ++i) {
const QDBusIntrospection::Argument &arg = outputArgs.at(i);
QString name = arg.name;
if (name.isEmpty())
name = u"out%1"_s.arg(i);
else
name.replace(u'-', u'_');
while (retval.contains(name))
name += "_"_L1;
retval << name;
}
return retval;
}
void QDBusXmlToCpp::writeArgList(QTextStream &ts, const QStringList &argNames,
const QDBusIntrospection::Annotations &annotations,
const QDBusIntrospection::Arguments &inputArgs,
const QDBusIntrospection::Arguments &outputArgs)
{
// input args:
bool first = true;
qsizetype argPos = 0;
for (qsizetype i = 0; i < inputArgs.size(); ++i) {
const QDBusIntrospection::Argument &arg = inputArgs.at(i);
QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "In"));
if (!first)
ts << ", ";
ts << type << argNames.at(argPos++);
first = false;
}
argPos++;
// output args
// yes, starting from 1
for (qsizetype i = 1; i < outputArgs.size(); ++i) {
const QDBusIntrospection::Argument &arg = outputArgs.at(i);
if (!first)
ts << ", ";
ts << nonConstRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out"))
<< argNames.at(argPos++);
first = false;
}
}
void QDBusXmlToCpp::writeSignalArgList(QTextStream &ts, const QStringList &argNames,
const QDBusIntrospection::Annotations &annotations,
const QDBusIntrospection::Arguments &outputArgs)
{
bool first = true;
qsizetype argPos = 0;
for (qsizetype i = 0; i < outputArgs.size(); ++i) {
const QDBusIntrospection::Argument &arg = outputArgs.at(i);
QString type = constRefArg(qtTypeName(arg.location, arg.type, annotations, i, "Out"));
if (!first)
ts << ", ";
ts << type << argNames.at(argPos++);
first = false;
}
}
QString QDBusXmlToCpp::propertyGetter(const QDBusIntrospection::Property &property)
{
auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertyGetter"_L1);
if (!annotation.value.isEmpty())
return annotation.value;
annotation = property.annotations.value("com.trolltech.QtDBus.propertyGetter"_L1);
if (!annotation.value.isEmpty()) {
reporter.warning(annotation.location,
"deprecated annotation 'com.trolltech.QtDBus.propertyGetter' found\n");
reporter.note(annotation.location,
"suggest updating to 'org.qtproject.QtDBus.PropertyGetter'\n");
return annotation.value;
}
QString getter = property.name;
getter[0] = getter[0].toLower();
return getter;
}
QString QDBusXmlToCpp::propertySetter(const QDBusIntrospection::Property &property)
{
auto annotation = property.annotations.value("org.qtproject.QtDBus.PropertySetter"_L1);
if (!annotation.value.isEmpty())
return annotation.value;
annotation = property.annotations.value("com.trolltech.QtDBus.propertySetter"_L1);
if (!annotation.value.isEmpty()) {
reporter.warning(annotation.location,
"deprecated annotation 'com.trolltech.QtDBus.propertySetter' found\n");
reporter.note(annotation.location,
"suggest updating to 'org.qtproject.QtDBus.PropertySetter'\n");
return annotation.value;
}
QString setter = "set"_L1 + property.name;
setter[3] = setter[3].toUpper();
return setter;
}
static QString methodName(const QDBusIntrospection::Method &method)
{
QString name = method.annotations.value(u"org.qtproject.QtDBus.MethodName"_s).value;
if (!name.isEmpty())
return name;
return method.name;
}
static QString stringify(const QString &data)
{
QString retval;
qsizetype i;
for (i = 0; i < data.size(); ++i) {
retval += u'\"';
for ( ; i < data.size() && data[i] != u'\n' && data[i] != u'\r'; ++i)
if (data[i] == u'\"')
retval += "\\\""_L1;
else
retval += data[i];
if (i+1 < data.size() && data[i] == u'\r' && data[i+1] == u'\n')
i++;
retval += "\\n\"\n"_L1;
}
return retval;
}
static bool openFile(const QString &fileName, QFile &file)
{
if (fileName.isEmpty())
return false;
bool isOk = false;
if (fileName == "-"_L1) {
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, "%s: Unable to open '%s': %s\n",
PROGRAMNAME, qPrintable(fileName), qPrintable(file.errorString()));
return isOk;
}
void QDBusXmlToCpp::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 != "-"_L1) {
includeGuard = headerName.toUpper().replace(u'.', u'_');
qsizetype pos = includeGuard.lastIndexOf(u'/');
if (pos != -1)
includeGuard = includeGuard.mid(pos + 1);
} else {
includeGuard = u"QDBUSXML2CPP_PROXY"_s;
}
hs << "#ifndef " << includeGuard << "\n"
"#define " << includeGuard << "\n\n";
// include our stuff:
hs << "#include <QtCore/QObject>\n"
<< includeList;
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
hs << "#include <QtDBus/QtDBus>\n";
#else
hs << "#include <QtDBus/QDBusAbstractInterface>\n"
"#include <QtDBus/QDBusPendingReply>\n";
#endif
for (const QString &include : std::as_const(includes)) {
hs << "#include \"" << include << "\"\n";
if (headerName.isEmpty())
cs << "#include \"" << include << "\"\n";
}
for (const QString &include : std::as_const(globalIncludes)) {
hs << "#include <" << include << ">\n";
if (headerName.isEmpty())
cs << "#include <" << include << ">\n";
}
hs << "\n";
if (cppName != headerName) {
if (!headerName.isEmpty() && headerName != "-"_L1)
cs << "#include \"" << headerName << "\"\n\n";
}
if (!customNamespace.isEmpty()) {
hs << "namespace " << customNamespace << " {\n"
"\n";
cs << "namespace " << customNamespace << " {\n"
"\n";
}
for (const QDBusIntrospection::Interface *interface : interfaces) {
QString className = classNameForInterface(interface->name, Proxy);
// comment:
hs << "/*\n"
" * Proxy class for interface " << interface->name << "\n"
" */\n";
cs << "/*\n"
" * Implementation of interface class " << className << "\n"
" */\n\n";
// class header:
hs << "class " << className << ": public QDBusAbstractInterface\n"
"{\n"
" Q_OBJECT\n";
// the interface name
hs << "public:\n"
" static inline const char *staticInterfaceName()\n"
" { return \"" << interface->name << "\"; }\n\n";
// constructors/destructors:
hs << "public:\n"
" " << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent = nullptr);\n\n"
" ~" << className << "();\n\n";
cs << className << "::" << className << "(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent)\n"
" : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent)\n"
"{\n"
"}\n\n"
<< className << "::~" << className << "()\n"
"{\n"
"}\n\n";
// properties:
for (const QDBusIntrospection::Property &property : interface->properties) {
QByteArray type = qtTypeName(property.location, property.type, property.annotations);
QString getter = propertyGetter(property);
QString setter = propertySetter(property);
hs << " Q_PROPERTY(" << type << " " << property.name;
// getter:
if (property.access != QDBusIntrospection::Property::Write)
// it's readable
hs << " READ " << getter;
// setter
if (property.access != QDBusIntrospection::Property::Read)
// it's writeable
hs << " WRITE " << setter;
hs << ")\n";
// getter:
if (property.access != QDBusIntrospection::Property::Write) {
hs << " inline " << type << " " << getter << "() const\n"
" { return qvariant_cast< " << type << " >(property(\""
<< property.name << "\")); }\n";
}
// setter:
if (property.access != QDBusIntrospection::Property::Read) {
hs << " inline void " << setter << "(" << constRefArg(type) << "value)\n"
" { setProperty(\"" << property.name
<< "\", QVariant::fromValue(value)); }\n";
}
hs << "\n";
}
// methods:
hs << "public Q_SLOTS: // METHODS\n";
for (const QDBusIntrospection::Method &method : interface->methods) {
bool isDeprecated = method.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value
== "true"_L1;
bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1;
if (isNoReply && !method.outputArgs.isEmpty()) {
reporter.warning(method.location,
"method %s in interface %s is marked 'no-reply' but has output "
"arguments.\n",
qPrintable(method.name), qPrintable(interface->name));
continue;
}
if (isDeprecated)
hs << " Q_DECL_DEPRECATED ";
else
hs << " ";
if (isNoReply) {
hs << "Q_NOREPLY inline void ";
} else {
hs << "inline QDBusPendingReply<";
for (qsizetype i = 0; i < method.outputArgs.size(); ++i)
hs << (i > 0 ? ", " : "")
<< templateArg(qtTypeName(method.outputArgs.at(i).location,
method.outputArgs.at(i).type, method.annotations,
i, "Out"));
hs << "> ";
}
hs << methodName(method) << "(";
QStringList argNames = makeArgNames(method.inputArgs);
writeArgList(hs, argNames, method.annotations, method.inputArgs);
hs << ")\n"
" {\n"
" QList<QVariant> argumentList;\n";
if (!method.inputArgs.isEmpty()) {
hs << " argumentList";
for (qsizetype argPos = 0; argPos < method.inputArgs.size(); ++argPos)
hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')';
hs << ";\n";
}
if (isNoReply)
hs << " callWithArgumentList(QDBus::NoBlock, "
"QStringLiteral(\"" << method.name << "\"), argumentList);\n";
else
hs << " return asyncCallWithArgumentList(QStringLiteral(\""
<< method.name << "\"), argumentList);\n";
// close the function:
hs << " }\n";
if (method.outputArgs.size() > 1) {
// generate the old-form QDBusReply methods with multiple incoming parameters
hs << (isDeprecated ? " Q_DECL_DEPRECATED " : " ") << "inline QDBusReply<"
<< templateArg(qtTypeName(method.outputArgs.first().location,
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 << ")\n"
" {\n"
" QList<QVariant> argumentList;\n";
qsizetype argPos = 0;
if (!method.inputArgs.isEmpty()) {
hs << " argumentList";
for (argPos = 0; argPos < method.inputArgs.size(); ++argPos)
hs << " << QVariant::fromValue(" << argNames.at(argPos) << ')';
hs << ";\n";
}
hs << " QDBusMessage reply = callWithArgumentList(QDBus::Block, "
"QStringLiteral(\"" << method.name << "\"), argumentList);\n";
argPos++;
hs << " if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == "
<< method.outputArgs.size() << ") {\n";
// yes, starting from 1
for (qsizetype i = 1; i < method.outputArgs.size(); ++i)
hs << " " << argNames.at(argPos++) << " = qdbus_cast<"
<< templateArg(qtTypeName(method.outputArgs.at(i).location,
method.outputArgs.at(i).type, method.annotations,
i, "Out"))
<< ">(reply.arguments().at(" << i << "));\n";
hs << " }\n"
" return reply;\n"
" }\n";
}
hs << "\n";
}
hs << "Q_SIGNALS: // SIGNALS\n";
for (const QDBusIntrospection::Signal &signal : interface->signals_) {
hs << " ";
if (signal.annotations.value("org.freedesktop.DBus.Deprecated"_L1).value == "true"_L1)
hs << "Q_DECL_DEPRECATED ";
hs << "void " << signal.name << "(";
QStringList argNames = makeArgNames(signal.outputArgs);
writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs);
hs << ");\n"; // finished for header
}
// close the class:
hs << "};\n\n";
}
if (!customNamespace.isEmpty()) {
hs << "} // end of namespace " << customNamespace << "\n"
<< "\n";
cs << "} // end of namespace " << customNamespace << "\n";
}
if (!skipNamespaces) {
QStringList last;
QDBusIntrospection::Interfaces::ConstIterator it = interfaces.constBegin();
do
{
QStringList current;
QString name;
if (it != interfaces.constEnd()) {
current = it->constData()->name.split(u'.');
name = current.takeLast();
}
qsizetype i = 0;
while (i < current.size() && i < last.size() && current.at(i) == last.at(i))
++i;
// i parts matched
// close last.arguments().size() - i namespaces:
for (qsizetype j = i; j < last.size(); ++j)
hs << QString((last.size() - j - 1 + i) * 2, u' ') << "}\n";
// open current.arguments().size() - i namespaces
for (qsizetype j = i; j < current.size(); ++j)
hs << QString(j * 2, u' ') << "namespace " << current.at(j) << " {\n";
// add this class:
if (!name.isEmpty()) {
hs << QString(current.size() * 2, u' ')
<< "using " << name << " = " << (customNamespace.isEmpty() ? "" : "::")
<< customNamespace << "::" << classNameForInterface(it->constData()->name, Proxy)
<< ";\n";
}
if (it == interfaces.constEnd())
break;
++it;
last = current;
} while (true);
hs << "\n";
}
// close the include guard
hs << "#endif\n";
QString mocName = moc(filename);
if (includeMocs && !mocName.isEmpty())
cs << "\n"
"#include \"" << mocName << "\"\n";
cs.flush();
hs.flush();
QFile file;
const bool headerOpen = openFile(headerName, file);
if (headerOpen)
file.write(headerData);
if (headerName == cppName) {
if (headerOpen)
file.write(cppData);
} else {
QFile cppFile;
if (openFile(cppName, cppFile))
cppFile.write(cppData);
}
}
void QDBusXmlToCpp::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 != "-"_L1) {
includeGuard = headerName.toUpper().replace(u'.', u'_');
qsizetype pos = includeGuard.lastIndexOf(u'/');
if (pos != -1)
includeGuard = includeGuard.mid(pos + 1);
} else {
includeGuard = u"QDBUSXML2CPP_ADAPTOR"_s;
}
hs << "#ifndef " << includeGuard << "\n"
"#define " << includeGuard << "\n\n";
// include our stuff:
hs << "#include <QtCore/QObject>\n";
if (cppName == headerName)
hs << "#include <QtCore/QMetaObject>\n"
"#include <QtCore/QVariant>\n";
#if QT_VERSION < QT_VERSION_CHECK(7, 0, 0)
hs << "#include <QtDBus/QtDBus>\n";
#else
hs << "#include <QtDBus/QDBusAbstractAdaptor>\n"
"#include <QtDBus/QDBusObjectPath>\n";
#endif
for (const QString &include : std::as_const(includes)) {
hs << "#include \"" << include << "\"\n";
if (headerName.isEmpty())
cs << "#include \"" << include << "\"\n";
}
for (const QString &include : std::as_const(globalIncludes)) {
hs << "#include <" << include << ">\n";
if (headerName.isEmpty())
cs << "#include <" << include << ">\n";
}
if (cppName != headerName) {
if (!headerName.isEmpty() && headerName != "-"_L1)
cs << "#include \"" << headerName << "\"\n";
cs << "#include <QtCore/QMetaObject>\n"
<< includeList
<< "\n";
hs << forwardDeclarations;
} else {
hs << includeList;
}
hs << "\n";
QString parent = parentClassName;
if (parentClassName.isEmpty())
parent = u"QObject"_s;
if (!customNamespace.isEmpty()) {
hs << "namespace " << customNamespace << " {\n"
"\n";
cs << "namespace " << customNamespace << " {\n"
"\n";
}
for (const QDBusIntrospection::Interface *interface : interfaces) {
QString className = classNameForInterface(interface->name, Adaptor);
// comment:
hs << "/*\n"
" * Adaptor class for interface " << interface->name << "\n"
" */\n";
cs << "/*\n"
" * Implementation of adaptor class " << className << "\n"
" */\n\n";
// class header:
hs << "class " << className << ": public QDBusAbstractAdaptor\n"
"{\n"
" Q_OBJECT\n"
" Q_CLASSINFO(\"D-Bus Interface\", \"" << interface->name << "\")\n"
" Q_CLASSINFO(\"D-Bus Introspection\", \"\"\n"
<< stringify(interface->introspection)
<< " \"\")\n"
"public:\n"
" " << className << "(" << parent << " *parent);\n"
" ~" << className << "() override;\n\n";
if (!parentClassName.isEmpty())
hs << " inline " << parent << " *parent() const\n"
" { return static_cast<" << parent << " *>(QObject::parent()); }\n\n";
// constructor/destructor
cs << className << "::" << className << "(" << parent << " *parent)\n"
" : QDBusAbstractAdaptor(parent)\n"
"{\n"
" // constructor\n"
" setAutoRelaySignals(true);\n"
"}\n\n"
<< className << "::~" << className << "()\n"
"{\n"
" // destructor\n"
"}\n\n";
hs << "public: // PROPERTIES\n";
for (const QDBusIntrospection::Property &property : interface->properties) {
QByteArray type = qtTypeName(property.location, 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 << ")\n";
// getter:
if (property.access != QDBusIntrospection::Property::Write) {
hs << " " << type << " " << getter << "() const;\n";
cs << type << " "
<< className << "::" << getter << "() const\n"
"{\n"
" // get the value of property " << property.name << "\n"
" return qvariant_cast< " << type <<" >(parent()->property(\"" << property.name << "\"));\n"
"}\n\n";
}
// setter
if (property.access != QDBusIntrospection::Property::Read) {
hs << " void " << setter << "(" << constRefType << "value);\n";
cs << "void " << className << "::" << setter << "(" << constRefType << "value)\n"
"{\n"
" // set the value of property " << property.name << "\n"
" parent()->setProperty(\"" << property.name << "\", QVariant::fromValue(value";
if (constRefType.contains("QDBusVariant"_L1))
cs << ".variant()";
cs << "));\n"
"}\n\n";
}
hs << "\n";
}
hs << "public Q_SLOTS: // METHODS\n";
for (const QDBusIntrospection::Method &method : interface->methods) {
bool isNoReply = method.annotations.value(ANNOTATION_NO_WAIT ""_L1).value == "true"_L1;
if (isNoReply && !method.outputArgs.isEmpty()) {
reporter.warning(method.location,
"method %s in interface %s is marked 'no-reply' but has output "
"arguments.\n",
qPrintable(method.name), qPrintable(interface->name));
continue;
}
hs << " ";
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().location,
method.outputArgs.first().type, method.annotations, 0, "Out");
hs << returnType << " ";
cs << returnType << " ";
}
QString name = methodName(method);
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 << ");\n"; // finished for header
cs << ")\n"
"{\n"
" // handle method call " << interface->name << "." << methodName(method) << "\n";
// make the call
bool usingInvokeMethod = false;
if (parentClassName.isEmpty() && method.inputArgs.size() <= 10
&& method.outputArgs.size() <= 1)
usingInvokeMethod = true;
if (usingInvokeMethod) {
// we are using QMetaObject::invokeMethod
if (!returnType.isEmpty()) {
cs << " " << returnType << " " << argNames.at(method.inputArgs.size())
<< "{};\n";
}
static const char invoke[] = " QMetaObject::invokeMethod(parent(), \"";
cs << invoke << name << "\"";
if (!method.outputArgs.isEmpty())
cs << ", Q_RETURN_ARG("
<< qtTypeName(method.outputArgs.at(0).location, method.outputArgs.at(0).type,
method.annotations, 0, "Out")
<< ", " << argNames.at(method.inputArgs.size()) << ")";
for (qsizetype i = 0; i < method.inputArgs.size(); ++i)
cs << ", Q_ARG("
<< qtTypeName(method.inputArgs.at(i).location, method.inputArgs.at(i).type,
method.annotations, i, "In")
<< ", " << argNames.at(i) << ")";
cs << ");\n";
if (!returnType.isEmpty())
cs << " return " << argNames.at(method.inputArgs.size()) << ";\n";
} 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 << "(";
qsizetype argPos = 0;
bool first = true;
for (qsizetype i = 0; i < method.inputArgs.size(); ++i) {
cs << (first ? "" : ", ") << argNames.at(argPos++);
first = false;
}
++argPos; // skip retval, if any
for (qsizetype i = 1; i < method.outputArgs.size(); ++i) {
cs << (first ? "" : ", ") << argNames.at(argPos++);
first = false;
}
cs << ");\n";
}
cs << "}\n\n";
}
hs << "Q_SIGNALS: // SIGNALS\n";
for (const QDBusIntrospection::Signal &signal : interface->signals_) {
hs << " void " << signal.name << "(";
QStringList argNames = makeArgNames(signal.outputArgs);
writeSignalArgList(hs, argNames, signal.annotations, signal.outputArgs);
hs << ");\n"; // finished for header
}
// close the class:
hs << "};\n\n";
}
if (!customNamespace.isEmpty()) {
hs << "} // end of namespace " << customNamespace << "\n"
<< "\n";
cs << "} // end of namespace " << customNamespace << "\n";
}
// close the include guard
hs << "#endif\n";
QString mocName = moc(filename);
if (includeMocs && !mocName.isEmpty())
cs << "\n"
"#include \"" << mocName << "\"\n";
cs.flush();
hs.flush();
QFile file;
const bool headerOpen = openFile(headerName, file);
if (headerOpen)
file.write(headerData);
if (headerName == cppName) {
if (headerOpen)
file.write(cppData);
} else {
QFile cppFile;
if (openFile(cppName, cppFile))
cppFile.write(cppData);
}
}
int QDBusXmlToCpp::run(const QCoreApplication &app)
{
QCommandLineParser parser;
parser.setApplicationDescription(
"Produces the C++ code to implement the interfaces defined in the input file.\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."_L1);
parser.addHelpOption();
parser.addVersionOption();
parser.addPositionalArgument(u"xml-or-xml-file"_s, u"XML file to use."_s);
parser.addPositionalArgument(u"interfaces"_s, u"List of interfaces to use."_s,
u"[interfaces ...]"_s);
QCommandLineOption adapterCodeOption(QStringList{u"a"_s, u"adaptor"_s},
u"Write the adaptor code to <filename>"_s, u"filename"_s);
parser.addOption(adapterCodeOption);
QCommandLineOption classNameOption(QStringList{u"c"_s, u"classname"_s},
u"Use <classname> as the class name for the generated classes. "
u"This option can only be used when processing a single interface."_s,
u"classname"_s);
parser.addOption(classNameOption);
QCommandLineOption namespaceOption(QStringList{u"namespace"_s},
u"Put all generated classes into the namespace <namespace>. "_s, u"namespace"_s);
parser.addOption(namespaceOption);
QCommandLineOption addIncludeOption(QStringList{u"i"_s, u"include"_s},
u"Add #include \"filename\" to the output"_s, u"filename"_s);
parser.addOption(addIncludeOption);
QCommandLineOption addGlobalIncludeOption(QStringList{u"I"_s, u"global-include"_s},
u"Add #include <filename> to the output"_s, u"filename"_s);
parser.addOption(addGlobalIncludeOption);
QCommandLineOption adapterParentOption(u"l"_s,
u"When generating an adaptor, use <classname> as the parent class"_s, u"classname"_s);
parser.addOption(adapterParentOption);
QCommandLineOption mocIncludeOption(QStringList{u"m"_s, u"moc"_s},
u"Generate #include \"filename.moc\" statements in the .cpp files"_s);
parser.addOption(mocIncludeOption);
QCommandLineOption noNamespaceOption(QStringList{u"N"_s, u"no-namespaces"_s},
u"Do not export the generated class into the D-Bus specific namespace"_s);
parser.addOption(noNamespaceOption);
QCommandLineOption proxyCodeOption(QStringList{u"p"_s, u"proxy"_s},
u"Write the proxy code to <filename>"_s, u"filename"_s);
parser.addOption(proxyCodeOption);
QCommandLineOption verboseOption(QStringList{u"V"_s, u"verbose"_s},
u"Be verbose."_s);
parser.addOption(verboseOption);
parser.process(app);
QString adaptorFile = parser.value(adapterCodeOption);
globalClassName = parser.value(classNameOption);
includes = parser.values(addIncludeOption);
globalIncludes = parser.values(addGlobalIncludeOption);
parentClassName = parser.value(adapterParentOption);
customNamespace = parser.value(namespaceOption);
includeMocs = parser.isSet(mocIncludeOption);
skipNamespaces = parser.isSet(noNamespaceOption);
QString proxyFile = parser.value(proxyCodeOption);
bool verbose = parser.isSet(verboseOption);
wantedInterfaces = parser.positionalArguments();
if (!wantedInterfaces.isEmpty()) {
inputFile = wantedInterfaces.takeFirst();
QFileInfo inputInfo(inputFile);
if (!inputInfo.exists() || !inputInfo.isFile() || !inputInfo.isReadable()) {
qCritical("Error: Input %s is not a file or cannot be accessed\n", qPrintable(inputFile));
return 1;
}
}
if (verbose)
QLoggingCategory::setFilterRules(u"dbus.parser.debug=true"_s);
QDBusIntrospection::Interfaces interfaces = readInput();
cleanInterfaces(interfaces);
if (!globalClassName.isEmpty() && interfaces.count() != 1) {
qCritical("Option -c/--classname can only be used with a single interface.\n");
return 1;
}
QStringList args = app.arguments();
args.removeFirst();
commandLine = PROGRAMNAME " "_L1 + args.join(u' ');
if (!proxyFile.isEmpty() || adaptorFile.isEmpty())
writeProxy(proxyFile, interfaces);
if (!adaptorFile.isEmpty())
writeAdaptor(adaptorFile, interfaces);
return 0;
}
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QCoreApplication::setApplicationName(QStringLiteral(PROGRAMNAME));
QCoreApplication::setApplicationVersion(QStringLiteral(PROGRAMVERSION));
return QDBusXmlToCpp().run(app);
}