tools/qdbus/qdbusviewer/qdbusviewer.cpp
changeset 0 1918ee327afb
child 3 41300fa6a67c
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the tools applications of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    11 ** This file contains pre-release code and may not be distributed.
       
    12 ** You may use this file in accordance with the terms and conditions
       
    13 ** contained in the Technology Preview License Agreement accompanying
       
    14 ** this package.
       
    15 **
       
    16 ** GNU Lesser General Public License Usage
       
    17 ** Alternatively, this file may be used under the terms of the GNU Lesser
       
    18 ** General Public License version 2.1 as published by the Free Software
       
    19 ** Foundation and appearing in the file LICENSE.LGPL included in the
       
    20 ** packaging of this file.  Please review the following information to
       
    21 ** ensure the GNU Lesser General Public License version 2.1 requirements
       
    22 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
       
    23 **
       
    24 ** In addition, as a special exception, Nokia gives you certain additional
       
    25 ** rights.  These rights are described in the Nokia Qt LGPL Exception
       
    26 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
       
    27 **
       
    28 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    38 ** $QT_END_LICENSE$
       
    39 **
       
    40 ****************************************************************************/
       
    41 
       
    42 #include "qdbusviewer.h"
       
    43 #include "qdbusmodel.h"
       
    44 #include "propertydialog.h"
       
    45 
       
    46 #include <QtXml/QtXml>
       
    47 #include <QtDBus/private/qdbusutil_p.h>
       
    48 
       
    49 class QDBusViewModel: public QDBusModel
       
    50 {
       
    51 public:
       
    52     inline QDBusViewModel(const QString &service, const QDBusConnection &connection)
       
    53         : QDBusModel(service, connection)
       
    54     {}
       
    55 
       
    56     QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
       
    57     {
       
    58         if (role == Qt::FontRole && itemType(index) == InterfaceItem) {
       
    59             QFont f;
       
    60             f.setItalic(true);
       
    61             return f;
       
    62         }
       
    63         return QDBusModel::data(index, role);
       
    64     }
       
    65 };
       
    66 
       
    67 QDBusViewer::QDBusViewer(const QDBusConnection &connection, QWidget *parent)  :
       
    68     QWidget(parent),
       
    69     c(connection),
       
    70     objectPathRegExp(QLatin1String("\\[ObjectPath: (.*)\\]"))
       
    71 {
       
    72     services = new QTreeWidget;
       
    73     services->setRootIsDecorated(false);
       
    74     services->setHeaderLabels(QStringList(QLatin1String("Services")));
       
    75 
       
    76     tree = new QTreeView;
       
    77     tree->setContextMenuPolicy(Qt::CustomContextMenu);
       
    78 
       
    79     connect(tree, SIGNAL(activated(const QModelIndex&)), this, SLOT(activate(const QModelIndex&)));
       
    80 
       
    81     refreshAction = new QAction(tr("&Refresh"), tree);
       
    82     refreshAction->setData(42); // increase the amount of 42 used as magic number by one
       
    83     refreshAction->setShortcut(QKeySequence::Refresh);
       
    84     connect(refreshAction, SIGNAL(triggered()), this, SLOT(refreshChildren()));
       
    85 
       
    86     QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, tree);
       
    87     connect(refreshShortcut, SIGNAL(activated()), this, SLOT(refreshChildren()));
       
    88 
       
    89     QVBoxLayout *topLayout = new QVBoxLayout(this);
       
    90     log = new QTextBrowser;
       
    91     connect(log, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl)));
       
    92 
       
    93     QHBoxLayout *layout = new QHBoxLayout;
       
    94     layout->addWidget(services, 1);
       
    95     layout->addWidget(tree, 2);
       
    96 
       
    97     topLayout->addLayout(layout);
       
    98     topLayout->addWidget(log);
       
    99 
       
   100     connect(services, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
       
   101             this, SLOT(serviceChanged(QTreeWidgetItem*)));
       
   102     connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
       
   103             this, SLOT(showContextMenu(QPoint)));
       
   104 
       
   105     QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection);
       
   106 
       
   107     if (c.isConnected()) {
       
   108         logMessage(QLatin1String("Connected to D-Bus."));
       
   109         QDBusConnectionInterface *iface = c.interface();
       
   110         connect(iface, SIGNAL(serviceRegistered(QString)),
       
   111                 this, SLOT(serviceRegistered(QString)));
       
   112         connect(iface, SIGNAL(serviceUnregistered(QString)),
       
   113                 this, SLOT(serviceUnregistered(QString)));
       
   114         connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
       
   115                 this, SLOT(serviceOwnerChanged(QString,QString,QString)));
       
   116     } else {
       
   117         logError(QLatin1String("Cannot connect to D-Bus: ") + c.lastError().message());
       
   118     }
       
   119 
       
   120     objectPathRegExp.setMinimal(true);
       
   121 
       
   122 }
       
   123 
       
   124 void QDBusViewer::logMessage(const QString &msg)
       
   125 {
       
   126     log->append(msg + QLatin1Char('\n'));
       
   127 }
       
   128 
       
   129 void QDBusViewer::logError(const QString &msg)
       
   130 {
       
   131     log->append(QLatin1String("<font color=\"red\">Error: </font>") + Qt::escape(msg) + QLatin1String("<br>"));
       
   132 }
       
   133 
       
   134 void QDBusViewer::refresh()
       
   135 {
       
   136     services->clear();
       
   137 
       
   138     if (c.isConnected()) {
       
   139         const QStringList serviceNames = c.interface()->registeredServiceNames();
       
   140         foreach (QString service, serviceNames)
       
   141             new QTreeWidgetItem(services, QStringList(service));
       
   142     }
       
   143 }
       
   144 
       
   145 void QDBusViewer::activate(const QModelIndex &item)
       
   146 {
       
   147     if (!item.isValid())
       
   148         return;
       
   149 
       
   150     const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
       
   151 
       
   152     BusSignature sig;
       
   153     sig.mService = currentService;
       
   154     sig.mPath = model->dBusPath(item);
       
   155     sig.mInterface = model->dBusInterface(item);
       
   156     sig.mName = model->dBusMethodName(item);
       
   157 
       
   158     switch (model->itemType(item)) {
       
   159     case QDBusModel::SignalItem:
       
   160         connectionRequested(sig);
       
   161         break;
       
   162     case QDBusModel::MethodItem:
       
   163         callMethod(sig);
       
   164         break;
       
   165     case QDBusModel::PropertyItem:
       
   166         getProperty(sig);
       
   167         break;
       
   168     default:
       
   169         break;
       
   170     }
       
   171 }
       
   172 
       
   173 void QDBusViewer::getProperty(const BusSignature &sig)
       
   174 {
       
   175     QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
       
   176     QList<QVariant> arguments;
       
   177     arguments << sig.mInterface << sig.mName;
       
   178     message.setArguments(arguments);
       
   179     c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
       
   180 }
       
   181 
       
   182 void QDBusViewer::setProperty(const BusSignature &sig)
       
   183 {
       
   184     QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c);
       
   185     QMetaProperty prop = iface.metaObject()->property(iface.metaObject()->indexOfProperty(sig.mName.toLatin1()));
       
   186 
       
   187     bool ok;
       
   188     QString input = QInputDialog::getText(this, tr("Arguments"),
       
   189                     tr("Please enter the value of the property %1 (type %2)").arg(
       
   190                         sig.mName, QString::fromLatin1(prop.typeName())),
       
   191                     QLineEdit::Normal, QString(), &ok);
       
   192     if (!ok)
       
   193         return;
       
   194 
       
   195     QVariant value = input;
       
   196     if (!value.convert(prop.type())) {
       
   197         QMessageBox::warning(this, tr("Unable to marshall"),
       
   198                 tr("Value conversion failed, unable to set property"));
       
   199         return;
       
   200     }
       
   201 
       
   202     QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Set"));
       
   203     QList<QVariant> arguments;
       
   204     arguments << sig.mInterface << sig.mName << qVariantFromValue(QDBusVariant(value));
       
   205     message.setArguments(arguments);
       
   206     c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
       
   207 
       
   208 }
       
   209 
       
   210 void QDBusViewer::callMethod(const BusSignature &sig)
       
   211 {
       
   212     QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c);
       
   213     const QMetaObject *mo = iface.metaObject();
       
   214 
       
   215     // find the method
       
   216     QMetaMethod method;
       
   217     for (int i = 0; i < mo->methodCount(); ++i) {
       
   218         const QString signature = QString::fromLatin1(mo->method(i).signature());
       
   219         if (signature.startsWith(sig.mName) && signature.at(sig.mName.length()) == QLatin1Char('('))
       
   220             method = mo->method(i);
       
   221     }
       
   222     if (!method.signature()) {
       
   223         QMessageBox::warning(this, tr("Unable to find method"),
       
   224                 tr("Unable to find method %1 on path %2 in interface %3").arg(
       
   225                     sig.mName).arg(sig.mPath).arg(sig.mInterface));
       
   226         return;
       
   227     }
       
   228 
       
   229     PropertyDialog dialog;
       
   230     QList<QVariant> args;
       
   231 
       
   232     const QList<QByteArray> paramTypes = method.parameterTypes();
       
   233     const QList<QByteArray> paramNames = method.parameterNames();
       
   234     QList<int> types; // remember the low-level D-Bus type
       
   235     for (int i = 0; i < paramTypes.count(); ++i) {
       
   236         const QByteArray paramType = paramTypes.at(i);
       
   237         if (paramType.endsWith('&'))
       
   238             continue; // ignore OUT parameters
       
   239 
       
   240         QVariant::Type type = QVariant::nameToType(paramType);
       
   241         dialog.addProperty(QString::fromLatin1(paramNames.value(i)), type);
       
   242         types.append(QMetaType::type(paramType));
       
   243     }
       
   244 
       
   245     if (!types.isEmpty()) {
       
   246         dialog.setInfo(tr("Please enter parameters for the method \"%1\"").arg(sig.mName));
       
   247 
       
   248         if (dialog.exec() != QDialog::Accepted)
       
   249             return;
       
   250 
       
   251         args = dialog.values();
       
   252     }
       
   253 
       
   254     // Special case - convert a value to a QDBusVariant if the
       
   255     // interface wants a variant
       
   256     for (int i = 0; i < args.count(); ++i) {
       
   257         if (types.at(i) == qMetaTypeId<QDBusVariant>())
       
   258             args[i] = qVariantFromValue(QDBusVariant(args.at(i)));
       
   259     }
       
   260 
       
   261     QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, sig.mInterface,
       
   262             sig.mName);
       
   263     message.setArguments(args);
       
   264     c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
       
   265 }
       
   266 
       
   267 void QDBusViewer::showContextMenu(const QPoint &point)
       
   268 {
       
   269     QModelIndex item = tree->indexAt(point);
       
   270     if (!item.isValid())
       
   271         return;
       
   272 
       
   273     const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
       
   274 
       
   275     BusSignature sig;
       
   276     sig.mService = currentService;
       
   277     sig.mPath = model->dBusPath(item);
       
   278     sig.mInterface = model->dBusInterface(item);
       
   279     sig.mName = model->dBusMethodName(item);
       
   280 
       
   281     QMenu menu;
       
   282     menu.addAction(refreshAction);
       
   283 
       
   284     switch (model->itemType(item)) {
       
   285     case QDBusModel::SignalItem: {
       
   286         QAction *action = new QAction(tr("&Connect"), &menu);
       
   287         action->setData(1);
       
   288         menu.addAction(action);
       
   289         break; }
       
   290     case QDBusModel::MethodItem: {
       
   291         QAction *action = new QAction(tr("&Call"), &menu);
       
   292         action->setData(2);
       
   293         menu.addAction(action);
       
   294         break; }
       
   295     case QDBusModel::PropertyItem: {
       
   296         QAction *actionSet = new QAction(tr("&Set value"), &menu);
       
   297         actionSet->setData(3);
       
   298         QAction *actionGet = new QAction(tr("&Get value"), &menu);
       
   299         actionGet->setData(4);
       
   300         menu.addAction(actionSet);
       
   301         menu.addAction(actionGet);
       
   302         break; }
       
   303     default:
       
   304         break;
       
   305     }
       
   306 
       
   307     QAction *selectedAction = menu.exec(tree->viewport()->mapToGlobal(point));
       
   308     if (!selectedAction)
       
   309         return;
       
   310 
       
   311     switch (selectedAction->data().toInt()) {
       
   312     case 1:
       
   313         connectionRequested(sig);
       
   314         break;
       
   315     case 2:
       
   316         callMethod(sig);
       
   317         break;
       
   318     case 3:
       
   319         setProperty(sig);
       
   320         break;
       
   321     case 4:
       
   322         getProperty(sig);
       
   323         break;
       
   324     }
       
   325 }
       
   326 
       
   327 void QDBusViewer::connectionRequested(const BusSignature &sig)
       
   328 {
       
   329     if (!c.connect(sig.mService, QString(), sig.mInterface, sig.mName, this,
       
   330               SLOT(dumpMessage(QDBusMessage)))) {
       
   331         logError(tr("Unable to connect to service %1, path %2, interface %3, signal %4").arg(
       
   332                     sig.mService).arg(sig.mPath).arg(sig.mInterface).arg(sig.mName));
       
   333     }
       
   334 }
       
   335 
       
   336 void QDBusViewer::dumpMessage(const QDBusMessage &message)
       
   337 {
       
   338     QList<QVariant> args = message.arguments();
       
   339     QString out = QLatin1String("Received ");
       
   340 
       
   341     switch (message.type()) {
       
   342     case QDBusMessage::SignalMessage:
       
   343         out += QLatin1String("signal ");
       
   344         break;
       
   345     case QDBusMessage::ErrorMessage:
       
   346         out += QLatin1String("error message ");
       
   347         break;
       
   348     case QDBusMessage::ReplyMessage:
       
   349         out += QLatin1String("reply ");
       
   350         break;
       
   351     default:
       
   352         out += QLatin1String("message ");
       
   353         break;
       
   354     }
       
   355 
       
   356     out += QLatin1String("from ");
       
   357     out += message.service();
       
   358     if (!message.path().isEmpty())
       
   359         out += QLatin1String(", path ") + message.path();
       
   360     if (!message.interface().isEmpty())
       
   361         out += QLatin1String(", interface <i>") + message.interface() + QLatin1String("</i>");
       
   362     if (!message.member().isEmpty())
       
   363         out += QLatin1String(", member ") + message.member();
       
   364     out += QLatin1String("<br>");
       
   365     if (args.isEmpty()) {
       
   366         out += QLatin1String("&nbsp;&nbsp;(no arguments)");
       
   367     } else {
       
   368         out += QLatin1String("&nbsp;&nbsp;Arguments: ");
       
   369         foreach (QVariant arg, args) {
       
   370             QString str = Qt::escape(QDBusUtil::argumentToString(arg));
       
   371             // turn object paths into clickable links
       
   372             str.replace(objectPathRegExp, QLatin1String("[ObjectPath: <a href=\"qdbus://bus\\1\">\\1</a>]"));
       
   373             out += str;
       
   374             out += QLatin1String(", ");
       
   375         }
       
   376         out.chop(2);
       
   377     }
       
   378 
       
   379     log->append(out);
       
   380 }
       
   381 
       
   382 void QDBusViewer::serviceChanged(QTreeWidgetItem *item)
       
   383 {
       
   384     delete tree->model();
       
   385 
       
   386     currentService.clear();
       
   387     if (!item)
       
   388         return;
       
   389     currentService = item->text(0);
       
   390 
       
   391     tree->setModel(new QDBusViewModel(currentService, c));
       
   392     connect(tree->model(), SIGNAL(busError(QString)), this, SLOT(logError(QString)));
       
   393 }
       
   394 
       
   395 void QDBusViewer::serviceRegistered(const QString &service)
       
   396 {
       
   397     if (service == c.baseService())
       
   398         return;
       
   399 
       
   400     new QTreeWidgetItem(services, QStringList(service));
       
   401 }
       
   402 
       
   403 static QTreeWidgetItem *findItem(const QTreeWidget *services, const QString &name)
       
   404 {
       
   405     for (int i = 0; i < services->topLevelItemCount(); ++i) {
       
   406         if (services->topLevelItem(i)->text(0) == name)
       
   407             return services->topLevelItem(i);
       
   408     }
       
   409     return 0;
       
   410 }
       
   411 
       
   412 void QDBusViewer::serviceUnregistered(const QString &name)
       
   413 {
       
   414     delete findItem(services, name);
       
   415 }
       
   416 
       
   417 void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner,
       
   418                                       const QString &newOwner)
       
   419 {
       
   420     QTreeWidgetItem *item = findItem(services, name);
       
   421 
       
   422     if (!item && oldOwner.isEmpty() && !newOwner.isEmpty())
       
   423         serviceRegistered(name);
       
   424     else if (item && !oldOwner.isEmpty() && newOwner.isEmpty())
       
   425         delete item;
       
   426     else if (item && !oldOwner.isEmpty() && !newOwner.isEmpty()) {
       
   427         delete item;
       
   428         serviceRegistered(name);
       
   429     }
       
   430 }
       
   431 
       
   432 void QDBusViewer::refreshChildren()
       
   433 {
       
   434     QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
       
   435     if (!model)
       
   436         return;
       
   437     model->refresh(tree->currentIndex());
       
   438 }
       
   439 
       
   440 void QDBusViewer::about()
       
   441 {
       
   442     QMessageBox box(this);
       
   443 
       
   444     box.setText(QString::fromLatin1("<center><img src=\":/trolltech/qdbusviewer/images/qdbusviewer-128.png\">"
       
   445                 "<h3>%1</h3>"
       
   446                 "<p>Version %2</p></center>"
       
   447                 "<p>Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).</p>")
       
   448             .arg(tr("D-Bus Viewer")).arg(QLatin1String(QT_VERSION_STR)));
       
   449     box.setWindowTitle(tr("D-Bus Viewer"));
       
   450     box.exec();
       
   451 }
       
   452 
       
   453 void QDBusViewer::anchorClicked(const QUrl &url)
       
   454 {
       
   455     if (url.scheme() != QLatin1String("qdbus"))
       
   456         // not ours
       
   457         return;
       
   458 
       
   459     // swallow the click without setting a new document
       
   460     log->setSource(QUrl());
       
   461 
       
   462     QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
       
   463     if (!model)
       
   464         return;
       
   465 
       
   466     QModelIndex idx = model->findObject(QDBusObjectPath(url.path()));
       
   467     if (!idx.isValid())
       
   468         return;
       
   469 
       
   470     tree->scrollTo(idx);
       
   471     tree->setCurrentIndex(idx);
       
   472 }
       
   473 
       
   474 /*!
       
   475   \page qdbusviewer.html
       
   476   \title D-Bus Viewer
       
   477   \keyword qdbusviewer
       
   478 
       
   479   The Qt D-Bus Viewer is a tool that lets you introspect D-Bus objects and messages. You can
       
   480   choose between the system bus and the session bus. Click on any service on the list
       
   481   on the left side to see all the exported objects.
       
   482 
       
   483   You can invoke methods by double-clicking on them. If a method takes one or more IN parameters,
       
   484   a property editor opens.
       
   485 
       
   486   Right-click on a signal to connect to it. All emitted signals including their parameters
       
   487   are output in the message view on the lower side of the window.
       
   488 */