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#if defined QT_DBUS_LIB
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 char appName[] = "solid-hardware";
31
32static const char version[] = "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#if defined QT_DBUS_LIB
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(sep: ",") << "} (int list)";
142#if defined QT_DBUS_LIB
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 << " " << QString(meta->className()).mid(position: 7) << "." << 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(name: "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(name: "command", description: QCoreApplication::translate(context: "solid-hardware", key: "Command to execute"), syntax: commandsHelp());
305
306 QCommandLineOption commands("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 == "list") {
328 parser.addPositionalArgument(name: "details", description: QCoreApplication::translate(context: "solid-hardware", key: "Show device details"));
329 parser.addPositionalArgument(name: "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 == "details") {
335 const QString udi = getUdiFromArguments(app, parser);
336 return app.hwCapabilities(udi);
337 } else if (command == "nonportableinfo") {
338 const QString udi = getUdiFromArguments(app, parser);
339 return app.hwProperties(udi);
340 } else if (command == "query") {
341 parser.addPositionalArgument(name: "udi", description: QCoreApplication::translate(context: "solid-hardware", key: "Device udi"));
342 parser.addPositionalArgument(name: "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 == "mount") {
357 const QString udi = getUdiFromArguments(app, parser);
358 return app.hwVolumeCall(type: SolidHardware::Mount, udi);
359 } else if (command == "unmount") {
360 const QString udi = getUdiFromArguments(app, parser);
361 return app.hwVolumeCall(type: SolidHardware::Unmount, udi);
362 } else if (command == "eject") {
363 const QString udi = getUdiFromArguments(app, parser);
364 return app.hwVolumeCall(type: SolidHardware::Eject, udi);
365 } else if (command == "listen") {
366 return app.listen();
367 } else if (command == "monitor") {
368 const QString udi = getUdiFromArguments(app, parser);
369 return app.monitor(udi);
370 } else if (command == "CanCheck") {
371 const QString udi = getUdiFromArguments(app, parser);
372 return app.hwVolumeCall(type: SolidHardware::CanCheck, udi);
373 } else if (command == "Check") {
374 const QString udi = getUdiFromArguments(app, parser);
375 return app.hwVolumeCall(type: SolidHardware::Check, udi);
376 } else if (command == "CanRepair") {
377 const QString udi = getUdiFromArguments(app, parser);
378 return app.hwVolumeCall(type: SolidHardware::CanRepair, udi);
379 } else if (command == "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 cout << tr(s: "Device Check: %1").arg(a: device.as<Solid::StorageAccess>()->check() == 0 ? tr(s: "has error") : tr(s: "no error")) << endl;
482 } else {
483 cout << tr(s: "Device Check: operation is not supported") << endl;
484 }
485 cout << "udi = '" << udi << "'" << endl;
486 return true;
487 case CanRepair:
488 cout << tr(s: "Device CanRepair: %1").arg(a: device.as<Solid::StorageAccess>()->canRepair() == 0 ? tr(s: "no") : tr(s: "yes")) << endl;
489 cout << "udi = '" << udi << "'" << endl;
490 return true;
491 case Repair:
492 connect(sender: device.as<Solid::StorageAccess>(),
493 SIGNAL(repairDone(Solid::ErrorType, QVariant, QString)),
494 receiver: this,
495 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
496 device.as<Solid::StorageAccess>()->repair();
497 break;
498 }
499
500 m_loop.exec();
501
502 if (m_error) {
503 cerr << tr(s: "Error: %1").arg(a: m_errorString) << endl;
504 return false;
505 }
506
507 return true;
508}
509
510bool SolidHardware::listen()
511{
512 Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
513 bool a = connect(sender: notifier, SIGNAL(deviceAdded(QString)), receiver: this, SLOT(deviceAdded(QString)));
514 bool d = connect(sender: notifier, SIGNAL(deviceRemoved(QString)), receiver: this, SLOT(deviceRemoved(QString)));
515
516 if (!a || !d) {
517 return false;
518 }
519
520 cout << "Listening to add/remove events: " << endl;
521 m_loop.exec();
522 return true;
523}
524
525bool SolidHardware::monitor(const QString &udi)
526{
527 Solid::Device device(udi);
528
529 if (!device.is<Solid::GenericInterface>())
530 return false;
531
532 auto genericInterface = device.as<Solid::GenericInterface>();
533
534 cout << "udi = '" << device.udi() << "'" << endl;
535 cout << genericInterface->allProperties();
536
537 connect(sender: genericInterface, signal: &Solid::GenericInterface::propertyChanged,
538 context: this, slot: [genericInterface](const auto &changes) {
539 cout << endl;
540 for (auto it = changes.begin(); it != changes.end(); ++it) {
541 cout << " " << it.key() << " = " << genericInterface->property(key: it.key()) << endl;
542 }
543 });
544
545 m_loop.exec();
546 return true;
547}
548
549void SolidHardware::deviceAdded(const QString &udi)
550{
551 cout << "Device Added:" << endl;
552 cout << "udi = '" << udi << "'" << endl;
553}
554
555void SolidHardware::deviceRemoved(const QString &udi)
556{
557 cout << "Device Removed:" << endl;
558 cout << "udi = '" << udi << "'" << endl;
559}
560
561void SolidHardware::slotStorageResult(Solid::ErrorType error, const QVariant &errorData)
562{
563 if (error) {
564 m_error = 1;
565 m_errorString = errorData.toString();
566 }
567 m_loop.exit();
568}
569
570#include "moc_solid-hardware.cpp"
571

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