/* This file is part of the KDE project SPDX-FileCopyrightText: 2006 Kevin Ottens SPDX-FileCopyrightText: 2014 Alejandro Fiestas Olivares SPDX-License-Identifier: LGPL-2.1-or-later */ #include "solid-hardware.h" #ifdef HAVE_DBUS #include #include #endif #include #include #include #include #include #include #include #include #include #include #include using namespace std; static const QString appName{QStringLiteral("solid-hardware")}; static const QString version{QStringLiteral("0.1a")}; std::ostream &operator<<(std::ostream &out, const QString &msg) { return (out << msg.toLocal8Bit().constData()); } std::ostream &operator<<(std::ostream &out, const QVariant &value); #ifdef HAVE_DBUS std::ostream &operator<<(std::ostream &out, const QDBusArgument &arg) { auto type = arg.currentType(); switch (type) { case QDBusArgument::ArrayType: out << " { "; arg.beginArray(); while (!arg.atEnd()) { out << arg; if (!arg.atEnd()) { out << ", "; } } arg.endArray(); out << " }"; break; case QDBusArgument::StructureType: out << " ( "; arg.beginStructure(); while (!arg.atEnd()) { out << arg.asVariant(); } arg.endStructure(); out << " )"; break; case QDBusArgument::MapType: out << " [ "; arg.beginMap(); if (!arg.atEnd()) { out << arg; } arg.endMap(); out << " ]"; break; case QDBusArgument::MapEntryType: arg.beginMapEntry(); out << arg.asVariant() << " = " << arg; arg.endMapEntry(); break; case QDBusArgument::UnknownType: out << "(unknown DBus type)"; break; case QDBusArgument::BasicType: case QDBusArgument::VariantType: out << arg.asVariant(); } return out; } #endif std::ostream &operator<<(std::ostream &out, const QVariant &value) { switch (value.userType()) { case QMetaType::QStringList: { out << "{"; const QStringList list = value.toStringList(); QStringList::ConstIterator it = list.constBegin(); QStringList::ConstIterator end = list.constEnd(); for (; it != end; ++it) { out << "'" << *it << "'"; if (it + 1 != end) { out << ", "; } } out << "} (string list)"; break; } case QMetaType::QString: out << "'" << value.toString() << "' (string)"; break; case QMetaType::Bool: out << (value.toBool() ? "true" : "false") << " (bool)"; break; case QMetaType::Int: case QMetaType::LongLong: out << value.toString() << " (0x" << QString::number(value.toLongLong(), 16) << ") (" << value.typeName() << ")"; break; case QMetaType::UInt: case QMetaType::ULongLong: out << value.toString() << " (0x" << QString::number(value.toULongLong(), 16) << ") (" << value.typeName() << ")"; break; case QMetaType::Double: out << value.toString() << " (double)"; break; case QMetaType::QByteArray: out << "'" << value.toString() << "' (bytes)"; break; case QMetaType::User: // qDebug() << "got variant type:" << value.typeName(); if (value.canConvert>()) { const QList intlist = value.value>(); QStringList tmp; for (const int val : intlist) { tmp.append(QString::number(val)); } out << "{" << tmp.join(QStringLiteral(",")) << "} (int list)"; #ifdef HAVE_DBUS } else if (value.canConvert()) { out << value.value().path() << " (ObjectPath)"; } else if (value.canConvert()) { out << value.value().variant() << "(Variant)"; } else if (value.canConvert()) { out << value.value(); #endif } else { out << value.toString() << " (unhandled)"; } break; default: out << "'" << value.toString() << "' (" << value.typeName() << ")"; break; } return out; } std::ostream &operator<<(std::ostream &out, const Solid::Device &device) { out << " parent = " << QVariant(device.parentUdi()) << endl; out << " vendor = " << QVariant(device.vendor()) << endl; out << " product = " << QVariant(device.product()) << endl; out << " description = " << QVariant(device.description()) << endl; out << " icon = " << QVariant(device.icon()) << endl; int index = Solid::DeviceInterface::staticMetaObject.indexOfEnumerator("Type"); QMetaEnum typeEnum = Solid::DeviceInterface::staticMetaObject.enumerator(index); for (int i = 0; i < typeEnum.keyCount(); i++) { Solid::DeviceInterface::Type type = (Solid::DeviceInterface::Type)typeEnum.value(i); const Solid::DeviceInterface *interface = device.asDeviceInterface(type); if (interface) { const QMetaObject *meta = interface->metaObject(); for (int i = meta->propertyOffset(); i < meta->propertyCount(); i++) { QMetaProperty property = meta->property(i); out << " " << QByteArray(meta->className()).mid(7).constData() << "." << property.name() << " = "; QVariant value = property.read(interface); if (property.isEnumType()) { QMetaEnum metaEnum = property.enumerator(); if (metaEnum.isFlag()) { out << "'" << metaEnum.valueToKeys(value.toInt()).constData() << "'" << " (0x" << QString::number(value.toInt(), 16) << ") (flag)"; } else { out << "'" << metaEnum.valueToKey(value.toInt()) << "'" << " (0x" << QString::number(value.toInt(), 16) << ") (enum)"; } out << endl; } else { out << value << endl; } } } } return out; } std::ostream &operator<<(std::ostream &out, const QMap &properties) { for (auto it = properties.cbegin(); it != properties.cend(); ++it) { out << " " << it.key() << " = " << it.value() << endl; } return out; } QString getUdiFromArguments(QCoreApplication &app, QCommandLineParser &parser) { parser.addPositionalArgument(QStringLiteral("udi"), QCoreApplication::translate("solid-hardware", "Device udi")); parser.process(app); if (parser.positionalArguments().count() < 2) { parser.showHelp(1); } return parser.positionalArguments().at(1); } static QString commandsHelp() { QString data; QTextStream cout(&data); cout << '\n' << QCoreApplication::translate("solid-hardware", "Syntax:") << '\n' << '\n'; cout << " solid-hardware list [details|nonportableinfo]" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # List the hardware available in the system.\n" " # - If the 'nonportableinfo' option is specified, the device\n" " # properties are listed (be careful, in this case property names\n" " # are backend dependent),\n" " # - If the 'details' option is specified, the device interfaces\n" " # and the corresponding properties are listed in a platform\n" " # neutral fashion,\n" " # - Otherwise only device UDIs are listed.\n") << '\n'; cout << " solid-hardware details 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Display all the interfaces and properties of the device\n" " # corresponding to 'udi' in a platform neutral fashion.\n") << '\n'; cout << " solid-hardware nonportableinfo 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Display all the properties of the device corresponding to 'udi'\n" " # (be careful, in this case property names are backend dependent).\n") << '\n'; cout << " solid-hardware query 'predicate' ['parentUdi']" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # List the UDI of devices corresponding to 'predicate'.\n" " # - If 'parentUdi' is specified, the search is restricted to the\n" " # branch of the corresponding device,\n" " # - Otherwise the search is done on all the devices.\n") << '\n'; cout << " solid-hardware mount 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # If applicable, mount the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware unmount 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # If applicable, unmount the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware eject 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # If applicable, eject the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware listen" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Listen to all add/remove events on supported hardware.\n") << '\n'; cout << " solid-hardware monitor 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Monitor devices for changes.\n") << '\n'; cout << " solid-hardware CanCheck 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Send \"CanCheck\" request to the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware Check 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Send \"Check\" request to the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware CanRepair 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Send \"CanRepair\" request to the device corresponding to 'udi'.\n") << '\n'; cout << " solid-hardware Repair 'udi'" << '\n'; cout << QCoreApplication::translate("solid-hardware", " # Send \"Repair\" request to the device corresponding to 'udi'.\n"); return data; } int main(int argc, char **argv) { SolidHardware app(argc, argv); app.setApplicationName(appName); app.setApplicationVersion(version); QCommandLineParser parser; parser.setApplicationDescription(QCoreApplication::translate("solid-hardware", "KDE tool for querying your hardware from the command line")); parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument(QStringLiteral("command"), QCoreApplication::translate("solid-hardware", "Command to execute"), commandsHelp()); QCommandLineOption commands(QStringLiteral("commands"), QCoreApplication::translate("solid-hardware", "Show available commands")); // --commands only for backwards compat, it's now in the "syntax help" // of the positional argument. commands.setFlags(QCommandLineOption::HiddenFromHelp); parser.addOption(commands); parser.process(app); if (parser.isSet(commands)) { cout << commandsHelp() << endl; return 0; } QStringList args = parser.positionalArguments(); if (args.count() < 1) { parser.showHelp(1); } parser.clearPositionalArguments(); QString command(args.at(0)); if (command == QLatin1String("list")) { parser.addPositionalArgument(QStringLiteral("details"), QCoreApplication::translate("solid-hardware", "Show device details")); parser.addPositionalArgument(QStringLiteral("nonportableinfo"), QCoreApplication::translate("solid-hardware", "Show non portable information")); parser.process(app); args = parser.positionalArguments(); QByteArray extra(args.count() == 2 ? args.at(1).toLocal8Bit() : QByteArray()); return app.hwList(extra == "details", extra == "nonportableinfo"); } else if (command == QLatin1String("details")) { const QString udi = getUdiFromArguments(app, parser); return app.hwCapabilities(udi); } else if (command == QLatin1String("nonportableinfo")) { const QString udi = getUdiFromArguments(app, parser); return app.hwProperties(udi); } else if (command == QLatin1String("query")) { parser.addPositionalArgument(QStringLiteral("udi"), QCoreApplication::translate("solid-hardware", "Device udi")); parser.addPositionalArgument(QStringLiteral("parent"), QCoreApplication::translate("solid-hardware", "Parent device udi")); parser.process(app); if (parser.positionalArguments().count() < 2 || parser.positionalArguments().count() > 3) { parser.showHelp(1); } QString query = args.at(1); QString parent; if (args.count() == 3) { parent = args.at(2); } return app.hwQuery(parent, query); } else if (command == QLatin1String("mount")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::Mount, udi); } else if (command == QLatin1String("unmount")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::Unmount, udi); } else if (command == QLatin1String("eject")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::Eject, udi); } else if (command == QLatin1String("listen")) { return app.listen(); } else if (command == QLatin1String("monitor")) { const QString udi = getUdiFromArguments(app, parser); return app.monitor(udi); } else if (command == QLatin1String("CanCheck")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::CanCheck, udi); } else if (command == QLatin1String("Check")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::Check, udi); } else if (command == QLatin1String("CanRepair")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::CanRepair, udi); } else if (command == QLatin1String("Repair")) { const QString udi = getUdiFromArguments(app, parser); return app.hwVolumeCall(SolidHardware::Repair, udi); } cerr << QCoreApplication::translate("solid-hardware", "Syntax Error: Unknown command '%1'").arg(command) << endl; return 1; } bool SolidHardware::hwList(bool interfaces, bool system) { const QList all = Solid::Device::allDevices(); for (const Solid::Device &device : all) { cout << "udi = '" << device.udi() << "'" << endl; if (interfaces) { cout << device << endl; } else if (system && device.is()) { QMap properties = device.as()->allProperties(); cout << properties << endl; } } return true; } bool SolidHardware::hwCapabilities(const QString &udi) { const Solid::Device device(udi); cout << "udi = '" << device.udi() << "'" << endl; cout << device << endl; return true; } bool SolidHardware::hwProperties(const QString &udi) { const Solid::Device device(udi); cout << "udi = '" << device.udi() << "'" << endl; if (device.is()) { QMap properties = device.as()->allProperties(); cout << properties << endl; } return true; } bool SolidHardware::hwQuery(const QString &parentUdi, const QString &query) { const QList devices = Solid::Device::listFromQuery(query, parentUdi); for (const Solid::Device &device : devices) { cout << "udi = '" << device.udi() << "'" << endl; } return true; } bool SolidHardware::hwVolumeCall(SolidHardware::VolumeCallType type, const QString &udi) { Solid::Device device(udi); if (!device.is() && type != Eject) { cerr << tr("Error: %1 does not have the interface StorageAccess.").arg(udi) << endl; return false; } else if (!device.is() && type == Eject) { cerr << tr("Error: %1 does not have the interface OpticalDrive.").arg(udi) << endl; return false; } switch (type) { case Mount: connect(device.as(), SIGNAL(setupDone(Solid::ErrorType, QVariant, QString)), this, SLOT(slotStorageResult(Solid::ErrorType, QVariant))); device.as()->setup(); break; case Unmount: connect(device.as(), SIGNAL(teardownDone(Solid::ErrorType, QVariant, QString)), this, SLOT(slotStorageResult(Solid::ErrorType, QVariant))); device.as()->teardown(); break; case Eject: connect(device.as(), SIGNAL(ejectDone(Solid::ErrorType, QVariant, QString)), this, SLOT(slotStorageResult(Solid::ErrorType, QVariant))); device.as()->eject(); break; case CanCheck: cout << tr("Device CanCheck: %1").arg(device.as()->canCheck() == 0 ? tr("no") : tr("yes")) << endl; cout << "udi = '" << udi << "'" << endl; return true; case Check: if (device.as()->canCheck()) { cout << tr("Device Check: %1").arg(device.as()->check() == 0 ? tr("has error") : tr("no error")) << endl; } else { cout << tr("Device Check: operation is not supported") << endl; } cout << "udi = '" << udi << "'" << endl; return true; case CanRepair: cout << tr("Device CanRepair: %1").arg(device.as()->canRepair() == 0 ? tr("no") : tr("yes")) << endl; cout << "udi = '" << udi << "'" << endl; return true; case Repair: connect(device.as(), SIGNAL(repairDone(Solid::ErrorType, QVariant, QString)), this, SLOT(slotStorageResult(Solid::ErrorType, QVariant))); device.as()->repair(); break; } m_loop.exec(); if (m_error) { cerr << tr("Error: %1").arg(m_errorString) << endl; return false; } return true; } bool SolidHardware::listen() { Solid::DeviceNotifier *notifier = Solid::DeviceNotifier::instance(); bool a = connect(notifier, SIGNAL(deviceAdded(QString)), this, SLOT(deviceAdded(QString))); bool d = connect(notifier, SIGNAL(deviceRemoved(QString)), this, SLOT(deviceRemoved(QString))); if (!a || !d) { return false; } cout << "Listening to add/remove events: " << endl; m_loop.exec(); return true; } bool SolidHardware::monitor(const QString &udi) { Solid::Device device(udi); if (!device.is()) return false; auto genericInterface = device.as(); cout << "udi = '" << device.udi() << "'" << endl; cout << genericInterface->allProperties(); connect(genericInterface, &Solid::GenericInterface::propertyChanged, this, [genericInterface](const auto &changes) { cout << endl; for (auto it = changes.begin(); it != changes.end(); ++it) { cout << " " << it.key() << " = " << genericInterface->property(it.key()) << endl; } }); m_loop.exec(); return true; } void SolidHardware::deviceAdded(const QString &udi) { cout << "Device Added:" << endl; cout << "udi = '" << udi << "'" << endl; } void SolidHardware::deviceRemoved(const QString &udi) { cout << "Device Removed:" << endl; cout << "udi = '" << udi << "'" << endl; } void SolidHardware::slotStorageResult(Solid::ErrorType error, const QVariant &errorData) { if (error) { m_error = 1; m_errorString = errorData.toString(); } m_loop.exit(); } #include "moc_solid-hardware.cpp"