1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 |
3 | |
4 | #include "qdbusmodel.h" |
5 | |
6 | #include <QtCore/QDebug> |
7 | #include <QtCore/QList> |
8 | |
9 | #include <QtDBus/QDBusObjectPath> |
10 | #include <QtDBus/QDBusInterface> |
11 | #include <QtDBus/QDBusReply> |
12 | |
13 | #include <QtXml/QDomDocument> |
14 | |
15 | using namespace Qt::StringLiterals; |
16 | |
17 | struct QDBusItem |
18 | { |
19 | inline QDBusItem(QDBusModel::Type aType, const QString &aName, QDBusItem *aParent = 0) |
20 | : type(aType), parent(aParent), isPrefetched(type != QDBusModel::PathItem), name(aName) |
21 | {} |
22 | inline ~QDBusItem() |
23 | { |
24 | qDeleteAll(c: children); |
25 | } |
26 | |
27 | QString path() const |
28 | { |
29 | Q_ASSERT(type == QDBusModel::PathItem); |
30 | |
31 | QString s; |
32 | const QDBusItem *item = this; |
33 | while (item) { |
34 | s.prepend(s: item->name); |
35 | item = item->parent; |
36 | } |
37 | if (s.size() > 1) |
38 | s.chop(n: 1); // remove tailing slash |
39 | return s; |
40 | } |
41 | |
42 | QDBusModel::Type type; |
43 | QDBusItem *parent; |
44 | QList<QDBusItem *> children; |
45 | bool isPrefetched; |
46 | QString name; |
47 | QString caption; |
48 | QString typeSignature; |
49 | }; |
50 | |
51 | QDomDocument QDBusModel::introspect(const QString &path) |
52 | { |
53 | QDomDocument doc; |
54 | |
55 | QDBusInterface iface(service, path, "org.freedesktop.DBus.Introspectable"_L1 , c); |
56 | if (!iface.isValid()) { |
57 | QDBusError err(iface.lastError()); |
58 | emit busError(text: tr(s: "Cannot introspect object %1 at %2:\n %3 (%4)\n" ) |
59 | .arg(a: path) |
60 | .arg(a: service) |
61 | .arg(a: err.name()) |
62 | .arg(a: err.message())); |
63 | return doc; |
64 | } |
65 | |
66 | QDBusReply<QString> xml = iface.call(method: "Introspect"_L1 ); |
67 | |
68 | if (!xml.isValid()) { |
69 | QDBusError err(xml.error()); |
70 | if (err.isValid()) { |
71 | emit busError(text: tr(s: "Call to object %1 at %2:\n %3 (%4) failed\n" ) |
72 | .arg(a: path) |
73 | .arg(a: service) |
74 | .arg(a: err.name()) |
75 | .arg(a: err.message())); |
76 | } else { |
77 | emit busError(text: tr(s: "Invalid XML received from object %1 at %2\n" ).arg(a: path).arg(a: service)); |
78 | } |
79 | return doc; |
80 | } |
81 | |
82 | doc.setContent(data: xml.value()); |
83 | return doc; |
84 | } |
85 | |
86 | void QDBusModel::addMethods(QDBusItem *parent, const QDomElement &iface) |
87 | { |
88 | Q_ASSERT(parent); |
89 | |
90 | QDomElement child = iface.firstChildElement(); |
91 | while (!child.isNull()) { |
92 | QDBusItem *item = nullptr; |
93 | if (child.tagName() == "method"_L1 ) { |
94 | item = new QDBusItem(QDBusModel::MethodItem, child.attribute(name: "name"_L1 ), parent); |
95 | item->caption = tr(s: "Method: %1" ).arg(a: item->name); |
96 | //get "type" from <arg> where "direction" is "in" |
97 | QDomElement n = child.firstChildElement(); |
98 | while (!n.isNull()) { |
99 | if (n.attribute(name: "direction"_L1 ) == "in"_L1 ) |
100 | item->typeSignature += n.attribute(name: "type"_L1 ); |
101 | n = n.nextSiblingElement(); |
102 | } |
103 | } else if (child.tagName() == "signal"_L1 ) { |
104 | item = new QDBusItem(QDBusModel::SignalItem, child.attribute(name: "name"_L1 ), parent); |
105 | item->caption = tr(s: "Signal: %1" ).arg(a: item->name); |
106 | } else if (child.tagName() == "property"_L1 ) { |
107 | item = new QDBusItem(QDBusModel::PropertyItem, child.attribute(name: "name"_L1 ), parent); |
108 | item->caption = tr(s: "Property: %1" ).arg(a: item->name); |
109 | } else { |
110 | qDebug() << "addMethods: unknown tag:" << child.tagName(); |
111 | } |
112 | if (item) |
113 | parent->children.append(t: item); |
114 | |
115 | child = child.nextSiblingElement(); |
116 | } |
117 | } |
118 | |
119 | void QDBusModel::addPath(QDBusItem *parent) |
120 | { |
121 | Q_ASSERT(parent); |
122 | |
123 | QString path = parent->path(); |
124 | |
125 | QDomDocument doc = introspect(path); |
126 | QDomElement node = doc.documentElement(); |
127 | QDomElement child = node.firstChildElement(); |
128 | while (!child.isNull()) { |
129 | if (child.tagName() == "node"_L1 ) { |
130 | QDBusItem *item = new QDBusItem(QDBusModel::PathItem, |
131 | child.attribute(name: "name"_L1 ) + '/'_L1, parent); |
132 | parent->children.append(t: item); |
133 | |
134 | addMethods(parent: item, iface: child); |
135 | } else if (child.tagName() == "interface"_L1 ) { |
136 | QDBusItem *item = |
137 | new QDBusItem(QDBusModel::InterfaceItem, child.attribute(name: "name"_L1 ), parent); |
138 | parent->children.append(t: item); |
139 | |
140 | addMethods(parent: item, iface: child); |
141 | } else { |
142 | qDebug() << "addPath: Unknown tag name:" << child.tagName(); |
143 | } |
144 | child = child.nextSiblingElement(); |
145 | } |
146 | |
147 | parent->isPrefetched = true; |
148 | } |
149 | |
150 | QDBusModel::QDBusModel(const QString &aService, const QDBusConnection &connection) |
151 | : service(aService), c(connection), root(0) |
152 | { |
153 | root = new QDBusItem(QDBusModel::PathItem, "/"_L1 ); |
154 | } |
155 | |
156 | QDBusModel::~QDBusModel() |
157 | { |
158 | delete root; |
159 | } |
160 | |
161 | QModelIndex QDBusModel::index(int row, int column, const QModelIndex &parent) const |
162 | { |
163 | const QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer()); |
164 | if (!item) |
165 | item = root; |
166 | |
167 | if (column != 0 || row < 0 || row >= item->children.size()) |
168 | return QModelIndex(); |
169 | |
170 | return createIndex(arow: row, acolumn: 0, adata: item->children.at(i: row)); |
171 | } |
172 | |
173 | QModelIndex QDBusModel::parent(const QModelIndex &child) const |
174 | { |
175 | QDBusItem *item = static_cast<QDBusItem *>(child.internalPointer()); |
176 | if (!item || !item->parent || !item->parent->parent) |
177 | return QModelIndex(); |
178 | |
179 | return createIndex(arow: item->parent->parent->children.indexOf(t: item->parent), acolumn: 0, adata: item->parent); |
180 | } |
181 | |
182 | int QDBusModel::rowCount(const QModelIndex &parent) const |
183 | { |
184 | QDBusItem *item = static_cast<QDBusItem *>(parent.internalPointer()); |
185 | if (!item) |
186 | item = root; |
187 | if (!item->isPrefetched) |
188 | const_cast<QDBusModel *>(this)->addPath(parent: item); |
189 | |
190 | return item->children.size(); |
191 | } |
192 | |
193 | int QDBusModel::columnCount(const QModelIndex &) const |
194 | { |
195 | return 1; |
196 | } |
197 | |
198 | QVariant QDBusModel::data(const QModelIndex &index, int role) const |
199 | { |
200 | const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
201 | if (!item) |
202 | return QVariant(); |
203 | |
204 | if (role != Qt::DisplayRole) |
205 | return QVariant(); |
206 | |
207 | return item->caption.isEmpty() ? item->name : item->caption; |
208 | } |
209 | |
210 | QVariant QDBusModel::(int section, Qt::Orientation orientation, int role) const |
211 | { |
212 | if (role != Qt::DisplayRole || orientation == Qt::Vertical || section != 0) |
213 | return QVariant(); |
214 | |
215 | return tr(s: "Methods" ); |
216 | } |
217 | |
218 | QDBusModel::Type QDBusModel::itemType(const QModelIndex &index) const |
219 | { |
220 | const QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
221 | return item ? item->type : PathItem; |
222 | } |
223 | |
224 | void QDBusModel::refresh(const QModelIndex &aIndex) |
225 | { |
226 | QModelIndex index = aIndex; |
227 | while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) { |
228 | index = index.parent(); |
229 | } |
230 | |
231 | QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
232 | if (!item) |
233 | item = root; |
234 | |
235 | if (!item->children.isEmpty()) { |
236 | beginRemoveRows(parent: index, first: 0, last: item->children.size() - 1); |
237 | qDeleteAll(c: item->children); |
238 | item->children.clear(); |
239 | endRemoveRows(); |
240 | } |
241 | |
242 | addPath(parent: item); |
243 | if (!item->children.isEmpty()) { |
244 | beginInsertRows(parent: index, first: 0, last: item->children.size() - 1); |
245 | endInsertRows(); |
246 | } |
247 | } |
248 | |
249 | QString QDBusModel::dBusPath(const QModelIndex &aIndex) const |
250 | { |
251 | QModelIndex index = aIndex; |
252 | while (index.isValid() && static_cast<QDBusItem *>(index.internalPointer())->type != PathItem) { |
253 | index = index.parent(); |
254 | } |
255 | |
256 | QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
257 | if (!item) |
258 | item = root; |
259 | |
260 | return item->path(); |
261 | } |
262 | |
263 | QString QDBusModel::dBusInterface(const QModelIndex &index) const |
264 | { |
265 | QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
266 | if (!item) |
267 | return QString(); |
268 | if (item->type == InterfaceItem) |
269 | return item->name; |
270 | if (item->parent && item->parent->type == InterfaceItem) |
271 | return item->parent->name; |
272 | return QString(); |
273 | } |
274 | |
275 | QString QDBusModel::dBusMethodName(const QModelIndex &index) const |
276 | { |
277 | QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
278 | return item ? item->name : QString(); |
279 | } |
280 | |
281 | QString QDBusModel::dBusTypeSignature(const QModelIndex &index) const |
282 | { |
283 | QDBusItem *item = static_cast<QDBusItem *>(index.internalPointer()); |
284 | return item ? item->typeSignature : QString(); |
285 | } |
286 | |
287 | QModelIndex QDBusModel::findObject(const QDBusObjectPath &objectPath) |
288 | { |
289 | QStringList path = objectPath.path().split(sep: '/'_L1, behavior: Qt::SkipEmptyParts); |
290 | |
291 | QDBusItem *item = root; |
292 | int childIdx = -1; |
293 | while (item && !path.isEmpty()) { |
294 | const QString branch = path.takeFirst() + '/'_L1; |
295 | childIdx = -1; |
296 | |
297 | // do a linear search over all the children |
298 | for (int i = 0; i < item->children.size(); ++i) { |
299 | QDBusItem *child = item->children.at(i); |
300 | if (child->type == PathItem && child->name == branch) { |
301 | item = child; |
302 | childIdx = i; |
303 | |
304 | // prefetch the found branch |
305 | if (!item->isPrefetched) |
306 | addPath(parent: item); |
307 | break; |
308 | } |
309 | } |
310 | |
311 | // branch not found - bail out |
312 | if (childIdx == -1) |
313 | return QModelIndex(); |
314 | } |
315 | |
316 | // found the right item |
317 | if (childIdx != -1 && item && path.isEmpty()) |
318 | return createIndex(arow: childIdx, acolumn: 0, adata: item); |
319 | |
320 | return QModelIndex(); |
321 | } |
322 | |
323 | |