source: branches/vendor/trolltech/qt/current/tools/qdbus/qdbusviewer/qdbusviewer.cpp @ 2

Last change on this file since 2 was 2, checked in by Dmitry A. Kuminov, 12 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 16.5 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information (qt-info@nokia.com)
5**
6** This file is part of the tools applications of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file.  Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file.  Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you are unsure which license is appropriate for your use, please
37** contact the sales department at qt-sales@nokia.com.
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
49class QDBusViewModel: public QDBusModel
50{
51public:
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
67QDBusViewer::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
124void QDBusViewer::logMessage(const QString &msg)
125{
126    log->append(msg + QLatin1Char('\n'));
127}
128
129void QDBusViewer::logError(const QString &msg)
130{
131    log->append(QLatin1String("<font color=\"red\">Error: </font>") + Qt::escape(msg) + QLatin1String("<br>"));
132}
133
134void 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
145void 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
173void 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
182void 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
210void 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
267void 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
327void 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
336void 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
382void 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
395void QDBusViewer::serviceRegistered(const QString &service)
396{
397    if (service == c.baseService())
398        return;
399
400    new QTreeWidgetItem(services, QStringList(service));
401}
402
403static 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
412void QDBusViewer::serviceUnregistered(const QString &name)
413{
414    delete findItem(services, name);
415}
416
417void 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
432void QDBusViewer::refreshChildren()
433{
434    QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
435    if (!model)
436        return;
437    model->refresh(tree->currentIndex());
438}
439
440void QDBusViewer::about()
441{
442    QMessageBox box(this);
443
444    // TODO: Remove these variables for 4.6.0.  Must keep this way for 4.5.x due to string freeze.
445    QString edition;
446    QString info;
447    QString moreInfo;
448
449    box.setText(QString::fromLatin1("<center><img src=\":/trolltech/qdbusviewer/images/qdbusviewer-128.png\">"
450                "<h3>%1</h3>"
451                "<p>Version %2 %3</p></center>"
452                "<p>%4</p>"
453                "<p>%5</p>"
454                "<p>Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).</p>"
455                "<p>The program is provided AS IS with NO WARRANTY OF ANY KIND,"
456                " INCLUDING THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A"
457                " PARTICULAR PURPOSE.<p/>")
458            .arg(tr("D-Bus Viewer")).arg(QLatin1String(QT_VERSION_STR)).arg(edition).arg(info).arg(moreInfo));
459    box.setWindowTitle(tr("D-Bus Viewer"));
460    box.exec();
461}
462
463void QDBusViewer::anchorClicked(const QUrl &url)
464{
465    if (url.scheme() != QLatin1String("qdbus"))
466        // not ours
467        return;
468
469    // swallow the click without setting a new document
470    log->setSource(QUrl());
471
472    QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
473    if (!model)
474        return;
475
476    QModelIndex idx = model->findObject(QDBusObjectPath(url.path()));
477    if (!idx.isValid())
478        return;
479
480    tree->scrollTo(idx);
481    tree->setCurrentIndex(idx);
482}
483
484/*!
485  \page qdbusviewer.html
486  \title D-Bus Viewer
487  \keyword qdbusviewer
488
489  The Qt D-Bus Viewer is a tool that lets you introspect D-Bus objects and messages. You can
490  choose between the system bus and the session bus. Click on any service on the list
491  on the left side to see all the exported objects.
492
493  You can invoke methods by double-clicking on them. If a method takes one or more IN parameters,
494  a property editor opens.
495
496  Right-click on a signal to connect to it. All emitted signals including their parameters
497  are output in the message view on the lower side of the window.
498*/
Note: See TracBrowser for help on using the repository browser.