You can download a copy of this document and all related example code here:
KDE window borders are controlled by the KDE ‘window manager’, known as ‘kwin’. There are two kwin theming mechanisms available.
At the time of writing this document, the pixmap theme mechanism had not stabilised. This tutorial will cover only code theming.
Update: KDE now supports IceWM themes. This feature should be in the next release, so if you want to make a pixmap theme, follow the guidelines for making an IceWM theme and it should work nicely with KDE !
kwin provides a useful abstraction for a window - the Client class. This class encapsulates a window decoration frame and contains a wrapper for the actual X11 window being managed.
kwin is a pure window manager in the sense that it does not handle extra desktop apparatus such as icons, docks, pagers etc. This means that the only part of the visual appearance that can be themed via a kwin theme is the client border.
The Client class inherits from QWidget, so parts of its API should be familiar. Also, the fact that QWidget derives from QObject means that you can use signals and slots.
You will want to be able to read user configuration from kwin, so you need to include headers from kwin and link your client with the kwin library. This will allow you to use simple constructs such as options->font(true) to discover the default font that has been requested for caption display.
kwin loads code themes as shared library modules. The library must contain an implementation of a class derived from KWinInternal::Client. Your implementation overrides the default, giving the custom behaviour you require.
The shared library must also define and export a single function which allows kwin to instantiate your derived class. The implementation is typically very simple, looking something like this:
Client * allocate(Workspace * workspace, WId windowID, int tool)
{
return new MyStyle(workspace, windowID, 0 != tool);
}
You might not need the workspace and windowID parameters in your derived class, but you must pass them to the base class constructor in your constructor’s initialisation list.
The tool parameter is actually a boolean value, stating whether the window should have a ‘toolbox’ style. You may wish to write two Client-derived classes, one for a ‘norma’l window and one for ‘toolbox’ windows, or you may find it easier to simply select some behavioural/appearance traits within one implementation.
I suggest you download the example and unpack it. This will allow you see how the information presented here translates to implementation.
As an example, I have created a ‘web’ theme, which will give a ‘flat’ look, with some simple effects similar to those you might see on web pages.
The first thing you need to do is to declare your class which is derived from KWinInternal::Client. Next you should declare the virtual methods which you will implement. I have implemented most of the available methods in my style. It is advisable to start by implementing the methods with a simple call to the base class implementation. This will get you up and running. You can alter the behaviour later.
The most important methods are paintEvent and mousePosition.
To avoid flicker, you should use setBackgroundMode(NoBackground) in your constructor and ensure you paint each pixel only once in paintEvent. This is easier than it sounds, as you can use QPainter::setClipRegion.
mousePosition is used by the base class to query the type of decoration that the mouse is over. You simply need to work out where your titlebar, borders, etc. are and return the appropriate value.
Handling the area called the ‘title bar’ (the place where the title text is displayed) is easiest if you use a QSpacerItem and add it to the layout. This way, you have a stretchable blank space but you don’t need to create a new widget. Just get the rect of the spacer in your paintEvent() and draw in that space.
Figure 1 gives a suggestion for a simple logical layout. Here you can see the ‘window wrapper’ (containing the window for which we are providing borders) in the centre. Above this, ‘titleSpacer’ is a QSpacerItem, held in a QHBoxLayout with buttons either side of it.
The very edge of the window is divided into eight sections. Each section has a different semantic. The semantics are defined by you, when you implement Client::mousePosition.
Of course, the logical layout may not be strictly visually delimited when painted on screen. For example, there may be no visual cues to differentiate border sections.
It is fairly sane to argue that the user will be able to adapt to layout which is different from their experience without too much difficulty, providing you either provide strong visual cues or use a fairly logical layout.
Figure 2 shows a slightly different logical layout. The changed upper part of this layout is similar to that implemented by the example plugin.
There are advantages and disadvantages to both example layouts, for instance, the second example allows the user to ‘throw’ the pointer to a screen edge or corner and immediately press the ‘iconify’ or ‘clos’e buttons or move the window by dragging the titlebar.
Similarly, it is possible to argue that a larger resize ‘handle’ on the lower right corner of the window is useful, as this is the most frequently ‘grabbed’ section. It could of course be argued that the first layout is more consistent with itself.
If you are concerned about usability, be sure to weigh up the relative merits of different logical layouts before settling on your visual design.
The easiest way to take control of your buttons is to write your own simple button class. If you then derive a new class from the base for each button type, custom behaviour is easy to achieve.
Another good reason for writing your own button class is that if your buttons extend to the edge of the border, you may want to draw differently depending on whether the button is at a corner or on an edge, or add some shaping code.
To make a non-rectangular shape for your windows, you need to create a mask. There are two ways to do this, using bitmaps or QRegion. Either way is fine, but remember that the more complex the mask, the more time it takes the X server to process, so you can slow the X server down quite a bit with a complex mask.
This code removes the pixel at the top-left corner of the widget. In the plugin example, you will find a more complex implementation of this function, which ‘rounds off’ all four corners of the window.
void MyClient::doShape()
{
QRegion mask(0, 0, width(), height());
mask -= QRegion(0, 0, 1, 1);
setMask(mask);
}
As you can see, it’s quite simple to create and apply a mask. The part which takes effort is figuring out exactly which pixels you want to remove.
I have provided support in my plugin for switching off the shaping. It’s nice to be able to ‘throw’ the mouse cursor to the edge or corner of the screen and be able to move the window or press a button. Shaping the corners stops me pressing the iconify and close buttons, which I find annoying, even if it does look prettier shaped.
This is your plugin, so you can do what you like with it. You have the choice of conforming to the standard behaviour of the KDE styles or implementing your own. You should at least briefly reflect on the fact that users will be familiar with standard behaviour, so if double-clicking the titlebar causes the window to be deleted, they might be surprised.
Note that standard behaviour depends on user configuration, so try not to hard code it in. For example, in mouseDoubleClickEvent() it’s best to call Workspace::performWindowOperation(this, options->operationTitlebarDblClick(), which will perform the user-configured action.
If kwin gets itself into a knot, debugging becomes difficult. You might not be able to give a window focus or type into it. The usual reason for this is that drkonqi is trying to start to tell you that kwin is dead, but kwin is dead (though still running) and everything just locks up. If you’re running X on the local machine, you can switch to a text terminal and kill kwin off. You might need to start another window manager (such as twm) to be able to give the focus to a window again.
To avoid this hassle, the best thing to do is to disable drkonqi. Setting the environment variable KDE_DEBUG signals that you want to debug, so for Bourne-shell users, export KDE_DEBUG=1 is the magic you need. Now you can do gdb kwin and when it crashes, you can read the backtrace and halt kwin. Much nicer.
Writing a kwin style is not difficult. The only hard work is figuring out how to draw, store pixmaps, etc. in the most efficient manner, where efficiency is a tradeoff between memory use, CPU time and X server roundtrips. The provided example plugin avoids efficiency problems by keeping things simple.
If you want to see a more complex client, which performs a few backflips in order to keep pixmap handling efficient, look at the code for the RISC OS style, which you can find in kdebase/kwin/clients/riscos. Most of the pixmap handling is concentrated in the Static class. Notice that it’s possible to use fairly complex pixmaps but keep the spirit of the user’s colour preferences.