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

Last change on this file since 280 was 2, checked in by Dmitry A. Kuminov, 16 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 ([email protected])
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 [email protected].
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.