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
47QT_BEGIN_NAMESPACE
48Q_DBUS_EXPORT extern bool qt_dbus_metaobject_skip_annotations;
49QT_END_NAMESPACE
50
51static QDBusConnection connection(QLatin1String(""));
52static bool printArgumentsLiterally = false;
53
54static 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
73static 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
111static 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
150static 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
204static 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
239static 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
253static 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
407static 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
422static 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
443int 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

source code of qttools/src/qdbus/qdbus/qdbus.cpp