Blog

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.

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.