File indexing completed on 2024-05-05 04:01:21

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2006 Kevin Ottens <ervin@kde.org>
0003     SPDX-FileCopyrightText: 2014 Alejandro Fiestas Olivares <afiestas@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.1-or-later
0006 */
0007 
0008 #include "solid-hardware.h"
0009 
0010 #if defined QT_DBUS_LIB
0011 #include <QDBusArgument>
0012 #include <QDBusObjectPath>
0013 #endif
0014 #include <QMetaEnum>
0015 #include <QMetaProperty>
0016 #include <QString>
0017 #include <QStringList>
0018 #include <QTextStream>
0019 
0020 #include <QCommandLineParser>
0021 
0022 #include <solid/device.h>
0023 #include <solid/genericinterface.h>
0024 #include <solid/opticaldrive.h>
0025 
0026 #include <iostream>
0027 #include <solid/devicenotifier.h>
0028 using namespace std;
0029 
0030 static const char appName[] = "solid-hardware";
0031 
0032 static const char version[] = "0.1a";
0033 
0034 std::ostream &operator<<(std::ostream &out, const QString &msg)
0035 {
0036     return (out << msg.toLocal8Bit().constData());
0037 }
0038 
0039 std::ostream &operator<<(std::ostream &out, const QVariant &value);
0040 #if defined QT_DBUS_LIB
0041 std::ostream &operator<<(std::ostream &out, const QDBusArgument &arg)
0042 {
0043     auto type = arg.currentType();
0044     switch (type) {
0045     case QDBusArgument::ArrayType:
0046         out << " { ";
0047         arg.beginArray();
0048         while (!arg.atEnd()) {
0049             out << arg;
0050             if (!arg.atEnd()) {
0051                 out << ", ";
0052             }
0053         }
0054         arg.endArray();
0055         out << " }";
0056         break;
0057     case QDBusArgument::StructureType:
0058         out << " ( ";
0059         arg.beginStructure();
0060         while (!arg.atEnd()) {
0061             out << arg.asVariant();
0062         }
0063         arg.endStructure();
0064         out << " )";
0065         break;
0066     case QDBusArgument::MapType:
0067         out << " [ ";
0068         arg.beginMap();
0069         if (!arg.atEnd()) {
0070             out << arg;
0071         }
0072         arg.endMap();
0073         out << " ]";
0074         break;
0075     case QDBusArgument::MapEntryType:
0076         arg.beginMapEntry();
0077         out << arg.asVariant() << " = " << arg;
0078         arg.endMapEntry();
0079         break;
0080     case QDBusArgument::UnknownType:
0081         out << "(unknown DBus type)";
0082         break;
0083     case QDBusArgument::BasicType:
0084     case QDBusArgument::VariantType:
0085         out << arg.asVariant();
0086     }
0087     return out;
0088 }
0089 #endif
0090 
0091 std::ostream &operator<<(std::ostream &out, const QVariant &value)
0092 {
0093     switch (value.userType()) {
0094     case QMetaType::QStringList: {
0095         out << "{";
0096 
0097         const QStringList list = value.toStringList();
0098 
0099         QStringList::ConstIterator it = list.constBegin();
0100         QStringList::ConstIterator end = list.constEnd();
0101 
0102         for (; it != end; ++it) {
0103             out << "'" << *it << "'";
0104 
0105             if (it + 1 != end) {
0106                 out << ", ";
0107             }
0108         }
0109 
0110         out << "} (string list)";
0111         break;
0112     }
0113     case QMetaType::QString:
0114         out << "'" << value.toString() << "' (string)";
0115         break;
0116     case QMetaType::Bool:
0117         out << (value.toBool() ? "true" : "false") << " (bool)";
0118         break;
0119     case QMetaType::Int:
0120     case QMetaType::LongLong:
0121         out << value.toString() << "  (0x" << QString::number(value.toLongLong(), 16) << ")  (" << value.typeName() << ")";
0122         break;
0123     case QMetaType::UInt:
0124     case QMetaType::ULongLong:
0125         out << value.toString() << "  (0x" << QString::number(value.toULongLong(), 16) << ")  (" << value.typeName() << ")";
0126         break;
0127     case QMetaType::Double:
0128         out << value.toString() << " (double)";
0129         break;
0130     case QMetaType::QByteArray:
0131         out << "'" << value.toString() << "' (bytes)";
0132         break;
0133     case QMetaType::User:
0134         // qDebug() << "got variant type:" << value.typeName();
0135         if (value.canConvert<QList<int>>()) {
0136             const QList<int> intlist = value.value<QList<int>>();
0137             QStringList tmp;
0138             for (const int val : intlist) {
0139                 tmp.append(QString::number(val));
0140             }
0141             out << "{" << tmp.join(",") << "} (int list)";
0142 #if defined QT_DBUS_LIB
0143         } else if (value.canConvert<QDBusObjectPath>()) {
0144             out << value.value<QDBusObjectPath>().path() << " (ObjectPath)";
0145         } else if (value.canConvert<QDBusVariant>()) {
0146             out << value.value<QDBusVariant>().variant() << "(Variant)";
0147         } else if (value.canConvert<QDBusArgument>()) {
0148             out << value.value<QDBusArgument>();
0149 #endif
0150         } else {
0151             out << value.toString() << " (unhandled)";
0152         }
0153 
0154         break;
0155     default:
0156         out << "'" << value.toString() << "' (" << value.typeName() << ")";
0157         break;
0158     }
0159 
0160     return out;
0161 }
0162 
0163 std::ostream &operator<<(std::ostream &out, const Solid::Device &device)
0164 {
0165     out << "  parent = " << QVariant(device.parentUdi()) << endl;
0166     out << "  vendor = " << QVariant(device.vendor()) << endl;
0167     out << "  product = " << QVariant(device.product()) << endl;
0168     out << "  description = " << QVariant(device.description()) << endl;
0169     out << "  icon = " << QVariant(device.icon()) << endl;
0170 
0171     int index = Solid::DeviceInterface::staticMetaObject.indexOfEnumerator("Type");
0172     QMetaEnum typeEnum = Solid::DeviceInterface::staticMetaObject.enumerator(index);
0173 
0174     for (int i = 0; i < typeEnum.keyCount(); i++) {
0175         Solid::DeviceInterface::Type type = (Solid::DeviceInterface::Type)typeEnum.value(i);
0176         const Solid::DeviceInterface *interface = device.asDeviceInterface(type);
0177 
0178         if (interface) {
0179             const QMetaObject *meta = interface->metaObject();
0180 
0181             for (int i = meta->propertyOffset(); i < meta->propertyCount(); i++) {
0182                 QMetaProperty property = meta->property(i);
0183                 out << "  " << QString(meta->className()).mid(7) << "." << property.name() << " = ";
0184 
0185                 QVariant value = property.read(interface);
0186 
0187                 if (property.isEnumType()) {
0188                     QMetaEnum metaEnum = property.enumerator();
0189                     if (metaEnum.isFlag()) {
0190                         out << "'" << metaEnum.valueToKeys(value.toInt()).constData() << "'"
0191                             << "  (0x" << QString::number(value.toInt(), 16) << ")  (flag)";
0192                     } else {
0193                         out << "'" << metaEnum.valueToKey(value.toInt()) << "'"
0194                             << "  (0x" << QString::number(value.toInt(), 16) << ")  (enum)";
0195                     }
0196                     out << endl;
0197                 } else {
0198                     out << value << endl;
0199                 }
0200             }
0201         }
0202     }
0203 
0204     return out;
0205 }
0206 
0207 std::ostream &operator<<(std::ostream &out, const QMap<QString, QVariant> &properties)
0208 {
0209     for (auto it = properties.cbegin(); it != properties.cend(); ++it) {
0210         out << "  " << it.key() << " = " << it.value() << endl;
0211     }
0212 
0213     return out;
0214 }
0215 
0216 QString getUdiFromArguments(QCoreApplication &app, QCommandLineParser &parser)
0217 {
0218     parser.addPositionalArgument("udi", QCoreApplication::translate("solid-hardware", "Device udi"));
0219     parser.process(app);
0220     if (parser.positionalArguments().count() < 2) {
0221         parser.showHelp(1);
0222     }
0223     return parser.positionalArguments().at(1);
0224 }
0225 
0226 static QString commandsHelp()
0227 {
0228     QString data;
0229     QTextStream cout(&data);
0230     cout << '\n' << QCoreApplication::translate("solid-hardware", "Syntax:") << '\n' << '\n';
0231 
0232     cout << "  solid-hardware list [details|nonportableinfo]" << '\n';
0233     cout << QCoreApplication::translate("solid-hardware",
0234                                         "             # List the hardware available in the system.\n"
0235                                         "             # - If the 'nonportableinfo' option is specified, the device\n"
0236                                         "             # properties are listed (be careful, in this case property names\n"
0237                                         "             # are backend dependent),\n"
0238                                         "             # - If the 'details' option is specified, the device interfaces\n"
0239                                         "             # and the corresponding properties are listed in a platform\n"
0240                                         "             # neutral fashion,\n"
0241                                         "             # - Otherwise only device UDIs are listed.\n")
0242          << '\n';
0243 
0244     cout << "  solid-hardware details 'udi'" << '\n';
0245     cout << QCoreApplication::translate("solid-hardware",
0246                                         "             # Display all the interfaces and properties of the device\n"
0247                                         "             # corresponding to 'udi' in a platform neutral fashion.\n")
0248          << '\n';
0249 
0250     cout << "  solid-hardware nonportableinfo 'udi'" << '\n';
0251     cout << QCoreApplication::translate("solid-hardware",
0252                                         "             # Display all the properties of the device corresponding to 'udi'\n"
0253                                         "             # (be careful, in this case property names are backend dependent).\n")
0254          << '\n';
0255 
0256     cout << "  solid-hardware query 'predicate' ['parentUdi']" << '\n';
0257     cout << QCoreApplication::translate("solid-hardware",
0258                                         "             # List the UDI of devices corresponding to 'predicate'.\n"
0259                                         "             # - If 'parentUdi' is specified, the search is restricted to the\n"
0260                                         "             # branch of the corresponding device,\n"
0261                                         "             # - Otherwise the search is done on all the devices.\n")
0262          << '\n';
0263 
0264     cout << "  solid-hardware mount 'udi'" << '\n';
0265     cout << QCoreApplication::translate("solid-hardware", "             # If applicable, mount the device corresponding to 'udi'.\n") << '\n';
0266 
0267     cout << "  solid-hardware unmount 'udi'" << '\n';
0268     cout << QCoreApplication::translate("solid-hardware", "             # If applicable, unmount the device corresponding to 'udi'.\n") << '\n';
0269 
0270     cout << "  solid-hardware eject 'udi'" << '\n';
0271     cout << QCoreApplication::translate("solid-hardware", "             # If applicable, eject the device corresponding to 'udi'.\n") << '\n';
0272 
0273     cout << "  solid-hardware listen" << '\n';
0274     cout << QCoreApplication::translate("solid-hardware", "             # Listen to all add/remove events on supported hardware.\n") << '\n';
0275 
0276     cout << "  solid-hardware monitor 'udi'" << '\n';
0277     cout << QCoreApplication::translate("solid-hardware", "             # Monitor devices for changes.\n");
0278 
0279     return data;
0280 }
0281 
0282 int main(int argc, char **argv)
0283 {
0284     SolidHardware app(argc, argv);
0285     app.setApplicationName(appName);
0286     app.setApplicationVersion(version);
0287 
0288     QCommandLineParser parser;
0289     parser.setApplicationDescription(QCoreApplication::translate("solid-hardware", "KDE tool for querying your hardware from the command line"));
0290     parser.addHelpOption();
0291     parser.addVersionOption();
0292     parser.addPositionalArgument("command", QCoreApplication::translate("solid-hardware", "Command to execute"), commandsHelp());
0293 
0294     QCommandLineOption commands("commands", QCoreApplication::translate("solid-hardware", "Show available commands"));
0295     // --commands only for backwards compat, it's now in the "syntax help"
0296     // of the positional argument.
0297     commands.setFlags(QCommandLineOption::HiddenFromHelp);
0298     parser.addOption(commands);
0299 
0300     parser.process(app);
0301     if (parser.isSet(commands)) {
0302         cout << commandsHelp() << endl;
0303         return 0;
0304     }
0305 
0306     QStringList args = parser.positionalArguments();
0307     if (args.count() < 1) {
0308         parser.showHelp(1);
0309     }
0310 
0311     parser.clearPositionalArguments();
0312 
0313     QString command(args.at(0));
0314 
0315     if (command == "list") {
0316         parser.addPositionalArgument("details", QCoreApplication::translate("solid-hardware", "Show device details"));
0317         parser.addPositionalArgument("nonportableinfo", QCoreApplication::translate("solid-hardware", "Show non portable information"));
0318         parser.process(app);
0319         args = parser.positionalArguments();
0320         QByteArray extra(args.count() == 2 ? args.at(1).toLocal8Bit() : QByteArray());
0321         return app.hwList(extra == "details", extra == "nonportableinfo");
0322     } else if (command == "details") {
0323         const QString udi = getUdiFromArguments(app, parser);
0324         return app.hwCapabilities(udi);
0325     } else if (command == "nonportableinfo") {
0326         const QString udi = getUdiFromArguments(app, parser);
0327         return app.hwProperties(udi);
0328     } else if (command == "query") {
0329         parser.addPositionalArgument("udi", QCoreApplication::translate("solid-hardware", "Device udi"));
0330         parser.addPositionalArgument("parent", QCoreApplication::translate("solid-hardware", "Parent device udi"));
0331         parser.process(app);
0332         if (parser.positionalArguments().count() < 2 || parser.positionalArguments().count() > 3) {
0333             parser.showHelp(1);
0334         }
0335 
0336         QString query = args.at(1);
0337         QString parent;
0338 
0339         if (args.count() == 3) {
0340             parent = args.at(2);
0341         }
0342 
0343         return app.hwQuery(parent, query);
0344     } else if (command == "mount") {
0345         const QString udi = getUdiFromArguments(app, parser);
0346         return app.hwVolumeCall(SolidHardware::Mount, udi);
0347     } else if (command == "unmount") {
0348         const QString udi = getUdiFromArguments(app, parser);
0349         return app.hwVolumeCall(SolidHardware::Unmount, udi);
0350     } else if (command == "eject") {
0351         const QString udi = getUdiFromArguments(app, parser);
0352         return app.hwVolumeCall(SolidHardware::Eject, udi);
0353     } else if (command == "listen") {
0354         return app.listen();
0355     } else if (command == "monitor") {
0356         const QString udi = getUdiFromArguments(app, parser);
0357         return app.monitor(udi);
0358     }
0359 
0360     cerr << QCoreApplication::translate("solid-hardware", "Syntax Error: Unknown command '%1'").arg(command) << endl;
0361 
0362     return 1;
0363 }
0364 
0365 bool SolidHardware::hwList(bool interfaces, bool system)
0366 {
0367     const QList<Solid::Device> all = Solid::Device::allDevices();
0368 
0369     for (const Solid::Device &device : all) {
0370         cout << "udi = '" << device.udi() << "'" << endl;
0371 
0372         if (interfaces) {
0373             cout << device << endl;
0374         } else if (system && device.is<Solid::GenericInterface>()) {
0375             QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
0376             cout << properties << endl;
0377         }
0378     }
0379 
0380     return true;
0381 }
0382 
0383 bool SolidHardware::hwCapabilities(const QString &udi)
0384 {
0385     const Solid::Device device(udi);
0386 
0387     cout << "udi = '" << device.udi() << "'" << endl;
0388     cout << device << endl;
0389 
0390     return true;
0391 }
0392 
0393 bool SolidHardware::hwProperties(const QString &udi)
0394 {
0395     const Solid::Device device(udi);
0396 
0397     cout << "udi = '" << device.udi() << "'" << endl;
0398     if (device.is<Solid::GenericInterface>()) {
0399         QMap<QString, QVariant> properties = device.as<Solid::GenericInterface>()->allProperties();
0400         cout << properties << endl;
0401     }
0402 
0403     return true;
0404 }
0405 
0406 bool SolidHardware::hwQuery(const QString &parentUdi, const QString &query)
0407 {
0408     const QList<Solid::Device> devices = Solid::Device::listFromQuery(query, parentUdi);
0409 
0410     for (const Solid::Device &device : devices) {
0411         cout << "udi = '" << device.udi() << "'" << endl;
0412     }
0413 
0414     return true;
0415 }
0416 
0417 bool SolidHardware::hwVolumeCall(SolidHardware::VolumeCallType type, const QString &udi)
0418 {
0419     Solid::Device device(udi);
0420 
0421     if (!device.is<Solid::StorageAccess>() && type != Eject) {
0422         cerr << tr("Error: %1 does not have the interface StorageAccess.").arg(udi) << endl;
0423         return false;
0424     } else if (!device.is<Solid::OpticalDrive>() && type == Eject) {
0425         cerr << tr("Error: %1 does not have the interface OpticalDrive.").arg(udi) << endl;
0426         return false;
0427     }
0428 
0429     switch (type) {
0430     case Mount:
0431         connect(device.as<Solid::StorageAccess>(),
0432                 SIGNAL(setupDone(Solid::ErrorType, QVariant, QString)),
0433                 this,
0434                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
0435         device.as<Solid::StorageAccess>()->setup();
0436         break;
0437     case Unmount:
0438         connect(device.as<Solid::StorageAccess>(),
0439                 SIGNAL(teardownDone(Solid::ErrorType, QVariant, QString)),
0440                 this,
0441                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
0442         device.as<Solid::StorageAccess>()->teardown();
0443         break;
0444     case Eject:
0445         connect(device.as<Solid::OpticalDrive>(),
0446                 SIGNAL(ejectDone(Solid::ErrorType, QVariant, QString)),
0447                 this,
0448                 SLOT(slotStorageResult(Solid::ErrorType, QVariant)));
0449         device.as<Solid::OpticalDrive>()->eject();
0450         break;
0451     }
0452 
0453     m_loop.exec();
0454 
0455     if (m_error) {
0456         cerr << tr("Error: %1").arg(m_errorString) << endl;
0457         return false;
0458     }
0459 
0460     return true;
0461 }
0462 
0463 bool SolidHardware::listen()
0464 {
0465     Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance();
0466     bool a = connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString)));
0467     bool d = connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString)));
0468 
0469     if (!a || !d) {
0470         return false;
0471     }
0472 
0473     cout << "Listening to add/remove events: " << endl;
0474     m_loop.exec();
0475     return true;
0476 }
0477 
0478 bool SolidHardware::monitor(const QString &udi)
0479 {
0480     Solid::Device device(udi);
0481 
0482     if (!device.is<Solid::GenericInterface>())
0483     return false;
0484 
0485     auto genericInterface = device.as<Solid::GenericInterface>();
0486 
0487     cout << "udi = '" << device.udi() << "'" << endl;
0488     cout << genericInterface->allProperties();
0489 
0490     connect(genericInterface, &Solid::GenericInterface::propertyChanged,
0491             this, [genericInterface](const auto &changes) {
0492     cout << endl;
0493     for (auto it = changes.begin(); it != changes.end(); ++it) {
0494             cout << "  " << it.key() << " =  " << genericInterface->property(it.key()) << endl;
0495     }
0496     });
0497 
0498     m_loop.exec();
0499     return true;
0500 }
0501 
0502 void SolidHardware::deviceAdded(const QString &udi)
0503 {
0504     cout << "Device Added:" << endl;
0505     cout << "udi = '" << udi << "'" << endl;
0506 }
0507 
0508 void SolidHardware::deviceRemoved(const QString &udi)
0509 {
0510     cout << "Device Removed:" << endl;
0511     cout << "udi = '" << udi << "'" << endl;
0512 }
0513 
0514 void SolidHardware::slotStorageResult(Solid::ErrorType error, const QVariant &errorData)
0515 {
0516     if (error) {
0517         m_error = 1;
0518         m_errorString = errorData.toString();
0519     }
0520     m_loop.exit();
0521 }
0522 
0523 #include "moc_solid-hardware.cpp"