window manager development thread - Desktop Customization & Workflow

Users browsing this thread: 4 Guest(s)
seninha
Long time nixers
We have some wm-writers here on the forums, I would like to call them for sharing their experiences, ideas and knowledge accumulated when writing their window managers.

I'm working on shod (here's its documentation), an hybrid (floating and tiling) window manager that heavily depends on EWMH hints and that is fully controlled by wmctrl.

It started as a sowm fork, then it expanded into something bigger, incorporating code from dwm, berry and katriawm, and inspiration from i3. I used Nye's Xlib Programming Manual (and its chapter 16) as my knowledge base.

One of the things I realized is how X11 imposes its limitations on the wm writers and how much of dummy windows I have to create in order to implement some features. I don't know if using dummy windows is the way to go, but that's what I did. One of the uses of dummy windows was to implement EWMH above and below windows (windows that are set to be above or below others).

Another tricky part was window moving and resizing with the mouse. That's the hackiest part of my code. At first I was using wmutils' xmmv and xmrs for doing that. But later I implemented it on the wm. dwm implements window moving/resizing in an event loop apart from the main xevent loop, but I implemented it in the main event loop. Again, I don't know if my way is the way to go.

Some routines in shod's code have grown into a complex thing, especially the routine that tiles windows and the routine that places window on the screen (when a window is open, shod tries to place it on an empty place on the screen (a place with few windows)).

I've not chosen a fancy algorithm for tiling windows like binary-tree splitting like bspwm, because I'm not an experienced programmer to deal with the data structures imposed by those algorithms. I've chosen something simpler, based on how I use tiling. I only used column layout: the screen is divided into columns and each column can have any number of windows tiled vertically (one above the other). For example, in the drawings bellow.

Code:
┌───┬───┬───┐
│   ├───┤   │
│   │   ├───┤
├───┤   │   │
│   ├───┤   │
└───┴───┴───┘

┌─────┬─────┐
│     │     │
│     │     │
│     ├─────┤
│     │     │
└─────┴─────┘

I realized that this is the only layout I ever use, so I created a wm that only uses this layout. I also realized that spliting-tree kind of tiling complicates the workflow (at least for me).
Even thus, I almost always use up to two columns with up to two windows each. The most common layout I use is the one in the second draw (two columns, the left one with a single window and the right one with up to two windows).

The last improvement I did in shod was to implement the _NET_WM_MOVERESIZE hint. Client-side decorated Gtk3 windows emit this signal when being dragged by their header bar. I think qt applications also use this hint when being dragged by a empty part of their UI.

And also focus.
Focus handling is one of the hardest parts of writing a wm. Even now sometimes I got a sloppy-focus behavior (when closing the last window in a workspace, for example), even when I use the click-to-focus mode. I think I will use more dummy windows for this (that's what katriawm does, I think). I implemented focus history later in the development of shod, so most of the code isn't aware of it. Also, deciding what to focus when a window is closed was one of the trickiest part when I was beginning to write it.

That's it. What is your experience with wm writing? What knowledge or ideas you had for your wm that you want to share to other wm writers? I think that sharing our experience is something good because it can inspire others who aspires writing their own wm. It was someone's report of his own experience in wm writing that inspired me to write mine.
venam
Administrators
(30-08-2020, 09:10 PM)phillbush Wrote: I think that sharing our experience is something good because it can inspire others who aspires writing their own wm.
I am of the same opinion.

(30-08-2020, 09:10 PM)phillbush Wrote: What is your experience with wm writing?
Writing a WM gives some insight into how a desktop is pieced together. There's a lot of things that you learn the hard way when writing a WM, especially when you thought you've done everything fine but suddenly certain features don't work as expected. This is especially true for pop-ups, panels and bars, for multi-window-hierarchy but that should be considered a single window, for workspaces handling, for multiple screen handling, and more.

(30-08-2020, 09:10 PM)phillbush Wrote: What knowledge or ideas you had for your wm that you want to share to other wm writers?
In my opinion, when we are the writer, we often over-estimate the usage of some features or envision things in a way but they end up not being used that way. The most important part is how the user, starting with you, interacts with the WM and if it's seemless. You could record yourself performing some action in multiple ways and see which one is more intuitive and fast. Or you could record which features you use the most over a week period.
seninha
Long time nixers
Other than the _NET_WM_ (EWMH) and the WM_ (ICCCM) hints, there are some hints used by a single wm (such as PEKWM's _PEKWM_FRAME_ID, to handle window tabbing in this wm) and others that multiple wms agree on using (such as ye old motif hints and the pre-gnome3 gnome hints (most wms use those for compatibility purposes, though)).
Do you know other set of hints that at least two different window manager agree to use?

I want to implement tabs in my wm, but I want to conform to the way other wms do.
Also, I discovered that motif hints use some atoms to specify the menu items that operate on X clients, I will also implement that on xmenu/pmenu (so the menu can communicate directly with the window manager) (TWM and CTWM seem to use those hints).

This is the most everything-compliant wm I have found: https://github.com/bbidulock/etwm/blob/master/ewmh.c
Guest0x0
Members
I am also developing my own window manager. It's really a good way to learn about X, but I do admire the architecture of Wayland more. I think Wayland works in a more intuitive way. What I dislike about Wayland is that the concept of composite manager and window manager are merged into the concept of compositor, which is less flexible in terms of composition effects, and is less friendly for people who want to implement a very simple WM.

I am getting much inspiration from subtle (sadly it looks unmaintained now, and the homepage seems dead). The "gravity" mechanism seems to me a good way of placing windows efficiently, without the need to structure windows in some not-completely-intuitive tway (like a tree. Surely tiling layouts are visible, but sometimes they are not so intuitive. For example, a 2x2 grid can be either a tree, two columns or two rows internally, but visually, it is just a 2x2 grid). Currently my design is based on some user-defined regions on the screen, called anchors. The user can perform operations like placing a window on the region covered by one or more anchors, or focusing the top-most window on an anchor.

(30-08-2020, 09:10 PM)phillbush Wrote: Focus handling is one of the hardest parts of writing a wm.
Totally agree. The Alt-Tab cycle way also seem unsatisfactory for me as the order is implicit. But I do have an idea that may improve focusing a little, both for Alt-Tab cycle and window closing. If the WM draw different border color for not only the focused window, but also the "next" and "prev" window (based on the internal order), then Alt-Tab in a setting with 3 to 4 windows become much more intuitive. And if the "prev" window is always focused when current focus is closed, this behavior also becomes visible and more intuitive.

The last thing is configuration. I like the way bspwm, herbstluwm, berry, etc. handle configuration: IPC. Because this way of configuration is not only convenient (simulation of simple text based config, as well as config modification on-the-fly, is possible), but also highly scriptable. AFAIK these WMs typically use domain sockets as the way of configuration. But I wonder if the configuration can be done via X client message towards the root window. This way configuration events and X events can trivially be merged into one event loop (X events and domain sockets don't seem to play well with each other).

Also, I am not very fond of sxhkd, because it lacks the feature of modal key bindings. I have been a long time i3 user, and i3's modal key binding feature is very convenient for me.
seninha
Long time nixers
(09-02-2021, 06:00 AM)Guest0x0 Wrote: AFAIK these WMs typically use domain sockets as the way of configuration. But I wonder if the configuration can be done via X client message towards the root window.
Configuration can be done with client messages. My wm is controlled solely via client messages to the root window and to the managed clients. The downside is that you have to stick with the EWMH hints (or create your own non-standard hints). I stuck with the EWMH hints, but I gave another interpretation to some of them (like using the maximization hints to do tiling, for example).

(09-02-2021, 06:00 AM)Guest0x0 Wrote: Also, I am not very fond of sxhkd, because it lacks the feature of modal key bindings. I have been a long time i3 user, and i3's modal key binding feature is very convenient for me.
You may find mxhkd useful then.

I just had a bad time trying to draw into borders. I thought the 0,0 position of the border was its top left corner, but it actually coincides with the 0,0 origin of the window. So I have to draw on the right and bottom edges of a pixmap in order for it to map into the left and top edges of the borders. Completely non-intuitive. I took a look at 2bwm code to see how it does. But then I gave up and stuck with the solid color border. Maybe I will try drawing borders when I try reparenting again.

I tried reparenting once but then drag-and-drop refused to work. For example, dragging the tabs in the firefox window to reorder them didn't work with reparenting. I probably forgot to do something, I have to read ICCCM more carefully later.
movq
Long time nixers
(21-02-2021, 10:57 PM)phillbush Wrote: I just had a bad time trying to draw into borders. I thought the 0,0 position of the border was its top left corner, but it actually coincides with the 0,0 origin of the window. So I have to draw on the right and bottom edges of a pixmap in order for it to map into the left and top edges of the borders.

Maybe this helps:

https://github.com/vain/dwm-vain/blob/d5...wm.c#L2012

I did normal drawing first and then did a bunch of XCopyArea(), so I don’t have to worry about this weird layout.

(For katriawm, I simply create additional windows and move them right next to the real window. Much easier. No reparenting, but who cares.)

(I have a déjà-vu, didn’t we already discuss this at some point? :))
seninha
Long time nixers
(22-02-2021, 08:37 AM)movq Wrote: (I have a déjà-vu, didn’t we already discuss this at some point? :))

I remember I have sent you a mail asking you why you move windows off screen rather than unmapping them to hide them. I think we talked a bit about reparenting. But I don't think we talked about how pixmap tiling works on borders.
By the way, I tried using XUnmapWindow to hide windows instead of moving them off screen, and it seems to be working.

The biggest problem I got from reparenting was the broken drag-and-drop, like I said. I will research more to see how to fix this.
seninha
Long time nixers
So, I figured out why drag-and-drop didn't work: I was not sending a ConfigureNotify event to the client when I resize/move it, so the client had no idea of the position of the dragged window in reference to its own window.
Guest0x0
Members
(21-02-2021, 10:57 PM)phillbush Wrote: I just had a bad time trying to draw into borders. I thought the 0,0 position of the border was its top left corner, but it actually coincides with the 0,0 origin of the window. So I have to draw on the right and bottom edges of a pixmap in order for it to map into the left and top edges of the borders. Completely non-intuitive. I took a look at 2bwm code to see how it does. But then I gave up and stuck with the solid color border. Maybe I will try drawing borders when I try reparenting again.
Exactly the same issue here... I did a hell lot of experiments to finally figure out how the coordinate system works. I have found this in 2bwm's source code, but no luck in X's manual... And I still don't know WHY the coordinate system was designed as such... Basically the coordinates goes to minus border_width after exceeding inner_width/inner_height + border_width.

(21-02-2021, 10:57 PM)phillbush Wrote: Configuration can be done with client messages. My wm is controlled solely via client messages to the root window and to the managed clients. The downside is that you have to stick with the EWMH hints (or create your own non-standard hints). I stuck with the EWMH hints, but I gave another interpretation to some of them (like using the maximization hints to do tiling, for example).
In my scenario I am not using client message to enable ordinary programs to communicate with the WM, simply as a way to communicate between the WM and a CLI client, just like bspwm, herbstlu, etc. So there is no special need to stick with EWMH here, I guess. I simply allocate a new atom for configuration messages, and pass plain text commands. BerrryWM uses this way, too. However my case is a trickier, because some commands in my WM involve names and are of variable length, while a X client message can hold only 20 bytes of data. So I have to slice every command into several client messages, and join these messages back at the WM side. Dirty, but works so far.
seninha
Long time nixers
I'm writing shod2, a new version of my window manager.
I tried to play with auto-resizing of windows: https://0x0.st/-x9U.mp4
But it is too slow to redraw the window at every mouse motion...
So I'm back at resizing a frame of the window and then commiting the resizing once later: https://0x0.st/-x95.mp4

It's a dirty hack, but works.
That's how other window managers such as fvwm and *box do IIRC.
z3bra
Grey Hair Nixers
(13-09-2021, 01:11 AM)seninha Wrote: But it is too slow to redraw the window at every mouse motion...

There is another solution: only redraw every once in a while. Mouse motion events occur at a really high frequency, so attempting to support that frequency is doomed to feeling sluggish and painful. I discovered that when trying to do it with wmutils (which is even worse than in your case, because linking an event to a resize request occured in a shell script).

I don't know Xlib well, so take my advice with a grain of salt (especially since Xlib is synchronous, while xcb is not), but X event (including mouse move) have a field named "time", which you can use to your advantage: if you save the time of each mouse event, you can compare the time of the current event to the last one, and ignore events that are too "close" from each others, thus forcing the window painting at a maximum framerate.
Here is some pseudo-code (using XCB, sorry):

Code:
int
handle_mouse_motion_event(xcb_motion_notify_event_t *event)
{
        /* declared static so its value is kept between function calls */
        static xcb_timestamp_t lasttime = 0;

        /* ignore motion events if they happen too often */
        if (event->time - lasttime < 32)
                return 0;

        /*
         * update last event time to the current event for later
         * comparison
         */
        lasttime = event->time;

        /*
         * code used to redraw the window according to event
         */

        return 0;
}

The value of "32" between two events was chosen semi-arbitrarily. 32 milliseconds means that you will (at most) repaint your window at ~31 frames per seconds. This is the frame rate used in cinematography, and due to retinal persistence ~24 frames per second will be perceived as "smooth" for most people. You can lower that number to get a higher framerate, but that's at the expense of hitting heavier load on your CPU. After some testing, I found 30 to be a pretty decent value (rounded at 32 because I like powers of 2 !), and you're allowed to steal it. See https://git.z3bra.org/glazier/file/glazier.c.html#l461 for a "real life" example (my own WM). Note that in my case, I also only redraw the window outline, but it's mostly because I find it more pleasant to the eye. I tried to redraw the whole window at that rate, and it worked well (it's just more ugly to see the window content shrink/grow multiple time as your resize the window IMO).

Edit: I modified my code to teleport/repaint borders instead of drawing the outline. No lags can be seen: https://0x0.st/-xVS.webm
seninha
Long time nixers
(13-09-2021, 08:58 AM)z3bra Wrote: you're allowed to steal it
And I did it!

32 is too fast! Window resizing costs a lot more in my case because I need to redecorate borders and title bars and recursively descend the window to recalculate the size of the subwindows and resize them (because the tiling is done inside the window*).
I set the constant to 64: here's how it works: https://0x0.st/-xYC.mp4

* That's the main reason I'm rewriting shod: tiles inside windows, something like the acme text editor and its columns of subwindows.