/* SPDX-FileCopyrightText: 2007 Christopher Blauvelt SPDX-License-Identifier: LGPL-2.0-only */ #include "soliddeviceengine.h" #include "soliddeviceservice.h" #include #include #include #include #include #include #include #include #include // TODO: implement in libsolid2 namespace { template DevIface *getAncestorAs(const Solid::Device &device) { for (Solid::Device parent = device.parent(); parent.isValid(); parent = parent.parent()) { if (parent.is()) { return parent.as(); } } return nullptr; } } SolidDeviceEngine::SolidDeviceEngine(QObject *parent) : Plasma5Support::DataEngine(parent) , m_temperature(nullptr) , m_notifier(nullptr) { m_signalmanager = new DeviceSignalMapManager(this); listenForNewDevices(); setMinimumPollingInterval(1000); connect(this, &Plasma5Support::DataEngine::sourceRemoved, this, &SolidDeviceEngine::sourceWasRemoved); } SolidDeviceEngine::~SolidDeviceEngine() { } Plasma5Support::Service *SolidDeviceEngine::serviceForSource(const QString &source) { return new SolidDeviceService(this, source); } void SolidDeviceEngine::listenForNewDevices() { if (m_notifier) { return; } // detect when new devices are added m_notifier = Solid::DeviceNotifier::instance(); connect(m_notifier, &Solid::DeviceNotifier::deviceAdded, this, &SolidDeviceEngine::deviceAdded); connect(m_notifier, &Solid::DeviceNotifier::deviceRemoved, this, &SolidDeviceEngine::deviceRemoved); } bool SolidDeviceEngine::sourceRequestEvent(const QString &name) { if (name.startsWith('/')) { Solid::Device device = Solid::Device(name); if (device.isValid()) { if (m_devicemap.contains(name)) { return true; } else { m_devicemap[name] = device; return populateDeviceData(name); } } } else { Solid::Predicate predicate = Solid::Predicate::fromString(name); if (predicate.isValid() && !m_predicatemap.contains(name)) { for (const auto devices = Solid::Device::listFromQuery(predicate); const Solid::Device &device : devices) { m_predicatemap[name] << device.udi(); } setData(name, m_predicatemap[name]); return true; } } qDebug() << "Source is not a predicate or a device."; return false; } void SolidDeviceEngine::sourceWasRemoved(const QString &source) { m_devicemap.remove(source); m_predicatemap.remove(source); } bool SolidDeviceEngine::populateDeviceData(const QString &name) { Solid::Device device = m_devicemap.value(name); if (!device.isValid()) { return false; } QStringList devicetypes; setData(name, kli18n("Parent UDI").untranslatedText(), device.parentUdi()); setData(name, kli18n("Vendor").untranslatedText(), device.vendor()); setData(name, kli18n("Product").untranslatedText(), device.product()); setData(name, kli18n("Description").untranslatedText(), device.description()); setData(name, kli18n("Icon").untranslatedText(), device.icon()); setData(name, kli18n("Emblems").untranslatedText(), device.emblems()); setData(name, kli18n("State").untranslatedText(), Idle); setData(name, kli18n("Operation result").untranslatedText(), Working); setData(name, kli18n("Timestamp").untranslatedText(), QDateTime::currentDateTimeUtc()); if (device.is()) { Solid::Processor *processor = device.as(); if (!processor) { return false; } devicetypes << kli18n("Processor").untranslatedText(); setData(name, kli18n("Number").untranslatedText(), processor->number()); setData(name, kli18n("Max Speed").untranslatedText(), processor->maxSpeed()); setData(name, kli18n("Can Change Frequency").untranslatedText(), processor->canChangeFrequency()); } if (device.is()) { Solid::Block *block = device.as(); if (!block) { return false; } devicetypes << kli18n("Block").untranslatedText(); setData(name, kli18n("Major").untranslatedText(), block->deviceMajor()); setData(name, kli18n("Minor").untranslatedText(), block->deviceMinor()); setData(name, kli18n("Device").untranslatedText(), block->device()); } if (device.is()) { Solid::StorageAccess *storageaccess = device.as(); if (!storageaccess) { return false; } devicetypes << kli18n("Storage Access").untranslatedText(); setData(name, kli18n("Accessible").untranslatedText(), storageaccess->isAccessible()); setData(name, kli18n("File Path").untranslatedText(), storageaccess->filePath()); if (storageaccess->isAccessible()) { updateStorageSpace(name); } m_signalmanager->mapDevice(storageaccess, device.udi()); } if (device.is()) { Solid::StorageDrive *storagedrive = device.as(); if (!storagedrive) { return false; } devicetypes << kli18n("Storage Drive").untranslatedText(); QStringList bus; bus << kli18n("Ide").untranslatedText() << kli18n("Usb").untranslatedText() << kli18n("Ieee1394").untranslatedText() << kli18n("Scsi").untranslatedText() << kli18n("Sata").untranslatedText() << kli18n("Platform").untranslatedText(); QStringList drivetype; drivetype << kli18n("Hard Disk").untranslatedText() << kli18n("Cdrom Drive").untranslatedText() << kli18n("Floppy").untranslatedText() << kli18n("Tape").untranslatedText() << kli18n("Compact Flash").untranslatedText() << kli18n("Memory Stick").untranslatedText() << kli18n("Smart Media").untranslatedText() << kli18n("SdMmc").untranslatedText() << kli18n("Xd").untranslatedText(); setData(name, kli18n("Bus").untranslatedText(), bus.at((int)storagedrive->bus())); setData(name, kli18n("Drive Type").untranslatedText(), drivetype.at((int)storagedrive->driveType())); setData(name, kli18n("Removable").untranslatedText(), storagedrive->isRemovable()); setData(name, kli18n("Hotpluggable").untranslatedText(), storagedrive->isHotpluggable()); updateHardDiskTemperature(name); } else { bool isRemovable = false; bool isHotpluggable = false; Solid::StorageDrive *drive = getAncestorAs(device); if (drive) { // remove check for isHotpluggable() when plasmoids are changed to check for both properties isRemovable = (drive->isRemovable() || drive->isHotpluggable()); isHotpluggable = drive->isHotpluggable(); } setData(name, kli18n("Removable").untranslatedText(), isRemovable); setData(name, kli18n("Hotpluggable").untranslatedText(), isHotpluggable); } if (device.is()) { Solid::OpticalDrive *opticaldrive = device.as(); if (!opticaldrive) { return false; } devicetypes << kli18n("Optical Drive").untranslatedText(); QStringList supportedtypes; Solid::OpticalDrive::MediumTypes mediatypes = opticaldrive->supportedMedia(); if (mediatypes & Solid::OpticalDrive::Cdr) { supportedtypes << kli18n("CD-R").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Cdrw) { supportedtypes << kli18n("CD-RW").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvd) { supportedtypes << kli18n("DVD").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdr) { supportedtypes << kli18n("DVD-R").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdrw) { supportedtypes << kli18n("DVD-RW").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdram) { supportedtypes << kli18n("DVD-RAM").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdplusr) { supportedtypes << kli18n("DVD+R").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdplusrw) { supportedtypes << kli18n("DVD+RW").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdplusdl) { supportedtypes << kli18n("DVD+DL").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Dvdplusdlrw) { supportedtypes << kli18n("DVD+DLRW").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Bd) { supportedtypes << kli18n("BD").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Bdr) { supportedtypes << kli18n("BD-R").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::Bdre) { supportedtypes << kli18n("BD-RE").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::HdDvd) { supportedtypes << kli18n("HDDVD").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::HdDvdr) { supportedtypes << kli18n("HDDVD-R").untranslatedText(); } if (mediatypes & Solid::OpticalDrive::HdDvdrw) { supportedtypes << kli18n("HDDVD-RW").untranslatedText(); } setData(name, kli18n("Supported Media").untranslatedText(), supportedtypes); setData(name, kli18n("Read Speed").untranslatedText(), opticaldrive->readSpeed()); setData(name, kli18n("Write Speed").untranslatedText(), opticaldrive->writeSpeed()); // the following method return QList so we need to convert it to QList const QList writespeeds = opticaldrive->writeSpeeds(); QList variantlist; for (int num : writespeeds) { variantlist << num; } setData(name, kli18n("Write Speeds").untranslatedText(), variantlist); } if (device.is()) { Solid::StorageVolume *storagevolume = device.as(); if (!storagevolume) { return false; } devicetypes << kli18n("Storage Volume").untranslatedText(); QStringList usagetypes; usagetypes << i18n("Other") << i18n("Unused") << i18n("File System") << i18n("Partition Table") << i18n("Raid") << i18n("Encrypted"); if (usagetypes.count() > storagevolume->usage()) { setData(name, kli18n("Usage").untranslatedText(), usagetypes.at((int)storagevolume->usage())); } else { setData(name, kli18n("Usage").untranslatedText(), i18n("Unknown")); } setData(name, kli18n("Ignored").untranslatedText(), storagevolume->isIgnored()); setData(name, kli18n("File System Type").untranslatedText(), storagevolume->fsType()); setData(name, kli18n("Label").untranslatedText(), storagevolume->label()); setData(name, kli18n("UUID").untranslatedText(), storagevolume->uuid()); updateInUse(name); // Check if the volume is part of an encrypted container // This needs to trigger an update for the encrypted container volume since // libsolid cannot notify us when the accessibility of the container changes Solid::Device encryptedContainer = storagevolume->encryptedContainer(); if (encryptedContainer.isValid()) { const QString containerUdi = encryptedContainer.udi(); setData(name, kli18n("Encrypted Container").untranslatedText(), containerUdi); m_encryptedContainerMap[name] = containerUdi; // TODO: compress the calls? forceUpdateAccessibility(containerUdi); } } if (device.is()) { Solid::OpticalDisc *opticaldisc = device.as(); if (!opticaldisc) { return false; } devicetypes << kli18n("OpticalDisc").untranslatedText(); // get the content types QStringList contenttypelist; const Solid::OpticalDisc::ContentTypes contenttypes = opticaldisc->availableContent(); if (contenttypes.testFlag(Solid::OpticalDisc::Audio)) { contenttypelist << kli18n("Audio").untranslatedText(); } if (contenttypes.testFlag(Solid::OpticalDisc::Data)) { contenttypelist << kli18n("Data").untranslatedText(); } if (contenttypes.testFlag(Solid::OpticalDisc::VideoCd)) { contenttypelist << kli18n("Video CD").untranslatedText(); } if (contenttypes.testFlag(Solid::OpticalDisc::SuperVideoCd)) { contenttypelist << kli18n("Super Video CD").untranslatedText(); } if (contenttypes.testFlag(Solid::OpticalDisc::VideoDvd)) { contenttypelist << kli18n("Video DVD").untranslatedText(); } if (contenttypes.testFlag(Solid::OpticalDisc::VideoBluRay)) { contenttypelist << kli18n("Video Blu Ray").untranslatedText(); } setData(name, kli18n("Available Content").untranslatedText(), contenttypelist); QStringList disctypes; disctypes << kli18n("Unknown Disc Type").untranslatedText() << kli18n("CD Rom").untranslatedText() << kli18n("CD Recordable").untranslatedText() << kli18n("CD Rewritable").untranslatedText() << kli18n("DVD Rom").untranslatedText() << kli18n("DVD Ram").untranslatedText() << kli18n("DVD Recordable").untranslatedText() << kli18n("DVD Rewritable").untranslatedText() << kli18n("DVD Plus Recordable").untranslatedText() << kli18n("DVD Plus Rewritable").untranslatedText() << kli18n("DVD Plus Recordable Duallayer").untranslatedText() << kli18n("DVD Plus Rewritable Duallayer").untranslatedText() << kli18n("Blu Ray Rom").untranslatedText() << kli18n("Blu Ray Recordable").untranslatedText() << kli18n("Blu Ray Rewritable").untranslatedText() << kli18n("HD DVD Rom").untranslatedText() << kli18n("HD DVD Recordable").untranslatedText() << kli18n("HD DVD Rewritable").untranslatedText(); //+1 because the enum starts at -1 setData(name, kli18n("Disc Type").untranslatedText(), disctypes.at((int)opticaldisc->discType() + 1)); setData(name, kli18n("Appendable").untranslatedText(), opticaldisc->isAppendable()); setData(name, kli18n("Blank").untranslatedText(), opticaldisc->isBlank()); setData(name, kli18n("Rewritable").untranslatedText(), opticaldisc->isRewritable()); setData(name, kli18n("Capacity").untranslatedText(), opticaldisc->capacity()); } if (device.is()) { Solid::Camera *camera = device.as(); if (!camera) { return false; } devicetypes << kli18n("Camera").untranslatedText(); setData(name, kli18n("Supported Protocols").untranslatedText(), camera->supportedProtocols()); setData(name, kli18n("Supported Drivers").untranslatedText(), camera->supportedDrivers()); // Cameras are necessarily Removable and Hotpluggable setData(name, kli18n("Removable").untranslatedText(), true); setData(name, kli18n("Hotpluggable").untranslatedText(), true); } if (device.is()) { Solid::PortableMediaPlayer *mediaplayer = device.as(); if (!mediaplayer) { return false; } devicetypes << kli18n("Portable Media Player").untranslatedText(); setData(name, kli18n("Supported Protocols").untranslatedText(), mediaplayer->supportedProtocols()); setData(name, kli18n("Supported Drivers").untranslatedText(), mediaplayer->supportedDrivers()); // Portable Media Players are necessarily Removable and Hotpluggable setData(name, kli18n("Removable").untranslatedText(), true); setData(name, kli18n("Hotpluggable").untranslatedText(), true); } if (device.is()) { Solid::Battery *battery = device.as(); if (!battery) { return false; } devicetypes << kli18n("Battery").untranslatedText(); QStringList batterytype; batterytype << kli18n("Unknown Battery").untranslatedText() << kli18n("PDA Battery").untranslatedText() << kli18n("UPS Battery").untranslatedText() << kli18n("Primary Battery").untranslatedText() << kli18n("Mouse Battery").untranslatedText() << kli18n("Keyboard Battery").untranslatedText() << kli18n("Keyboard Mouse Battery").untranslatedText() << kli18n("Camera Battery").untranslatedText() << kli18n("Phone Battery").untranslatedText() << kli18n("Monitor Battery").untranslatedText() << kli18n("Gaming Input Battery").untranslatedText() << kli18n("Bluetooth Battery").untranslatedText() << kli18n("Tablet Battery").untranslatedText() << kli18n("Headphone Battery").untranslatedText() << kli18n("Headset Battery").untranslatedText() << kli18n("Touchpad Battery").untranslatedText(); QStringList chargestate; chargestate << kli18n("Not Charging").untranslatedText() << kli18n("Charging").untranslatedText() << kli18n("Discharging").untranslatedText() << kli18n("Fully Charged").untranslatedText(); setData(name, kli18n("Plugged In").untranslatedText(), battery->isPresent()); // FIXME Rename when interested parties are adjusted setData(name, kli18n("Type").untranslatedText(), batterytype.value((int)battery->type())); setData(name, kli18n("Charge Percent").untranslatedText(), battery->chargePercent()); setData(name, kli18n("Rechargeable").untranslatedText(), battery->isRechargeable()); setData(name, kli18n("Charge State").untranslatedText(), chargestate.at((int)battery->chargeState())); m_signalmanager->mapDevice(battery, device.udi()); } using namespace Solid; // we cannot just iterate the enum in reverse order since Battery comes second to last // and then our phone which also has a battery gets treated as battery :( static const Solid::DeviceInterface::Type typeOrder[] = { Solid::DeviceInterface::PortableMediaPlayer, Solid::DeviceInterface::Camera, Solid::DeviceInterface::OpticalDisc, Solid::DeviceInterface::StorageVolume, Solid::DeviceInterface::OpticalDrive, Solid::DeviceInterface::StorageDrive, Solid::DeviceInterface::NetworkShare, Solid::DeviceInterface::StorageAccess, Solid::DeviceInterface::Block, Solid::DeviceInterface::Battery, Solid::DeviceInterface::Processor, }; for (int i = 0; i < 11; ++i) { const Solid::DeviceInterface *interface = device.asDeviceInterface(typeOrder[i]); if (interface) { setData(name, kli18n("Type Description").untranslatedText(), Solid::DeviceInterface::typeDescription(typeOrder[i])); break; } } setData(name, kli18n("Device Types").untranslatedText(), devicetypes); return true; } void SolidDeviceEngine::deviceAdded(const QString &udi) { Solid::Device device(udi); for (auto it = m_predicatemap.cbegin(); it != m_predicatemap.cend(); it = std::next(it)) { Solid::Predicate predicate = Solid::Predicate::fromString(it.key()); if (predicate.matches(device)) { m_predicatemap[it.key()] << udi; setData(it.key(), m_predicatemap[it.key()]); } } if (device.is()) { Solid::OpticalDrive *drive = getAncestorAs(device); if (drive) { connect(drive, &Solid::OpticalDrive::ejectRequested, this, &SolidDeviceEngine::setUnmountingState); connect(drive, &Solid::OpticalDrive::ejectDone, this, &SolidDeviceEngine::setIdleState); } } else if (device.is()) { // update the volume in case of 2-stage devices if (m_devicemap.contains(udi) && containerForSource(udi)->data().value(kli18n("Size").untranslatedText()).toULongLong() == 0) { Solid::GenericInterface *iface = device.as(); if (iface) { iface->setProperty("udi", udi); connect(iface, SIGNAL(propertyChanged(QMap)), this, SLOT(deviceChanged(QMap))); } } Solid::StorageAccess *access = device.as(); if (access) { connect(access, &Solid::StorageAccess::setupRequested, this, &SolidDeviceEngine::setMountingState); connect(access, &Solid::StorageAccess::setupDone, this, &SolidDeviceEngine::setIdleState); connect(access, &Solid::StorageAccess::teardownRequested, this, &SolidDeviceEngine::setUnmountingState); connect(access, &Solid::StorageAccess::teardownDone, this, &SolidDeviceEngine::setIdleState); } } } void SolidDeviceEngine::setMountingState(const QString &udi) { setData(udi, kli18n("State").untranslatedText(), Mounting); setData(udi, kli18n("Operation result").untranslatedText(), Working); } void SolidDeviceEngine::setUnmountingState(const QString &udi) { setData(udi, kli18n("State").untranslatedText(), Unmounting); setData(udi, kli18n("Operation result").untranslatedText(), Working); } void SolidDeviceEngine::setIdleState(Solid::ErrorType error, QVariant errorData, const QString &udi) { Q_UNUSED(errorData) if (error == Solid::NoError) { setData(udi, kli18n("Operation result").untranslatedText(), Successful); } else { setData(udi, kli18n("Operation result").untranslatedText(), Unsuccessful); } setData(udi, kli18n("State").untranslatedText(), Idle); Solid::Device device = m_devicemap.value(udi); if (!device.isValid()) { return; } Solid::StorageAccess *storageaccess = device.as(); if (!storageaccess) { return; } setData(udi, kli18n("Accessible").untranslatedText(), storageaccess->isAccessible()); setData(udi, kli18n("File Path").untranslatedText(), storageaccess->filePath()); } void SolidDeviceEngine::deviceChanged(const QMap &props) { Solid::GenericInterface *iface = qobject_cast(sender()); if (iface && iface->isValid() && props.contains(QLatin1String("Size")) && iface->property(QStringLiteral("Size")).toInt() > 0) { const QString udi = qobject_cast(iface)->property("udi").toString(); if (populateDeviceData(udi)) forceImmediateUpdateOfAllVisualizations(); } } bool SolidDeviceEngine::updateStorageSpace(const QString &udi) { Solid::Device device = m_devicemap.value(udi); Solid::StorageAccess *storageaccess = device.as(); if (!storageaccess || !storageaccess->isAccessible()) { return false; } QString path = storageaccess->filePath(); if (!m_paths.contains(path)) { QTimer *timer = new QTimer(this); timer->setSingleShot(true); connect(timer, &QTimer::timeout, [path]() { KNotification::event(KNotification::Error, i18n("Filesystem is not responding"), i18n("Filesystem mounted at '%1' is not responding", path)); }); m_paths.insert(path); // create job KIO::FileSystemFreeSpaceJob *job = KIO::fileSystemFreeSpace(QUrl::fromLocalFile(path)); // delete later timer connect(job, &KJob::result, timer, &QTimer::deleteLater); // collect and process info connect(job, &KJob::result, this, [this, timer, path, udi, job]() { timer->stop(); if (!job->error()) { KIO::filesize_t size = job->size(); KIO::filesize_t available = job->availableSize(); setData(udi, kli18n("Free Space").untranslatedText(), QVariant(available).toDouble()); setData(udi, kli18n("Free Space Text").untranslatedText(), KFormat().formatByteSize(available)); setData(udi, kli18n("Size").untranslatedText(), QVariant(size).toDouble()); setData(udi, kli18n("Size Text").untranslatedText(), KFormat().formatByteSize(size)); } m_paths.remove(path); }); // start timer: after 15 seconds we will get an error timer->start(15000); } return false; } bool SolidDeviceEngine::updateHardDiskTemperature(const QString &udi) { Solid::Device device = m_devicemap.value(udi); Solid::Block *block = device.as(); if (!block) { return false; } if (!m_temperature) { m_temperature = new HddTemp(this); } if (m_temperature->sources().contains(block->device())) { setData(udi, kli18n("Temperature").untranslatedText(), m_temperature->data(block->device(), HddTemp::Temperature)); setData(udi, kli18n("Temperature Unit").untranslatedText(), m_temperature->data(block->device(), HddTemp::Unit)); return true; } return false; } bool SolidDeviceEngine::updateEmblems(const QString &udi) { Solid::Device device = m_devicemap.value(udi); setData(udi, kli18n("Emblems").untranslatedText(), device.emblems()); return true; } bool SolidDeviceEngine::forceUpdateAccessibility(const QString &udi) { Solid::Device device = m_devicemap.value(udi); if (!device.isValid()) { return false; } updateEmblems(udi); Solid::StorageAccess *storageaccess = device.as(); if (storageaccess) { setData(udi, kli18n("Accessible").untranslatedText(), storageaccess->isAccessible()); } return true; } bool SolidDeviceEngine::updateInUse(const QString &udi) { Solid::Device device = m_devicemap.value(udi); if (!device.isValid()) { return false; } Solid::StorageAccess *storageaccess = device.as(); if (!storageaccess) { return false; } if (storageaccess->isAccessible()) { setData(udi, kli18n("In Use").untranslatedText(), true); } else { Solid::StorageDrive *drive = getAncestorAs(Solid::Device(udi)); if (drive) { setData(udi, kli18n("In Use").untranslatedText(), drive->isInUse()); } } return true; } bool SolidDeviceEngine::updateSourceEvent(const QString &source) { bool update1 = updateStorageSpace(source); bool update2 = updateHardDiskTemperature(source); bool update3 = updateEmblems(source); bool update4 = updateInUse(source); return (update1 || update2 || update3 || update4); } void SolidDeviceEngine::deviceRemoved(const QString &udi) { // libsolid cannot notify us when an encrypted container is closed, // hence we trigger an update when a device contained in an encrypted container device dies const QString containerUdi = m_encryptedContainerMap.value(udi, QString()); if (!containerUdi.isEmpty()) { forceUpdateAccessibility(containerUdi); m_encryptedContainerMap.remove(udi); } for (const QString &query : m_predicatemap.keys()) { m_predicatemap[query].removeAll(udi); setData(query, m_predicatemap[query]); } Solid::Device device(udi); if (device.is()) { Solid::StorageAccess *access = device.as(); if (access) { disconnect(access, nullptr, this, nullptr); } } else if (device.is()) { Solid::OpticalDrive *drive = getAncestorAs(device); if (drive) { disconnect(drive, nullptr, this, nullptr); } } m_devicemap.remove(udi); removeSource(udi); } void SolidDeviceEngine::deviceChanged(const QString &udi, const QString &property, const QVariant &value) { setData(udi, property, value); updateSourceEvent(udi); } K_PLUGIN_CLASS_WITH_JSON(SolidDeviceEngine, "plasma-dataengine-soliddevice.json") #include "soliddeviceengine.moc"