diff options
author | Lars Knoll <lars.knoll@nokia.com> | 2009-03-23 09:18:55 (GMT) |
---|---|---|
committer | Simon Hausmann <simon.hausmann@nokia.com> | 2009-03-23 09:18:55 (GMT) |
commit | e5fcad302d86d316390c6b0f62759a067313e8a9 (patch) | |
tree | c2afbf6f1066b6ce261f14341cf6d310e5595bc1 /tools/qdbus | |
download | Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.zip Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.gz Qt-e5fcad302d86d316390c6b0f62759a067313e8a9.tar.bz2 |
Long live Qt 4.5!
Diffstat (limited to 'tools/qdbus')
22 files changed, 3472 insertions, 0 deletions
diff --git a/tools/qdbus/qdbus.pro b/tools/qdbus/qdbus.pro new file mode 100644 index 0000000..01cd246 --- /dev/null +++ b/tools/qdbus/qdbus.pro @@ -0,0 +1,2 @@ +TEMPLATE = subdirs +SUBDIRS = qdbus qdbusxml2cpp qdbuscpp2xml qdbusviewer diff --git a/tools/qdbus/qdbus/qdbus.cpp b/tools/qdbus/qdbus/qdbus.cpp new file mode 100644 index 0000000..d290eab --- /dev/null +++ b/tools/qdbus/qdbus/qdbus.cpp @@ -0,0 +1,483 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> + +#include <QtCore/QCoreApplication> +#include <QtCore/QStringList> +#include <QtCore/qmetaobject.h> +#include <QtXml/QDomDocument> +#include <QtXml/QDomElement> +#include <QtDBus/QtDBus> +#include <private/qdbusutil_p.h> + +static QDBusConnection connection(QLatin1String("")); +static bool printArgumentsLiterally = false; + +static void showUsage() +{ + printf("Usage: qdbus [--system] [--literal] [servicename] [path] [method] [args]\n" + "\n" + " servicename the service to connect to (e.g., org.freedesktop.DBus)\n" + " path the path to the object (e.g., /)\n" + " method the method to call, with or without the interface\n" + " args arguments to pass to the call\n" + "With 0 arguments, qdbus will list the services available on the bus\n" + "With just the servicename, qdbus will list the object paths available on the service\n" + "With service name and object path, qdbus will list the methods, signals and properties available on the object\n" + "\n" + "Options:\n" + " --system connect to the system bus\n" + " --literal print replies literally\n"); +} + +static void printArg(const QVariant &v) +{ + if (printArgumentsLiterally) { + printf("%s\n", qPrintable(QDBusUtil::argumentToString(v))); + return; + } + + if (v.userType() == QVariant::StringList) { + foreach (QString s, v.toStringList()) + printf("%s\n", qPrintable(s)); + } else if (v.userType() == QVariant::List) { + foreach (const QVariant &var, v.toList()) + printArg(var); + } else if (v.userType() == QVariant::Map) { + const QVariantMap map = v.toMap(); + QVariantMap::ConstIterator it = map.constBegin(); + for ( ; it != map.constEnd(); ++it) { + printf("%s: ", qPrintable(it.key())); + printArg(it.value()); + } + } else if (v.userType() == qMetaTypeId<QDBusVariant>()) { + printArg(qvariant_cast<QDBusVariant>(v).variant()); + } else if (v.userType() == qMetaTypeId<QDBusArgument>()) { + QDBusArgument arg = qvariant_cast<QDBusArgument>(v); + if (arg.currentSignature() == QLatin1String("av")) + printArg(qdbus_cast<QVariantList>(arg)); + else if (arg.currentSignature() == QLatin1String("a{sv}")) + printArg(qdbus_cast<QVariantMap>(arg)); + else + printf("qdbus: I don't know how to display an argument of type '%s'\n", + qPrintable(arg.currentSignature())); + } else { + printf("%s\n", qPrintable(v.toString())); + } +} + +static void listObjects(const QString &service, const QString &path) +{ + QDBusInterface iface(service, path.isEmpty() ? QLatin1String("/") : path, + QLatin1String("org.freedesktop.DBus.Introspectable"), connection); + if (!iface.isValid()) { + QDBusError err(iface.lastError()); + fprintf(stderr, "Cannot introspect object %s at %s:\n%s (%s)\n", + qPrintable(path.isEmpty() ? QString(QLatin1String("/")) : path), qPrintable(service), + qPrintable(err.name()), qPrintable(err.message())); + exit(1); + } + QDBusReply<QString> xml = iface.call(QLatin1String("Introspect")); + + if (!xml.isValid()) + return; // silently + + QDomDocument doc; + doc.setContent(xml); + QDomElement node = doc.documentElement(); + QDomElement child = node.firstChildElement(); + while (!child.isNull()) { + if (child.tagName() == QLatin1String("node")) { + QString sub = path + QLatin1Char('/') + child.attribute(QLatin1String("name")); + printf("%s\n", qPrintable(sub)); + listObjects(service, sub); + } + child = child.nextSiblingElement(); + } +} + +static void listInterface(const QString &service, const QString &path, const QString &interface) +{ + QDBusInterface iface(service, path, interface, connection); + if (!iface.isValid()) { + QDBusError err(iface.lastError()); + fprintf(stderr, "Interface '%s' not available in object %s at %s:\n%s (%s)\n", + qPrintable(interface), qPrintable(path), qPrintable(service), + qPrintable(err.name()), qPrintable(err.message())); + exit(1); + } + const QMetaObject *mo = iface.metaObject(); + + // properties + for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) { + QMetaProperty mp = mo->property(i); + printf("property "); + + if (mp.isReadable() && mp.isWritable()) + printf("readwrite"); + else if (mp.isReadable()) + printf("read"); + else + printf("write"); + + printf(" %s %s.%s\n", mp.typeName(), qPrintable(interface), mp.name()); + } + + // methods (signals and slots) + for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) { + QMetaMethod mm = mo->method(i); + + QByteArray signature = mm.signature(); + signature.truncate(signature.indexOf('(')); + printf("%s %s%s%s %s.%s(", + mm.methodType() == QMetaMethod::Signal ? "signal" : "method", + mm.tag(), *mm.tag() ? " " : "", + *mm.typeName() ? mm.typeName() : "void", + qPrintable(interface), signature.constData()); + + QList<QByteArray> types = mm.parameterTypes(); + QList<QByteArray> names = mm.parameterNames(); + bool first = true; + for (int i = 0; i < types.count(); ++i) { + printf("%s%s", + first ? "" : ", ", + types.at(i).constData()); + if (!names.at(i).isEmpty()) + printf(" %s", names.at(i).constData()); + first = false; + } + printf(")\n"); + } +} + +static void listAllInterfaces(const QString &service, const QString &path) +{ + QDBusInterface iface(service, path, QLatin1String("org.freedesktop.DBus.Introspectable"), connection); + if (!iface.isValid()) { + QDBusError err(iface.lastError()); + fprintf(stderr, "Cannot introspect object %s at %s:\n%s (%s)\n", + qPrintable(path), qPrintable(service), + qPrintable(err.name()), qPrintable(err.message())); + exit(1); + } + QDBusReply<QString> xml = iface.call(QLatin1String("Introspect")); + + if (!xml.isValid()) + return; // silently + + QDomDocument doc; + doc.setContent(xml); + QDomElement node = doc.documentElement(); + QDomElement child = node.firstChildElement(); + while (!child.isNull()) { + if (child.tagName() == QLatin1String("interface")) { + QString ifaceName = child.attribute(QLatin1String("name")); + if (QDBusUtil::isValidInterfaceName(ifaceName)) + listInterface(service, path, ifaceName); + else { + qWarning("Invalid D-BUS interface name '%s' found while parsing introspection", + qPrintable(ifaceName)); + } + } + child = child.nextSiblingElement(); + } +} + +static QStringList readList(QStringList &args) +{ + args.takeFirst(); + + QStringList retval; + while (!args.isEmpty() && args.at(0) != QLatin1String(")")) + retval += args.takeFirst(); + + if (args.value(0) == QLatin1String(")")) + args.takeFirst(); + + return retval; +} + +static void placeCall(const QString &service, const QString &path, const QString &interface, + const QString &member, QStringList args) +{ + QDBusInterface iface(service, path, interface, connection); + if (!iface.isValid()) { + QDBusError err(iface.lastError()); + fprintf(stderr, "Interface '%s' not available in object %s at %s:\n%s (%s)\n", + qPrintable(interface), qPrintable(path), qPrintable(service), + qPrintable(err.name()), qPrintable(err.message())); + exit(1); + } + + QVariantList params; + if (!args.isEmpty()) { + const QMetaObject *mo = iface.metaObject(); + QByteArray match = member.toLatin1(); + match += '('; + + int midx = -1; + for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) { + QMetaMethod mm = mo->method(i); + QByteArray signature = mm.signature(); + if (signature.startsWith(match)) { + midx = i; + break; + } + } + + if (midx == -1) { + fprintf(stderr, "Cannot find '%s.%s' in object %s at %s\n", + qPrintable(interface), qPrintable(member), qPrintable(path), + qPrintable(service)); + exit(1); + } + + QMetaMethod mm = mo->method(midx); + QList<QByteArray> types = mm.parameterTypes(); + for (int i = 0; i < types.count(); ++i) { + if (types.at(i).endsWith('&')) { + // reference (and not a reference to const): output argument + // we're done with the inputs + while (types.count() > i) + types.removeLast(); + break; + } + } + + for (int i = 0; !args.isEmpty() && i < types.count(); ++i) { + int id = QVariant::nameToType(types.at(i)); + if (id == QVariant::UserType) + id = QMetaType::type(types.at(i)); + Q_ASSERT(id); + + QVariant p; + QString argument; + if ((id == QVariant::List || id == QVariant::StringList) + && args.at(0) == QLatin1String("(")) + p = readList(args); + else + p = argument = args.takeFirst(); + + if (id == int(QMetaType::UChar)) { + // special case: QVariant::convert doesn't convert to/from + // UChar because it can't decide if it's a character or a number + p = qVariantFromValue<uchar>(p.toUInt()); + } else if (id < int(QMetaType::User) && id != int(QVariant::Map)) { + p.convert(QVariant::Type(id)); + if (p.type() == QVariant::Invalid) { + fprintf(stderr, "Could not convert '%s' to type '%s'.\n", + qPrintable(argument), types.at(i).constData()); + exit(1); + } + } else if (id == qMetaTypeId<QDBusVariant>()) { + QDBusVariant tmp(p); + p = qVariantFromValue(tmp); + } else if (id == qMetaTypeId<QDBusObjectPath>()) { + QDBusObjectPath path(argument); + if (path.path().isNull()) { + fprintf(stderr, "Cannot pass argument '%s' because it is not a valid object path.\n", + qPrintable(argument)); + exit(1); + } + p = qVariantFromValue(path); + } else if (id == qMetaTypeId<QDBusSignature>()) { + QDBusSignature sig(argument); + if (sig.signature().isNull()) { + fprintf(stderr, "Cannot pass argument '%s' because it is not a valid signature.\n", + qPrintable(argument)); + exit(1); + } + p = qVariantFromValue(sig); + } else { + fprintf(stderr, "Sorry, can't pass arg of type '%s'.\n", + types.at(i).constData()); + exit(1); + } + params += p; + } + if (params.count() != types.count() || !args.isEmpty()) { + fprintf(stderr, "Invalid number of parameters\n"); + exit(1); + } + } + + QDBusMessage reply = iface.callWithArgumentList(QDBus::Block, member, params); + if (reply.type() == QDBusMessage::ErrorMessage) { + QDBusError err = reply; + printf("Error: %s\n%s\n", qPrintable(err.name()), qPrintable(err.message())); + exit(2); + } else if (reply.type() != QDBusMessage::ReplyMessage) { + fprintf(stderr, "Invalid reply type %d\n", int(reply.type())); + exit(1); + } + + foreach (QVariant v, reply.arguments()) + printArg(v); + + exit(0); +} + +static bool globServices(QDBusConnectionInterface *bus, const QString &glob) +{ + QRegExp pattern(glob, Qt::CaseSensitive, QRegExp::Wildcard); + if (!pattern.isValid()) + return false; + + QStringList names = bus->registeredServiceNames(); + names.sort(); + foreach (const QString &name, names) + if (pattern.exactMatch(name)) + printf("%s\n", qPrintable(name)); + + return true; +} + +static void printAllServices(QDBusConnectionInterface *bus) +{ + const QStringList services = bus->registeredServiceNames(); + QMap<QString, QStringList> servicesWithAliases; + + foreach (QString serviceName, services) { + QDBusReply<QString> reply = bus->serviceOwner(serviceName); + QString owner = reply; + if (owner.isEmpty()) + owner = serviceName; + servicesWithAliases[owner].append(serviceName); + } + + for (QMap<QString,QStringList>::const_iterator it = servicesWithAliases.constBegin(); + it != servicesWithAliases.constEnd(); ++it) { + QStringList names = it.value(); + names.sort(); + printf("%s\n", qPrintable(names.join(QLatin1String("\n ")))); + } +} + +int main(int argc, char **argv) +{ + QCoreApplication app(argc, argv); + QStringList args = app.arguments(); + args.takeFirst(); + + bool connectionOpened = false; + while (!args.isEmpty() && args.at(0).startsWith(QLatin1Char('-'))) { + QString arg = args.takeFirst(); + if (arg == QLatin1String("--system")) { + connection = QDBusConnection::systemBus(); + connectionOpened = true; + } else if (arg == QLatin1String("--literal")) { + printArgumentsLiterally = true; + } else if (arg == QLatin1String("--help")) { + showUsage(); + return 0; + } + } + + if (!connectionOpened) + connection = QDBusConnection::sessionBus(); + + if (!connection.isConnected()) { + fprintf(stderr, "Could not connect to D-Bus server: %s: %s\n", + qPrintable(connection.lastError().name()), + qPrintable(connection.lastError().message())); + return 1; + } + + QDBusConnectionInterface *bus = connection.interface(); + if (args.isEmpty()) { + printAllServices(bus); + exit(0); + } + + QString service = args.takeFirst(); + if (!QDBusUtil::isValidBusName(service)) { + if (service.contains(QLatin1Char('*'))) { + if (globServices(bus, service)) + return 0; + } + fprintf(stderr, "Service '%s' is not a valid name.\n", qPrintable(service)); + exit(1); + } + if (!bus->isServiceRegistered(service)) { + fprintf(stderr, "Service '%s' does not exist.\n", qPrintable(service)); + exit(1); + } + + if (args.isEmpty()) { + printf("/\n"); + listObjects(service, QString()); + exit(0); + } + + QString path = args.takeFirst(); + if (!QDBusUtil::isValidObjectPath(path)) { + fprintf(stderr, "Path '%s' is not a valid path name.\n", qPrintable(path)); + exit(1); + } + if (args.isEmpty()) { + listAllInterfaces(service, path); + exit(0); + } + + QString interface = args.takeFirst(); + QString member; + int pos = interface.lastIndexOf(QLatin1Char('.')); + if (pos == -1) { + member = interface; + interface.clear(); + } else { + member = interface.mid(pos + 1); + interface.truncate(pos); + } + if (!interface.isEmpty() && !QDBusUtil::isValidInterfaceName(interface)) { + fprintf(stderr, "Interface '%s' is not a valid interface name.\n", qPrintable(interface)); + exit(1); + } + if (!QDBusUtil::isValidMemberName(member)) { + fprintf(stderr, "Method name '%s' is not a valid member name.\n", qPrintable(member)); + exit(1); + } + + placeCall(service, path, interface, member, args); +} + diff --git a/tools/qdbus/qdbus/qdbus.pro b/tools/qdbus/qdbus/qdbus.pro new file mode 100644 index 0000000..ea732ef --- /dev/null +++ b/tools/qdbus/qdbus/qdbus.pro @@ -0,0 +1,10 @@ +SOURCES = qdbus.cpp +DESTDIR = ../../../bin +TARGET = qdbus +QT = core xml +CONFIG += qdbus +CONFIG -= app_bundle +win32:CONFIG += console + +target.path=$$[QT_INSTALL_BINS] +INSTALLS += target diff --git a/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.cpp b/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.cpp new file mode 100644 index 0000000..9e789b0 --- /dev/null +++ b/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.cpp @@ -0,0 +1,446 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QByteArray> +#include <QString> +#include <QVarLengthArray> +#include <QFile> +#include <QProcess> +#include <QMetaObject> +#include <QList> +#include <QRegExp> +#include <QCoreApplication> +#include <QLibraryInfo> + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <stdlib.h> + +#include "qdbusconnection.h" // for the Export* flags + +// 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"; + +// in qdbusxmlgenerator.cpp +QT_BEGIN_NAMESPACE +extern QDBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, + const QMetaObject *base, int flags); +QT_END_NAMESPACE + +#define PROGRAMNAME "qdbuscpp2xml" +#define PROGRAMVERSION "0.1" +#define PROGRAMCOPYRIGHT "Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies)." + +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"; + +class MocParser +{ + void parseError(); + QByteArray readLine(); + void loadIntData(uint *&data); + void loadStringData(char *&stringdata); + + QIODevice *input; + const char *filename; + int lineNumber; +public: + ~MocParser(); + void parse(const char *filename, QIODevice *input, int lineNumber = 0); + + QList<QMetaObject> objects; +}; + +void MocParser::parseError() +{ + fprintf(stderr, PROGRAMNAME ": error parsing input file '%s' line %d \n", filename, lineNumber); + exit(1); +} + +QByteArray MocParser::readLine() +{ + ++lineNumber; + return input->readLine(); +} + +void MocParser::loadIntData(uint *&data) +{ + data = 0; // initialise + QVarLengthArray<uint> array; + QRegExp rx(QLatin1String("(\\d+|0x[0-9abcdef]+)"), Qt::CaseInsensitive); + + while (!input->atEnd()) { + QString line = QLatin1String(readLine()); + int pos = line.indexOf(QLatin1String("//")); + if (pos != -1) + line.truncate(pos); // drop comments + + if (line == QLatin1String("};\n")) { + // end of data + data = new uint[array.count()]; + memcpy(data, array.data(), array.count() * sizeof(*data)); + return; + } + + pos = 0; + while ((pos = rx.indexIn(line, pos)) != -1) { + QString num = rx.cap(1); + if (num.startsWith(QLatin1String("0x"))) + array.append(num.mid(2).toUInt(0, 16)); + else + array.append(num.toUInt()); + pos += rx.matchedLength(); + } + } + + parseError(); +} + +void MocParser::loadStringData(char *&stringdata) +{ + stringdata = 0; + QVarLengthArray<char, 1024> array; + + while (!input->atEnd()) { + QByteArray line = readLine(); + if (line == "};\n") { + // end of data + stringdata = new char[array.count()]; + memcpy(stringdata, array.data(), array.count() * sizeof(*stringdata)); + return; + } + + int start = line.indexOf('"'); + if (start == -1) + parseError(); + + int len = line.length() - 1; + line.truncate(len); // drop ending \n + if (line.at(len - 1) != '"') + parseError(); + + --len; + ++start; + for ( ; start < len; ++start) + if (line.at(start) == '\\') { + // parse escaped sequence + ++start; + if (start == len) + parseError(); + + QChar c(QLatin1Char(line.at(start))); + if (!c.isDigit()) { + switch (c.toLatin1()) { + case 'a': + array.append('\a'); + break; + case 'b': + array.append('\b'); + break; + case 'f': + array.append('\f'); + break; + case 'n': + array.append('\n'); + break; + case 'r': + array.append('\r'); + break; + case 't': + array.append('\t'); + break; + case 'v': + array.append('\v'); + break; + case '\\': + case '?': + case '\'': + case '"': + array.append(c.toLatin1()); + break; + + case 'x': + if (start + 2 <= len) + parseError(); + array.append(char(line.mid(start + 1, 2).toInt(0, 16))); + break; + + default: + array.append(c.toLatin1()); + fprintf(stderr, PROGRAMNAME ": warning: invalid escape sequence '\\%c' found in input", + c.toLatin1()); + } + } else { + // octal + QRegExp octal(QLatin1String("([0-7]+)")); + if (octal.indexIn(QLatin1String(line), start) == -1) + parseError(); + array.append(char(octal.cap(1).toInt(0, 8))); + } + } else { + array.append(line.at(start)); + } + } + + parseError(); +} + +void MocParser::parse(const char *fname, QIODevice *io, int lineNum) +{ + filename = fname; + input = io; + lineNumber = lineNum; + + while (!input->atEnd()) { + QByteArray line = readLine(); + if (line.startsWith("static const uint qt_meta_data_")) { + // start of new class data + uint *data; + loadIntData(data); + + // find the start of the string data + do { + line = readLine(); + if (input->atEnd()) + parseError(); + } while (!line.startsWith("static const char qt_meta_stringdata_")); + + char *stringdata; + loadStringData(stringdata); + + QMetaObject mo; + mo.d.superdata = &QObject::staticMetaObject; + mo.d.stringdata = stringdata; + mo.d.data = data; + mo.d.extradata = 0; + objects.append(mo); + } + } + + fname = 0; + input = 0; +} + +MocParser::~MocParser() +{ + foreach (QMetaObject mo, objects) { + delete const_cast<char *>(mo.d.stringdata); + delete const_cast<uint *>(mo.d.data); + } +} + +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) +{ + for (int i = 1; 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; + // fall through + case 'p': + flags |= QDBusConnection::ExportScriptableProperties; + break; + + case 'S': + flags |= QDBusConnection::ExportNonScriptableSignals; + // fall through + case 's': + flags |= QDBusConnection::ExportScriptableSignals; + break; + + case 'M': + flags |= QDBusConnection::ExportNonScriptableSlots; + // fall through + case 'm': + flags |= QDBusConnection::ExportScriptableSlots; + break; + + case 'A': + flags |= QDBusConnection::ExportNonScriptableContents; + // fall through + 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) +{ + QCoreApplication app(argc, argv); + QStringList args = app.arguments(); + + MocParser parser; + parseCmdLine(args); + + for (int i = 1; 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; + } + + f.readLine(); + + QByteArray line = f.readLine(); + if (line.contains("Meta object code from reading C++ file")) + // this is a moc-generated file + parser.parse(argv[i], &f, 3); + else { + // run moc on this file + QProcess proc; + proc.start(QLibraryInfo::location(QLibraryInfo::BinariesPath) + QLatin1String("/moc"), QStringList() << QFile::decodeName(argv[i]), QIODevice::ReadOnly | QIODevice::Text); + + if (!proc.waitForStarted()) { + fprintf(stderr, PROGRAMNAME ": could not execute moc! Aborting.\n"); + return 1; + } + + proc.closeWriteChannel(); + + if (!proc.waitForFinished() || proc.exitStatus() != QProcess::NormalExit || + proc.exitCode() != 0) { + // output the moc errors: + fprintf(stderr, "%s", proc.readAllStandardError().constData()); + fprintf(stderr, PROGRAMNAME ": exit code %d from moc. Aborting\n", proc.exitCode()); + return 1; + } + fprintf(stderr, "%s", proc.readAllStandardError().constData()); + + parser.parse(argv[i], &proc, 1); + } + + 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"); + foreach (QMetaObject mo, parser.objects) { + QString xml = qDBusGenerateMetaObjectXml(QString(), &mo, &QObject::staticMetaObject, + flags); + output.write(xml.toLocal8Bit()); + } + output.write("</node>\n"); + + return 0; +} + diff --git a/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.pro b/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.pro new file mode 100644 index 0000000..9ee1d37 --- /dev/null +++ b/tools/qdbus/qdbuscpp2xml/qdbuscpp2xml.pro @@ -0,0 +1,10 @@ +SOURCES = qdbuscpp2xml.cpp +DESTDIR = ../../../bin +TARGET = qdbuscpp2xml +QT = core xml +CONFIG += qdbus +CONFIG -= app_bundle +win32:CONFIG += console + +target.path=$$[QT_INSTALL_BINS] +INSTALLS += target diff --git a/tools/qdbus/qdbusviewer/Info_mac.plist b/tools/qdbus/qdbusviewer/Info_mac.plist new file mode 100644 index 0000000..b351409 --- /dev/null +++ b/tools/qdbus/qdbusviewer/Info_mac.plist @@ -0,0 +1,18 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist SYSTEM "file://localhost/System/Library/DTDs/PropertyList.dtd"> +<plist version="0.9"> +<dict> + <key>CFBundleIconFile</key> + <string>@ICON@</string> + <key>CFBundlePackageType</key> + <string>APPL</string> + <key>CFBundleGetInfoString</key> + <string>Created by Qt/QMake</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleIdentifier</key> + <string>com.trolltech.dbusviewer</string> + <key>CFBundleExecutable</key> + <string>@EXECUTABLE@</string> +</dict> +</plist> diff --git a/tools/qdbus/qdbusviewer/images/qdbusviewer-128.png b/tools/qdbus/qdbusviewer/images/qdbusviewer-128.png Binary files differnew file mode 100644 index 0000000..0754912 --- /dev/null +++ b/tools/qdbus/qdbusviewer/images/qdbusviewer-128.png diff --git a/tools/qdbus/qdbusviewer/images/qdbusviewer.icns b/tools/qdbus/qdbusviewer/images/qdbusviewer.icns Binary files differnew file mode 100644 index 0000000..b6f39b9 --- /dev/null +++ b/tools/qdbus/qdbusviewer/images/qdbusviewer.icns diff --git a/tools/qdbus/qdbusviewer/images/qdbusviewer.ico b/tools/qdbus/qdbusviewer/images/qdbusviewer.ico Binary files differnew file mode 100644 index 0000000..49edb09 --- /dev/null +++ b/tools/qdbus/qdbusviewer/images/qdbusviewer.ico diff --git a/tools/qdbus/qdbusviewer/images/qdbusviewer.png b/tools/qdbus/qdbusviewer/images/qdbusviewer.png Binary files differnew file mode 100644 index 0000000..5a8c5a3 --- /dev/null +++ b/tools/qdbus/qdbusviewer/images/qdbusviewer.png diff --git a/tools/qdbus/qdbusviewer/main.cpp b/tools/qdbus/qdbusviewer/main.cpp new file mode 100644 index 0000000..b670337 --- /dev/null +++ b/tools/qdbus/qdbusviewer/main.cpp @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtGui/qapplication.h> +#include <QtGui/qmainwindow.h> +#include <QtGui/qtabwidget.h> +#include <QtDBus/qdbusconnection.h> +#include "qdbusviewer.h" + +#include <stdio.h> + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + QMainWindow mw; +#ifndef Q_WS_MAC + app.setWindowIcon(QIcon(QLatin1String(":/trolltech/qdbusviewer/images/qdbusviewer.png"))); +#else + mw.setWindowTitle(qApp->translate("QtDBusViewer", "Qt D-Bus Viewer")); +#endif + + + QTabWidget *mainWidget = new QTabWidget; + mw.setCentralWidget(mainWidget); + QDBusViewer *sessionBusViewer = new QDBusViewer(QDBusConnection::sessionBus()); + QDBusViewer *systemBusViewer = new QDBusViewer(QDBusConnection::systemBus()); + mainWidget->addTab(sessionBusViewer, QObject::tr("Session Bus")); + mainWidget->addTab(systemBusViewer, QObject::tr("System Bus")); + + QMenu *fileMenu = mw.menuBar()->addMenu(QObject::tr("&File")); + QAction *quitAction = fileMenu->addAction(QObject::tr("&Quit"), &mw, SLOT(close())); + Q_UNUSED(quitAction); + + QMenu *helpMenu = mw.menuBar()->addMenu(QObject::tr("&Help")); + QAction *aboutAction = helpMenu->addAction(QObject::tr("&About")); + aboutAction->setMenuRole(QAction::AboutRole); + QObject::connect(aboutAction, SIGNAL(triggered()), sessionBusViewer, SLOT(about())); + + QAction *aboutQtAction = helpMenu->addAction(QObject::tr("About &Qt")); + aboutQtAction->setMenuRole(QAction::AboutQtRole); + QObject::connect(aboutQtAction, SIGNAL(triggered()), &app, SLOT(aboutQt())); + + mw.show(); + + return app.exec(); +} + diff --git a/tools/qdbus/qdbusviewer/propertydialog.cpp b/tools/qdbus/qdbusviewer/propertydialog.cpp new file mode 100644 index 0000000..6359cd7 --- /dev/null +++ b/tools/qdbus/qdbusviewer/propertydialog.cpp @@ -0,0 +1,114 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "propertydialog.h" + +#include <QHeaderView> +#include <QLayout> +#include <QDebug> + +PropertyDialog::PropertyDialog(QWidget *parent, Qt::WindowFlags f) + : QDialog(parent, f) +{ + buttonBox = new QDialogButtonBox; + propertyTable = new QTableWidget; + label = new QLabel; + + buttonBox->setStandardButtons(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + propertyTable->setColumnCount(2); + const QStringList labels = QStringList() << QLatin1String("Name") << QLatin1String("Value"); + propertyTable->setHorizontalHeaderLabels(labels); + propertyTable->horizontalHeader()->setStretchLastSection(true); + propertyTable->setEditTriggers(QAbstractItemView::AllEditTriggers); + + connect(buttonBox, SIGNAL(accepted()), SLOT(accept()), Qt::QueuedConnection); + connect(buttonBox, SIGNAL(rejected()), SLOT(reject()), Qt::QueuedConnection); + + QVBoxLayout *layout = new QVBoxLayout(this); + layout->addWidget(label); + layout->addWidget(propertyTable); + layout->addWidget(buttonBox); +} + +void PropertyDialog::setInfo(const QString &caption) +{ + label->setText(caption); +} + +void PropertyDialog::addProperty(const QString &aname, QVariant::Type type) +{ + int rowCount = propertyTable->rowCount(); + propertyTable->setRowCount(rowCount + 1); + + QString name = aname; + if (name.isEmpty()) + name = QLatin1String("argument ") + QString::number(rowCount + 1); + name += QLatin1String(" ("); + name += QLatin1String(QVariant::typeToName(type)); + name += QLatin1String(")"); + QTableWidgetItem *nameItem = new QTableWidgetItem(name); + nameItem->setFlags(nameItem->flags() & + ~(Qt::ItemIsEditable | Qt::ItemIsSelectable)); + propertyTable->setItem(rowCount, 0, nameItem); + + QTableWidgetItem *valueItem = new QTableWidgetItem; + valueItem->setData(Qt::DisplayRole, QVariant(type)); + propertyTable->setItem(rowCount, 1, valueItem); +} + +int PropertyDialog::exec() +{ + propertyTable->resizeColumnToContents(0); + propertyTable->setFocus(); + propertyTable->setCurrentCell(0, 1); + return QDialog::exec(); +} + +QList<QVariant> PropertyDialog::values() const +{ + QList<QVariant> result; + + for (int i = 0; i < propertyTable->rowCount(); ++i) + result << propertyTable->item(i, 1)->data(Qt::EditRole); + + return result; +} + diff --git a/tools/qdbus/qdbusviewer/propertydialog.h b/tools/qdbus/qdbusviewer/propertydialog.h new file mode 100644 index 0000000..2ba30e17 --- /dev/null +++ b/tools/qdbus/qdbusviewer/propertydialog.h @@ -0,0 +1,70 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef PROPERTYDIALOG_H +#define PROPERTYDIALOG_H + +#include <QLabel> +#include <QDialog> +#include <QTableWidget> +#include <QDialogButtonBox> + +class PropertyDialog: public QDialog +{ + Q_OBJECT +public: + explicit PropertyDialog(QWidget *parent = 0, Qt::WindowFlags f = 0); + + void addProperty(const QString &name, QVariant::Type type); + void setInfo(const QString &caption); + + QList<QVariant> values() const; + + int exec(); + +private: + QLabel *label; + QTableWidget *propertyTable; + QDialogButtonBox *buttonBox; +}; + +#endif + diff --git a/tools/qdbus/qdbusviewer/qdbusmodel.cpp b/tools/qdbus/qdbusviewer/qdbusmodel.cpp new file mode 100644 index 0000000..88e3524 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusmodel.cpp @@ -0,0 +1,336 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbusmodel.h" + +#include <QtCore/qvector.h> +#include <QtXml/QtXml> + +struct QDBusItem +{ + inline QDBusItem(QDBusModel::Type aType, const QString &aName, QDBusItem *aParent = 0) + : type(aType), parent(aParent), isPrefetched(type != QDBusModel::PathItem), name(aName) + {} + inline ~QDBusItem() + { + qDeleteAll(children); + } + + QString path() const + { + Q_ASSERT(type == QDBusModel::PathItem); + + QString s; + const QDBusItem *item = this; + while (item) { + s.prepend(item->name); + item = item->parent; + } + if (s.length() > 1) + s.chop(1); // remove tailing slash + return s; + } + + QDBusModel::Type type; + QDBusItem *parent; + QVector<QDBusItem *> children; + bool isPrefetched; + QString name; + QString caption; +}; + +QDomDocument QDBusModel::introspect(const QString &path) +{ + QDomDocument doc; + + QDBusInterface iface(service, path, QLatin1String("org.freedesktop.DBus.Introspectable"), c); + if (!iface.isValid()) { + QDBusError err(iface.lastError()); + emit busError(QString::fromLatin1("Cannot introspect object %1 at %2:\n %3 (%4)\n").arg(path).arg( + service).arg(err.name()).arg(err.message())); + return doc; + } + + QDBusReply<QString> xml = iface.call(QLatin1String("Introspect")); + + if (!xml.isValid()) { + QDBusError err(xml.error()); + if (err.isValid()) { + emit busError(QString::fromLatin1("Call to object %1 at %2:\n %3 (%4) failed\n").arg( + path).arg(service).arg(err.name()).arg(err.message())); + } else { + emit busError(QString::fromLatin1("Invalid XML received from object %1 at %2\n").arg( + path).arg(service)); + } + return doc; + } + + doc.setContent(xml); + return doc; +} + +void QDBusModel::addMethods(QDBusItem *parent, const QDomElement &iface) +{ + Q_ASSERT(parent); + + QDomElement child = iface.firstChildElement(); + while (!child.isNull()) { + QDBusItem *item = 0; + if (child.tagName() == QLatin1String("method")) { + item = new QDBusItem(QDBusModel::MethodItem, + child.attribute(QLatin1String("name")), parent); + item->caption = QLatin1String("Method: ") + item->name; + } else if (child.tagName() == QLatin1String("signal")) { + item = new QDBusItem(QDBusModel::SignalItem, + child.attribute(QLatin1String("name")), parent); + item->caption = QLatin1String("Signal: ") + item->name; + } else if (child.tagName() == QLatin1String("property")) { + item = new QDBusItem(QDBusModel::PropertyItem, + child.attribute(QLatin1String("name")), parent); + item->caption = QLatin1String("Property: ") + item->name; + } else { + qDebug() << "addMethods: unknown tag:" << child.tagName(); + } + if (item) + parent->children.append(item); + + child = child.nextSiblingElement(); + } +} + +void QDBusModel::addPath(QDBusItem *parent) +{ + Q_ASSERT(parent); + + QString path = parent->path(); + + QDomDocument doc = introspect(path); + QDomElement node = doc.documentElement(); + QDomElement child = node.firstChildElement(); + while (!child.isNull()) { + if (child.tagName() == QLatin1String("node")) { + QDBusItem *item = new QDBusItem(QDBusModel::PathItem, + child.attribute(QLatin1String("name")) + QLatin1Char('/'), parent); + parent->children.append(item); + + addMethods(item, child); + } else if (child.tagName() == QLatin1String("interface")) { + QDBusItem *item = new QDBusItem(QDBusModel::InterfaceItem, + child.attribute(QLatin1String("name")), parent); + parent->children.append(item); + + addMethods(item, child); + } else { + qDebug() << "addPath: Unknown tag name:" << child.tagName(); + } + child = child.nextSiblingElement(); + } + + parent->isPrefetched = true; +} + +QDBusModel::QDBusModel(const QString &aService, const QDBusConnection &connection) + : service(aService), c(connection), root(0) +{ + root = new QDBusItem(QDBusModel::PathItem, QLatin1String("/")); +} + +QDBusModel::~QDBusModel() +{ + delete root; +} + +QModelIndex QDBusModel::index(int row, int column, const QModelIndex &parent) const +{ + const QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer()); + if (!item) + item = root; + + if (column != 0 || row < 0 || row >= item->children.count()) + return QModelIndex(); + + return createIndex(row, 0, item->children.at(row)); +} + +QModelIndex QDBusModel::parent(const QModelIndex &child) const +{ + QDBusItem *item = static_cast<QDBusItem *>(child.internalPointer()); + if (!item || !item->parent || !item->parent->parent) + return QModelIndex(); + + return createIndex(item->parent->parent->children.indexOf(item->parent), 0, item->parent); +} + +int QDBusModel::rowCount(const QModelIndex &parent) const +{ + QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer()); + if (!item) + item = root; + if (!item->isPrefetched) + const_cast<QDBusModel *>(this)->addPath(item); + + return item->children.count(); +} + +int QDBusModel::columnCount(const QModelIndex &) const +{ + return 1; +} + +QVariant QDBusModel::data(const QModelIndex &index, int role) const +{ + const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + if (!item) + return QVariant(); + + if (role != Qt::DisplayRole) + return QVariant(); + + return item->caption.isEmpty() ? item->name : item->caption; +} + +QVariant QDBusModel::headerData(int section, Qt::Orientation orientation, int role) const +{ + if (role != Qt::DisplayRole || orientation == Qt::Vertical || section != 0) + return QVariant(); + + return QLatin1String("Methods"); +} + +QDBusModel::Type QDBusModel::itemType(const QModelIndex &index) const +{ + const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + return item ? item->type : PathItem; +} + +void QDBusModel::refresh(const QModelIndex &aIndex) +{ + QModelIndex index = aIndex; + while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) { + index = index.parent(); + } + + QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + if (!item) + item = root; + + if (!item->children.isEmpty()) { + beginRemoveRows(index, 0, item->children.count() - 1); + qDeleteAll(item->children); + item->children.clear(); + endRemoveRows(); + } + + addPath(item); + if (!item->children.isEmpty()) { + beginInsertRows(index, 0, item->children.count() - 1); + endInsertRows(); + } +} + +QString QDBusModel::dBusPath(const QModelIndex &aIndex) const +{ + QModelIndex index = aIndex; + while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) { + index = index.parent(); + } + + QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + if (!item) + item = root; + + return item->path(); +} + +QString QDBusModel::dBusInterface(const QModelIndex &index) const +{ + QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + if (!item) + return QString(); + if (item->type == InterfaceItem) + return item->name; + if (item->parent && item->parent->type == InterfaceItem) + return item->parent->name; + return QString(); +} + +QString QDBusModel::dBusMethodName(const QModelIndex &index) const +{ + QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); + return item ? item->name : QString(); +} + +QModelIndex QDBusModel::findObject(const QDBusObjectPath &objectPath) +{ + QStringList path = objectPath.path().split(QLatin1Char('/'), QString::SkipEmptyParts); + + QDBusItem *item = root; + int childIdx = -1; + while (item && !path.isEmpty()) { + const QString branch = path.takeFirst() + QLatin1Char('/'); + childIdx = -1; + + // do a linear search over all the children + for (int i = 0; i < item->children.count(); ++i) { + QDBusItem *child = item->children.at(i); + if (child->type == PathItem && child->name == branch) { + item = child; + childIdx = i; + + // prefetch the found branch + if (!item->isPrefetched) + addPath(item); + break; + } + } + + // branch not found - bail out + if (childIdx == -1) + return QModelIndex(); + } + + // found the right item + if (childIdx != -1 && item && path.isEmpty()) + return createIndex(childIdx, 0, item); + + return QModelIndex(); +} + diff --git a/tools/qdbus/qdbusviewer/qdbusmodel.h b/tools/qdbus/qdbusviewer/qdbusmodel.h new file mode 100644 index 0000000..50f928e --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusmodel.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSMODEL_H +#define QDBUSMODEL_H + +#include <QtCore/qabstractitemmodel.h> +#include <QtDBus/QtDBus> + +struct QDBusItem; + +QT_FORWARD_DECLARE_CLASS(QDomDocument); +QT_FORWARD_DECLARE_CLASS(QDomElement); + + +class QDBusModel: public QAbstractItemModel +{ + Q_OBJECT + +public: + enum Type { InterfaceItem, PathItem, MethodItem, SignalItem, PropertyItem }; + + QDBusModel(const QString &service, const QDBusConnection &connection); + ~QDBusModel(); + + QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent = QModelIndex()) const; + int columnCount(const QModelIndex &parent = QModelIndex()) const; + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; + QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; + + Type itemType(const QModelIndex &index) const; + QString dBusPath(const QModelIndex &index) const; + QString dBusInterface(const QModelIndex &index) const; + QString dBusMethodName(const QModelIndex &index) const; + + void refresh(const QModelIndex &index = QModelIndex()); + + QModelIndex findObject(const QDBusObjectPath &objectPath); + +Q_SIGNALS: + void busError(const QString &text); + +private: + QDomDocument introspect(const QString &path); + void addMethods(QDBusItem *parent, const QDomElement &iface); + void addPath(QDBusItem *parent); + + QString service; + QDBusConnection c; + QDBusItem *root; +}; + +#endif + diff --git a/tools/qdbus/qdbusviewer/qdbusviewer.cpp b/tools/qdbus/qdbusviewer/qdbusviewer.cpp new file mode 100644 index 0000000..e8ac3a6 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusviewer.cpp @@ -0,0 +1,509 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbusviewer.h" +#include "qdbusmodel.h" +#include "propertydialog.h" + +#include <QtXml/QtXml> +#include <QtDBus/private/qdbusutil_p.h> + +class QDBusViewModel: public QDBusModel +{ +public: + inline QDBusViewModel(const QString &service, const QDBusConnection &connection) + : QDBusModel(service, connection) + {} + + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const + { + if (role == Qt::FontRole && itemType(index) == InterfaceItem) { + QFont f; + f.setItalic(true); + return f; + } + return QDBusModel::data(index, role); + } +}; + +QDBusViewer::QDBusViewer(const QDBusConnection &connection, QWidget *parent) : + QWidget(parent), + c(connection), + objectPathRegExp(QLatin1String("\\[ObjectPath: (.*)\\]")) +{ + services = new QTreeWidget; + services->setRootIsDecorated(false); + services->setHeaderLabels(QStringList(QLatin1String("Services"))); + + tree = new QTreeView; + tree->setContextMenuPolicy(Qt::CustomContextMenu); + + connect(tree, SIGNAL(activated(const QModelIndex&)), this, SLOT(activate(const QModelIndex&))); + + refreshAction = new QAction(tr("&Refresh"), tree); + refreshAction->setData(42); // increase the amount of 42 used as magic number by one + refreshAction->setShortcut(QKeySequence::Refresh); + connect(refreshAction, SIGNAL(triggered()), this, SLOT(refreshChildren())); + + QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, tree); + connect(refreshShortcut, SIGNAL(activated()), this, SLOT(refreshChildren())); + + QVBoxLayout *topLayout = new QVBoxLayout(this); + log = new QTextBrowser; + connect(log, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl))); + + QHBoxLayout *layout = new QHBoxLayout; + layout->addWidget(services, 1); + layout->addWidget(tree, 2); + + topLayout->addLayout(layout); + topLayout->addWidget(log); + + connect(services, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)), + this, SLOT(serviceChanged(QTreeWidgetItem*))); + connect(tree, SIGNAL(customContextMenuRequested(QPoint)), + this, SLOT(showContextMenu(QPoint))); + + QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection); + + if (c.isConnected()) { + logMessage(QLatin1String("Connected to D-Bus.")); + QDBusConnectionInterface *iface = c.interface(); + connect(iface, SIGNAL(serviceRegistered(QString)), + this, SLOT(serviceRegistered(QString))); + connect(iface, SIGNAL(serviceUnregistered(QString)), + this, SLOT(serviceUnregistered(QString))); + connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)), + this, SLOT(serviceOwnerChanged(QString,QString,QString))); + } else { + logError(QLatin1String("Cannot connect to D-Bus: ") + c.lastError().message()); + } + + objectPathRegExp.setMinimal(true); + +} + +void QDBusViewer::logMessage(const QString &msg) +{ + log->append(msg + QLatin1Char('\n')); +} + +void QDBusViewer::logError(const QString &msg) +{ + log->append(QLatin1String("<font color=\"red\">Error: </font>") + Qt::escape(msg) + QLatin1String("<br>")); +} + +void QDBusViewer::refresh() +{ + services->clear(); + + if (c.isConnected()) { + const QStringList serviceNames = c.interface()->registeredServiceNames(); + foreach (QString service, serviceNames) + new QTreeWidgetItem(services, QStringList(service)); + } +} + +void QDBusViewer::activate(const QModelIndex &item) +{ + if (!item.isValid()) + return; + + const QDBusModel *model = static_cast<const QDBusModel *>(item.model()); + + BusSignature sig; + sig.mService = currentService; + sig.mPath = model->dBusPath(item); + sig.mInterface = model->dBusInterface(item); + sig.mName = model->dBusMethodName(item); + + switch (model->itemType(item)) { + case QDBusModel::SignalItem: + connectionRequested(sig); + break; + case QDBusModel::MethodItem: + callMethod(sig); + break; + case QDBusModel::PropertyItem: + getProperty(sig); + break; + default: + break; + } +} + +void QDBusViewer::getProperty(const BusSignature &sig) +{ + QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get")); + QList<QVariant> arguments; + arguments << sig.mInterface << sig.mName; + message.setArguments(arguments); + c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage))); +} + +void QDBusViewer::setProperty(const BusSignature &sig) +{ + QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c); + QMetaProperty prop = iface.metaObject()->property(iface.metaObject()->indexOfProperty(sig.mName.toLatin1())); + + bool ok; + QString input = QInputDialog::getText(this, tr("Arguments"), + tr("Please enter the value of the property %1 (type %2)").arg( + sig.mName, QString::fromLatin1(prop.typeName())), + QLineEdit::Normal, QString(), &ok); + if (!ok) + return; + + QVariant value = input; + if (!value.convert(prop.type())) { + QMessageBox::warning(this, tr("Unable to marshall"), + tr("Value conversion failed, unable to set property")); + return; + } + + QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Set")); + QList<QVariant> arguments; + arguments << sig.mInterface << sig.mName << qVariantFromValue(QDBusVariant(value)); + message.setArguments(arguments); + c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage))); + +} + +void QDBusViewer::callMethod(const BusSignature &sig) +{ + QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c); + const QMetaObject *mo = iface.metaObject(); + + // find the method + QMetaMethod method; + for (int i = 0; i < mo->methodCount(); ++i) { + const QString signature = QString::fromLatin1(mo->method(i).signature()); + if (signature.startsWith(sig.mName) && signature.at(sig.mName.length()) == QLatin1Char('(')) + method = mo->method(i); + } + if (!method.signature()) { + QMessageBox::warning(this, tr("Unable to find method"), + tr("Unable to find method %1 on path %2 in interface %3").arg( + sig.mName).arg(sig.mPath).arg(sig.mInterface)); + return; + } + + PropertyDialog dialog; + QList<QVariant> args; + + const QList<QByteArray> paramTypes = method.parameterTypes(); + const QList<QByteArray> paramNames = method.parameterNames(); + QList<int> types; // remember the low-level D-Bus type + for (int i = 0; i < paramTypes.count(); ++i) { + const QByteArray paramType = paramTypes.at(i); + if (paramType.endsWith('&')) + continue; // ignore OUT parameters + + QVariant::Type type = QVariant::nameToType(paramType); + dialog.addProperty(QString::fromLatin1(paramNames.value(i)), type); + types.append(QMetaType::type(paramType)); + } + + if (!types.isEmpty()) { + dialog.setInfo(tr("Please enter parameters for the method \"%1\"").arg(sig.mName)); + + if (dialog.exec() != QDialog::Accepted) + return; + + args = dialog.values(); + } + + // Special case - convert a value to a QDBusVariant if the + // interface wants a variant + for (int i = 0; i < args.count(); ++i) { + if (types.at(i) == qMetaTypeId<QDBusVariant>()) + args[i] = qVariantFromValue(QDBusVariant(args.at(i))); + } + + QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, sig.mInterface, + sig.mName); + message.setArguments(args); + c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage))); +} + +void QDBusViewer::showContextMenu(const QPoint &point) +{ + QModelIndex item = tree->indexAt(point); + if (!item.isValid()) + return; + + const QDBusModel *model = static_cast<const QDBusModel *>(item.model()); + + BusSignature sig; + sig.mService = currentService; + sig.mPath = model->dBusPath(item); + sig.mInterface = model->dBusInterface(item); + sig.mName = model->dBusMethodName(item); + + QMenu menu; + menu.addAction(refreshAction); + + switch (model->itemType(item)) { + case QDBusModel::SignalItem: { + QAction *action = new QAction(tr("&Connect"), &menu); + action->setData(1); + menu.addAction(action); + break; } + case QDBusModel::MethodItem: { + QAction *action = new QAction(tr("&Call"), &menu); + action->setData(2); + menu.addAction(action); + break; } + case QDBusModel::PropertyItem: { + QAction *actionSet = new QAction(tr("&Set value"), &menu); + actionSet->setData(3); + QAction *actionGet = new QAction(tr("&Get value"), &menu); + actionGet->setData(4); + menu.addAction(actionSet); + menu.addAction(actionGet); + break; } + default: + break; + } + + QAction *selectedAction = menu.exec(tree->viewport()->mapToGlobal(point)); + if (!selectedAction) + return; + + switch (selectedAction->data().toInt()) { + case 1: + connectionRequested(sig); + break; + case 2: + callMethod(sig); + break; + case 3: + setProperty(sig); + break; + case 4: + getProperty(sig); + break; + } +} + +void QDBusViewer::connectionRequested(const BusSignature &sig) +{ + if (!c.connect(sig.mService, QString(), sig.mInterface, sig.mName, this, + SLOT(dumpMessage(QDBusMessage)))) { + logError(tr("Unable to connect to service %1, path %2, interface %3, signal %4").arg( + sig.mService).arg(sig.mPath).arg(sig.mInterface).arg(sig.mName)); + } +} + +void QDBusViewer::dumpMessage(const QDBusMessage &message) +{ + QList<QVariant> args = message.arguments(); + QString out = QLatin1String("Received "); + + switch (message.type()) { + case QDBusMessage::SignalMessage: + out += QLatin1String("signal "); + break; + case QDBusMessage::ErrorMessage: + out += QLatin1String("error message "); + break; + case QDBusMessage::ReplyMessage: + out += QLatin1String("reply "); + break; + default: + out += QLatin1String("message "); + break; + } + + out += QLatin1String("from "); + out += message.service(); + if (!message.path().isEmpty()) + out += QLatin1String(", path ") + message.path(); + if (!message.interface().isEmpty()) + out += QLatin1String(", interface <i>") + message.interface() + QLatin1String("</i>"); + if (!message.member().isEmpty()) + out += QLatin1String(", member ") + message.member(); + out += QLatin1String("<br>"); + if (args.isEmpty()) { + out += QLatin1String(" (no arguments)"); + } else { + out += QLatin1String(" Arguments: "); + foreach (QVariant arg, args) { + QString str = Qt::escape(QDBusUtil::argumentToString(arg)); + // turn object paths into clickable links + str.replace(objectPathRegExp, QLatin1String("[ObjectPath: <a href=\"qdbus://bus\\1\">\\1</a>]")); + out += str; + out += QLatin1String(", "); + } + out.chop(2); + } + + log->append(out); +} + +void QDBusViewer::serviceChanged(QTreeWidgetItem *item) +{ + delete tree->model(); + + currentService.clear(); + if (!item) + return; + currentService = item->text(0); + + tree->setModel(new QDBusViewModel(currentService, c)); + connect(tree->model(), SIGNAL(busError(QString)), this, SLOT(logError(QString))); +} + +void QDBusViewer::serviceRegistered(const QString &service) +{ + if (service == c.baseService()) + return; + + new QTreeWidgetItem(services, QStringList(service)); +} + +static QTreeWidgetItem *findItem(const QTreeWidget *services, const QString &name) +{ + for (int i = 0; i < services->topLevelItemCount(); ++i) { + if (services->topLevelItem(i)->text(0) == name) + return services->topLevelItem(i); + } + return 0; +} + +void QDBusViewer::serviceUnregistered(const QString &name) +{ + delete findItem(services, name); +} + +void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner, + const QString &newOwner) +{ + QTreeWidgetItem *item = findItem(services, name); + + if (!item && oldOwner.isEmpty() && !newOwner.isEmpty()) + serviceRegistered(name); + else if (item && !oldOwner.isEmpty() && newOwner.isEmpty()) + delete item; + else if (item && !oldOwner.isEmpty() && !newOwner.isEmpty()) { + delete item; + serviceRegistered(name); + } +} + +void QDBusViewer::refreshChildren() +{ + QDBusModel *model = qobject_cast<QDBusModel *>(tree->model()); + if (!model) + return; + model->refresh(tree->currentIndex()); +} + +void QDBusViewer::about() +{ + QMessageBox box(this); +#if QT_EDITION == QT_EDITION_OPENSOURCE + QString edition = tr("Open Source Edition"); + QString info = tr("This version of Qt's D-Bus Viewer is part of the Qt Open Source Edition. " + "Qt is a comprehensive C++ framework for cross-platform application " + "development."); + QString moreInfo = tr("You need a commercial Qt license for development of proprietary (closed " + "source) applications. Please see <a href=\"http://qtsoftware.com/company/model" + ".html\">qtsoftware.com/company/model.html</a> for an overview of Qt licensing."); +#else + QString edition; + QString info; + QString moreInfo(tr("This program is licensed to you under the terms of the " + "Qt Commercial License Agreement. For details, see the file LICENSE " + "that came with this software distribution.")); + +#endif + + box.setText(QString::fromLatin1("<center><img src=\":/trolltech/qdbusviewer/images/qdbusviewer-128.png\">" + "<h3>%1</h3>" + "<p>Version %2 %3</p></center>" + "<p>%4</p>" + "<p>%5</p>" + "<p>Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).</p>" + "<p>The program is provided AS IS with NO WARRANTY OF ANY KIND," + " INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A" + " PARTICULAR PURPOSE.<p/>") + .arg(tr("D-Bus Viewer")).arg(QLatin1String(QT_VERSION_STR)).arg(edition).arg(info).arg(moreInfo)); + box.setWindowTitle(tr("D-Bus Viewer")); + box.exec(); +} + +void QDBusViewer::anchorClicked(const QUrl &url) +{ + if (url.scheme() != QLatin1String("qdbus")) + // not ours + return; + + // swallow the click without setting a new document + log->setSource(QUrl()); + + QDBusModel *model = qobject_cast<QDBusModel *>(tree->model()); + if (!model) + return; + + QModelIndex idx = model->findObject(QDBusObjectPath(url.path())); + if (!idx.isValid()) + return; + + tree->scrollTo(idx); + tree->setCurrentIndex(idx); +} + +/*! + \page qdbusviewer.html + \title D-Bus Viewer + \keyword qdbusviewer + + The Qt D-Bus Viewer is a tool that lets you introspect D-Bus objects and messages. You can + choose between the system bus and the session bus. Click on any service on the list + on the left side to see all the exported objects. + + You can invoke methods by double-clicking on them. If a method takes one or more IN parameters, + a property editor opens. + + Right-click on a signal to connect to it. All emitted signals including their parameters + are output in the message view on the lower side of the window. +*/ diff --git a/tools/qdbus/qdbusviewer/qdbusviewer.h b/tools/qdbus/qdbusviewer/qdbusviewer.h new file mode 100644 index 0000000..75511b7 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusviewer.h @@ -0,0 +1,98 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSVIEWER_H +#define QDBUSVIEWER_H + +#include <QtGui/QtGui> +#include <QtDBus/QtDBus> + +QT_FORWARD_DECLARE_CLASS(QTreeView); +QT_FORWARD_DECLARE_CLASS(QDomDocument); +QT_FORWARD_DECLARE_CLASS(QDomElement); + +struct BusSignature +{ + QString mService, mPath, mInterface, mName; +}; + +class QDBusViewer: public QWidget +{ + Q_OBJECT +public: + QDBusViewer(const QDBusConnection &connection, QWidget *parent = 0); + +public slots: + void refresh(); + void about(); + +private slots: + void serviceChanged(QTreeWidgetItem *item); + void showContextMenu(const QPoint &); + void connectionRequested(const BusSignature &sig); + void callMethod(const BusSignature &sig); + void getProperty(const BusSignature &sig); + void setProperty(const BusSignature &sig); + void dumpMessage(const QDBusMessage &msg); + void refreshChildren(); + + void serviceRegistered(const QString &service); + void serviceUnregistered(const QString &service); + void serviceOwnerChanged(const QString &name, const QString &oldOwner, const QString &newOwner); + + void activate(const QModelIndex &item); + + void logError(const QString &msg); + void anchorClicked(const QUrl &url); + +private: + void logMessage(const QString &msg); + + QDBusConnection c; + QString currentService; + QTreeView *tree; + QAction *refreshAction; + QTreeWidget *services; + QTextBrowser *log; + QRegExp objectPathRegExp; +}; + +#endif diff --git a/tools/qdbus/qdbusviewer/qdbusviewer.pro b/tools/qdbus/qdbusviewer/qdbusviewer.pro new file mode 100644 index 0000000..6727691 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusviewer.pro @@ -0,0 +1,30 @@ +TEMPLATE = app +TARGET = qdbusviewer + +HEADERS = qdbusviewer.h \ + qdbusmodel.h \ + propertydialog.h + +SOURCES = qdbusviewer.cpp \ + qdbusmodel.cpp \ + propertydialog.cpp \ + main.cpp + +RESOURCES += qdbusviewer.qrc + +DESTDIR = ../../../bin + +CONFIG += qdbus +QT += xml + +target.path=$$[QT_INSTALL_BINS] +INSTALLS += target + +mac { + ICON = images/qdbusviewer.icns + QMAKE_INFO_PLIST = Info_mac.plist +} + +win32 { + RC_FILE = qdbusviewer.rc +} diff --git a/tools/qdbus/qdbusviewer/qdbusviewer.qrc b/tools/qdbus/qdbusviewer/qdbusviewer.qrc new file mode 100644 index 0000000..7d592f3 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusviewer.qrc @@ -0,0 +1,6 @@ +<RCC> + <qresource prefix="/trolltech/qdbusviewer" > + <file>images/qdbusviewer-128.png</file> + <file>images/qdbusviewer.png</file> + </qresource> +</RCC> diff --git a/tools/qdbus/qdbusviewer/qdbusviewer.rc b/tools/qdbus/qdbusviewer/qdbusviewer.rc new file mode 100644 index 0000000..c4b1d60 --- /dev/null +++ b/tools/qdbus/qdbusviewer/qdbusviewer.rc @@ -0,0 +1 @@ +IDI_ICON1 ICON DISCARDABLE "images/qdbusviewer.ico" diff --git a/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.cpp b/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.cpp new file mode 100644 index 0000000..a871fe4 --- /dev/null +++ b/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.cpp @@ -0,0 +1,1150 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies). +** Contact: Qt Software Information (qt-info@nokia.com) +** +** This file is part of the tools applications of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** No Commercial Usage +** This file contains pre-release code and may not be distributed. +** You may use this file in accordance with the terms and conditions +** contained in the either Technology Preview License Agreement or the +** Beta Release License Agreement. +** +** GNU Lesser General Public License Usage +** Alternatively, 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.0, 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. +** +** If you are unsure which license is appropriate for your use, please +** contact the sales department at qt-sales@nokia.com. +** $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 <sys/types.h> +#include <stdio.h> +#include <stdlib.h> + +#ifdef Q_WS_WIN +#include <process.h> +#endif + +#define PROGRAMNAME "qdbusxml2cpp" +#define PROGRAMVERSION "0.7" +#define PROGRAMCOPYRIGHT "Copyright (C) 2009 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" + "If no options are given, the code is written to the standard output.\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"; + +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; + if (type != "QVariant") + hs << " { return qvariant_cast< " << type << " >(internalPropGet(\"" + << property.name << "\")); }" << endl; + else + hs << " { return internalPropGet(\"" << property.name << "\"); }" << endl; + } + + // setter: + if (property.access != QDBusIntrospection::Property::Read) { + hs << " inline void " << setter << "(" << constRefArg(type) << "value)" << endl + << " { internalPropSet(\"" << 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 qdbusxml2dcpp 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 qdbusxml2dcpp 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. +*/ diff --git a/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.pro b/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.pro new file mode 100644 index 0000000..6f78c7c --- /dev/null +++ b/tools/qdbus/qdbusxml2cpp/qdbusxml2cpp.pro @@ -0,0 +1,10 @@ +SOURCES = qdbusxml2cpp.cpp +DESTDIR = ../../../bin +TARGET = qdbusxml2cpp +QT = core xml +CONFIG += qdbus +CONFIG -= app_bundle +win32:CONFIG += console + +target.path=$$[QT_INSTALL_BINS] +INSTALLS += target |