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>
28 using namespace std;
29 
30 static const char appName[] = "solid-hardware";
31 
32 static const char version[] = "0.1a";
33 
operator <<(std::ostream & out,const QString & msg)34 std::ostream &operator<<(std::ostream &out, const QString &msg)
35 {
36     return (out << msg.toLocal8Bit().constData());
37 }
38 
39 std::ostream &operator<<(std::ostream &out, const QVariant &value);
40 #if defined QT_DBUS_LIB
operator <<(std::ostream & out,const QDBusArgument & arg)41 std::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 
operator <<(std::ostream & out,const QVariant & value)91 std::ostream &operator<<(std::ostream &out, const QVariant &value)
92 {
93     switch (value.type()) {
94     case QVariant::StringList: {
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 QVariant::String:
114         out << "'" << value.toString() << "' (string)";
115         break;
116     case QVariant::Bool:
117         out << (value.toBool() ? "true" : "false") << " (bool)";
118         break;
119     case QVariant::Int:
120     case QVariant::LongLong:
121         out << value.toString() << "  (0x" << QString::number(value.toLongLong(), 16) << ")  (" << QVariant::typeToName(value.type()) << ")";
122         break;
123     case QVariant::UInt:
124     case QVariant::ULongLong:
125         out << value.toString() << "  (0x" << QString::number(value.toULongLong(), 16) << ")  (" << QVariant::typeToName(value.type()) << ")";
126         break;
127     case QVariant::Double:
128         out << value.toString() << " (double)";
129         break;
130     case QVariant::ByteArray:
131         out << "'" << value.toString() << "' (bytes)";
132         break;
133     case QVariant::UserType:
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(QString::number(val));
140             }
141             out << "{" << tmp.join(",") << "} (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() << "' (" << QVariant::typeToName(value.type()) << ")";
157         break;
158     }
159 
160     return out;
161 }
162 
operator <<(std::ostream & out,const Solid::Device & device)163 std::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("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(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(i);
183                 out << "  " << QString(meta->className()).mid(7) << "." << property.name() << " = ";
184 
185                 QVariant value = property.read(interface);
186 
187                 if (property.isEnumType()) {
188                     QMetaEnum metaEnum = property.enumerator();
189                     if (metaEnum.isFlag()) {
190                         out << "'" << metaEnum.valueToKeys(value.toInt()).constData() << "'"
191                             << "  (0x" << QString::number(value.toInt(), 16) << ")  (flag)";
192                     } else {
193                         out << "'" << metaEnum.valueToKey(value.toInt()) << "'"
194                             << "  (0x" << QString::number(value.toInt(), 16) << ")  (enum)";
195                     }
196                     out << endl;
197                 } else {
198                     out << value << endl;
199                 }
200             }
201         }
202     }
203 
204     return out;
205 }
206 
operator <<(std::ostream & out,const QMap<QString,QVariant> & properties)207 std::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 
getUdiFromArguments(QCoreApplication & app,QCommandLineParser & parser)216 QString getUdiFromArguments(QCoreApplication &app, QCommandLineParser &parser)
217 {
218     parser.addPositionalArgument("udi", QCoreApplication::translate("solid-hardware", "Device udi"));
219     parser.process(app);
220     if (parser.positionalArguments().count() < 2) {
221         parser.showHelp(1);
222     }
223     return parser.positionalArguments().at(1);
224 }
225 
commandsHelp()226 static QString commandsHelp()
227 {
228     QString data;
229     QTextStream cout(&data);
230     cout << '\n' << QCoreApplication::translate("solid-hardware", "Syntax:") << '\n' << '\n';
231 
232     cout << "  solid-hardware list [details|nonportableinfo]" << '\n';
233     cout << QCoreApplication::translate("solid-hardware",
234                                         "             # 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("solid-hardware",
246                                         "             # 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("solid-hardware",
252                                         "             # 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("solid-hardware",
258                                         "             # 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("solid-hardware", "             # If applicable, mount the device corresponding to 'udi'.\n") << '\n';
266 
267     cout << "  solid-hardware unmount 'udi'" << '\n';
268     cout << QCoreApplication::translate("solid-hardware", "             # If applicable, unmount the device corresponding to 'udi'.\n") << '\n';
269 
270     cout << "  solid-hardware eject 'udi'" << '\n';
271     cout << QCoreApplication::translate("solid-hardware", "             # If applicable, eject the device corresponding to 'udi'.\n") << '\n';
272 
273     cout << "  solid-hardware listen" << '\n';
274     cout << QCoreApplication::translate("solid-hardware", "             # Listen to all add/remove events on supported hardware.") << '\n';
275 
276     return data;
277 }
278 
main(int argc,char ** argv)279 int main(int argc, char **argv)
280 {
281     SolidHardware app(argc, argv);
282     app.setApplicationName(appName);
283     app.setApplicationVersion(version);
284 
285     QCommandLineParser parser;
286     parser.setApplicationDescription(QCoreApplication::translate("solid-hardware", "KDE tool for querying your hardware from the command line"));
287     parser.addHelpOption();
288     parser.addVersionOption();
289     parser.addPositionalArgument("command", QCoreApplication::translate("solid-hardware", "Command to execute"), commandsHelp());
290 
291     QCommandLineOption commands("commands", QCoreApplication::translate("solid-hardware", "Show available commands"));
292     // --commands only for backwards compat, it's now in the "syntax help"
293     // of the positional argument.
294     commands.setFlags(QCommandLineOption::HiddenFromHelp);
295     parser.addOption(commands);
296 
297     parser.process(app);
298     if (parser.isSet(commands)) {
299         cout << commandsHelp() << endl;
300         return 0;
301     }
302 
303     QStringList args = parser.positionalArguments();
304     if (args.count() < 1) {
305         parser.showHelp(1);
306     }
307 
308     parser.clearPositionalArguments();
309 
310     QString command(args.at(0));
311 
312     if (command == "list") {
313         parser.addPositionalArgument("details", QCoreApplication::translate("solid-hardware", "Show device details"));
314         parser.addPositionalArgument("nonportableinfo", QCoreApplication::translate("solid-hardware", "Show non portable information"));
315         parser.process(app);
316         args = parser.positionalArguments();
317         QByteArray extra(args.count() == 2 ? args.at(1).toLocal8Bit() : "");
318         return app.hwList(extra == "details", extra == "nonportableinfo");
319     } else if (command == "details") {
320         const QString udi = getUdiFromArguments(app, parser);
321         return app.hwCapabilities(udi);
322     } else if (command == "nonportableinfo") {
323         const QString udi = getUdiFromArguments(app, parser);
324         return app.hwProperties(udi);
325     } else if (command == "query") {
326         parser.addPositionalArgument("udi", QCoreApplication::translate("solid-hardware", "Device udi"));
327         parser.addPositionalArgument("parent", QCoreApplication::translate("solid-hardware", "Parent device udi"));
328         parser.process(app);
329         if (parser.positionalArguments().count() < 2 || parser.positionalArguments().count() > 3) {
330             parser.showHelp(1);
331         }
332 
333         QString query = args.at(1);
334         QString parent;
335 
336         if (args.count() == 3) {
337             parent = args.at(2);
338         }
339 
340         return app.hwQuery(parent, query);
341     } else if (command == "mount") {
342         const QString udi = getUdiFromArguments(app, parser);
343         return app.hwVolumeCall(SolidHardware::Mount, udi);
344     } else if (command == "unmount") {
345         const QString udi = getUdiFromArguments(app, parser);
346         return app.hwVolumeCall(SolidHardware::Unmount, udi);
347     } else if (command == "eject") {
348         const QString udi = getUdiFromArguments(app, parser);
349         return app.hwVolumeCall(SolidHardware::Eject, udi);
350     } else if (command == "listen") {
351         return app.listen();
352     }
353 
354     cerr << QCoreApplication::translate("solid-hardware", "Syntax Error: Unknown command '%1'").arg(command) << endl;
355 
356     return 1;
357 }
358 
hwList(bool interfaces,bool system)359 bool SolidHardware::hwList(bool interfaces, bool system)
360 {
361     const QList<Solid::Device> all = Solid::Device::allDevices();
362 
363     for (const Solid::Device &device : all) {
364         cout << "udi = '" << device.udi() << "'" << endl;
365 
366         if (interfaces) {
367             cout << device << endl;
368         } else if (system && device.is<Solid::GenericInterface>()) {
369             QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
370             cout << properties << endl;
371         }
372     }
373 
374     return true;
375 }
376 
hwCapabilities(const QString & udi)377 bool SolidHardware::hwCapabilities(const QString &udi)
378 {
379     const Solid::Device device(udi);
380 
381     cout << "udi = '" << device.udi() << "'" << endl;
382     cout << device << endl;
383 
384     return true;
385 }
386 
hwProperties(const QString & udi)387 bool SolidHardware::hwProperties(const QString &udi)
388 {
389     const Solid::Device device(udi);
390 
391     cout << "udi = '" << device.udi() << "'" << endl;
392     if (device.is<Solid::GenericInterface>()) {
393         QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
394         cout << properties << endl;
395     }
396 
397     return true;
398 }
399 
hwQuery(const QString & parentUdi,const QString & query)400 bool SolidHardware::hwQuery(const QString &parentUdi, const QString &query)
401 {
402     const QList<Solid::Device> devices = Solid::Device::listFromQuery(query, parentUdi);
403 
404     for (const Solid::Device &device : devices) {
405         cout << "udi = '" << device.udi() << "'" << endl;
406     }
407 
408     return true;
409 }
410 
hwVolumeCall(SolidHardware::VolumeCallType type,const QString & udi)411 bool SolidHardware::hwVolumeCall(SolidHardware::VolumeCallType type, const QString &udi)
412 {
413     Solid::Device device(udi);
414 
415     if (!device.is<Solid::StorageAccess>() && type != Eject) {
416         cerr << tr("Error: %1 does not have the interface StorageAccess.").arg(udi) << endl;
417         return false;
418     } else if (!device.is<Solid::OpticalDrive>() && type == Eject) {
419         cerr << tr("Error: %1 does not have the interface OpticalDrive.").arg(udi) << endl;
420         return false;
421     }
422 
423     switch (type) {
424     case Mount:
425         connect(device.as<Solid::StorageAccess>(),
426                 SIGNAL(setupDone(Solid::ErrorType, QVariant, QString)),
427                 this,
428                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
429         device.as<Solid::StorageAccess>()->setup();
430         break;
431     case Unmount:
432         connect(device.as<Solid::StorageAccess>(),
433                 SIGNAL(teardownDone(Solid::ErrorType, QVariant, QString)),
434                 this,
435                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
436         device.as<Solid::StorageAccess>()->teardown();
437         break;
438     case Eject:
439         connect(device.as<Solid::OpticalDrive>(),
440                 SIGNAL(ejectDone(Solid::ErrorType, QVariant, QString)),
441                 this,
442                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
443         device.as<Solid::OpticalDrive>()->eject();
444         break;
445     }
446 
447     m_loop.exec();
448 
449     if (m_error) {
450         cerr << tr("Error: %1").arg(m_errorString) << endl;
451         return false;
452     }
453 
454     return true;
455 }
456 
listen()457 bool SolidHardware::listen()
458 {
459     Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
460     bool a = connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString)));
461     bool d = connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString)));
462 
463     if (!a || !d) {
464         return false;
465     }
466 
467     cout << "Listening to add/remove events: " << endl;
468     m_loop.exec();
469     return true;
470 }
471 
deviceAdded(const QString & udi)472 void SolidHardware::deviceAdded(const QString &udi)
473 {
474     cout << "Device Added:" << endl;
475     cout << "udi = '" << udi << "'" << endl;
476 }
477 
deviceRemoved(const QString & udi)478 void SolidHardware::deviceRemoved(const QString &udi)
479 {
480     cout << "Device Removed:" << endl;
481     cout << "udi = '" << udi << "'" << endl;
482 }
483 
slotStorageResult(Solid::ErrorType error,const QVariant & errorData)484 void SolidHardware::slotStorageResult(Solid::ErrorType error, const QVariant &errorData)
485 {
486     if (error) {
487         m_error = 1;
488         m_errorString = errorData.toString();
489     }
490     m_loop.exit();
491 }
492