blob: 768594efb468b5930ae7c3f89f9a6e22669fd8e1 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the tools applications of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL-EXCEPT$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 as published by the Free Software
** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#include <qbytearray.h>
#include <qstring.h>
#include <qvarlengtharray.h>
#include <qfile.h>
#include <qlist.h>
#include <qbuffer.h>
#include <qvector.h>
#include <qdebug.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <qdbusconnection.h> // for the Export* flags
#include <private/qdbusconnection_p.h> // for the qDBusCheckAsyncTag
// copied from dbus-protocol.h:
static const char docTypeHeader[] =
"<!DOCTYPE node PUBLIC \"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\" "
"\"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\">\n";
#define ANNOTATION_NO_WAIT "org.freedesktop.DBus.Method.NoReply"
#define QCLASSINFO_DBUS_INTERFACE "D-Bus Interface"
#define QCLASSINFO_DBUS_INTROSPECTION "D-Bus Introspection"
#include <qdbusmetatype.h>
#include <private/qdbusmetatype_p.h>
#include <private/qdbusutil_p.h>
#include "moc.h"
#include "generator.h"
#include "preprocessor.h"
#define PROGRAMNAME "qdbuscpp2xml"
#define PROGRAMVERSION "0.2"
#define PROGRAMCOPYRIGHT "Copyright (C) 2020 The Qt Company Ltd."
static QString outputFile;
static int flags;
static const char help[] =
"Usage: " PROGRAMNAME " [options...] [files...]\n"
"Parses the C++ source or header file containing a QObject-derived class and\n"
"produces the D-Bus Introspection XML."
"\n"
"Options:\n"
" -p|-s|-m Only parse scriptable Properties, Signals and Methods (slots)\n"
" -P|-S|-M Parse all Properties, Signals and Methods (slots)\n"
" -a Output all scriptable contents (equivalent to -psm)\n"
" -A Output all contents (equivalent to -PSM)\n"
" -o <filename> Write the output to file <filename>\n"
" -h Show this information\n"
" -V Show the program version and quit.\n"
"\n";
int qDBusParametersForMethod(const FunctionDef &mm, QVector<int>& metaTypes, QString &errorMsg)
{
QList<QByteArray> parameterTypes;
parameterTypes.reserve(mm.arguments.size());
for (const ArgumentDef &arg : mm.arguments)
parameterTypes.append(arg.normalizedType);
return qDBusParametersForMethod(parameterTypes, metaTypes, errorMsg);
}
static inline QString typeNameToXml(const char *typeName)
{
QString plain = QLatin1String(typeName);
return plain.toHtmlEscaped();
}
static QString addFunction(const FunctionDef &mm, bool isSignal = false) {
QString xml = QString::asprintf(" <%s name=\"%s\">\n",
isSignal ? "signal" : "method", mm.name.constData());
// check the return type first
int typeId = QMetaType::type(mm.normalizedType.constData());
if (typeId != QMetaType::Void) {
if (typeId) {
const char *typeName = QDBusMetaType::typeToSignature(typeId);
if (typeName) {
xml += QString::fromLatin1(" <arg type=\"%1\" direction=\"out\"/>\n")
.arg(typeNameToXml(typeName));
// do we need to describe this argument?
if (QDBusMetaType::signatureToType(typeName) == QMetaType::UnknownType)
xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
.arg(typeNameToXml(mm.normalizedType.constData()));
} else {
return QString();
}
} else if (!mm.normalizedType.isEmpty()) {
return QString(); // wasn't a valid type
}
}
QVector<ArgumentDef> names = mm.arguments;
QVector<int> types;
QString errorMsg;
int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
if (inputCount == -1) {
qWarning() << qPrintable(errorMsg);
return QString(); // invalid form
}
if (isSignal && inputCount + 1 != types.count())
return QString(); // signal with output arguments?
if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message())
return QString(); // signal with QDBusMessage argument?
bool isScriptable = mm.isScriptable;
for (int j = 1; j < types.count(); ++j) {
// input parameter for a slot or output for a signal
if (types.at(j) == QDBusMetaTypeId::message()) {
isScriptable = true;
continue;
}
QString name;
if (!names.at(j - 1).name.isEmpty())
name = QString::fromLatin1("name=\"%1\" ").arg(QString::fromLatin1(names.at(j - 1).name));
bool isOutput = isSignal || j > inputCount;
const char *signature = QDBusMetaType::typeToSignature(types.at(j));
xml += QString::fromLatin1(" <arg %1type=\"%2\" direction=\"%3\"/>\n")
.arg(name,
QLatin1String(signature),
isOutput ? QLatin1String("out") : QLatin1String("in"));
// do we need to describe this argument?
if (QDBusMetaType::signatureToType(signature) == QMetaType::UnknownType) {
const char *typeName = QMetaType::typeName(types.at(j));
xml += QString::fromLatin1(" <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
.arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
.arg(isOutput && !isSignal ? j - inputCount : j - 1)
.arg(typeNameToXml(typeName));
}
}
int wantedMask;
if (isScriptable)
wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
: QDBusConnection::ExportScriptableSlots;
else
wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
: QDBusConnection::ExportNonScriptableSlots;
if ((flags & wantedMask) != wantedMask)
return QString();
if (qDBusCheckAsyncTag(mm.tag.constData()))
// add the no-reply annotation
xml += QLatin1String(" <annotation name=\"" ANNOTATION_NO_WAIT "\""
" value=\"true\"/>\n");
QString retval = xml;
retval += QString::fromLatin1(" </%1>\n")
.arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
return retval;
}
static QString generateInterfaceXml(const ClassDef *mo)
{
QString retval;
// start with properties:
if (flags & (QDBusConnection::ExportScriptableProperties |
QDBusConnection::ExportNonScriptableProperties)) {
static const char *accessvalues[] = {0, "read", "write", "readwrite"};
for (const PropertyDef &mp : mo->propertyList) {
if (!((!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportScriptableProperties)) ||
(!mp.scriptable.isEmpty() && (flags & QDBusConnection::ExportNonScriptableProperties))))
continue;
int access = 0;
if (!mp.read.isEmpty())
access |= 1;
if (!mp.write.isEmpty())
access |= 2;
int typeId = QMetaType::type(mp.type.constData());
if (!typeId) {
fprintf(stderr, PROGRAMNAME ": unregistered type: '%s', ignoring\n",
mp.type.constData());
continue;
}
const char *signature = QDBusMetaType::typeToSignature(typeId);
if (!signature)
continue;
retval += QString::fromLatin1(" <property name=\"%1\" type=\"%2\" access=\"%3\"")
.arg(QLatin1String(mp.name),
QLatin1String(signature),
QLatin1String(accessvalues[access]));
if (QDBusMetaType::signatureToType(signature) == QMetaType::UnknownType) {
retval += QString::fromLatin1(">\n <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n </property>\n")
.arg(typeNameToXml(mp.type.constData()));
} else {
retval += QLatin1String("/>\n");
}
}
}
// now add methods:
if (flags & (QDBusConnection::ExportScriptableSignals | QDBusConnection::ExportNonScriptableSignals)) {
for (const FunctionDef &mm : mo->signalList) {
if (mm.wasCloned)
continue;
if (!mm.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSignals))
continue;
retval += addFunction(mm, true);
}
}
if (flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) {
for (const FunctionDef &slot : mo->slotList) {
if (!slot.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
continue;
if (slot.access == FunctionDef::Public)
retval += addFunction(slot);
}
for (const FunctionDef &method : mo->methodList) {
if (!method.isScriptable && !(flags & QDBusConnection::ExportNonScriptableSlots))
continue;
if (method.access == FunctionDef::Public)
retval += addFunction(method);
}
}
return retval;
}
QString qDBusInterfaceFromClassDef(const ClassDef *mo)
{
QString interface;
for (const ClassInfoDef &cid : mo->classInfoList) {
if (cid.name == QCLASSINFO_DBUS_INTERFACE)
return QString::fromUtf8(cid.value);
}
interface = QLatin1String(mo->classname);
interface.replace(QLatin1String("::"), QLatin1String("."));
if (interface.startsWith(QLatin1String("QDBus"))) {
interface.prepend(QLatin1String("org.qtproject.QtDBus."));
} else if (interface.startsWith(QLatin1Char('Q')) &&
interface.length() >= 2 && interface.at(1).isUpper()) {
// assume it's Qt
interface.prepend(QLatin1String("local.org.qtproject.Qt."));
} else {
interface.prepend(QLatin1String("local."));
}
return interface;
}
QString qDBusGenerateClassDefXml(const ClassDef *cdef)
{
for (const ClassInfoDef &cid : cdef->classInfoList) {
if (cid.name == QCLASSINFO_DBUS_INTROSPECTION)
return QString::fromUtf8(cid.value);
}
// generate the interface name from the meta object
QString interface = qDBusInterfaceFromClassDef(cdef);
QString xml = generateInterfaceXml(cdef);
if (xml.isEmpty())
return QString(); // don't add an empty interface
return QString::fromLatin1(" <interface name=\"%1\">\n%2 </interface>\n")
.arg(interface, xml);
}
static void showHelp()
{
printf("%s", help);
exit(0);
}
static void showVersion()
{
printf("%s version %s\n", PROGRAMNAME, PROGRAMVERSION);
printf("D-Bus QObject-to-XML converter\n");
exit(0);
}
static void parseCmdLine(QStringList &arguments)
{
flags = 0;
for (int i = 0; i < arguments.count(); ++i) {
const QString arg = arguments.at(i);
if (arg == QLatin1String("--help"))
showHelp();
if (!arg.startsWith(QLatin1Char('-')))
continue;
char c = arg.count() == 2 ? arg.at(1).toLatin1() : char(0);
switch (c) {
case 'P':
flags |= QDBusConnection::ExportNonScriptableProperties;
Q_FALLTHROUGH();
case 'p':
flags |= QDBusConnection::ExportScriptableProperties;
break;
case 'S':
flags |= QDBusConnection::ExportNonScriptableSignals;
Q_FALLTHROUGH();
case 's':
flags |= QDBusConnection::ExportScriptableSignals;
break;
case 'M':
flags |= QDBusConnection::ExportNonScriptableSlots;
Q_FALLTHROUGH();
case 'm':
flags |= QDBusConnection::ExportScriptableSlots;
break;
case 'A':
flags |= QDBusConnection::ExportNonScriptableContents;
Q_FALLTHROUGH();
case 'a':
flags |= QDBusConnection::ExportScriptableContents;
break;
case 'o':
if (arguments.count() < i + 2 || arguments.at(i + 1).startsWith(QLatin1Char('-'))) {
printf("-o expects a filename\n");
exit(1);
}
outputFile = arguments.takeAt(i + 1);
break;
case 'h':
case '?':
showHelp();
break;
case 'V':
showVersion();
break;
default:
printf("unknown option: \"%s\"\n", qPrintable(arg));
exit(1);
}
}
if (flags == 0)
flags = QDBusConnection::ExportScriptableContents
| QDBusConnection::ExportNonScriptableContents;
}
int main(int argc, char **argv)
{
QStringList args;
args.reserve(argc - 1);
for (int n = 1; n < argc; ++n)
args.append(QString::fromLocal8Bit(argv[n]));
parseCmdLine(args);
QVector<ClassDef> classes;
for (int i = 0; i < args.count(); ++i) {
const QString arg = args.at(i);
if (arg.startsWith(QLatin1Char('-')))
continue;
QFile f(arg);
if (!f.open(QIODevice::ReadOnly|QIODevice::Text)) {
fprintf(stderr, PROGRAMNAME ": could not open '%s': %s\n",
qPrintable(arg), qPrintable(f.errorString()));
return 1;
}
Preprocessor pp;
Moc moc;
pp.macros["Q_MOC_RUN"];
pp.macros["__cplusplus"];
const QByteArray filename = arg.toLocal8Bit();
moc.filename = filename;
moc.currentFilenames.push(filename);
moc.symbols = pp.preprocessed(moc.filename, &f);
moc.parse();
if (moc.classList.isEmpty())
return 0;
classes = moc.classList;
f.close();
}
QFile output;
if (outputFile.isEmpty()) {
output.open(stdout, QIODevice::WriteOnly);
} else {
output.setFileName(outputFile);
if (!output.open(QIODevice::WriteOnly)) {
fprintf(stderr, PROGRAMNAME ": could not open output file '%s': %s",
qPrintable(outputFile), qPrintable(output.errorString()));
return 1;
}
}
output.write(docTypeHeader);
output.write("<node>\n");
for (const ClassDef &cdef : qAsConst(classes)) {
QString xml = qDBusGenerateClassDefXml(&cdef);
output.write(std::move(xml).toLocal8Bit());
}
output.write("</node>\n");
return 0;
}