00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024 #include "qdbusviewer.h"
00025 #include "qdbusmodel.h"
00026
00027 #include <QtXml/QtXml>
00028
00029 class QDBusViewModel: public QDBusModel
00030 {
00031 public:
00032 inline QDBusViewModel(const QString &service, const QDBusConnection &connection)
00033 : QDBusModel(service, connection)
00034 {}
00035
00036 QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const
00037 {
00038 if (role == Qt::FontRole && itemType(index) == InterfaceItem) {
00039 QFont f;
00040 f.setItalic(true);
00041 return f;
00042 }
00043 return QDBusModel::data(index, role);
00044 }
00045 };
00046
00047 QDBusViewer::QDBusViewer(const QDBusConnection &connection, QWidget *parent)
00048 : QWidget(parent), c(connection)
00049 {
00050 services = new QTreeWidget;
00051 services->setRootIsDecorated(false);
00052 services->setHeaderLabels(QStringList("Services"));
00053
00054 tree = new QTreeView;
00055 tree->setContextMenuPolicy(Qt::CustomContextMenu);
00056
00057 connect(tree, SIGNAL(activated(const QModelIndex&)), this, SLOT(activate(const QModelIndex&)));
00058
00059 refreshAction = new QAction(tr("&Refresh"), tree);
00060 refreshAction->setData(42);
00061 refreshAction->setShortcut(QKeySequence::Refresh);
00062 connect(refreshAction, SIGNAL(triggered()), this, SLOT(refreshChildren()));
00063
00064 QShortcut *refreshShortcut = new QShortcut(QKeySequence::Refresh, tree);
00065 connect(refreshShortcut, SIGNAL(activated()), this, SLOT(refreshChildren()));
00066
00067 QVBoxLayout *topLayout = new QVBoxLayout(this);
00068 log = new QTextEdit;
00069 log->setReadOnly(true);
00070
00071 QHBoxLayout *layout = new QHBoxLayout;
00072 layout->addWidget(services, 1);
00073 layout->addWidget(tree, 2);
00074
00075 topLayout->addLayout(layout);
00076 topLayout->addWidget(log);
00077
00078 connect(services, SIGNAL(currentItemChanged(QTreeWidgetItem*,QTreeWidgetItem*)),
00079 this, SLOT(serviceChanged(QTreeWidgetItem*)));
00080 connect(tree, SIGNAL(customContextMenuRequested(QPoint)),
00081 this, SLOT(showContextMenu(QPoint)));
00082
00083 QMetaObject::invokeMethod(this, "refresh", Qt::QueuedConnection);
00084
00085 if (c.isConnected()) {
00086 logMessage("Connected to D-Bus.");
00087 QDBusConnectionInterface *iface = c.interface();
00088 connect(iface, SIGNAL(serviceRegistered(QString)),
00089 this, SLOT(serviceRegistered(QString)));
00090 connect(iface, SIGNAL(serviceUnregistered(QString)),
00091 this, SLOT(serviceUnregistered(QString)));
00092 connect(iface, SIGNAL(serviceOwnerChanged(QString,QString,QString)),
00093 this, SLOT(serviceOwnerChanged(QString,QString,QString)));
00094 } else {
00095 logError("Cannot connect to D-Bus: " + c.lastError().message());
00096 }
00097 }
00098
00099 void QDBusViewer::logMessage(const QString &msg)
00100 {
00101 log->append(msg + "\n");
00102 }
00103
00104 void QDBusViewer::logError(const QString &msg)
00105 {
00106 log->append("<font color=\"red\">Error: </font>" + Qt::escape(msg) + "<br>");
00107 }
00108
00109 void QDBusViewer::refresh()
00110 {
00111 services->clear();
00112
00113 const QStringList serviceNames = c.interface()->registeredServiceNames();
00114 foreach (QString service, serviceNames)
00115 new QTreeWidgetItem(services, QStringList(service));
00116 }
00117
00118 void QDBusViewer::activate(const QModelIndex &item)
00119 {
00120 if (!item.isValid())
00121 return;
00122
00123 const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
00124
00125 BusSignature sig;
00126 sig.mService = currentService;
00127 sig.mPath = model->dBusPath(item);
00128 sig.mInterface = model->dBusInterface(item);
00129 sig.mName = model->dBusMethodName(item);
00130
00131 switch (model->itemType(item)) {
00132 case QDBusModel::SignalItem:
00133 connectionRequested(sig);
00134 break;
00135 case QDBusModel::MethodItem:
00136 callMethod(sig);
00137 break;
00138 #if 0
00139 case QDBusModel::PropertyItem:
00140 break;
00141 #endif
00142 default:
00143 break;
00144 }
00145 }
00146
00147 void QDBusViewer::callMethod(const BusSignature &sig)
00148 {
00149 QDBusInterface iface(sig.mService, sig.mPath, sig.mInterface, c);
00150 const QMetaObject *mo = iface.metaObject();
00151
00152
00153 QMetaMethod method;
00154 for (int i = 0; i < mo->methodCount(); ++i) {
00155 const QString signature = QString::fromLatin1(mo->method(i).signature());
00156 if (signature.startsWith(sig.mName) && signature.at(sig.mName.length()) == '(')
00157 method = mo->method(i);
00158 }
00159 if (!method.signature()) {
00160 QMessageBox::warning(this, "Unable to find method",
00161 QString("Unable to find method %1 on path %2 in interface %3").arg(
00162 sig.mName).arg(sig.mPath).arg(sig.mInterface));
00163 return;
00164 }
00165
00166 QList<QByteArray> paramTypes = method.parameterTypes();
00167 int paramTypeCount = 0;
00168 foreach (QByteArray paramType, paramTypes) {
00169 if (paramType.endsWith('&'))
00170 continue;
00171
00172 if (!QVariant(QVariant::nameToType(paramType)).canConvert(QVariant::String)) {
00173 QMessageBox::warning(this, "Unable to call method",
00174 QString("Cannot marshall parameter of type %1").arg(
00175 QVariant::nameToType(paramType)));
00176 return;
00177 }
00178 ++paramTypeCount;
00179 }
00180
00181 QList<QVariant> arguments;
00182 if (!paramTypes.isEmpty()) {
00183 QString input;
00184 bool ok = true;
00185 while (arguments.isEmpty()) {
00186 input = QInputDialog::getText(this, "Arguments",
00187 "Please enter the arguments for the call, separated by comma."
00188 "<br>Example: <i>hello,world,2.3,-34</i><br><br><b>Signature:</b> "
00189 + Qt::escape(QString::fromUtf8(method.signature())),
00190 QLineEdit::Normal, input, &ok);
00191 if (!ok)
00192 return;
00193
00194 QStringList argStrings = input.split(',');
00195 if (argStrings.count() != paramTypeCount)
00196 continue;
00197
00198 for (int i = 0; i < argStrings.count(); ++i) {
00199 if (paramTypes.at(i).endsWith('&'))
00200 continue;
00201
00202 QVariant v = argStrings.at(i);
00203 if (!v.convert(QVariant::nameToType(paramTypes.at(i)))) {
00204 arguments.clear();
00205 break;
00206 }
00207 arguments += v;
00208 }
00209 }
00210 }
00211
00212 QDBusMessage message = QDBusMessage::createMethodCall(sig.mService, sig.mPath, sig.mInterface,
00213 sig.mName);
00214 message.setArguments(arguments);
00215 c.callWithCallback(message, this, SLOT(dumpMessage(QDBusMessage)));
00216 }
00217
00218 void QDBusViewer::showContextMenu(const QPoint &point)
00219 {
00220 QModelIndex item = tree->indexAt(point);
00221 if (!item.isValid())
00222 return;
00223
00224 const QDBusModel *model = static_cast<const QDBusModel *>(item.model());
00225
00226 BusSignature sig;
00227 sig.mService = currentService;
00228 sig.mPath = model->dBusPath(item);
00229 sig.mInterface = model->dBusInterface(item);
00230 sig.mName = model->dBusMethodName(item);
00231
00232 QMenu menu;
00233 menu.addAction(refreshAction);
00234
00235 switch (model->itemType(item)) {
00236 case QDBusModel::SignalItem: {
00237 QAction *action = new QAction("&Connect", &menu);
00238 action->setData(1);
00239 menu.addAction(action);
00240 break; }
00241 case QDBusModel::MethodItem: {
00242 QAction *action = new QAction("&Call", &menu);
00243 action->setData(2);
00244 menu.addAction(action);
00245 break; }
00246 #if 0
00247 case PropertyItem: {
00248 QAction *actionSet = new QAction("&Set value", &menu);
00249 actionSet->setData(3);
00250 QAction *actionGet = new QAction("&Get value", &menu);
00251 actionGet->setData(4);
00252 menu.addAction(actionSet);
00253 menu.addAction(actionGet);
00254 break; }
00255 #endif
00256 default:
00257 break;
00258 }
00259
00260 QAction *selectedAction = menu.exec(tree->viewport()->mapToGlobal(point));
00261 if (!selectedAction)
00262 return;
00263
00264 switch (selectedAction->data().toInt()) {
00265 case 1:
00266 connectionRequested(sig);
00267 break;
00268 case 2:
00269 callMethod(sig);
00270 break;
00271 }
00272 }
00273
00274 void QDBusViewer::connectionRequested(const BusSignature &sig)
00275 {
00276 if (!c.connect(sig.mService, QString(), sig.mInterface, sig.mName, this,
00277 SLOT(dumpMessage(QDBusMessage)))) {
00278 logError(QString("Unable to connect to service %1, path %2, interface %3, signal %4").arg(
00279 sig.mService).arg(sig.mPath).arg(sig.mInterface).arg(sig.mName));
00280 }
00281 }
00282
00283 void QDBusViewer::dumpMessage(const QDBusMessage &message)
00284 {
00285 QList<QVariant> args = message.arguments();
00286 QString out = "Received ";
00287
00288 switch (message.type()) {
00289 case QDBusMessage::SignalMessage:
00290 out += "signal ";
00291 break;
00292 case QDBusMessage::ErrorMessage:
00293 out += "error message ";
00294 break;
00295 case QDBusMessage::ReplyMessage:
00296 out += "reply ";
00297 break;
00298 default:
00299 out += "message ";
00300 break;
00301 }
00302
00303 out += "from ";
00304 out += message.service();
00305 if (!message.path().isEmpty())
00306 out += ", path " + message.path();
00307 if (!message.interface().isEmpty())
00308 out += ", interface <i>" + message.interface() + "</i>";
00309 if (!message.member().isEmpty())
00310 out += ", member " + message.member();
00311 out += "<br>";
00312 if (!args.isEmpty()) {
00313 out += " Parameters: ";
00314 foreach (QVariant arg, args) {
00315 if (arg.canConvert(QVariant::StringList)) {
00316 out += "<b>{</b>";
00317 QStringList list = arg.toStringList();
00318 foreach (QString item, list)
00319 out += "<b>\"</b>" + Qt::escape(item) + "<b>\"</b>, ";
00320 if (!list.isEmpty())
00321 out.chop(2);
00322 out += "<b>}</b>";
00323 } else if (arg.canConvert(QVariant::Rect)) {
00324 QRect r = arg.toRect();
00325 out += QString::fromLatin1("QRect(%1, %2, %3, %4)").arg(r.left()).arg(r.top()).arg(
00326 r.right()).arg(r.bottom());
00327 } else if (qVariantCanConvert<QDBusArgument>(arg)) {
00328 out += "[QDBusArgument: " + qvariant_cast<QDBusArgument>(arg).currentSignature();
00329 out += "]";
00330 } else if (arg.canConvert(QVariant::String)) {
00331 out += "<b>\"</b>" + Qt::escape(arg.toString()) + "<b>\"</b>";
00332 } else {
00333 out += "[";
00334 out += arg.typeName();
00335 out += "]";
00336 }
00337 out += ", ";
00338 }
00339 out.chop(2);
00340 }
00341
00342 log->append(out);
00343 }
00344
00345 void QDBusViewer::serviceChanged(QTreeWidgetItem *item)
00346 {
00347 delete tree->model();
00348
00349 currentService.clear();
00350 if (!item)
00351 return;
00352 currentService = item->text(0);
00353
00354 tree->setModel(new QDBusViewModel(currentService, c));
00355 connect(tree->model(), SIGNAL(busError(QString)), this, SLOT(logError(QString)));
00356 }
00357
00358 void QDBusViewer::serviceRegistered(const QString &service)
00359 {
00360 if (service == c.baseService())
00361 return;
00362
00363 new QTreeWidgetItem(services, QStringList(service));
00364 }
00365
00366 static QTreeWidgetItem *findItem(const QTreeWidget *services, const QString &name)
00367 {
00368 for (int i = 0; i < services->topLevelItemCount(); ++i) {
00369 if (services->topLevelItem(i)->text(0) == name)
00370 return services->topLevelItem(i);
00371 }
00372 return 0;
00373 }
00374
00375 void QDBusViewer::serviceUnregistered(const QString &name)
00376 {
00377 delete findItem(services, name);
00378 }
00379
00380 void QDBusViewer::serviceOwnerChanged(const QString &name, const QString &oldOwner,
00381 const QString &newOwner)
00382 {
00383 QTreeWidgetItem *item = findItem(services, name);
00384
00385 if (!item && oldOwner.isEmpty() && !newOwner.isEmpty())
00386 serviceRegistered(name);
00387 else if (item && !oldOwner.isEmpty() && newOwner.isEmpty())
00388 delete item;
00389 else if (item && !oldOwner.isEmpty() && !newOwner.isEmpty()) {
00390 delete item;
00391 serviceRegistered(name);
00392 }
00393 }
00394
00395 void QDBusViewer::refreshChildren()
00396 {
00397 QDBusModel *model = qobject_cast<QDBusModel *>(tree->model());
00398 if (!model)
00399 return;
00400 model->refresh(tree->currentIndex());
00401 }
00402