1/* This file is part of the KDE project
2 SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
3 SPDX-FileCopyrightText: 2014 Alejandro Fiestas Olivares <afiestas@kde.org>
4
5 SPDX-License-Identifier: LGPL-2.1-or-later
6*/
7
8#include "solid-hardware.h"
9
10#ifdef HAVE_DBUS
11#include <QDBusArgument>
12#include <QDBusObjectPath>
13#endif
14#include <QMetaEnum>
15#include <QMetaProperty>
16#include <QString>
17#include <QStringList>
18#include <QTextStream>
19
20#include <QCommandLineParser>
21
22#include <solid/device.h>
23#include <solid/genericinterface.h>
24#include <solid/opticaldrive.h>
25
26#include <iostream>
27#include <solid/devicenotifier.h>
28using namespace std;
29
30static const QString appName{QStringLiteral("solid-hardware")};
31
32static const QString version{QStringLiteral("0.1a")};
33
34std::ostream &operator<<(std::ostream &out, const QString &msg)
35{
36 return (out << msg.toLocal8Bit().constData());
37}
38
39std::ostream &operator<<(std::ostream &out, const QVariant &value);
40#ifdef HAVE_DBUS
41std::ostream &operator<<(std::ostream &out, const QDBusArgument &arg)
42{
43 auto type = arg.currentType();
44 switch (type) {
45 case QDBusArgument::ArrayType:
46 out << " { ";
47 arg.beginArray();
48 while (!arg.atEnd()) {
49 out << arg;
50 if (!arg.atEnd()) {
51 out << ", ";
52 }
53 }
54 arg.endArray();
55 out << " }";
56 break;
57 case QDBusArgument::StructureType:
58 out << " ( ";
59 arg.beginStructure();
60 while (!arg.atEnd()) {
61 out << arg.asVariant();
62 }
63 arg.endStructure();
64 out << " )";
65 break;
66 case QDBusArgument::MapType:
67 out << " [ ";
68 arg.beginMap();
69 if (!arg.atEnd()) {
70 out << arg;
71 }
72 arg.endMap();
73 out << " ]";
74 break;
75 case QDBusArgument::MapEntryType:
76 arg.beginMapEntry();
77 out << arg.asVariant() << " = " << arg;
78 arg.endMapEntry();
79 break;
80 case QDBusArgument::UnknownType:
81 out << "(unknown DBus type)";
82 break;
83 case QDBusArgument::BasicType:
84 case QDBusArgument::VariantType:
85 out << arg.asVariant();
86 }
87 return out;
88}
89#endif
90
91std::ostream &operator<<(std::ostream &out, const QVariant &value)
92{
93 switch (value.userType()) {
94 case QMetaType::QStringList: {
95 out << "{";
96
97 const QStringList list = value.toStringList();
98
99 QStringList::ConstIterator it = list.constBegin();
100 QStringList::ConstIterator end = list.constEnd();
101
102 for (; it != end; ++it) {
103 out << "'" << *it << "'";
104
105 if (it + 1 != end) {
106 out << ", ";
107 }
108 }
109
110 out << "} (string list)";
111 break;
112 }
113 case QMetaType::QString:
114 out << "'" << value.toString() << "' (string)";
115 break;
116 case QMetaType::Bool:
117 out << (value.toBool() ? "true" : "false") << " (bool)";
118 break;
119 case QMetaType::Int:
120 case QMetaType::LongLong:
121 out << value.toString() << " (0x" << QString::number(value.toLongLong(), base: 16) << ") (" << value.typeName() << ")";
122 break;
123 case QMetaType::UInt:
124 case QMetaType::ULongLong:
125 out << value.toString() << " (0x" << QString::number(value.toULongLong(), base: 16) << ") (" << value.typeName() << ")";
126 break;
127 case QMetaType::Double:
128 out << value.toString() << " (double)";
129 break;
130 case QMetaType::QByteArray:
131 out << "'" << value.toString() << "' (bytes)";
132 break;
133 case QMetaType::User:
134 // qDebug() << "got variant type:" << value.typeName();
135 if (value.canConvert<QList<int>>()) {
136 const QList<int> intlist = value.value<QList<int>>();
137 QStringList tmp;
138 for (const int val : intlist) {
139 tmp.append(t: QString::number(val));
140 }
141 out << "{" << tmp.join(QStringLiteral(",")) << "} (int list)";
142#ifdef HAVE_DBUS
143 } else if (value.canConvert<QDBusObjectPath>()) {
144 out << value.value<QDBusObjectPath>().path() << " (ObjectPath)";
145 } else if (value.canConvert<QDBusVariant>()) {
146 out << value.value<QDBusVariant>().variant() << "(Variant)";
147 } else if (value.canConvert<QDBusArgument>()) {
148 out << value.value<QDBusArgument>();
149#endif
150 } else {
151 out << value.toString() << " (unhandled)";
152 }
153
154 break;
155 default:
156 out << "'" << value.toString() << "' (" << value.typeName() << ")";
157 break;
158 }
159
160 return out;
161}
162
163std::ostream &operator<<(std::ostream &out, const Solid::Device &device)
164{
165 out << " parent = " << QVariant(device.parentUdi()) << endl;
166 out << " vendor = " << QVariant(device.vendor()) << endl;
167 out << " product = " << QVariant(device.product()) << endl;
168 out << " description = " << QVariant(device.description()) << endl;
169 out << " icon = " << QVariant(device.icon()) << endl;
170
171 int index = Solid::DeviceInterface::staticMetaObject.indexOfEnumerator(name: "Type");
172 QMetaEnum typeEnum = Solid::DeviceInterface::staticMetaObject.enumerator(index);
173
174 for (int i = 0; i < typeEnum.keyCount(); i++) {
175 Solid::DeviceInterface::Type type = (Solid::DeviceInterface::Type)typeEnum.value(index: i);
176 const Solid::DeviceInterface *interface = device.asDeviceInterface(type);
177
178 if (interface) {
179 const QMetaObject *meta = interface->metaObject();
180
181 for (int i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
182 QMetaProperty property = meta->property(index: i);
183 out << " " << QByteArray(meta->className()).mid(index: 7).constData() << "." << property.name() << " = ";
184
185 QVariant value = property.read(obj: interface);
186
187 if (property.isEnumType()) {
188 QMetaEnum metaEnum = property.enumerator();
189 if (metaEnum.isFlag()) {
190 out << "'" << metaEnum.valueToKeys(value: value.toInt()).constData() << "'"
191 << " (0x" << QString::number(value.toInt(), base: 16) << ") (flag)";
192 } else {
193 out << "'" << metaEnum.valueToKey(value: value.toInt()) << "'"
194 << " (0x" << QString::number(value.toInt(), base: 16) << ") (enum)";
195 }
196 out << endl;
197 } else {
198 out << value << endl;
199 }
200 }
201 }
202 }
203
204 return out;
205}
206
207std::ostream &operator<<(std::ostream &out, const QMap<QString, QVariant> &properties)
208{
209 for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
210 out << " " << it.key() << " = " << it.value() << endl;
211 }
212
213 return out;
214}
215
216QString getUdiFromArguments(QCoreApplication &app, QCommandLineParser &parser)
217{
218 parser.addPositionalArgument(QStringLiteral("udi"), description: QCoreApplication::translate(context: "solid-hardware", key: "Device udi"));
219 parser.process(app);
220 if (parser.positionalArguments().count() < 2) {
221 parser.showHelp(exitCode: 1);
222 }
223 return parser.positionalArguments().at(i: 1);
224}
225
226static QString commandsHelp()
227{
228 QString data;
229 QTextStream cout(&data);
230 cout << '\n' << QCoreApplication::translate(context: "solid-hardware", key: "Syntax:") << '\n' << '\n';
231
232 cout << " solid-hardware list [details|nonportableinfo]" << '\n';
233 cout << QCoreApplication::translate(context: "solid-hardware",
234 key: " # List the hardware available in the system.\n"
235 " # - If the 'nonportableinfo' option is specified, the device\n"
236 " # properties are listed (be careful, in this case property names\n"
237 " # are backend dependent),\n"
238 " # - If the 'details' option is specified, the device interfaces\n"
239 " # and the corresponding properties are listed in a platform\n"
240 " # neutral fashion,\n"
241 " # - Otherwise only device UDIs are listed.\n")
242 << '\n';
243
244 cout << " solid-hardware details 'udi'" << '\n';
245 cout << QCoreApplication::translate(context: "solid-hardware",
246 key: " # Display all the interfaces and properties of the device\n"
247 " # corresponding to 'udi' in a platform neutral fashion.\n")
248 << '\n';
249
250 cout << " solid-hardware nonportableinfo 'udi'" << '\n';
251 cout << QCoreApplication::translate(context: "solid-hardware",
252 key: " # Display all the properties of the device corresponding to 'udi'\n"
253 " # (be careful, in this case property names are backend dependent).\n")
254 << '\n';
255
256 cout << " solid-hardware query 'predicate' ['parentUdi']" << '\n';
257 cout << QCoreApplication::translate(context: "solid-hardware",
258 key: " # List the UDI of devices corresponding to 'predicate'.\n"
259 " # - If 'parentUdi' is specified, the search is restricted to the\n"
260 " # branch of the corresponding device,\n"
261 " # - Otherwise the search is done on all the devices.\n")
262 << '\n';
263
264 cout << " solid-hardware mount 'udi'" << '\n';
265 cout << QCoreApplication::translate(context: "solid-hardware", key: " # If applicable, mount the device corresponding to 'udi'.\n") << '\n';
266
267 cout << " solid-hardware unmount 'udi'" << '\n';
268 cout << QCoreApplication::translate(context: "solid-hardware", key: " # If applicable, unmount the device corresponding to 'udi'.\n") << '\n';
269
270 cout << " solid-hardware eject 'udi'" << '\n';
271 cout << QCoreApplication::translate(context: "solid-hardware", key: " # If applicable, eject the device corresponding to 'udi'.\n") << '\n';
272
273 cout << " solid-hardware listen" << '\n';
274 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Listen to all add/remove events on supported hardware.\n") << '\n';
275
276 cout << " solid-hardware monitor 'udi'" << '\n';
277 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Monitor devices for changes.\n") << '\n';
278
279 cout << " solid-hardware CanCheck 'udi'" << '\n';
280 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Send \"CanCheck\" request to the device corresponding to 'udi'.\n") << '\n';
281
282 cout << " solid-hardware Check 'udi'" << '\n';
283 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Send \"Check\" request to the device corresponding to 'udi'.\n") << '\n';
284
285 cout << " solid-hardware CanRepair 'udi'" << '\n';
286 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Send \"CanRepair\" request to the device corresponding to 'udi'.\n") << '\n';
287
288 cout << " solid-hardware Repair 'udi'" << '\n';
289 cout << QCoreApplication::translate(context: "solid-hardware", key: " # Send \"Repair\" request to the device corresponding to 'udi'.\n");
290
291 return data;
292}
293
294int main(int argc, char **argv)
295{
296 SolidHardware app(argc, argv);
297 app.setApplicationName(appName);
298 app.setApplicationVersion(version);
299
300 QCommandLineParser parser;
301 parser.setApplicationDescription(QCoreApplication::translate(context: "solid-hardware", key: "KDE tool for querying your hardware from the command line"));
302 parser.addHelpOption();
303 parser.addVersionOption();
304 parser.addPositionalArgument(QStringLiteral("command"), description: QCoreApplication::translate(context: "solid-hardware", key: "Command to execute"), syntax: commandsHelp());
305
306 QCommandLineOption commands(QStringLiteral("commands"), QCoreApplication::translate(context: "solid-hardware", key: "Show available commands"));
307 // --commands only for backwards compat, it's now in the "syntax help"
308 // of the positional argument.
309 commands.setFlags(QCommandLineOption::HiddenFromHelp);
310 parser.addOption(commandLineOption: commands);
311
312 parser.process(app);
313 if (parser.isSet(option: commands)) {
314 cout << commandsHelp() << endl;
315 return 0;
316 }
317
318 QStringList args = parser.positionalArguments();
319 if (args.count() < 1) {
320 parser.showHelp(exitCode: 1);
321 }
322
323 parser.clearPositionalArguments();
324
325 QString command(args.at(i: 0));
326
327 if (command == QLatin1String("list")) {
328 parser.addPositionalArgument(QStringLiteral("details"), description: QCoreApplication::translate(context: "solid-hardware", key: "Show device details"));
329 parser.addPositionalArgument(QStringLiteral("nonportableinfo"), description: QCoreApplication::translate(context: "solid-hardware", key: "Show non portable information"));
330 parser.process(app);
331 args = parser.positionalArguments();
332 QByteArray extra(args.count() == 2 ? args.at(i: 1).toLocal8Bit() : QByteArray());
333 return app.hwList(interfaces: extra == "details", system: extra == "nonportableinfo");
334 } else if (command == QLatin1String("details")) {
335 const QString udi = getUdiFromArguments(app, parser);
336 return app.hwCapabilities(udi);
337 } else if (command == QLatin1String("nonportableinfo")) {
338 const QString udi = getUdiFromArguments(app, parser);
339 return app.hwProperties(udi);
340 } else if (command == QLatin1String("query")) {
341 parser.addPositionalArgument(QStringLiteral("udi"), description: QCoreApplication::translate(context: "solid-hardware", key: "Device udi"));
342 parser.addPositionalArgument(QStringLiteral("parent"), description: QCoreApplication::translate(context: "solid-hardware", key: "Parent device udi"));
343 parser.process(app);
344 if (parser.positionalArguments().count() < 2 || parser.positionalArguments().count() > 3) {
345 parser.showHelp(exitCode: 1);
346 }
347
348 QString query = args.at(i: 1);
349 QString parent;
350
351 if (args.count() == 3) {
352 parent = args.at(i: 2);
353 }
354
355 return app.hwQuery(parentUdi: parent, query);
356 } else if (command == QLatin1String("mount")) {
357 const QString udi = getUdiFromArguments(app, parser);
358 return app.hwVolumeCall(type: SolidHardware::Mount, udi);
359 } else if (command == QLatin1String("unmount")) {
360 const QString udi = getUdiFromArguments(app, parser);
361 return app.hwVolumeCall(type: SolidHardware::Unmount, udi);
362 } else if (command == QLatin1String("eject")) {
363 const QString udi = getUdiFromArguments(app, parser);
364 return app.hwVolumeCall(type: SolidHardware::Eject, udi);
365 } else if (command == QLatin1String("listen")) {
366 return app.listen();
367 } else if (command == QLatin1String("monitor")) {
368 const QString udi = getUdiFromArguments(app, parser);
369 return app.monitor(udi);
370 } else if (command == QLatin1String("CanCheck")) {
371 const QString udi = getUdiFromArguments(app, parser);
372 return app.hwVolumeCall(type: SolidHardware::CanCheck, udi);
373 } else if (command == QLatin1String("Check")) {
374 const QString udi = getUdiFromArguments(app, parser);
375 return app.hwVolumeCall(type: SolidHardware::Check, udi);
376 } else if (command == QLatin1String("CanRepair")) {
377 const QString udi = getUdiFromArguments(app, parser);
378 return app.hwVolumeCall(type: SolidHardware::CanRepair, udi);
379 } else if (command == QLatin1String("Repair")) {
380 const QString udi = getUdiFromArguments(app, parser);
381 return app.hwVolumeCall(type: SolidHardware::Repair, udi);
382 }
383
384 cerr << QCoreApplication::translate(context: "solid-hardware", key: "Syntax Error: Unknown command '%1'").arg(a: command) << endl;
385
386 return 1;
387}
388
389bool SolidHardware::hwList(bool interfaces, bool system)
390{
391 const QList<Solid::Device> all = Solid::Device::allDevices();
392
393 for (const Solid::Device &device : all) {
394 cout << "udi = '" << device.udi() << "'" << endl;
395
396 if (interfaces) {
397 cout << device << endl;
398 } else if (system && device.is<Solid::GenericInterface>()) {
399 QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
400 cout << properties << endl;
401 }
402 }
403
404 return true;
405}
406
407bool SolidHardware::hwCapabilities(const QString &udi)
408{
409 const Solid::Device device(udi);
410
411 cout << "udi = '" << device.udi() << "'" << endl;
412 cout << device << endl;
413
414 return true;
415}
416
417bool SolidHardware::hwProperties(const QString &udi)
418{
419 const Solid::Device device(udi);
420
421 cout << "udi = '" << device.udi() << "'" << endl;
422 if (device.is<Solid::GenericInterface>()) {
423 QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
424 cout << properties << endl;
425 }
426
427 return true;
428}
429
430bool SolidHardware::hwQuery(const QString &parentUdi, const QString &query)
431{
432 const QList<Solid::Device> devices = Solid::Device::listFromQuery(predicate: query, parentUdi);
433
434 for (const Solid::Device &device : devices) {
435 cout << "udi = '" << device.udi() << "'" << endl;
436 }
437
438 return true;
439}
440
441bool SolidHardware::hwVolumeCall(SolidHardware::VolumeCallType type, const QString &udi)
442{
443 Solid::Device device(udi);
444
445 if (!device.is<Solid::StorageAccess>() && type != Eject) {
446 cerr << tr(s: "Error: %1 does not have the interface StorageAccess.").arg(a: udi) << endl;
447 return false;
448 } else if (!device.is<Solid::OpticalDrive>() && type == Eject) {
449 cerr << tr(s: "Error: %1 does not have the interface OpticalDrive.").arg(a: udi) << endl;
450 return false;
451 }
452
453 switch (type) {
454 case Mount:
455 connect(sender: device.as<Solid::StorageAccess>(),
456 SIGNAL(setupDone(Solid::ErrorType, QVariant, QString)),
457 receiver: this,
458 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
459 device.as<Solid::StorageAccess>()->setup();
460 break;
461 case Unmount:
462 connect(sender: device.as<Solid::StorageAccess>(),
463 SIGNAL(teardownDone(Solid::ErrorType, QVariant, QString)),
464 receiver: this,
465 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
466 device.as<Solid::StorageAccess>()->teardown();
467 break;
468 case Eject:
469 connect(sender: device.as<Solid::OpticalDrive>(),
470 SIGNAL(ejectDone(Solid::ErrorType, QVariant, QString)),
471 receiver: this,
472 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
473 device.as<Solid::OpticalDrive>()->eject();
474 break;
475 case CanCheck:
476 cout << tr(s: "Device CanCheck: %1").arg(a: device.as<Solid::StorageAccess>()->canCheck() == 0 ? tr(s: "no") : tr(s: "yes")) << endl;
477 cout << "udi = '" << udi << "'" << endl;
478 return true;
479 case Check:
480 if (device.as<Solid::StorageAccess>()->canCheck()) {
481 connect(sender: device.as<Solid::StorageAccess>(),
482 SIGNAL(checkDone(Solid::ErrorType, QVariant, QString)),
483 receiver: this,
484 SLOT(slotStorageCheckResult(Solid::ErrorType, QVariant, QString)));
485 device.as<Solid::StorageAccess>()->check();
486 } else {
487 cout << tr(s: "Device Check: operation is not supported") << endl;
488 }
489 break;
490 case CanRepair:
491 cout << tr(s: "Device CanRepair: %1").arg(a: device.as<Solid::StorageAccess>()->canRepair() == 0 ? tr(s: "no") : tr(s: "yes")) << endl;
492 cout << "udi = '" << udi << "'" << endl;
493 return true;
494 case Repair:
495 connect(sender: device.as<Solid::StorageAccess>(),
496 SIGNAL(repairDone(Solid::ErrorType, QVariant, QString)),
497 receiver: this,
498 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
499 device.as<Solid::StorageAccess>()->repair();
500 break;
501 }
502
503 m_loop.exec();
504
505 if (m_error) {
506 cerr << tr(s: "Error: %1").arg(a: m_errorString) << endl;
507 return false;
508 }
509
510 return true;
511}
512
513bool SolidHardware::listen()
514{
515 Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
516 bool a = connect(sender: notifier, SIGNAL(deviceAdded(QString)), receiver: this, SLOT(deviceAdded(QString)));
517 bool d = connect(sender: notifier, SIGNAL(deviceRemoved(QString)), receiver: this, SLOT(deviceRemoved(QString)));
518
519 if (!a || !d) {
520 return false;
521 }
522
523 cout << "Listening to add/remove events: " << endl;
524 m_loop.exec();
525 return true;
526}
527
528bool SolidHardware::monitor(const QString &udi)
529{
530 Solid::Device device(udi);
531
532 if (!device.is<Solid::GenericInterface>())
533 return false;
534
535 auto genericInterface = device.as<Solid::GenericInterface>();
536
537 cout << "udi = '" << device.udi() << "'" << endl;
538 cout << genericInterface->allProperties();
539
540 connect(sender: genericInterface, signal: &Solid::GenericInterface::propertyChanged,
541 context: this, slot: [genericInterface](const auto &changes) {
542 cout << endl;
543 for (auto it = changes.begin(); it != changes.end(); ++it) {
544 cout << " " << it.key() << " = " << genericInterface->property(key: it.key()) << endl;
545 }
546 });
547
548 m_loop.exec();
549 return true;
550}
551
552void SolidHardware::deviceAdded(const QString &udi)
553{
554 cout << "Device Added:" << endl;
555 cout << "udi = '" << udi << "'" << endl;
556}
557
558void SolidHardware::deviceRemoved(const QString &udi)
559{
560 cout << "Device Removed:" << endl;
561 cout << "udi = '" << udi << "'" << endl;
562}
563
564void SolidHardware::slotStorageResult(Solid::ErrorType error, const QVariant &errorData)
565{
566 if (error) {
567 m_error = 1;
568 m_errorString = errorData.toString();
569 }
570 m_loop.exit();
571}
572
573void SolidHardware::slotStorageCheckResult(Solid::ErrorType error, const QVariant &errorData, const QString &udi)
574{
575 slotStorageResult(error, errorData);
576 if (error == Solid::NoError) {
577 cout << tr(s: "Device check: %1").arg(a: errorData.toBool() ? tr(s: "no error") : tr(s: "has error")) << endl;
578 cout << "udi = '" << udi << "'" << endl;
579 }
580}
581
582#include "moc_solid-hardware.cpp"
583

source code of solid/src/tools/solid-hardware/solid-hardware.cpp