Kde

Do Not Block the KIO Job

I changed a file-opening routine from local paths to URLs and immediately made the window feel stuck. The transfer was not especially slow. My code was waiting synchronously in the user-interface path, so even a short delay became visible.

KIO is built around jobs. A call such as KIO::get() starts work and returns a KIO::TransferJob; data and completion arrive through signals while the event loop continues processing input and painting.

KIO::TransferJob *job = KIO::get(url, false, false);
connect(job, SIGNAL(data(KIO::Job *, const QByteArray &)),
        this, SLOT(slotData(KIO::Job *, const QByteArray &)));
connect(job, SIGNAL(result(KIO::Job *)),
        this, SLOT(slotResult(KIO::Job *)));

The job chooses an appropriate KIO slave from the URL’s protocol. The slave runs separately, speaks the protocol, and reports data, progress, redirections, and errors back through KIO. My application consumes a common job interface instead of containing FTP, HTTP, and file code.

There are two details I now handle every time. First, data may arrive in several chunks. The data signal is not a promise that one byte array equals one document. I append or parse incrementally. Second, I treat result as the final authority. An empty final chunk does not mean success, and a non-empty first chunk does not mean the transfer will finish.

For a tiny operation, KIO::NetAccess can provide a convenient synchronous wrapper. I still avoid it from slots reached directly through buttons and menus. Nested event loops and blocked windows are an expensive price for saving two slots.

Cancellation also matters. I keep the job pointer, clear it when the job finishes, and kill the job when the owning view goes away. Because jobs are QObjects, sensible parentage helps, but explicit user cancellation should still be reflected in the interface.

Redirection is another reason not to reduce a transfer to one blocking read. The final URL may differ from the requested one, and policy about accepting it belongs in the job flow. Authentication and certificate questions can also require interaction supplied by KDE. A hand-written socket loop tends to rediscover these cases individually, generally when somebody is trying to use the program rather than when I am prepared to debug it.

For uploads I apply the same rule in reverse: feed data when the job requests it and finish according to the job’s protocol. I do not assume that writing a local temporary file and copying it afterward has identical overwrite and error behavior.

I prefer KIO jobs even for local URLs when the operation already accepts a KURL. The mechanism remains uniform and remote files stop being an awkward special case. The caveat is that asynchronous code forces the state to be honest. That is inconvenient for about an hour and useful for the rest of the program’s life.

KParts Ownership in One Minute

I embedded a viewer part, closed its window, and received a crash during shutdown. The viewer worked. My ownership did not.

A KPart is a QObject, and its widget is usually parented into the host’s widget tree. Those are related lifetimes, but they are not an invitation to delete everything in sight. I now keep the part pointer in the host, give the constructor the intended object parent, and let that relationship determine destruction.

The shape is roughly this:

KParts::ReadOnlyPart *part = factory->createPart(
    parentWidget, "viewer widget",
    this, "viewer part",
    "KParts::ReadOnlyPart");

setCentralWidget(part->widget());

The factory creates the component. The part owns its document behavior and supplies a widget for presentation. The shell owns the part through the QObject parent passed to the factory. I do not separately delete the widget; its parent chain handles that.

The other lifetime to remember is the component factory or library handle used to create the part. KDE’s loader machinery normally keeps the code available while objects from it exist. Bypassing that machinery and unloading a library while its C++ object remains is an efficient way to make a virtual call jump into empty space.

My preference is one obvious owner and no emergency deletes in destructors. If I cannot explain who owns the part without drawing several arrows, I fix that before debugging anything else. The crash at exit is rarely impressed by a beautiful toolbar.

A Small DCOP Command Is a Good Test

I needed to tell a running application to refresh a document, and my first instinct was to add a socket and invent a tiny protocol. Then I remembered that a KDE session already has DCOP, including discovery, calls, and argument marshalling. It seemed wasteful to build a worse version before lunch.

The dcop command is the quickest way to see what is available:

dcop
dcop konqueror
dcop konqueror KonquerorIface

The first command lists registered applications. Supplying an application lists its objects and interfaces; adding an object shows callable functions. This is useful beyond scripting. It tells me whether the object registered under the name I expected and whether the signature exported by the program matches my mental version of it.

For a simple application object, I derive from DCOPObject and name the interface:

class RefreshIface : virtual public DCOPObject
{
    K_DCOP
    k_dcop:
        virtual void refresh(const QString &path) = 0;
};

The implementation registers through the application’s DCOPClient. Calls are sent through the session’s DCOP server, which locates the target client and carries the serialized arguments. The caller does not need the target’s process identifier, and the receiver can expose a small interface rather than its internal objects.

I keep DCOP methods coarse. Sending refresh(QString) is reasonable; reproducing every setter on a widget is not. Once the interface mirrors the user interface, outside programs depend on details I will want to change.

There is also a choice between a call that waits for a reply and one that merely sends a message. I use a synchronous call only when the result is genuinely needed. Blocking while another desktop process opens a file or asks a question can make both programs feel frozen.

Application names need care as well. A second instance may register with a numbered name rather than replacing the first one. If the operation concerns a particular document, guessing the first process in the list is unsafe. I either arrange single-instance behavior deliberately or discover the intended application and object from information I already have. DCOP removes the need for process identifiers; it does not remove the need to choose the right process.

Failures should remain visible. A call can fail because the application exited, the object disappeared, or the signature changed. I report that to the caller instead of treating a missing reply as an empty successful result.

My rule now is to prove the exchange with the command-line client first. If dcop cannot see or call the object, another page of C++ is unlikely to improve matters. This is one of those rare debugging techniques that removes code, which makes it suspiciously pleasant.

Porting an Application to KDE 3

KDE 3.0 is out, which turned my experimental port into work that other people may actually try to compile. The application was modest, but it touched enough of KDE 2 and Qt 2 to make a straight rebuild unrealistic. I expected a grand rewrite. The successful approach was much duller: establish a clean baseline, move one boundary at a time, and keep the program runnable.

I started with a separate source copy and a fresh build directory. Building outside the source tree made stale generated files much easier to spot.

make -f Makefile.cvs
mkdir build-kde3
cd build-kde3
../configure --prefix="$KDEDIR" --enable-debug
make

The first pass was entirely mechanical. I fixed renamed headers, methods whose signatures changed, and code that depended on implicit conversions no longer accepted by Qt 3. I did not improve the design while doing this. Combining a port with a cleanup makes each failure ambiguous, and ambiguity is already supplied free of charge by C++ error messages.

Qt’s collection classes deserved special attention. Code that stored pointers in a QList-style class or relied on automatic deletion needed its ownership checked rather than merely adjusted until it compiled. Qt 3’s classes are familiar, but familiar is not identical. I wrote down who owned each object, especially widgets, list items, and objects parented to a QObject. Parent-child deletion is useful only when the parent is the intended owner.

Signals and slots were the next boundary. Every class using them needed Q_OBJECT, and every connection needed matching argument types. The string notation hides mistakes from the compiler:

connect(action, SIGNAL(activated()),
        this, SLOT(openDocument()));

I ran the application from a terminal and treated every QObject::connect warning as a real defect. It is tempting to ignore one because the window still appears. That tends to last until the ignored connection is the Save action.

The KDE layer was easier once Qt was quiet. I checked command-line handling through KCmdLineArgs, application setup through KApplication, actions, standard shortcuts, and XMLGUI resource files. An old .rc file can leave menus looking plausible while actions are missing or duplicated. Removing installed test copies before checking the new one prevented Konqueror and the application from finding an obsolete resource in another prefix.

Configuration code also needed a deliberate pass. I kept reads and writes together and checked defaults rather than assuming the old configuration file contained every key:

KConfig *config = KGlobal::config();
config->setGroup("View");
bool showStatus = config->readBoolEntry("ShowStatusBar", true);

For document handling, I resisted replacing everything at once. The application already separated its document widget reasonably well, so I first made that compile, then considered whether it should become a KPart. A port is a poor time to discover that the document, window, and network code have secretly been one object wearing three hats.

The most useful testing sequence was deliberately boring:

  1. Start with an empty configuration directory.
  2. Open a local file from the command line.
  3. Open it through the file dialog.
  4. Modify, save, and reopen it.
  5. Exercise every action and shortcut.
  6. Close with unsaved changes.
  7. Repeat with an existing KDE 2 configuration.

I also built with debugging enabled and ran under gdb. A port often exposes old lifetime errors because widget destruction order or an event sequence changes slightly. The new library did not necessarily create the dangling pointer; it merely stopped arranging the furniture around it.

Binary compatibility was not a goal for local plug-ins compiled against KDE 2. They had to be rebuilt against the KDE 3 and Qt 3 headers and libraries. Trying to preserve old C++ objects across that boundary would save a compilation and purchase a much stranger crash.

Before calling the port complete, I also built a source archive and compiled that archive in an empty directory. A working source tree can quietly depend on generated files, unlisted headers, or resources left by yesterday’s build. make distcheck is useful when the project supports it, but even the plain exercise of unpacking the archive elsewhere catches embarrassing omissions. I then installed into a temporary prefix and started a KDE session with that prefix in its search path. This tests what was shipped rather than what happens to be within reach of the compiler.

Warnings received their own pass after behavior was correct. I enabled the warnings supported by the older compiler and looked especially for suspicious casts, hidden overloads, and variables whose type changed under Qt 3. I did not turn every warning cleanup into part of the port, but I did investigate each one. A warning newly exposed by a library transition often points directly at an assumption that deserves daylight.

My preference after this exercise is to make the smallest source change that produces correct KDE 3 behavior, then do cleanup separately. The port remains reviewable, regressions have fewer hiding places, and users get a working application sooner. It is not heroic engineering, but heroic engineering has an alarming habit of requiring heroic debugging.

What moc Actually Generates

I added a signal to a small Qt 3 class today, rebuilt, and received an undefined reference to the class’s virtual table. That message sounds like a C++ problem. In this case it meant I had forgotten about moc.

Qt’s signals and slots need information that the C++ compiler does not produce. A class using them declares Q_OBJECT:

class Counter : public QObject
{
    Q_OBJECT
public:
    Counter(QObject *parent = 0, const char *name = 0);

signals:
    void changed(int value);

public slots:
    void setValue(int value);
};

The Meta Object Compiler reads that declaration and writes ordinary C++ containing the meta-object table and dispatch code. The generated file supplies, among other things, the pieces behind className(), signal activation, and calls made through the string-based connect() mechanism.

In a KDE autotools project the build rules normally run moc for headers containing Q_OBJECT. If I put the class declaration in a .cpp file, I include its generated output at the bottom:

#include "counter.moc"

After adding or removing Q_OBJECT, regenerating the build files is sometimes necessary. A clean rebuild is also cheaper than interpreting every vtable error as an omen.

I like signals and slots because the sender does not need to know the receiver. The caveat is that the compiler cannot check the old SIGNAL() and SLOT() strings fully. A connection can compile and then complain at run time, so I watch the diagnostic output and keep signatures simple.