/**************************************************************************** ** ** Copyright (C) 2016 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 #include #include #include #include #include #include #include #include #include #include #include #include // for the Export* flags #include // for the qDBusCheckAsyncTag // copied from dbus-protocol.h: static const char docTypeHeader[] = "\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 #include #include #include "moc.h" #include "generator.h" #include "preprocessor.h" #define PROGRAMNAME "qdbuscpp2xml" #define PROGRAMVERSION "0.2" #define PROGRAMCOPYRIGHT "Copyright (C) 2016 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 Write the output to file \n" " -h Show this information\n" " -V Show the program version and quit.\n" "\n"; int qDBusParametersForMethod(const FunctionDef &mm, QVector& metaTypes, QString &errorMsg) { QList 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(" \n") .arg(typeNameToXml(typeName)); // do we need to describe this argument? if (QDBusMetaType::signatureToType(typeName) == QVariant::Invalid) xml += QString::fromLatin1(" \n") .arg(typeNameToXml(mm.normalizedType.constData())); } else { return QString(); } } else if (!mm.normalizedType.isEmpty()) { return QString(); // wasn't a valid type } } QVector names = mm.arguments; QVector 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(" \n") .arg(name, QLatin1String(signature), isOutput ? QLatin1String("out") : QLatin1String("in")); // do we need to describe this argument? if (QDBusMetaType::signatureToType(signature) == QVariant::Invalid) { const char *typeName = QMetaType::typeName(types.at(j)); xml += QString::fromLatin1(" \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(" \n"); QString retval = xml; retval += QString::fromLatin1(" \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) continue; const char *signature = QDBusMetaType::typeToSignature(typeId); if (!signature) continue; retval += QString::fromLatin1(" \n \n \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(" \n%2 \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 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("\n"); for (const ClassDef &cdef : qAsConst(classes)) { QString xml = qDBusGenerateClassDefXml(&cdef); output.write(std::move(xml).toLocal8Bit()); } output.write("\n"); return 0; }