The Fun of writing a Window Manager

13.04.2019

Since the beginning of this year I have been working on rather a large project, writing my own window manager.

For those who do not know what that means, a window manager is a program and client of the X display server, that controls how the windows of other clients are arranged. It updates the placement and size of the windows based on received events, like mouse clicks or key presses or new windows appearing or others. There is a ton of events to respond to, so one can get rather creative.

Window managers can be separated into three basic categories: Stacking, manual tiling and dynamic tiling. A stacking window manager is the most basic one and also the most common one. It allows windows to overlap each other. They usually let the user move the windows via clicking and dragging on the windows title bar. A tiling window manager on the other hand prevents windows from overlapping each other. Instead they are tiled, meaning they are all displayed next to each other, only using space not occupied by other windows. They usually expand the windows to use the maximal usable space and tend to be controlled by key binds rather than mouse clicks. A manual tiling window manager enables the user to finely control the placement and size of each window and the overall layout, while a dynamic window manager automatically generates the layout based on a few user controlled variables.

I decided that I wanted antares (which, by the way, is the name I chose for this project) to implement dynamic tiling. Tiling because after a year of constant usage of i3 and dwm I find stacking to be quite uncomfortable. Dynamic because it reduces how much I have to think to get the windows into an efficient layout. I also decided to give it two layers, one tiling (the main layer) and a secondary one that is stacking (so antares is in fact both dynamic tiling and stacking). The reasoning for this is simple: I want windows like dialog boxes or basically anything I want to quickly use and then close again to appear in the middle of the screen, on top of the other windows, so it is easy to spot and does not disturb the layout. Also some graphical applications really do not like their windows being resized the way a tiling window manager does and some even have windows of a fixed size.

The layout I decided to use is the same as implemented in dwm and awesome: Clients stored in a linear stack (manual tiling window managers often use a tree-like data structure). This linear stack is then represented by the clients windows being displayed in a linear vertical stack. A live configurable amount of windows is displayed in a separate stack (the so called "master area") to the left of the main stack. I also implemented a secondary layout, which arranges all windows into equal sized columns, which is handy for people like me who use ultra wide monitors, because, unlike the main layout, it allows to have more than two windows side-to-side (I usually work with three). The layouts can be switched during runtime and the arranging mechanics are designed in a way, that implementing additional layouts is quite easy. The layout of the stacking layer is the simplest: It operates just like any stacking window manager, meaning one can move the windows, which can overlap and are always on top of tiled windows, with the mouse (as well as with key binds). To resize windows in the stacking area, the user can scroll on the windows title bar.

Antares features multiple workspaces, which can all have different layouts. Support for multiple monitors is also there in theory, however this is currently broken and will cause antares to crash. This should in theory be rather easy to fix, however the discovery of the solution will come after a painful time of staring at the monitor stack mechanisms, so I have preferred other tasks.

Let's get technical: Antares is written in C (over 3400 lines at the time of writing this) and uses the Xlib library to interact with the X server. I would have preferred to use the newer XCB library, but Xlib is far more approachable for a simple hobbyist like me. I did write a window manager for X instead of a Wayland compositor, because I do not like how the Wayland protocol forces the compositor to do compositing, window management, being a server as well as multiple other things usually handled by separate programs in X.

I wanted to have title bars for the windows as well as a status panel, so I had to come up with some way of drawing text on X windows. I decided to go with the cairo library, which appears to be one of the nicest ways to do so (at least compared with suckles' drw.c), but I might switch to pango in the future. This also allows me to draw basically anything to the title bars and status panel, like fancy shapes or even images.

Because I wanted title bars, I had to make antares into a reparenting window manager. This means that all windows spawned by clients are moved into a container window, which also houses the title bar window. I made the annoying observation that quite a lot of windows belonging to graphical applications do not like being reparented. Even after implementing quite a lot of EWMH, I still have problems with certain programs. I mostly use the command line, meaning that I will rarely open anything graphical, but it is still annoying. Luckily Firefox works well.

Speaking of EWMH (Extended Window Manager Hints); I love and hate this standard at the same time. While it enables a window manager to easily recognize what type of window it deals with and also allows the clients and the window manager to communicate, it is also terribly bloated and understanding how to implement it was definitely not trivial. Antares now supports a medium sized subset of EWMH, which is enough to recognize fixed sized windows, recognize windows that have to be ignored, if a client wants to be full-screened and a few other things. However, I believe that I will have to implement way more of it to solve my problems regarding graphical applications.

Another problem I have is cairo. Although using it is generally nice and easy, it has some serious issues. Most annoyingly, it fails to correctly calculate the size of text. This means that highlighting text with a different background is not fun. For the workspace indicator, I have cairo draw all names not highlighted and later go back to draw the background as well as the name again. This is a really dirty solution and I do not like that I had to use it. Maybe pango on top of cairo will solve this, otherwise I will have to search for a different way of drawing text to a X window, which would be a shame, as cairo allows drawing fancy shapes, a feature I am already using.

The other issue with cairo is also annoying: Everything drawn with it will randomly flicker. I might be able to solve this with double buffering, meaning first drawing to an invisible canvas and when that is finished porting the canvas to the X window I want to draw on, but I did not have the time yet to try this.