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 <stdio.h> |
30 | #include <stdlib.h> |
31 | |
32 | #include <QtCore/QCoreApplication> |
33 | #include <QtCore/QRegExp> |
34 | #include <QtCore/QStringList> |
35 | #include <QtCore/qmetaobject.h> |
36 | #include <QtXml/QDomDocument> |
37 | #include <QtXml/QDomElement> |
38 | #include <QtDBus/QDBusConnection> |
39 | #include <QtDBus/QDBusInterface> |
40 | #include <QtDBus/QDBusConnectionInterface> |
41 | #include <QtDBus/QDBusVariant> |
42 | #include <QtDBus/QDBusArgument> |
43 | #include <QtDBus/QDBusMessage> |
44 | #include <QtDBus/QDBusReply> |
45 | #include <private/qdbusutil_p.h> |
46 | |
47 | QT_BEGIN_NAMESPACE |
48 | Q_DBUS_EXPORT extern bool qt_dbus_metaobject_skip_annotations; |
49 | QT_END_NAMESPACE |
50 | |
51 | static QDBusConnection connection(QLatin1String("" )); |
52 | static bool printArgumentsLiterally = false; |
53 | |
54 | static void showUsage() |
55 | { |
56 | printf(format: "Usage: qdbus [--system] [--bus busaddress] [--literal] [servicename] [path] [method] [args]\n" |
57 | "\n" |
58 | " servicename the service to connect to (e.g., org.freedesktop.DBus)\n" |
59 | " path the path to the object (e.g., /)\n" |
60 | " method the method to call, with or without the interface\n" |
61 | " args arguments to pass to the call\n" |
62 | "With 0 arguments, qdbus will list the services available on the bus\n" |
63 | "With just the servicename, qdbus will list the object paths available on the service\n" |
64 | "With service name and object path, qdbus will list the methods, signals and properties available on the object\n" |
65 | "\n" |
66 | "Options:\n" |
67 | " --system connect to the system bus\n" |
68 | " --bus busaddress connect to a custom bus\n" |
69 | " --literal print replies literally\n" |
70 | ); |
71 | } |
72 | |
73 | static void printArg(const QVariant &v) |
74 | { |
75 | if (printArgumentsLiterally) { |
76 | printf(format: "%s\n" , qPrintable(QDBusUtil::argumentToString(v))); |
77 | return; |
78 | } |
79 | |
80 | if (v.userType() == QVariant::StringList) { |
81 | const QStringList sl = v.toStringList(); |
82 | for (const QString &s : sl) |
83 | printf(format: "%s\n" , qPrintable(s)); |
84 | } else if (v.userType() == QVariant::List) { |
85 | const QVariantList vl = v.toList(); |
86 | for (const QVariant &var : vl) |
87 | printArg(v: var); |
88 | } else if (v.userType() == QVariant::Map) { |
89 | const QVariantMap map = v.toMap(); |
90 | QVariantMap::ConstIterator it = map.constBegin(); |
91 | for ( ; it != map.constEnd(); ++it) { |
92 | printf(format: "%s: " , qPrintable(it.key())); |
93 | printArg(v: it.value()); |
94 | } |
95 | } else if (v.userType() == qMetaTypeId<QDBusVariant>()) { |
96 | printArg(v: qvariant_cast<QDBusVariant>(v).variant()); |
97 | } else if (v.userType() == qMetaTypeId<QDBusArgument>()) { |
98 | QDBusArgument arg = qvariant_cast<QDBusArgument>(v); |
99 | if (arg.currentSignature() == QLatin1String("av" )) |
100 | printArg(v: qdbus_cast<QVariantList>(arg)); |
101 | else if (arg.currentSignature() == QLatin1String("a{sv}" )) |
102 | printArg(v: qdbus_cast<QVariantMap>(arg)); |
103 | else |
104 | printf(format: "qdbus: I don't know how to display an argument of type '%s', run with --literal.\n" , |
105 | qPrintable(arg.currentSignature())); |
106 | } else if (v.userType() != QVariant::Invalid) { |
107 | printf(format: "%s\n" , qPrintable(v.toString())); |
108 | } |
109 | } |
110 | |
111 | static void listObjects(const QString &service, const QString &path) |
112 | { |
113 | // make a low-level call, to avoid introspecting the Introspectable interface |
114 | QDBusMessage call = QDBusMessage::createMethodCall(destination: service, path: path.isEmpty() ? QLatin1String("/" ) : path, |
115 | interface: QLatin1String("org.freedesktop.DBus.Introspectable" ), |
116 | method: QLatin1String("Introspect" )); |
117 | QDBusReply<QString> xml = connection.call(message: call); |
118 | |
119 | if (path.isEmpty()) { |
120 | // top-level |
121 | if (xml.isValid()) { |
122 | printf(format: "/\n" ); |
123 | } else { |
124 | QDBusError err = xml.error(); |
125 | if (err.type() == QDBusError::ServiceUnknown) |
126 | fprintf(stderr, format: "Service '%s' does not exist.\n" , qPrintable(service)); |
127 | else |
128 | printf(format: "Error: %s\n%s\n" , qPrintable(err.name()), qPrintable(err.message())); |
129 | exit(status: 2); |
130 | } |
131 | } else if (!xml.isValid()) { |
132 | // this is not the first object, just fail silently |
133 | return; |
134 | } |
135 | |
136 | QDomDocument doc; |
137 | doc.setContent(text: xml); |
138 | QDomElement node = doc.documentElement(); |
139 | QDomElement child = node.firstChildElement(); |
140 | while (!child.isNull()) { |
141 | if (child.tagName() == QLatin1String("node" )) { |
142 | QString sub = path + QLatin1Char('/') + child.attribute(name: QLatin1String("name" )); |
143 | printf(format: "%s\n" , qPrintable(sub)); |
144 | listObjects(service, path: sub); |
145 | } |
146 | child = child.nextSiblingElement(); |
147 | } |
148 | } |
149 | |
150 | static void listInterface(const QString &service, const QString &path, const QString &interface) |
151 | { |
152 | QDBusInterface iface(service, path, interface, connection); |
153 | if (!iface.isValid()) { |
154 | QDBusError err(iface.lastError()); |
155 | fprintf(stderr, format: "Interface '%s' not available in object %s at %s:\n%s (%s)\n" , |
156 | qPrintable(interface), qPrintable(path), qPrintable(service), |
157 | qPrintable(err.name()), qPrintable(err.message())); |
158 | exit(status: 1); |
159 | } |
160 | const QMetaObject *mo = iface.metaObject(); |
161 | |
162 | // properties |
163 | for (int i = mo->propertyOffset(); i < mo->propertyCount(); ++i) { |
164 | QMetaProperty mp = mo->property(index: i); |
165 | printf(format: "property " ); |
166 | |
167 | if (mp.isReadable() && mp.isWritable()) |
168 | printf(format: "readwrite" ); |
169 | else if (mp.isReadable()) |
170 | printf(format: "read" ); |
171 | else |
172 | printf(format: "write" ); |
173 | |
174 | printf(format: " %s %s.%s\n" , mp.typeName(), qPrintable(interface), mp.name()); |
175 | } |
176 | |
177 | // methods (signals and slots) |
178 | for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) { |
179 | QMetaMethod mm = mo->method(index: i); |
180 | |
181 | QByteArray signature = mm.methodSignature(); |
182 | signature.truncate(pos: signature.indexOf(c: '(')); |
183 | printf(format: "%s %s%s%s %s.%s(" , |
184 | mm.methodType() == QMetaMethod::Signal ? "signal" : "method" , |
185 | mm.tag(), *mm.tag() ? " " : "" , |
186 | *mm.typeName() ? mm.typeName() : "void" , |
187 | qPrintable(interface), signature.constData()); |
188 | |
189 | QList<QByteArray> types = mm.parameterTypes(); |
190 | QList<QByteArray> names = mm.parameterNames(); |
191 | bool first = true; |
192 | for (int i = 0; i < types.count(); ++i) { |
193 | printf(format: "%s%s" , |
194 | first ? "" : ", " , |
195 | types.at(i).constData()); |
196 | if (!names.at(i).isEmpty()) |
197 | printf(format: " %s" , names.at(i).constData()); |
198 | first = false; |
199 | } |
200 | printf(format: ")\n" ); |
201 | } |
202 | } |
203 | |
204 | static void listAllInterfaces(const QString &service, const QString &path) |
205 | { |
206 | // make a low-level call, to avoid introspecting the Introspectable interface |
207 | QDBusMessage call = QDBusMessage::createMethodCall(destination: service, path: path.isEmpty() ? QLatin1String("/" ) : path, |
208 | interface: QLatin1String("org.freedesktop.DBus.Introspectable" ), |
209 | method: QLatin1String("Introspect" )); |
210 | QDBusReply<QString> xml = connection.call(message: call); |
211 | |
212 | if (!xml.isValid()) { |
213 | QDBusError err = xml.error(); |
214 | if (err.type() == QDBusError::ServiceUnknown) |
215 | fprintf(stderr, format: "Service '%s' does not exist.\n" , qPrintable(service)); |
216 | else |
217 | printf(format: "Error: %s\n%s\n" , qPrintable(err.name()), qPrintable(err.message())); |
218 | exit(status: 2); |
219 | } |
220 | |
221 | QDomDocument doc; |
222 | doc.setContent(text: xml); |
223 | QDomElement node = doc.documentElement(); |
224 | QDomElement child = node.firstChildElement(); |
225 | while (!child.isNull()) { |
226 | if (child.tagName() == QLatin1String("interface" )) { |
227 | QString ifaceName = child.attribute(name: QLatin1String("name" )); |
228 | if (QDBusUtil::isValidInterfaceName(ifaceName)) |
229 | listInterface(service, path, interface: ifaceName); |
230 | else { |
231 | qWarning(msg: "Invalid D-BUS interface name '%s' found while parsing introspection" , |
232 | qPrintable(ifaceName)); |
233 | } |
234 | } |
235 | child = child.nextSiblingElement(); |
236 | } |
237 | } |
238 | |
239 | static QStringList readList(QStringList &args) |
240 | { |
241 | args.takeFirst(); |
242 | |
243 | QStringList retval; |
244 | while (!args.isEmpty() && args.at(i: 0) != QLatin1String(")" )) |
245 | retval += args.takeFirst(); |
246 | |
247 | if (args.value(i: 0) == QLatin1String(")" )) |
248 | args.takeFirst(); |
249 | |
250 | return retval; |
251 | } |
252 | |
253 | static int placeCall(const QString &service, const QString &path, const QString &interface, |
254 | const QString &member, const QStringList& arguments, bool try_prop=true) |
255 | { |
256 | QDBusInterface iface(service, path, interface, connection); |
257 | |
258 | // Don't check whether the interface is valid to allow DBus try to |
259 | // activate the service if possible. |
260 | |
261 | QList<int> knownIds; |
262 | bool matchFound = false; |
263 | QStringList args = arguments; |
264 | QVariantList params; |
265 | if (!args.isEmpty()) { |
266 | const QMetaObject *mo = iface.metaObject(); |
267 | QByteArray match = member.toLatin1(); |
268 | match += '('; |
269 | |
270 | for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) { |
271 | QMetaMethod mm = mo->method(index: i); |
272 | QByteArray signature = mm.methodSignature(); |
273 | if (signature.startsWith(a: match)) |
274 | knownIds += i; |
275 | } |
276 | |
277 | |
278 | while (!matchFound) { |
279 | args = arguments; // reset |
280 | params.clear(); |
281 | if (knownIds.isEmpty()) { |
282 | // Failed to set property after falling back? |
283 | // Bail out without displaying an error |
284 | if (!try_prop) |
285 | return 1; |
286 | if (try_prop && args.size() == 1) { |
287 | QStringList proparg; |
288 | proparg += interface; |
289 | proparg += member; |
290 | proparg += args.first(); |
291 | if (!placeCall(service, path, interface: "org.freedesktop.DBus.Properties" , member: "Set" , arguments: proparg, try_prop: false)) |
292 | return 0; |
293 | } |
294 | fprintf(stderr, format: "Cannot find '%s.%s' in object %s at %s\n" , |
295 | qPrintable(interface), qPrintable(member), qPrintable(path), |
296 | qPrintable(service)); |
297 | return 1; |
298 | } |
299 | |
300 | QMetaMethod mm = mo->method(index: knownIds.takeFirst()); |
301 | QList<QByteArray> types = mm.parameterTypes(); |
302 | for (int i = 0; i < types.count(); ++i) { |
303 | if (types.at(i).endsWith(c: '&')) { |
304 | // reference (and not a reference to const): output argument |
305 | // we're done with the inputs |
306 | while (types.count() > i) |
307 | types.removeLast(); |
308 | break; |
309 | } |
310 | } |
311 | |
312 | for (int i = 0; !args.isEmpty() && i < types.count(); ++i) { |
313 | int id = QVariant::nameToType(name: types.at(i)); |
314 | if (id == QVariant::UserType) |
315 | id = QMetaType::type(typeName: types.at(i)); |
316 | if (!id) { |
317 | fprintf(stderr, format: "Cannot call method '%s' because type '%s' is unknown to this tool\n" , |
318 | qPrintable(member), types.at(i).constData()); |
319 | return 1; |
320 | } |
321 | |
322 | QVariant p; |
323 | QString argument; |
324 | if ((id == QVariant::List || id == QVariant::StringList) |
325 | && args.at(i: 0) == QLatin1String("(" )) |
326 | p = readList(args); |
327 | else |
328 | p = argument = args.takeFirst(); |
329 | |
330 | if (id == int(QMetaType::UChar)) { |
331 | // special case: QVariant::convert doesn't convert to/from |
332 | // UChar because it can't decide if it's a character or a number |
333 | p = QVariant::fromValue<uchar>(value: p.toUInt()); |
334 | } else if (id < int(QMetaType::User) && id != int(QVariant::Map)) { |
335 | p.convert(targetTypeId: id); |
336 | if (p.type() == QVariant::Invalid) { |
337 | fprintf(stderr, format: "Could not convert '%s' to type '%s'.\n" , |
338 | qPrintable(argument), types.at(i).constData()); |
339 | return 1 ; |
340 | } |
341 | } else if (id == qMetaTypeId<QDBusVariant>()) { |
342 | QDBusVariant tmp(p); |
343 | p = QVariant::fromValue(value: tmp); |
344 | } else if (id == qMetaTypeId<QDBusObjectPath>()) { |
345 | QDBusObjectPath path(argument); |
346 | if (path.path().isNull()) { |
347 | fprintf(stderr, format: "Cannot pass argument '%s' because it is not a valid object path.\n" , |
348 | qPrintable(argument)); |
349 | return 1; |
350 | } |
351 | p = QVariant::fromValue(value: path); |
352 | } else if (id == qMetaTypeId<QDBusSignature>()) { |
353 | QDBusSignature sig(argument); |
354 | if (sig.signature().isNull()) { |
355 | fprintf(stderr, format: "Cannot pass argument '%s' because it is not a valid signature.\n" , |
356 | qPrintable(argument)); |
357 | return 1; |
358 | } |
359 | p = QVariant::fromValue(value: sig); |
360 | } else { |
361 | fprintf(stderr, format: "Sorry, can't pass arg of type '%s'.\n" , |
362 | types.at(i).constData()); |
363 | return 1; |
364 | } |
365 | params += p; |
366 | } |
367 | if (params.count() == types.count() && args.isEmpty()) |
368 | matchFound = true; |
369 | else if (knownIds.isEmpty()) { |
370 | fprintf(stderr, format: "Invalid number of parameters\n" ); |
371 | return 1; |
372 | } |
373 | } // while (!matchFound) |
374 | } // if (!args.isEmpty() |
375 | |
376 | QDBusMessage reply = iface.callWithArgumentList(mode: QDBus::Block, method: member, args: params); |
377 | if (reply.type() == QDBusMessage::ErrorMessage) { |
378 | QDBusError err = reply; |
379 | // Failed to retrieve property after falling back? |
380 | // Bail out without displaying an error |
381 | if (!try_prop) |
382 | return 1; |
383 | if (err.type() == QDBusError::UnknownMethod && try_prop) { |
384 | QStringList proparg; |
385 | proparg += interface; |
386 | proparg += member; |
387 | if (!placeCall(service, path, interface: "org.freedesktop.DBus.Properties" , member: "Get" , arguments: proparg, try_prop: false)) |
388 | return 0; |
389 | } |
390 | if (err.type() == QDBusError::ServiceUnknown) |
391 | fprintf(stderr, format: "Service '%s' does not exist.\n" , qPrintable(service)); |
392 | else |
393 | printf(format: "Error: %s\n%s\n" , qPrintable(err.name()), qPrintable(err.message())); |
394 | return 2; |
395 | } else if (reply.type() != QDBusMessage::ReplyMessage) { |
396 | fprintf(stderr, format: "Invalid reply type %d\n" , int(reply.type())); |
397 | return 1; |
398 | } |
399 | |
400 | const QVariantList replyArguments = reply.arguments(); |
401 | for (const QVariant &v : replyArguments) |
402 | printArg(v); |
403 | |
404 | return 0; |
405 | } |
406 | |
407 | static bool globServices(QDBusConnectionInterface *bus, const QString &glob) |
408 | { |
409 | QRegExp pattern(glob, Qt::CaseSensitive, QRegExp::Wildcard); |
410 | if (!pattern.isValid()) |
411 | return false; |
412 | |
413 | QStringList names = bus->registeredServiceNames(); |
414 | names.sort(); |
415 | for (const QString &name : qAsConst(t&: names)) |
416 | if (pattern.exactMatch(str: name)) |
417 | printf(format: "%s\n" , qPrintable(name)); |
418 | |
419 | return true; |
420 | } |
421 | |
422 | static void printAllServices(QDBusConnectionInterface *bus) |
423 | { |
424 | const QStringList services = bus->registeredServiceNames(); |
425 | QMap<QString, QStringList> servicesWithAliases; |
426 | |
427 | for (const QString &serviceName : services) { |
428 | QDBusReply<QString> reply = bus->serviceOwner(name: serviceName); |
429 | QString owner = reply; |
430 | if (owner.isEmpty()) |
431 | owner = serviceName; |
432 | servicesWithAliases[owner].append(t: serviceName); |
433 | } |
434 | |
435 | for (QMap<QString,QStringList>::const_iterator it = servicesWithAliases.constBegin(); |
436 | it != servicesWithAliases.constEnd(); ++it) { |
437 | QStringList names = it.value(); |
438 | names.sort(); |
439 | printf(format: "%s\n" , qPrintable(names.join(QLatin1String("\n " )))); |
440 | } |
441 | } |
442 | |
443 | int main(int argc, char **argv) |
444 | { |
445 | QT_PREPEND_NAMESPACE(qt_dbus_metaobject_skip_annotations) = true; |
446 | QCoreApplication app(argc, argv); |
447 | QStringList args = app.arguments(); |
448 | args.takeFirst(); |
449 | |
450 | bool connectionOpened = false; |
451 | while (!args.isEmpty() && args.at(i: 0).startsWith(c: QLatin1Char('-'))) { |
452 | QString arg = args.takeFirst(); |
453 | if (arg == QLatin1String("--system" )) { |
454 | connection = QDBusConnection::systemBus(); |
455 | connectionOpened = true; |
456 | } else if (arg == QLatin1String("--bus" )) { |
457 | if (!args.isEmpty()) { |
458 | connection = QDBusConnection::connectToBus(address: args.takeFirst(), name: "QDBus" ); |
459 | connectionOpened = true; |
460 | } |
461 | } else if (arg == QLatin1String("--literal" )) { |
462 | printArgumentsLiterally = true; |
463 | } else if (arg == QLatin1String("--help" )) { |
464 | showUsage(); |
465 | return 0; |
466 | } |
467 | } |
468 | |
469 | if (!connectionOpened) |
470 | connection = QDBusConnection::sessionBus(); |
471 | |
472 | if (!connection.isConnected()) { |
473 | const QDBusError lastError = connection.lastError(); |
474 | if (lastError.isValid()) { |
475 | fprintf(stderr, format: "Could not connect to D-Bus server: %s: %s\n" , |
476 | qPrintable(lastError.name()), |
477 | qPrintable(lastError.message())); |
478 | } else { |
479 | // an invalid last error means that we were not able to even load the D-Bus library |
480 | fprintf(stderr, format: "Could not connect to D-Bus server: Unable to load dbus libraries\n" ); |
481 | } |
482 | return 1; |
483 | } |
484 | |
485 | QDBusConnectionInterface *bus = connection.interface(); |
486 | if (args.isEmpty()) { |
487 | printAllServices(bus); |
488 | return 0; |
489 | } |
490 | |
491 | QString service = args.takeFirst(); |
492 | if (!QDBusUtil::isValidBusName(busName: service)) { |
493 | if (service.contains(c: QLatin1Char('*'))) { |
494 | if (globServices(bus, glob: service)) |
495 | return 0; |
496 | } |
497 | fprintf(stderr, format: "Service '%s' is not a valid name.\n" , qPrintable(service)); |
498 | return 1; |
499 | } |
500 | |
501 | if (args.isEmpty()) { |
502 | listObjects(service, path: QString()); |
503 | return 0; |
504 | } |
505 | |
506 | QString path = args.takeFirst(); |
507 | if (!QDBusUtil::isValidObjectPath(path)) { |
508 | fprintf(stderr, format: "Path '%s' is not a valid path name.\n" , qPrintable(path)); |
509 | return 1; |
510 | } |
511 | if (args.isEmpty()) { |
512 | listAllInterfaces(service, path); |
513 | return 0; |
514 | } |
515 | |
516 | QString interface = args.takeFirst(); |
517 | QString member; |
518 | int pos = interface.lastIndexOf(c: QLatin1Char('.')); |
519 | if (pos == -1) { |
520 | member = interface; |
521 | interface.clear(); |
522 | } else { |
523 | member = interface.mid(position: pos + 1); |
524 | interface.truncate(pos); |
525 | } |
526 | if (!interface.isEmpty() && !QDBusUtil::isValidInterfaceName(ifaceName: interface)) { |
527 | fprintf(stderr, format: "Interface '%s' is not a valid interface name.\n" , qPrintable(interface)); |
528 | exit(status: 1); |
529 | } |
530 | if (!QDBusUtil::isValidMemberName(memberName: member)) { |
531 | fprintf(stderr, format: "Method name '%s' is not a valid member name.\n" , qPrintable(member)); |
532 | return 1; |
533 | } |
534 | |
535 | int ret = placeCall(service, path, interface, member, arguments: args); |
536 | return ret; |
537 | } |
538 | |
539 | |