Blog

Oxygen Is Not the Desktop

I tried the latest Oxygen icons today and immediately decided the whole desktop looked better. Then I used it for an hour and discovered that “looks better” and “is easier to read” are not synonyms.

My first attempt was to set every available Oxygen component at once: icons, window decoration, widget style and colours. The result was certainly consistent. It was also difficult to distinguish a few small toolbar actions, especially disabled ones. At 16 pixels, subtle gradients become subtle grey soup.

The mechanism is mundane. An icon theme must communicate shape at several fixed sizes, not merely survive being scaled down from a handsome drawing. The new SVG source helps artists produce multiple sizes and keeps larger icons crisp, but it does not remove the need for deliberate small variants. A tiny icon has almost no room for texture, perspective or philosophical nuance.

I switched the toolbar to larger icons and kept text beside the less obvious actions. That made Dolphin and the configuration tools much easier to scan. I also stopped blaming Oxygen for missing actions and unfinished panels. Artwork can make incomplete software pleasant to inspect, but it cannot implement a file operation through sheer shininess.

Oxygen already gives KDE 4 a strong identity, and I prefer its restrained colours to the louder themes I usually replace after five minutes. Still, visual consistency is only useful when the symbols remain distinct. My practical conclusion: judge icons at the size actually used, not in the preview window. The preview window is where all icons go to lie about their career prospects.

KDE 4 Alpha on a Spare Partition

I installed a pre-alpha KDE 4 SVN snapshot on a spare partition because running it beside my normal files seemed like a more entertaining mistake than merely reading about it. The announced alpha release was due the next day; this was still a snapshot from the day before. The packages installed cleanly, the session appeared in the login manager, and that was approximately where normality ended.

My naive plan was to reproduce my KDE 3 setup immediately: restore the same panel arrangement, point everything at the same home directory, and judge whether the new desktop was faster. That plan survived for perhaps ten minutes. Several settings had no equivalent, applications still looked inconsistent, and the desktop shell was plainly being assembled while I watched.

What does work is more interesting than the rough edges. Qt 4 painting feels noticeably less burdened when windows resize. The new artwork gives the session a coherent direction rather than the collection-of-themes appearance I feared. A few applications already show cleaner separation between their data and their widgets, which should matter long after the screenshots stop being novel.

What does not work is still substantial. The panel is not something I would trust for a working day. Desktop actions occasionally disappear. Some configuration modules are present but ineffective, a particularly refined form of optimism. Logging out once left processes behind, which then made the next login look broken until I killed them from a console.

This is not KDE 3 with fresh icons. KDE 4 is changing the toolkit, desktop shell, hardware and multimedia layers, and a fair amount of application plumbing at once. That explains why familiar checkboxes cannot simply be carried over. It also means measuring this snapshot as a replacement desktop misses the point.

After a second installation with a clean test user, many of the mysterious failures vanished. Sharing an old configuration directory had introduced stale service files and assumptions from KDE 3. The snapshot still crashes and still lacks ordinary conveniences, but at least those failures now belong to the new code rather than to my archaeological collection of dot files.

Don’t install it on the account used to earn money. Still, the architecture is visible enough to poke at, and a bug report from a clean account may help. Complaining that a pre-alpha snapshot is unfinished is accurate, but not exactly investigative journalism.

Qt Model/View Without the Magic

I added sorting to a log viewer with QSortFilterProxyModel, clicked the third visible row, and opened the wrong log entry. The view had done exactly what I asked. I had used a proxy row as an index into the source list.

A proxy has its own indexes and its own order. Once sorting or filtering is active, row 3 in the view says nothing useful about row 3 in the source. The handoff needs to be explicit:

QModelIndex proxyIndex = view->currentIndex();
QModelIndex sourceIndex = proxy->mapToSource(proxyIndex);
if (sourceIndex.isValid())
    openEntry(sourceIndex.row());

The reverse direction matters too. When the parser reports a source index that should be selected, mapFromSource() finds its current position in the proxy. Calling view->setCurrentIndex() with the source index may look plausible, but the index still belongs to a different model and should not be handed to that view.

My next bug was staler. I changed a severity threshold stored by the proxy, emitted a signal from the dialog, and nothing disappeared. The filter result was cached. After changing the threshold, the proxy had to invalidate its filtering so that filterAcceptsRow() ran again:

void LogProxy::setMinimumLevel(int level)
{
    if (minimumLevel == level)
        return;

    minimumLevel = level;
    invalidateFilter();
}

Resetting the source model also made the screen update, but it was the wrong hammer. It discarded selections and told every attached view that the source structure had vanished when only the proxy’s mapping was stale.

Dynamic sorting has a similar edge. If a source value used by the sort or filter changes, the source must emit dataChanged() for that value. The proxy can then reconsider the affected mapping. Without that notification, asking the proxy to repaint merely gives it another chance to use the same old answer.

Selections and drag-and-drop code are where I now look first for mapping mistakes. Anything crossing the proxy boundary should say which side its index belongs to. That small bit of bookkeeping beats opening the wrong error at two in the morning.

The Desktop Is Still a Workbench

I ran today’s KDE 4 session long enough to open a ported application, play a sound through Phonon, and watch a Plasma applet redraw. Then the panel vanished after a configuration change.

That sequence is a useful status report. The architecture is becoming tangible, but this is still a workbench, not a finished desktop. Ported applications exercise Qt 4 and the new libraries. Plasma prototypes test containment and data ideas. Solid and Phonon test boundaries around hardware and multimedia. Integration exposes where those boundaries disagree.

My current smoke test is intentionally small:

cmake ..
make
ctest
startkde

The last command belongs on a test account and display. Configuration formats and services are moving, and preserving my everyday session is more valuable than proving bravery.

I prefer testing one complete path, such as device arrival through Solid to an applet update, over collecting screenshots of disconnected pieces. The caveat is that integration failures can obscure which component broke, so each layer still needs its own focused test.

Today’s vanished panel turned out to be a containment lifecycle mistake, not a rendering failure. That distinction is the work now: finding ownership, sequencing, and API problems while change is still affordable.

It is exciting work. It is also unfinished work. Both statements fit comfortably in the same build, though not always in the same process.

One Event Loop, One Owner

A worker emitted a progress signal today, the label updated twice, and then the application hung while closing. The visible update tempted me to declare the cross-thread code correct. The shutdown was less charitable.

Qt gives each QObject thread affinity. Queued events for that object are processed by the event loop in its owning thread. A signal connected across threads normally becomes a queued call, so the receiver’s slot runs where the receiver lives. This permits a worker to report results without directly touching GUI objects.

The safe shape is simple:

connect(worker, SIGNAL(progress(int)),
        dialog, SLOT(setProgress(int)),
        Qt::QueuedConnection);

The explicit connection type documents the boundary. The worker emits an integer; the dialog owns the label and updates it on the GUI thread.

My hang came from destruction. The dialog waited for the worker while handling its close event, and the worker was waiting for a queued reply from the dialog’s event loop. The GUI event loop could not deliver the reply because it was blocked waiting. Both sides were admirably patient.

I removed the synchronous callback. Closing now requests cancellation, disables the interface, and lets normal event processing continue until the worker announces completion. Only then is it destroyed. Data passed in signals is copied as needed, so custom value types must be registered if they cross a queued connection.

I prefer message-like queued signals between worker and GUI code. They make ownership visible and avoid shared widget state. The caveat is that queued delivery is asynchronous: the sender cannot assume the slot has run, event loops must remain active, and ordering guarantees do not replace a proper state machine.

Nested event loops are another temptation. Opening a modal operation or manually calling event processing can keep paints moving, but it also permits unrelated events to re-enter code whose invariants are half changed. I use that technique only with a very clear boundary, never as a general cure for long work.

The practical test now closes the window at several stages of the job. Starting and finishing are easy; cancellation and teardown reveal who actually owns what. If a design works only while the user behaves, the user is not the unreliable component.