Revision control
Copy as Markdown
// Copyright (C) 2016 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qdbusmodel.h"
#include <QtCore/QDebug>
#include <QtCore/QList>
#include <QtDBus/QDBusObjectPath>
#include <QtDBus/QDBusInterface>
#include <QtDBus/QDBusReply>
#include <QtXml/QDomDocument>
using namespace Qt::StringLiterals;
struct QDBusItem
{
inline QDBusItem(QDBusModel::Type aType, const QString &aName, QDBusItem *aParent = 0)
: type(aType), parent(aParent), isPrefetched(type != QDBusModel::PathItem), name(aName)
{}
inline ~QDBusItem()
{
qDeleteAll(children);
}
QString path() const
{
Q_ASSERT(type == QDBusModel::PathItem);
QString s;
const QDBusItem *item = this;
while (item) {
s.prepend(item->name);
item = item->parent;
}
if (s.size() > 1)
s.chop(1); // remove tailing slash
return s;
}
QDBusModel::Type type;
QDBusItem *parent;
QList<QDBusItem *> children;
bool isPrefetched;
QString name;
QString caption;
QString typeSignature;
};
QDomDocument QDBusModel::introspect(const QString &path)
{
QDomDocument doc;
QDBusInterface iface(service, path, "org.freedesktop.DBus.Introspectable"_L1, c);
if (!iface.isValid()) {
QDBusError err(iface.lastError());
emit busError(tr("Cannot introspect object %1 at %2:\n %3 (%4)\n")
.arg(path)
.arg(service)
.arg(err.name())
.arg(err.message()));
return doc;
}
QDBusReply<QString> xml = iface.call("Introspect"_L1);
if (!xml.isValid()) {
QDBusError err(xml.error());
if (err.isValid()) {
emit busError(tr("Call to object %1 at %2:\n %3 (%4) failed\n")
.arg(path)
.arg(service)
.arg(err.name())
.arg(err.message()));
} else {
emit busError(tr("Invalid XML received from object %1 at %2\n").arg(path).arg(service));
}
return doc;
}
doc.setContent(xml.value());
return doc;
}
void QDBusModel::addMethods(QDBusItem *parent, const QDomElement &iface)
{
Q_ASSERT(parent);
QDomElement child = iface.firstChildElement();
while (!child.isNull()) {
QDBusItem *item = nullptr;
if (child.tagName() == "method"_L1) {
item = new QDBusItem(QDBusModel::MethodItem, child.attribute("name"_L1), parent);
item->caption = tr("Method: %1").arg(item->name);
//get "type" from <arg> where "direction" is "in"
QDomElement n = child.firstChildElement();
while (!n.isNull()) {
if (n.attribute("direction"_L1) == "in"_L1)
item->typeSignature += n.attribute("type"_L1);
n = n.nextSiblingElement();
}
} else if (child.tagName() == "signal"_L1) {
item = new QDBusItem(QDBusModel::SignalItem, child.attribute("name"_L1), parent);
item->caption = tr("Signal: %1").arg(item->name);
} else if (child.tagName() == "property"_L1) {
item = new QDBusItem(QDBusModel::PropertyItem, child.attribute("name"_L1), parent);
item->caption = tr("Property: %1").arg(item->name);
} else {
qDebug() << "addMethods: unknown tag:" << child.tagName();
}
if (item)
parent->children.append(item);
child = child.nextSiblingElement();
}
}
void QDBusModel::addPath(QDBusItem *parent)
{
Q_ASSERT(parent);
QString path = parent->path();
QDomDocument doc = introspect(path);
QDomElement node = doc.documentElement();
QDomElement child = node.firstChildElement();
while (!child.isNull()) {
if (child.tagName() == "node"_L1) {
QDBusItem *item = new QDBusItem(QDBusModel::PathItem,
child.attribute("name"_L1) + '/'_L1, parent);
parent->children.append(item);
addMethods(item, child);
} else if (child.tagName() == "interface"_L1) {
QDBusItem *item =
new QDBusItem(QDBusModel::InterfaceItem, child.attribute("name"_L1), parent);
parent->children.append(item);
addMethods(item, child);
} else {
qDebug() << "addPath: Unknown tag name:" << child.tagName();
}
child = child.nextSiblingElement();
}
parent->isPrefetched = true;
}
QDBusModel::QDBusModel(const QString &aService, const QDBusConnection &connection)
: service(aService), c(connection), root(0)
{
root = new QDBusItem(QDBusModel::PathItem, "/"_L1);
}
QDBusModel::~QDBusModel()
{
delete root;
}
QModelIndex QDBusModel::index(int row, int column, const QModelIndex &parent) const
{
const QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer());
if (!item)
item = root;
if (column != 0 || row < 0 || row >= item->children.size())
return QModelIndex();
return createIndex(row, 0, item->children.at(row));
}
QModelIndex QDBusModel::parent(const QModelIndex &child) const
{
QDBusItem *item = static_cast<QDBusItem *>(child.internalPointer());
if (!item || !item->parent || !item->parent->parent)
return QModelIndex();
return createIndex(item->parent->parent->children.indexOf(item->parent), 0, item->parent);
}
int QDBusModel::rowCount(const QModelIndex &parent) const
{
QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer());
if (!item)
item = root;
if (!item->isPrefetched)
const_cast<QDBusModel *>(this)->addPath(item);
return item->children.size();
}
int QDBusModel::columnCount(const QModelIndex &) const
{
return 1;
}
QVariant QDBusModel::data(const QModelIndex &index, int role) const
{
const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
if (!item)
return QVariant();
if (role != Qt::DisplayRole)
return QVariant();
return item->caption.isEmpty() ? item->name : item->caption;
}
QVariant QDBusModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (role != Qt::DisplayRole || orientation == Qt::Vertical || section != 0)
return QVariant();
return tr("Methods");
}
QDBusModel::Type QDBusModel::itemType(const QModelIndex &index) const
{
const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
return item ? item->type : PathItem;
}
void QDBusModel::refresh(const QModelIndex &aIndex)
{
QModelIndex index = aIndex;
while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) {
index = index.parent();
}
QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
if (!item)
item = root;
if (!item->children.isEmpty()) {
beginRemoveRows(index, 0, item->children.size() - 1);
qDeleteAll(item->children);
item->children.clear();
endRemoveRows();
}
addPath(item);
if (!item->children.isEmpty()) {
beginInsertRows(index, 0, item->children.size() - 1);
endInsertRows();
}
}
QString QDBusModel::dBusPath(const QModelIndex &aIndex) const
{
QModelIndex index = aIndex;
while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) {
index = index.parent();
}
QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
if (!item)
item = root;
return item->path();
}
QString QDBusModel::dBusInterface(const QModelIndex &index) const
{
QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
if (!item)
return QString();
if (item->type == InterfaceItem)
return item->name;
if (item->parent && item->parent->type == InterfaceItem)
return item->parent->name;
return QString();
}
QString QDBusModel::dBusMethodName(const QModelIndex &index) const
{
QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
return item ? item->name : QString();
}
QString QDBusModel::dBusTypeSignature(const QModelIndex &index) const
{
QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
return item ? item->typeSignature : QString();
}
QModelIndex QDBusModel::findObject(const QDBusObjectPath &objectPath)
{
QStringList path = objectPath.path().split('/'_L1, Qt::SkipEmptyParts);
QDBusItem *item = root;
int childIdx = -1;
while (item && !path.isEmpty()) {
const QString branch = path.takeFirst() + '/'_L1;
childIdx = -1;
// do a linear search over all the children
for (int i = 0; i < item->children.size(); ++i) {
QDBusItem *child = item->children.at(i);
if (child->type == PathItem && child->name == branch) {
item = child;
childIdx = i;
// prefetch the found branch
if (!item->isPrefetched)
addPath(item);
break;
}
}
// branch not found - bail out
if (childIdx == -1)
return QModelIndex();
}
// found the right item
if (childIdx != -1 && item && path.isEmpty())
return createIndex(childIdx, 0, item);
return QModelIndex();
}