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