In the winterbreeze branch, we evolve herbstluftwm into a modern, object-oriented design. Development takes place here on github.
This effort started with the idea of porting hlwm to C++ and removing the Glib dependency. While hlwm master currently is already C++ code, it is not fundamentally different from a typical C program. E.g., it uses global variables, passes around function pointers and does not follow any basic OOP principles.
The branch is maintained by Thorsten and ypnos (Johannes). You can discuss with us on IRC and via the mailing list.
Any input on the design, proposals, or the offer to work with us on the branch is always welcome.
A window manager is, like most GUI-related systems, inherently object-oriented. Herbstluftwm brings some great innovations and does several other things just the “right way” from the user point of view. However, internally, it has grown out of its original plain C software design. For example, the object system that allows inspection and manipulation of the window manager's behavior from outside, was added later on in the game. It is a great help to users, but a burden to the programmer. Many design choices are hardwired into herbstluftwm. For example, a fixed set of hooks exist and care has to be taken to trigger a certain hook at the specific place in the code.
In winterbreeze, the object system becomes a first citizen in the herbstluftwm software design. This is just natural, given the object-oriented software model that comes with C++. It allows us to abstract all the overhead of objects, hooks, etc. away from the core components. Furthermore, it solves the problem of how different modules of herbstluftwm interact. Currently, they are very interwoven, and most source files include pretty much every other module's header. In winterbreeze, the object tree can also be used internally to pass information along or trigger actions. The object tree serves as a global interface between modules.
Here is a short overview of what we aim for:
Here is what we don't care about:
Let's have a look at the classes first:
Entity ← Attribute ← Attribute_<T> ← DynamicAttribute ← Action ← Directory ← Root ← Object ← Hook ← ClientManager ← HookManager
As you can see, basically everything that will end up in the object tree (i.e., externally accessible through the tree) is derived from
Entity itself doesn't do much, but it always has a name and a type that can be printed. We will walk through all the derived classes in a minute. Let's talk about the
Root class first, though. It is a singleton and it is global in a sense that it can always be fetched by calling
Root::get(). The root node holds hlwm modules and can be used internally to obtain them. Right now we have modules that manage clients and hooks. So this is how the tree looks like:
Root → HookManager → Hook 1 → Hook 2 → … → ClientManager → Client 1 → Client 2 → …
The list will go on, with TagManager, MonitorManager etc.
Object can hold certain attributes. An important concept is that attributes are dumb. Whenever they are accessed externally, their owner will process the access. E.g., when an attribute, even a static one, is read through the object tree interface, it is passing the request to its owner; which then typically just returns the value, but might also do something else.
These are member variables of their owning
Object, but they are not just simple types like
Attribute_<Color> and so on. This makes them accessible through the object tree for reading and writing. It also makes them hookable. That means, whenever the attribute is changed, a hook pointing to that attribute will be notified. Hooks are covered later.
In the previous object tree design (master branch), some attributes would be special.
The first case is dynamic variables. For example, an attribute
count might not return a stored value, but be computed on demand. This is what
DynamicAttribute is for. It is a shallow object; the owner of the attribute needs to provide the answer to the inquiry. There is also a possible case for a dynamic writeable attribute.
The second case is triggers. Now, in the previous design, the user could “write” a value into certain attributes to let some magic happen. We change this to the explicit
Action type. An action can be triggered with optional arguments.
Directory is an
Entity that has children, which are also directories. It does not have attributes or actions.
Object is a
Directory that also can also hold attributes and actions.
Directories (and therefore, also objects) have the important property that they are always held by shared pointers (
std::shared_ptr). This means they can never disappear while being referenced.
They are also aware of being held by a shared pointer and derived from
An example use for this is self-passing of the object to a hook (as explained later) watching on it. The hook does not keep shared pointers though, it uses
std::weak_ptr instead. This is how you should hold a directory/object that you don't want to keep alive. In the hook scenario, an object should not be prevented from being cleaned up (e.g., a tag being removed) just because a hook is watching on it. On the contrary, a tag should not disappear as long as there are clients on it.
Previously, hooks were hardwired additions to the code like the object tree. Because we make the object tree inherent to the herbstluftwm design, we can instead exploit the object tree for hooks. The objects themselves (e.g.
Tag) do not explicitely handle hooks in the typical case. So in our example, adding or removing tags, or changing a tags attribute, can just be done without taking care of any hooks. The second change is that because of this, basically (almost) every directory and attribute (or action) is hookable.
Hook is derived from
Object, so that the hooks themselves can be inspected and externally triggered through the object tree.
A hook is identified by its name, which is the full path of a directory or attribute/action. If the hook points to a directory, it will notify of additions or removals of children. For example, if you need to watch focus changes, you can hook into
clients.focus.name. To watch for added or removed tags, you watch either
tags.by-name. In the latter case, you will get notified of the name of the new (or removed tag), while in the former case, you will get notified of index changes (which are typically less helpful).
HookManager is responsible for adding and removing hooks, which can be triggered through the object tree using the respective actions. So the user will issue:
While we now gain the power to define custom hooks and watch them, no special interface is introduced to manage hooks. The object tree can be used for all kinds of operations this way, removing the need to define tailored commands.
Hooks can also be externally triggered using their
emit action, with custom arguments. A custom hook that is not in relation to any real directories or attributes therefore can be called, for example,
user.foo. This basically resembles the old
emit_hook command, but makes custom hooks explicit. It could make that command obsolete, but right now remaining backwards-compatibility is one of our goals.
We are current working on this component.