Solid
Introduction
Solid, in sonic-frameworks-device-integration (find_package(KF6Solid), KF6::Solid), lets you write applications that work with hardware. Its API provides the abstraction needed to support several platforms.
Solid finds devices and gives you the information it has about them: you can look at the features of the CPU, the driver of your camera, or the mount point of your USB stick.
You get information about a device without knowing its specifics, and can then control it with another library. This works for network devices too.
Finding devices
Listing devices
The first program is a simple console application that lists all hardware devices:
#include <Solid/Device>
// ...
for(const Solid::Device &device : Solid::Device::allDevices()) {
qInfo() << device.udi();
}
device.udi() returns the device's unique device identifier (UDI) as a QString. It is guaranteed to be unique: if a machine has two identical TV capture cards, you can refer to each by its UDI.
Finding specific devices
The second program uses Solid's filters, which list only devices of given types, devices below a given parent, or devices matching predicates. This example lists only batteries. The device types are listed in Solid::DeviceInterface::Type.
#include <Solid/Device>
#include <Solid/DeviceInterface>
// ...
// get a list of battery devices
const auto devices = Solid::Device::listFromType(Solid::DeviceInterface::Battery, QString());
for (const Solid::Device &device : devices)
{
qDebug() << device.udi();
}
Solid::Device::listFromType() finds devices of type Solid::DeviceInterface::Battery with any parent. To find only batteries below the parent /org/freedesktop/UPower (the UPower backend's devices), write:
Solid::Device::listFromType(Solid::DeviceInterface::Battery, QStringLiteral("/org/freedesktop/UPower"));
What to do with a device
A Solid::Device can have several types (device interfaces). Test for one and use it like this:
#include <Solid/Device>
#include <Solid/DeviceInterface>
#include <Solid/Processor>
// ...
const auto list = Solid::Device::listFromType(Solid::DeviceInterface::Processor, QString());
// take the first processor in the list
Solid::Device device = list[0];
if (device.is<Solid::Processor>()) {
qDebug() << "We've got a processor!";
} else {
qDebug() << "Device is not a processor.";
}
auto processor = device.as<Solid::Processor>();
qInfo() << "This processors maximum speed is:" << processor->maxSpeed();
Being notified of new devices
The device notifier signals when devices are added, so your application can react:
#include <Solid/Device>
#include <Solid/DeviceNotifier>
#include <Solid/StorageDrive>
// ...
auto notifier = Solid::DeviceNotifier::instance();
auto refresh = [this] (const QString &udi) {
Solid::Device device(udi);
if (device.is<Solid::StorageDrive>()) {
qInfo() << "A new storage drive was added!";
}
};
QObject::connect(notifier, &Solid::DeviceNotifier::deviceAdded, refresh);
Network devices
With NetworkShare you can find network shares and handle each kind differently:
const auto devices = Solid::Device::listFromType(Solid::DeviceInterface::NetworkShare);
for (const auto &device : devices) {
switch (device.as<Solid::NetworkShare>()->type()) {
case Solid::NetworkShare::Cifs:
hasCifsShare = true;
continue;
case Solid::NetworkShare::Smb3:
hasSmb3Share = true;
continue;
case Solid::NetworkShare::Nfs:
hasNfsShare = true;
continue;
default:
continue;
}
}
The solid-hardware6 tool
Solid also provides solid-hardware6, a command-line tool for looking at the available devices.
List the UDIs of all devices:
solid-hardware6 list
List all devices with all their details:
solid-hardware6 list details
List all batteries:
solid-hardware6 query 'IS Battery'
This query is a Solid predicate.
Device actions
When Solid finds a new data device, it appears in SonicDE's "Disks & Devices" widget, which handles device events, in the Places panel of the Ecco file manager, and elsewhere in the user interface.
Each device has actions the user can perform on it, depending on its type and contents. For an audio CD, for example, "Disks & Devices" may offer to play it with a music player. Default actions are defined by desktop files that the application handling the action installs, and they disappear when the application is removed.
Users add and change actions in System Settings → Disks & Cameras → Device Actions; those changes only apply to that user. To install an action with your application, read on.
Anatomy of an action
An action is a desktop file in ~/.local/share/solid/actions or /usr/share/solid/actions with an additional application action, like this:
[Desktop Entry]
X-KDE-Solid-Predicate=OpticalDisc.availableContent & 'Audio'
Type=Service
Actions=Play;
[Desktop Action Play]
Name=Play Audio CD with Amarok
Name[fr]=Jouer CD Audio avec Amarok
# names in other languages
Icon=amarok
Exec=amarok --cdplay %f
The value of Actions= must match the Desktop Action group.
Actions and devices
"Disks & Devices" gets the devices from Solid and reads the actions from the solid/actions folders (~/.local/share/solid/actions, /usr/share/solid/actions). It decides which actions belong to a device by evaluating each action's predicate against the device's physical and logical properties: if the predicate matches, the action is offered. Any application can query Solid for the same actions in the same way.
Action predicates
The predicate of an action is in its X-KDE-Solid-Predicate entry. Its syntax is that of Solid::Predicate.
Predicates have the form:
DeviceClass.attribute == valueDeviceClass.attribute & value
The second form means the attribute can have a set of values, and the given value must be one of them. Which values exist depends on the device class.
For example, OpticalDisc.availableContent & 'Audio' means the medium is a Solid::OpticalDisc whose content includes audio tracks and possibly more, while OpticalDisc.availableContent == 'Audio' matches a disc with audio tracks and nothing else.
Predicates can be combined with AND, OR and parentheses; IS <type> matches devices of a type. The easiest way to write one is to create a custom action in System Settings and look at the desktop file it writes in your user folder.
Executing actions
When the user chooses an action, the command in its Exec= key is run with the device as a parameter. These placeholders are replaced:
%fthe device's mount point, if it has one%dthe device file (such as the path of a block device)%ithe device's UDI
Use whichever your application's command line supports. Note that the Desktop Entry specification deprecates %d and %i, so they may go away.
Installing actions
Install system-wide actions where the device notifier looks for them, with ECM's KDEInstallDirs:
install(FILES someSolidAction.desktop
DESTINATION ${KDE_INSTALL_DATADIR}/solid/actions/)
The action is available as soon as it is installed: the device notifier watches the folders.