Kde

Let DCOP Tell You the Signature

A DCOP call failed today even though the method name was correct. The method name, unfortunately, was only part of the name that mattered. I had sent an int; the exported function expected an unsigned value.

Before changing code, I ask the running object what it exposes:

dcop myapp MainIface

The output includes complete function signatures. In this case it shows void setPage(unsigned int). I then call it from the command line with a small test argument:

dcop myapp MainIface setPage 3

DCOP identifies methods by their signatures and marshals known Qt types through QDataStream. The sending and receiving sides must therefore agree on argument types, order, and return type. A visually similar C++ declaration is not necessarily the same wire contract.

For custom values, both programs need compatible streaming operators and type registration. I avoid custom structures in small public interfaces when a QString, QCString, or simple list expresses the operation clearly. Fewer private types also make the command-line client useful.

When writing the C++ caller, I keep the request and reply types visible next to each other:

QByteArray data;
QDataStream args(data, IO_WriteOnly);
unsigned int page = 3;
args << page;

QCString replyType;
QByteArray replyData;
if (!client->call(app, "MainIface", "setPage(unsigned int)",
                  data, replyType, replyData)) {
    kdWarning() << "DCOP setPage call failed" << endl;
}

For a function with a return value, I verify replyType before extracting from replyData. Deserializing a reply under the wrong assumption merely converts a useful interface error into strange application state.

The application and object names should not be copied from a single run if multiple instances are possible. I list registered clients and decide whether the program is meant to address one instance, every instance, or the instance owning a particular document. A suffix added to keep names unique is normal, not a DCOP server losing its arithmetic.

I also preserve failures in scripts. The command-line client returns useful status, and a shell script should check it rather than carrying on after the target application has exited. Interprocess calls cross a lifetime boundary; yesterday’s object name is not a reservation for today.

I keep the exported interface narrow and test it independently of the window. If the command works but a toolbar action does not, DCOP has been acquitted and I can inspect the connection instead.

I let dcop provide the authoritative spelling instead of copying it from memory. Memory is fast, local, and apparently not type-safe.

Moving a KDE 3 Application to 3.1

After installing KDE 3.1, I rebuilt a small KDE 3.0 application expecting either complete success or a useful compiler failure. It built cleanly and then displayed a toolbar assembled from an older installed XML file. Compatibility had done its job; my installation habits had not.

The move from 3.0 to 3.1 is not the upheaval that the KDE 2 to KDE 3 port was. The first task should be a clean rebuild against the new headers and libraries, not a rewrite:

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

I check the configure summary to ensure it found the intended Qt and KDE prefixes. Having 3.0 under one prefix and 3.1 under another can produce a convincing but incoherent mixture of headers, libraries, services, and data files. kde-config --prefix and kde-config --path data help explain what the running session will search.

Installed resources matter as much as the executable. XMLGUI files, icons, desktop entries, service descriptions, and translations may survive from an earlier build. During testing I inspect the installed paths and remove only obsolete copies belonging to my application. Otherwise a changed action name in the source can appear to have no effect because the shell merged an old resource file.

I then exercise integration points: actions and standard shortcuts, session restoration, DCOP interfaces, KIO URLs, printing, and embedded parts. These boundaries are more likely to expose an assumption than an ordinary button click. Running from a terminal catches connection warnings and missing service messages that a polished window politely conceals.

If I choose to use a new 3.1 facility, I make that a separate change after the compatible build works. This keeps the required minimum version honest. Merely compiling on my workstation does not mean every user has the same minor release.

I also keep the old configuration file for one run and start with an empty one for another. Migration defects and default-setting defects often cancel each other on a developer’s account. Testing both states prevents an old saved value from making the fresh installation appear correct.

My preference is to preserve KDE 3.0 compatibility unless the new feature materially improves the program. The caveat is that testing both versions then becomes real work, including compile tests against both sets of development files. Compatibility declared but never built is just a hopeful comment with better publicity.

Adding a Source File to KDE Autotools

I added history.cpp to an application, ran make, and spent several minutes wondering why none of my mistakes appeared in the compiler output. The build was not unusually forgiving. I had neglected to tell it that the file existed.

In a typical KDE source tree, the immediate place to look is Makefile.am. The target’s source list must contain both implementation and relevant headers:

bin_PROGRAMS = notebook

notebook_SOURCES = main.cpp notebook.cpp history.cpp \
                   notebook.h history.h

notebook_LDADD = $(LIB_KDEUI)

Automake uses this description to generate the lower-level make rules. KDE’s admin machinery and configure input then arrange compiler flags, library checks, translations, installation paths, and moc handling. Editing the generated Makefile is temporary theatre; the next regeneration removes the change with admirable confidence.

After changing the build description, I regenerate from the source root:

make -f Makefile.cvs
./configure --prefix="$KDEDIR" --enable-debug
make

For an out-of-tree build I run configure from the build directory after regenerating the source-side files. If I have added a new subdirectory, it also needs to appear in the parent’s SUBDIRS, and its makefile must be included by the configure machinery. A source file alone is simpler and normally needs only the target list.

Headers with Q_OBJECT are worth checking after the first build. If moc output is absent, I confirm the header is listed and that the macro is visible in the class declaration. Stale dependencies can confuse matters, so I prefer a fresh build directory before rewriting build rules that may already be correct.

Installation lists are a separate concern from compilation lists. A desktop file belongs in the appropriate data variable with its destination, translations belong in their directory, and public headers need an installation rule only if they truly form an interface. Putting every file into EXTRA_DIST may make an archive complete, but it does not make installation correct.

I check both operations before sending a change:

make dist
make install DESTDIR=/tmp/notebook-install

The staged installation makes missing icons and resources visible without scattering test files through the real prefix. I inspect the archive too, because a local build can use a file that never entered the distribution. Generated files and source files have different rules, and guessing which will be recreated on another machine is avoidable.

I keep custom shell commands out of Makefile.am unless the normal primary and source variables cannot express the job. Automake syntax can feel indirect, but the standard forms understand clean builds, distribution archives, and dependency tracking. A handcrafted shortcut often understands only the exact machine on which it was written, which is a rather narrow audience.

KDEPrint and CUPS: Where the Options Go

I was chasing a printing bug because the application appeared to ignore duplex selection. The drawing code was innocent. The useful clue was that printing to a PostScript file worked while the CUPS queue did not apply the requested option.

KDEPrint sits between the application and the print system. The application paints pages through Qt’s printer interface, KDEPrint presents the common and driver-specific choices, and the selected backend submits the resulting job. With CUPS, queue capabilities and options come from CUPS rather than being invented by every application.

For an application, the core remains ordinary painting:

KPrinter printer;
if (printer.setup(this)) {
    QPainter painter(&printer);
    painter.drawText(72, 72, "Printer test");
    painter.end();
}

The setup dialog gathers destination, page range, copies, and available properties. KPrinter then carries those choices into the KDEPrint path. The application’s responsibility is to paginate correctly and draw each requested page. It should not run lpr itself and then wonder why the desktop’s settings vanished.

When options misbehave, I now separate the stages. First I print to a file and inspect whether the PostScript pages are sane. Next I check the queue and its advertised defaults:

lpstat -p -d
lpoptions -p office -l

Then I submit a small known document directly with lp to see whether CUPS and the printer agree without the application involved. This distinguishes a rendering defect from a queue, PPD, filter, or device problem.

Driver-specific settings deserve some humility. Duplex names, available resolutions, and media trays are described by the printer’s PPD and may differ across queues. Hard-coding one printer’s option into application code is therefore both fragile and impolite. KDEPrint already has the unenviable job of asking the printer what it thinks it can do.

Page ranges require coordination with the application. If the print dialog says pages 3 through 5, my painting loop must honor the values reported by the printer object and call newPage() between pages. I test one page, a middle range, reverse order when offered, and several copies. These cases expose off-by-one errors that a ten-page full print politely hides under ten sheets of paper.

I also end the painter explicitly before inspecting the job. That completes PostScript generation and lets the backend submit a finished stream. Error reporting after setup deserves attention; cancellation from the dialog is not the same event as a backend rejecting a job.

I prefer letting KDEPrint expose those details and keeping application settings limited to document matters. The caveat is that a successful job submission only means the print system accepted responsibility. It does not mean paper will emerge. Printers retain a small but determined independence from software engineering.

Testing KHTML with a Small Page

A page rendered incorrectly in Konqueror today, and the original example was nearly two hundred lines long. CSS, tables, scripts, and invalid markup were all competing for suspicion. Reading KHTML code at random was not going to settle the argument.

I reduced the page until only the bad behavior remained:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<html>
<head>
  <title>KHTML test</title>
  <style type="text/css">
    .box { width: 120px; padding: 10px; border: 1px solid black; }
  </style>
</head>
<body>
  <div class="box">A small test.</div>
</body>
</html>

That small file answers several questions quickly. Does standards or quirks handling matter? Is the problem in parsing, style selection, or layout? Does removing the width make it disappear? It also gives me something suitable for a regression test rather than a private copy of somebody’s entire site.

Konqueror embeds KHTML as a part. KHTML parses the document into its DOM representation, applies style rules, creates rendering objects, and lays them out in the available view. A symptom on screen can therefore begin much earlier than painting. If the DOM tree is wrong because the markup triggered error recovery, adjusting the renderer only conceals the first mistake.

I use khtml’s debug areas selectively when running Konqueror from a terminal. Turning on every message produces a heroic quantity of text and very little enlightenment. A focused trace plus a ten-line document usually beats scrolling.

I also check the same local file after clearing variables from the experiment: no proxy, no stale cache, no external style sheet, and no script unless script is essential to the failure. Then I add pieces back one at a time.

When the reduced case involves script, I separate the DOM mutation from the layout result. First I save a static document representing the tree after the script has run. If the static page still fails, the problem is no longer dependent on timing or the script interpreter. If it succeeds, I inspect when the mutation occurs and which notification should have caused style recalculation or layout.

Fonts can produce similarly misleading reports. A line wrapping differently is not automatically a box calculation bug. I note the selected family and size and try a common fixed font before changing layout code. Image dimensions and delayed loading deserve the same treatment because they can trigger a later relayout.

Finally, I state the expected result in plain words beside the test. A small HTML file without an expectation becomes a puzzle for the next person, who may reasonably conclude that the current rendering was the intended one.

My preference is to preserve the smallest failing page alongside the fix. The caveat is that reduction can accidentally remove malformed input that caused the original path, so I keep the original page until the reduced case is proven equivalent. Minimal examples are excellent witnesses, but occasionally they change their story under questioning.