source: trunk/tools/qdbus/qdbusviewer/qdbusviewer.cpp@ 846

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

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 17.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 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** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
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** 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 have questions regarding the use of this file, please contact
37** Nokia at qt-info@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 servicesModel = new QStringListModel(this);
73 servicesFilterModel = new QSortFilterProxyModel(this);
74 servicesFilterModel->setSourceModel(servicesModel);
75 servicesFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
76 serviceFilterLine = new QLineEdit(this);
77 serviceFilterLine->setPlaceholderText(tr("Search..."));
78 servicesView = new QListView(this);
79 servicesView->setModel(servicesFilterModel);
80
81 connect(serviceFilterLine, SIGNAL(textChanged(QString)), servicesFilterModel, SLOT(setFilterFixedString(QString)));
82
83 tree = new QTreeView;
84 tree->setContextMenuPolicy(Qt::CustomContextMenu);
85
86 connect(tree, SIGNAL(activated(QModelIndex)), this, SLOT(activate(QModelIndex)));
87
88 refreshAction = new QAction(tr("&Refresh"), tree);
89 refreshAction->setData(42); // increase the amount of 42 used as magic number by one
90 refreshAction->setShortcut(QKeySequence::Refresh);
91 connect(refreshAction, SIGNAL(triggered()), this, SLOT(refreshChildren()));
92
93 QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, tree);
94 connect(refreshShortcut, SIGNAL(activated()), this, SLOT(refreshChildren()));
95
96 QVBoxLayout *layout = new QVBoxLayout(this);
97 QSplitter *topSplitter = new QSplitter(Qt::Vertical, this);
98 layout->addWidget(topSplitter);
99
100 log = new QTextBrowser;
101 connect(log, SIGNAL(anchorClicked(QUrl)), this, SLOT(anchorClicked(QUrl)));
102
103 QSplitter *splitter = new QSplitter(topSplitter);
104 splitter->addWidget(servicesView);
105
106 QWidget *servicesWidget = new QWidget;
107 QVBoxLayout *servicesLayout = new QVBoxLayout(servicesWidget);
108 servicesLayout->addWidget(serviceFilterLine);
109 servicesLayout->addWidget(servicesView);
110 splitter->addWidget(servicesWidget);
111 splitter->addWidget(tree);
112
113 topSplitter->addWidget(splitter);
114 topSplitter->addWidget(log);
115
116 connect(servicesView->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)),
117 this, SLOT(serviceChanged(QModelIndex)));
118 connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
119 this, SLOT(showContextMenu(QPoint)));
120
121 QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection);
122
123 if (c.isConnected()) {
124 logMessage(QLatin1String("Connected to D-Bus."));
125 QDBusConnectionInterface *iface = c.interface();
126 connect(iface, SIGNAL(serviceRegistered(QString)),
127 this, SLOT(serviceRegistered(QString)));
128 connect(iface, SIGNAL(serviceUnregistered(QString)),
129 this, SLOT(serviceUnregistered(QString)));
130 connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
131 this, SLOT(serviceOwnerChanged(QString,QString,QString)));
132 } else {
133 logError(QLatin1String("Cannot connect to D-Bus: ") + c.lastError().message());
134 }
135
136 objectPathRegExp.setMinimal(true);
137
138}
139
140void QDBusViewer::logMessage(const QString &msg)
141{
142 log->append(msg + QLatin1Char('\n'));
143}
144
145void QDBusViewer::logError(const QString &msg)
146{
147 log->append(QLatin1String("<font color=\"red\">Error: </font>") + Qt::escape(msg) + QLatin1String("<br>"));
148}
149
150void QDBusViewer::refresh()
151{
152 servicesModel->removeRows(0, servicesModel->rowCount());
153
154 if (c.isConnected()) {
155 const QStringList serviceNames = c.interface()->registeredServiceNames();
156 servicesModel->setStringList(serviceNames);
157 }
158}
159
160void QDBusViewer::activate(const QModelIndex &item)
161{
162 if (!item.isValid())
163 return;
164
165 const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
166
167 BusSignature sig;
168 sig.mService = currentService;
169 sig.mPath = model->dBusPath(item);
170 sig.mInterface = model->dBusInterface(item);
171 sig.mName = model->dBusMethodName(item);
172 sig.mTypeSig = model->dBusTypeSignature(item);
173
174 switch (model->itemType(item)) {
175 case QDBusModel::SignalItem:
176 connectionRequested(sig);
177 break;
178 case QDBusModel::MethodItem:
179 callMethod(sig);
180 break;
181 case QDBusModel::PropertyItem:
182 getProperty(sig);
183 break;
184 default:
185 break;
186 }
187}
188
189void QDBusViewer::getProperty(const BusSignature &sig)
190{
191 QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Get"));
192 QList<QVariant> arguments;
193 arguments << sig.mInterface << sig.mName;
194 message.setArguments(arguments);
195 c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
196}
197
198void QDBusViewer::setProperty(const BusSignature &sig)
199{
200 QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c);
201 QMetaProperty prop = iface.metaObject()->property(iface.metaObject()->indexOfProperty(sig.mName.toLatin1()));
202
203 bool ok;
204 QString input = QInputDialog::getText(this, tr("Arguments"),
205 tr("Please enter the value of the property %1 (type %2)").arg(
206 sig.mName, QString::fromLatin1(prop.typeName())),
207 QLineEdit::Normal, QString(), &ok);
208 if (!ok)
209 return;
210
211 QVariant value = input;
212 if (!value.convert(prop.type())) {
213 QMessageBox::warning(this, tr("Unable to marshall"),
214 tr("Value conversion failed, unable to set property"));
215 return;
216 }
217
218 QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, QLatin1String("org.freedesktop.DBus.Properties"), QLatin1String("Set"));
219 QList<QVariant> arguments;
220 arguments << sig.mInterface << sig.mName << qVariantFromValue(QDBusVariant(value));
221 message.setArguments(arguments);
222 c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
223
224}
225
226static QString getDbusSignature(const QMetaMethod& method)
227{
228 // create a D-Bus type signature from QMetaMethod's parameters
229 QString sig;
230 for (int i = 0; i < method.parameterTypes().count(); ++i) {
231 QVariant::Type type = QVariant::nameToType(method.parameterTypes().at(i));
232 sig.append(QString::fromLatin1(QDBusMetaType::typeToSignature(type)));
233 }
234 return sig;
235}
236
237void QDBusViewer::callMethod(const BusSignature &sig)
238{
239 QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c);
240 const QMetaObject *mo = iface.metaObject();
241
242 // find the method
243 QMetaMethod method;
244 for (int i = 0; i < mo->methodCount(); ++i) {
245 const QString signature = QString::fromLatin1(mo->method(i).signature());
246 if (signature.startsWith(sig.mName) && signature.at(sig.mName.length()) == QLatin1Char('('))
247 if (getDbusSignature(mo->method(i)) == sig.mTypeSig)
248 method = mo->method(i);
249 }
250 if (!method.signature()) {
251 QMessageBox::warning(this, tr("Unable to find method"),
252 tr("Unable to find method %1 on path %2 in interface %3").arg(
253 sig.mName).arg(sig.mPath).arg(sig.mInterface));
254 return;
255 }
256
257 PropertyDialog dialog;
258 QList<QVariant> args;
259
260 const QList<QByteArray> paramTypes = method.parameterTypes();
261 const QList<QByteArray> paramNames = method.parameterNames();
262 QList<int> types; // remember the low-level D-Bus type
263 for (int i = 0; i < paramTypes.count(); ++i) {
264 const QByteArray paramType = paramTypes.at(i);
265 if (paramType.endsWith('&'))
266 continue; // ignore OUT parameters
267
268 QVariant::Type type = QVariant::nameToType(paramType);
269 dialog.addProperty(QString::fromLatin1(paramNames.value(i)), type);
270 types.append(QMetaType::type(paramType));
271 }
272
273 if (!types.isEmpty()) {
274 dialog.setInfo(tr("Please enter parameters for the method \"%1\"").arg(sig.mName));
275
276 if (dialog.exec() != QDialog::Accepted)
277 return;
278
279 args = dialog.values();
280 }
281
282 // Special case - convert a value to a QDBusVariant if the
283 // interface wants a variant
284 for (int i = 0; i < args.count(); ++i) {
285 if (types.at(i) == qMetaTypeId<QDBusVariant>())
286 args[i] = qVariantFromValue(QDBusVariant(args.at(i)));
287 }
288
289 QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, sig.mInterface,
290 sig.mName);
291 message.setArguments(args);
292 c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
293}
294
295void QDBusViewer::showContextMenu(const QPoint &point)
296{
297 QModelIndex item = tree->indexAt(point);
298 if (!item.isValid())
299 return;
300
301 const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
302
303 BusSignature sig;
304 sig.mService = currentService;
305 sig.mPath = model->dBusPath(item);
306 sig.mInterface = model->dBusInterface(item);
307 sig.mName = model->dBusMethodName(item);
308 sig.mTypeSig = model->dBusTypeSignature(item);
309
310 QMenu menu;
311 menu.addAction(refreshAction);
312
313 switch (model->itemType(item)) {
314 case QDBusModel::SignalItem: {
315 QAction *action = new QAction(tr("&Connect"), &menu);
316 action->setData(1);
317 menu.addAction(action);
318 break; }
319 case QDBusModel::MethodItem: {
320 QAction *action = new QAction(tr("&Call"), &menu);
321 action->setData(2);
322 menu.addAction(action);
323 break; }
324 case QDBusModel::PropertyItem: {
325 QAction *actionSet = new QAction(tr("&Set value"), &menu);
326 actionSet->setData(3);
327 QAction *actionGet = new QAction(tr("&Get value"), &menu);
328 actionGet->setData(4);
329 menu.addAction(actionSet);
330 menu.addAction(actionGet);
331 break; }
332 default:
333 break;
334 }
335
336 QAction *selectedAction = menu.exec(tree->viewport()->mapToGlobal(point));
337 if (!selectedAction)
338 return;
339
340 switch (selectedAction->data().toInt()) {
341 case 1:
342 connectionRequested(sig);
343 break;
344 case 2:
345 callMethod(sig);
346 break;
347 case 3:
348 setProperty(sig);
349 break;
350 case 4:
351 getProperty(sig);
352 break;
353 }
354}
355
356void QDBusViewer::connectionRequested(const BusSignature &sig)
357{
358 if (!c.connect(sig.mService, QString(), sig.mInterface, sig.mName, this,
359 SLOT(dumpMessage(QDBusMessage)))) {
360 logError(tr("Unable to connect to service %1, path %2, interface %3, signal %4").arg(
361 sig.mService).arg(sig.mPath).arg(sig.mInterface).arg(sig.mName));
362 }
363}
364
365void QDBusViewer::dumpMessage(const QDBusMessage &message)
366{
367 QList<QVariant> args = message.arguments();
368 QString out = QLatin1String("Received ");
369
370 switch (message.type()) {
371 case QDBusMessage::SignalMessage:
372 out += QLatin1String("signal ");
373 break;
374 case QDBusMessage::ErrorMessage:
375 out += QLatin1String("error message ");
376 break;
377 case QDBusMessage::ReplyMessage:
378 out += QLatin1String("reply ");
379 break;
380 default:
381 out += QLatin1String("message ");
382 break;
383 }
384
385 out += QLatin1String("from ");
386 out += message.service();
387 if (!message.path().isEmpty())
388 out += QLatin1String(", path ") + message.path();
389 if (!message.interface().isEmpty())
390 out += QLatin1String(", interface <i>") + message.interface() + QLatin1String("</i>");
391 if (!message.member().isEmpty())
392 out += QLatin1String(", member ") + message.member();
393 out += QLatin1String("<br>");
394 if (args.isEmpty()) {
395 out += QLatin1String("&nbsp;&nbsp;(no arguments)");
396 } else {
397 out += QLatin1String("&nbsp;&nbsp;Arguments: ");
398 foreach (QVariant arg, args) {
399 QString str = Qt::escape(QDBusUtil::argumentToString(arg));
400 // turn object paths into clickable links
401 str.replace(objectPathRegExp, QLatin1String("[ObjectPath: <a href=\"qdbus://bus\\1\">\\1</a>]"));
402 out += str;
403 out += QLatin1String(", ");
404 }
405 out.chop(2);
406 }
407
408 log->append(out);
409}
410
411void QDBusViewer::serviceChanged(const QModelIndex &index)
412{
413 delete tree->model();
414
415 currentService.clear();
416 if (!index.isValid())
417 return;
418 currentService = index.data().toString();
419
420 tree->setModel(new QDBusViewModel(currentService, c));
421 connect(tree->model(), SIGNAL(busError(QString)), this, SLOT(logError(QString)));
422}
423
424void QDBusViewer::serviceRegistered(const QString &service)
425{
426 if (service == c.baseService())
427 return;
428
429 servicesModel->insertRows(0, 1);
430 servicesModel->setData(servicesModel->index(0, 0), service);
431}
432
433static QModelIndex findItem(QStringListModel *servicesModel, const QString &name)
434{
435 QModelIndexList hits = servicesModel->match(servicesModel->index(0, 0), Qt::DisplayRole, name);
436 if (hits.isEmpty())
437 return QModelIndex();
438
439 return hits.first();
440}
441
442void QDBusViewer::serviceUnregistered(const QString &name)
443{
444 QModelIndex hit = findItem(servicesModel, name);
445 if (!hit.isValid())
446 return;
447 servicesModel->removeRows(hit.row(), 1);
448}
449
450void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner,
451 const QString &newOwner)
452{
453 QModelIndex hit = findItem(servicesModel, name);
454
455 if (!hit.isValid() && oldOwner.isEmpty() && !newOwner.isEmpty())
456 serviceRegistered(name);
457 else if (hit.isValid() && !oldOwner.isEmpty() && newOwner.isEmpty())
458 servicesModel->removeRows(hit.row(), 1);
459 else if (hit.isValid() && !oldOwner.isEmpty() && !newOwner.isEmpty()) {
460 servicesModel->removeRows(hit.row(), 1);
461 serviceRegistered(name);
462 }
463}
464
465void QDBusViewer::refreshChildren()
466{
467 QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
468 if (!model)
469 return;
470 model->refresh(tree->currentIndex());
471}
472
473void QDBusViewer::about()
474{
475 QMessageBox box(this);
476
477 box.setText(QString::fromLatin1("<center><img src=\":/trolltech/qdbusviewer/images/qdbusviewer-128.png\">"
478 "<h3>%1</h3>"
479 "<p>Version %2</p></center>"
480 "<p>Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).</p>")
481 .arg(tr("D-Bus Viewer")).arg(QLatin1String(QT_VERSION_STR)));
482 box.setWindowTitle(tr("D-Bus Viewer"));
483 box.exec();
484}
485
486void QDBusViewer::anchorClicked(const QUrl &url)
487{
488 if (url.scheme() != QLatin1String("qdbus"))
489 // not ours
490 return;
491
492 // swallow the click without setting a new document
493 log->setSource(QUrl());
494
495 QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
496 if (!model)
497 return;
498
499 QModelIndex idx = model->findObject(QDBusObjectPath(url.path()));
500 if (!idx.isValid())
501 return;
502
503 tree->scrollTo(idx);
504 tree->setCurrentIndex(idx);
505}
506
507/*!
508 \page qdbusviewer.html
509 \title D-Bus Viewer
510 \keyword qdbusviewer
511
512 The Qt D-Bus Viewer is a tool that lets you introspect D-Bus objects and messages. You can
513 choose between the system bus and the session bus. Click on any service on the list
514 on the left side to see all the exported objects.
515
516 You can invoke methods by double-clicking on them. If a method takes one or more IN parameters,
517 a property editor opens.
518
519 Right-click on a signal to connect to it. All emitted signals including their parameters
520 are output in the message view on the lower side of the window.
521*/
Note: See TracBrowser for help on using the repository browser.