My network widget broke today because a property changed underneath it. More precisely, the widget knew enough about the hardware backend to parse a backend-specific property, and not enough to notice when that property was absent. This is the sort of knowledge that makes software feel powerful right up to the first different machine.

Solid is the KDE 4 attempt to draw a stable line between applications and hardware discovery mechanisms. An application should ask for devices and capabilities in KDE terms. A backend can obtain those facts from HAL, NetworkManager, or another platform facility. The abstraction is not meant to make all hardware identical; it is meant to stop every application from inventing a private translation layer.

The useful mental model is a device plus interfaces. A device has an identity and general properties. Interfaces describe capabilities such as storage access, battery status, or networking. Code can first ask whether a capability exists, then use the corresponding interface:

Solid::Device device(udi);

if (device.isDeviceInterface(Solid::DeviceInterface::StorageVolume)) {
    Solid::StorageVolume *volume =
        device.as<Solid::StorageVolume>();
    qDebug() << volume->label() << volume->size();
}

The pointer still needs checking because devices disappear and backends can fail. A USB disk is not contractually obliged to remain attached while my slot admires it.

Identity is harder than it first appears. A display name is for humans and can change. A device path may reflect discovery order. A HAL UDI is useful within that backend but should not leak into an application’s persistent file format if the application claims backend independence. For a short-lived object map, the current unique identifier is fine. For durable preferences, I need a stable property appropriate to the device class, and sometimes no truly stable identifier exists.

Hotplug events introduce ordering questions. My first implementation reacted to a “device added” signal by immediately requesting every interface. Some properties were not ready yet, and parent devices could appear after children. The safer path is to tolerate incomplete devices, query only required capabilities, and update when relevant properties change. Applications must not infer a complete topology from the first signal they see.

Networking demonstrates why policy should remain separate. NetworkManager can report devices, available networks, activation state, and changes. Solid can present that information consistently to KDE code. It should not mean that a text editor decides which wireless network to activate. A desktop networking component may own that policy; most applications only need to know whether connectivity is currently available, perhaps with enough uncertainty to avoid promising too much.

Storage has a similar distinction. Discovering a volume is not the same as mounting it, and mounting it is not the same as opening a file manager. Those are three mechanisms with three possible policy owners. When a camera appears, a photo application may offer an import action. Automatically performing one because a hardware signal arrived would be efficient in the same way as a trapdoor.

For the widget, I replaced direct HAL property access with a tiny internal state derived from Solid interfaces:

struct NetworkState
{
    QString name;
    bool available;
    bool active;
};

The widget receives this state and paints it. It no longer knows whether the information came from NetworkManager or a test backend. The adapter listens for device and property changes, rebuilds the affected state, and emits one application-level update. This also keeps backend callbacks away from paint code.

I prefer capability queries over a deep inheritance tree of device types. Real hardware combines roles inconveniently, and capability composition lets one object expose several useful interfaces. The caveat is discoverability: callers must know which combinations make sense, and an abstraction can become a bag of optional pointers if its contracts are weak.

I also prefer asynchronous operations for actions such as mounting or network activation. Hardware can wait for media, authentication, or a helper process. Blocking the GUI thread while reality decides what to do is poor manners. Completion needs an error path that preserves enough backend detail for diagnosis without requiring the application to parse backend internals.

None of this makes Solid finished. We still need to learn which properties are genuinely portable, how device lifetimes should be represented, and how much backend-specific information may escape without defeating the purpose. HAL itself is evolving, and NetworkManager behaviour differs across the machines I can test. A clean interface can expose uncertainty; it cannot abolish it.

My practical rule after today’s failure is simple: application code depends on capabilities, UI code depends on application state, and raw backend properties stop at the adapter boundary. Exceptions may be necessary for specialised tools, but they should look like exceptions rather than quietly becoming architecture.

The widget now survives the missing property. It also reports my wired interface as “network cable,” which is accurate, stable, and not likely to win a naming award.