Kde4

Solid Lines Around Hardware

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.

Phonon Is Not a Codec Collection

A test player handled one Ogg file and failed on another machine before lunch. My first reaction was to ask which Phonon codec was missing. That question contains the bug in my mental model.

Phonon is intended as a multimedia API above actual playback engines. The application builds a graph of media objects, audio outputs, and paths. A backend translates that graph to whatever engine is available. Codec support therefore depends on the backend and its installed components, not on Phonon carrying a suitcase of decoders.

The small playback case looks roughly like this:

Phonon::MediaObject *media = new Phonon::MediaObject(this);
Phonon::AudioOutput *output =
    new Phonon::AudioOutput(Phonon::MusicCategory, this);

Phonon::createPath(media, output);
media->setCurrentSource(Phonon::MediaSource(fileName));
media->play();

The category matters. It describes the purpose of the sound so system policy can distinguish music from notifications or communication. It is not merely a friendly enum to satisfy the compiler.

Errors remain asynchronous. Calling play() starts a process; it does not certify that bytes will reach speakers. The application must listen for state changes and present the backend’s error without pretending every failure is a missing file.

I prefer depending on a narrow playback abstraction instead of teaching each KDE application the details of several multimedia engines. That should make device selection and policy consistent. The caveat is the least-common-denominator problem: specialised editors and unusual pipelines may need capabilities the abstraction cannot sensibly expose.

I am also avoiding backend detection in the application. Branching on a backend name would make today’s workaround tomorrow’s required behaviour. If a capability is necessary, the API should expose that capability or the application should report that it cannot perform the operation.

For porting, I am keeping the boundary obvious. The document remembers a URL and playback position; a small controller owns Phonon objects; widgets issue commands and display state. This makes backend surprises testable without turning the main window into an audio engine wearing buttons.

The architecture is still settling, and backend behaviour will not be perfectly identical. Today’s useful certainty is modest: “works through one engine here” is not the same claim as “Phonon supports this format everywhere.” Computers remain attentive readers of fine print.

ThreadWeaver and the Size of a Job

I fed 12,000 thumbnail jobs into a ThreadWeaver experiment today. The queue accepted them with admirable composure. The application then spent more time managing tiny jobs than producing thumbnails.

The mechanism is straightforward: jobs enter a queue, worker threads take runnable jobs, and completion is reported back. This is much better than creating one thread per thumbnail. Threads have stacks, scheduling cost, and an unfortunate tendency to outlive the code that launched them.

My mistake was choosing a job that was too small. Each item performed a little path work, checked a cache, and often returned without decoding anything. Queue locking and signal delivery became a noticeable fraction of total work.

Batching nearby items helped:

class ThumbnailBatch : public ThreadWeaver::Job
{
public:
    ThumbnailBatch(const QStringList &paths)
        : m_paths(paths)
    {
    }

protected:
    void run()
    {
        foreach (const QString &path, m_paths)
            createThumbnail(path);
    }

private:
    QStringList m_paths;
};

The batch is large enough to amortise scheduling but small enough that several workers remain busy. There is no universal number. Disk latency, decoding cost, and cache hit rate all change the useful grain size, so I am measuring rather than engraving 32 on a tablet.

Cancellation also becomes a design issue. Removing queued jobs is easy; stopping one already decoding is cooperative. Long operations need checkpoints, and the job must own or safely reference everything it touches. A window closing while a worker holds its raw pointer is not cancellation. It is delayed excitement.

I prefer a shared job queue such as ThreadWeaver for independent background work because it bounds concurrency and centralises scheduling. The caveat is that it does not make unsafe code safe. Shared caches still need locking, GUI objects still belong to the GUI thread, and dependencies between jobs still need explicit representation.

Priority needs restraint as well. Marking every visible thumbnail urgent merely recreates a first-in queue with more bookkeeping. I reserve priority for work whose delay is observable now, and still ensure ordinary jobs eventually run.

The current prototype sends immutable input into a job and returns a completed image through a queued signal. That copy may cost something, but the ownership story fits in one sentence. For now, clarity beats shaving a copy whose cost I have not measured.

A Panel Made of Rectangles

I spent the evening moving three coloured rectangles around a blank window. This sounds less impressive than “working on the KDE 4 desktop,” which is precisely why it is a more accurate description.

The prototype is testing a Plasma idea: applets live in a containment, and the containment decides how available space is assigned. The applet should express useful size hints rather than assume that the panel is horizontal, 32 pixels high, and forever attached to the bottom edge.

My first attempt stored absolute geometry. Rotating the panel exposed the mistake immediately. The second stores constraints and lets the containment lay things out:

QSize Applet::minimumSize() const
{
    return QSize(24, 24);
}

QSize Applet::preferredSize() const
{
    return QSize(96, 32);
}

The prototype then reruns layout when its own size or form changes. Nothing here proves the final API. It proves that geometry policy has to live somewhere other than each applet’s collection of special cases.

Painting is the second experiment. Keeping content separate from the surrounding frame means a theme can alter margins and background without every applet learning the new decoration. That also means the layout must account for frame insets before assigning content space. I forgot this and produced a clock whose final digit lived just outside civilisation.

I prefer small visual prototypes with deliberately fake data at this stage. They make resizing, invalidation, and composition bugs obvious before a real calendar or device monitor introduces its own failures. The caveat is that rectangles do not exercise accessibility, input, localisation, or expensive content. A prototype can validate one mechanism while quietly avoiding four others.

I am saving each experiment as a tiny runnable case. When the graphics or layout API changes, these cases answer whether the concept still works without requiring a complete desktop session to survive long enough for the test.

There is no finished Plasma desktop hiding behind these rectangles. There are unresolved questions about applet lifecycle, configuration, persistence, and rendering. The useful result tonight is narrower: containment-driven layout survives changing orientation better than hard-coded applet geometry.

Tomorrow I may replace one rectangle with a clock. This will increase the prototype’s accuracy by one time zone and its bug count by an unknown but probably larger number.

Converting One CMake Target at a Time

Today’s CMake conversion failed at the final link with an undefined reference from a file I had not changed. Naturally, I first blamed the linker. Linkers enjoy this arrangement because they never have to attend the meeting.

The actual cause was dependency order. The old build inherited libraries from a parent directory, so this target had never declared what it used. Moving it exposed the omission.

I started recording requirements next to the target:

set(indexer_SRCS
    indexer.cpp
    document.cpp
    tokenizer.cpp
)

kde4_add_executable(indexer ${indexer_SRCS})
target_link_libraries(indexer
    ${KDE4_KDECORE_LIBS}
    searchcore
)

Then I configured from an empty build directory. Reusing yesterday’s cache can conceal a missing check or preserve a path that no clean machine has. During conversion, this sequence is worth the extra minute:

rm -rf build
mkdir build
cd build
cmake -DCMAKE_BUILD_TYPE=debugfull ..
make VERBOSE=1

I am not recommending casual rm -rf; I am recommending a disposable directory whose purpose is printed on the tin. Source remains elsewhere.

Generated files need similar care. If a header is produced during the build, its output belongs in the binary tree and the target must depend on the generation step. Writing generated headers into the source tree makes an apparently clean checkout depend on debris from an earlier build. It also makes parallel builds unusually creative.

I prefer explicit source lists over collecting every *.cpp automatically. Adding a file should change the build description, which gives review and version control a chance to notice it. The caveat is maintenance: source lists can drift if developers forget them, and globbing can be reasonable for generated collections. For hand-written application code, explicit still wins for me.

I am applying the same rule to include directories. A target should receive only paths it needs; a global include path can let one directory compile by accident against another directory’s private header.

The best conversion checkpoint is not “CMake completed.” It is a clean configure, complete build, and one small executable run from the binary tree. Configuration only proves that CMake understood my instructions, not that my instructions were wise.

One target now builds without inherited flags. Nine remain. This is slower to describe than a bulk conversion and faster to debug than one.