1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the tools applications of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 #include "qdbusmodel.h"
30 
31 #include <QtCore/qvector.h>
32 #include <QtCore/QDebug>
33 #include <QtXml/QDomDocument>
34 #include <QtDBus/QDBusObjectPath>
35 #include <QtDBus/QDBusInterface>
36 #include <QtDBus/QDBusReply>
37 
38 struct QDBusItem
39 {
QDBusItemQDBusItem40     inline QDBusItem(QDBusModel::Type aType, const QString &aName, QDBusItem *aParent = 0)
41         : type(aType), parent(aParent), isPrefetched(type != QDBusModel::PathItem), name(aName)
42         {}
~QDBusItemQDBusItem43     inline ~QDBusItem()
44     {
45         qDeleteAll(children);
46     }
47 
pathQDBusItem48     QString path() const
49     {
50         Q_ASSERT(type == QDBusModel::PathItem);
51 
52         QString s;
53         const QDBusItem *item = this;
54         while (item) {
55             s.prepend(item->name);
56             item = item->parent;
57         }
58         if (s.length() > 1)
59             s.chop(1); // remove tailing slash
60         return s;
61     }
62 
63     QDBusModel::Type type;
64     QDBusItem *parent;
65     QVector<QDBusItem *> children;
66     bool isPrefetched;
67     QString name;
68     QString caption;
69     QString typeSignature;
70 };
71 
introspect(const QString & path)72 QDomDocument QDBusModel::introspect(const QString &path)
73 {
74     QDomDocument doc;
75 
76     QDBusInterface iface(service, path, QLatin1String("org.freedesktop.DBus.Introspectable"), c);
77     if (!iface.isValid()) {
78         QDBusError err(iface.lastError());
79         emit busError(QString::fromLatin1("Cannot introspect object %1 at %2:\n  %3 (%4)\n").arg(path).arg(
80                       service).arg(err.name()).arg(err.message()));
81         return doc;
82     }
83 
84     QDBusReply<QString> xml = iface.call(QLatin1String("Introspect"));
85 
86     if (!xml.isValid()) {
87         QDBusError err(xml.error());
88         if (err.isValid()) {
89             emit busError(QString::fromLatin1("Call to object %1 at %2:\n  %3 (%4) failed\n").arg(
90                         path).arg(service).arg(err.name()).arg(err.message()));
91         } else {
92             emit busError(QString::fromLatin1("Invalid XML received from object %1 at %2\n").arg(
93                     path).arg(service));
94         }
95         return doc;
96     }
97 
98     doc.setContent(xml);
99     return doc;
100 }
101 
addMethods(QDBusItem * parent,const QDomElement & iface)102 void QDBusModel::addMethods(QDBusItem *parent, const QDomElement &iface)
103 {
104     Q_ASSERT(parent);
105 
106     QDomElement child = iface.firstChildElement();
107     while (!child.isNull()) {
108         QDBusItem *item = 0;
109         if (child.tagName() == QLatin1String("method")) {
110             item = new QDBusItem(QDBusModel::MethodItem,
111                     child.attribute(QLatin1String("name")), parent);
112             item->caption = QLatin1String("Method: ") + item->name;
113             //get "type" from <arg> where "direction" is "in"
114             QDomElement n = child.firstChildElement();
115             while (!n.isNull()) {
116                 if (n.attribute(QLatin1String("direction")) == QLatin1String("in"))
117                     item->typeSignature += n.attribute(QLatin1String("type"));
118                 n = n.nextSiblingElement();
119             }
120         } else if (child.tagName() == QLatin1String("signal")) {
121             item = new QDBusItem(QDBusModel::SignalItem,
122                     child.attribute(QLatin1String("name")), parent);
123             item->caption = QLatin1String("Signal: ") + item->name;
124         } else if (child.tagName() == QLatin1String("property")) {
125             item = new QDBusItem(QDBusModel::PropertyItem,
126                     child.attribute(QLatin1String("name")), parent);
127             item->caption = QLatin1String("Property: ") + item->name;
128         } else {
129             qDebug() << "addMethods: unknown tag:" << child.tagName();
130         }
131         if (item)
132             parent->children.append(item);
133 
134         child = child.nextSiblingElement();
135     }
136 }
137 
addPath(QDBusItem * parent)138 void QDBusModel::addPath(QDBusItem *parent)
139 {
140     Q_ASSERT(parent);
141 
142     QString path = parent->path();
143 
144     QDomDocument doc = introspect(path);
145     QDomElement node = doc.documentElement();
146     QDomElement child = node.firstChildElement();
147     while (!child.isNull()) {
148         if (child.tagName() == QLatin1String("node")) {
149             QDBusItem *item = new QDBusItem(QDBusModel::PathItem,
150                         child.attribute(QLatin1String("name")) + QLatin1Char('/'), parent);
151             parent->children.append(item);
152 
153             addMethods(item, child);
154         } else if (child.tagName() == QLatin1String("interface")) {
155             QDBusItem *item = new QDBusItem(QDBusModel::InterfaceItem,
156                         child.attribute(QLatin1String("name")), parent);
157             parent->children.append(item);
158 
159             addMethods(item, child);
160         } else {
161             qDebug() << "addPath: Unknown tag name:" << child.tagName();
162         }
163         child = child.nextSiblingElement();
164     }
165 
166     parent->isPrefetched = true;
167 }
168 
QDBusModel(const QString & aService,const QDBusConnection & connection)169 QDBusModel::QDBusModel(const QString &aService, const QDBusConnection &connection)
170     : service(aService), c(connection), root(0)
171 {
172     root = new QDBusItem(QDBusModel::PathItem, QLatin1String("/"));
173 }
174 
~QDBusModel()175 QDBusModel::~QDBusModel()
176 {
177     delete root;
178 }
179 
index(int row,int column,const QModelIndex & parent) const180 QModelIndex QDBusModel::index(int row, int column, const QModelIndex &parent) const
181 {
182     const QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer());
183     if (!item)
184         item = root;
185 
186     if (column != 0 || row < 0 || row >= item->children.count())
187         return QModelIndex();
188 
189     return createIndex(row, 0, item->children.at(row));
190 }
191 
parent(const QModelIndex & child) const192 QModelIndex QDBusModel::parent(const QModelIndex &child) const
193 {
194     QDBusItem *item = static_cast<QDBusItem *>(child.internalPointer());
195     if (!item || !item->parent || !item->parent->parent)
196         return QModelIndex();
197 
198     return createIndex(item->parent->parent->children.indexOf(item->parent), 0, item->parent);
199 }
200 
rowCount(const QModelIndex & parent) const201 int QDBusModel::rowCount(const QModelIndex &parent) const
202 {
203     QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer());
204     if (!item)
205         item = root;
206     if (!item->isPrefetched)
207         const_cast<QDBusModel *>(this)->addPath(item);
208 
209     return item->children.count();
210 }
211 
columnCount(const QModelIndex &) const212 int QDBusModel::columnCount(const QModelIndex &) const
213 {
214     return 1;
215 }
216 
data(const QModelIndex & index,int role) const217 QVariant QDBusModel::data(const QModelIndex &index, int role) const
218 {
219     const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
220     if (!item)
221         return QVariant();
222 
223     if (role != Qt::DisplayRole)
224         return QVariant();
225 
226     return item->caption.isEmpty() ? item->name : item->caption;
227 }
228 
headerData(int section,Qt::Orientation orientation,int role) const229 QVariant QDBusModel::headerData(int section, Qt::Orientation orientation, int role) const
230 {
231     if (role != Qt::DisplayRole || orientation == Qt::Vertical || section != 0)
232         return QVariant();
233 
234     return QLatin1String("Methods");
235 }
236 
itemType(const QModelIndex & index) const237 QDBusModel::Type QDBusModel::itemType(const QModelIndex &index) const
238 {
239     const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
240     return item ? item->type : PathItem;
241 }
242 
refresh(const QModelIndex & aIndex)243 void QDBusModel::refresh(const QModelIndex &aIndex)
244 {
245     QModelIndex index = aIndex;
246     while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) {
247         index = index.parent();
248     }
249 
250     QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
251     if (!item)
252         item = root;
253 
254     if (!item->children.isEmpty()) {
255         beginRemoveRows(index, 0, item->children.count() - 1);
256         qDeleteAll(item->children);
257         item->children.clear();
258         endRemoveRows();
259     }
260 
261     addPath(item);
262     if (!item->children.isEmpty()) {
263         beginInsertRows(index, 0, item->children.count() - 1);
264         endInsertRows();
265     }
266 }
267 
dBusPath(const QModelIndex & aIndex) const268 QString QDBusModel::dBusPath(const QModelIndex &aIndex) const
269 {
270     QModelIndex index = aIndex;
271     while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) {
272         index = index.parent();
273     }
274 
275     QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
276     if (!item)
277         item = root;
278 
279     return item->path();
280 }
281 
dBusInterface(const QModelIndex & index) const282 QString QDBusModel::dBusInterface(const QModelIndex &index) const
283 {
284     QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
285     if (!item)
286         return QString();
287     if (item->type == InterfaceItem)
288         return item->name;
289     if (item->parent && item->parent->type == InterfaceItem)
290         return item->parent->name;
291     return QString();
292 }
293 
dBusMethodName(const QModelIndex & index) const294 QString QDBusModel::dBusMethodName(const QModelIndex &index) const
295 {
296     QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
297     return item ? item->name : QString();
298 }
299 
dBusTypeSignature(const QModelIndex & index) const300 QString QDBusModel::dBusTypeSignature(const QModelIndex &index) const
301 {
302     QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer());
303     return item ? item->typeSignature : QString();
304 }
305 
findObject(const QDBusObjectPath & objectPath)306 QModelIndex QDBusModel::findObject(const QDBusObjectPath &objectPath)
307 {
308     QStringList path = objectPath.path().split(QLatin1Char('/'), Qt::SkipEmptyParts);
309 
310     QDBusItem *item = root;
311     int childIdx = -1;
312     while (item && !path.isEmpty()) {
313         const QString branch = path.takeFirst() + QLatin1Char('/');
314         childIdx = -1;
315 
316         // do a linear search over all the children
317         for (int i = 0; i < item->children.count(); ++i) {
318             QDBusItem *child = item->children.at(i);
319             if (child->type == PathItem && child->name == branch) {
320                 item = child;
321                 childIdx = i;
322 
323                 // prefetch the found branch
324                 if (!item->isPrefetched)
325                     addPath(item);
326                 break;
327             }
328         }
329 
330         // branch not found - bail out
331         if (childIdx == -1)
332             return QModelIndex();
333     }
334 
335     // found the right item
336     if (childIdx != -1 && item && path.isEmpty())
337         return createIndex(childIdx, 0, item);
338 
339     return QModelIndex();
340 }
341 
342