X-DWM -- help me fix a bug in C source code

680 Views Asked by At

I am using dwm (6.2) window manager and I found a bug which I would love to solve.

Window manager uses "master area" and "stack area" where windows are put:

enter image description here

It is possible to move window at the top of the "stack area" to the bottom of the "master area" using ALT + i. It is also possible to move windows from the bottom of the "master area" back to the top of "stack area" using ALT + d.

Now in this case, if I use ALT + i, layout changes and after the key combination there are two windows in the "master area":

enter image description here

I repeat it again and now there are three windows in the "master area":

enter image description here

I repeat it yet again and now there are three windows in the "master area" which has 100% width:

enter image description here

If I would at this point decide to return windows from the "master area" to the "stack area" I would start pressing ALT + d and windows would imediately return back to the "stack area". This works okay.

But I intentionaly make a mistake and instead press ALT + i again for example three more times. It looks like nothing happens...

But now if I try to return windows from the "master area" to the "stack area" I first need to press ALT + d three more times and nothing will happen! And then finaly, when I press ALT + d for the fourth time, window manager will return the first window from the bottom of the "master area" to the top of the "stack area".

So this is not well thought out and should be considered a bug...


There must be some sort of a counter in the source code which was incremented three more times by pressing ALT + i but it should not increase after all windows are already in the "master area".


In config.def.h source file (www) there is a part of the code where keys are assigned. And here I can see that when user presses ALT + i function incnmaster() is called and is passed an argument .i = +1 (I don't understand this argument).

static Key keys[] = {
    /* modifier                     key        function        argument */
    ...
    { MODKEY,                       XK_i,      incnmaster,     {.i = +1 } },
    { MODKEY,                       XK_d,      incnmaster,     {.i = -1 } },
    ... 
};

Key is a structure inside dwm.c source file (www):

typedef struct {
    unsigned int mod;
    KeySym keysym;
    void (*func)(const Arg *);
    const Arg arg;
} Key;

Function incnmaster() is defined in dwm.c source file (www):

void
incnmaster(const Arg *arg)
{
    selmon->nmaster = MAX(selmon->nmaster + arg->i, 0);
    arrange(selmon);
} 

where arg is a pointer to Arg (Arg*) which is a union (I don't quite understand how to deal with the argument .i = +1):

 typedef union {
    int i;
    unsigned int ui;
    float f;
    const void *v;
 } Arg;

selmon is a structure Monitor:

struct Monitor {
    char ltsymbol[16];
    float mfact;
    int nmaster;
    int num;
    int by;               /* bar geometry */
    int mx, my, mw, mh;   /* screen size */
    int wx, wy, ww, wh;   /* window area  */
    unsigned int seltags;
    unsigned int sellt;
    unsigned int tagset[2];
    int showbar;
    int topbar;
    Client *clients;
    Client *sel;
    Client *stack;
    Monitor *next;
    Window barwin;
    const Layout *lt[2];
};

MAX is defined in a separate source file util.h (www) as:

#define MAX(A, B)    ((A) > (B) ? (A) : (B))

and function arrange() is defined like this:

 void
 arrange(Monitor *m)
 {
    if (m)
        showhide(m->stack);
    else for (m = mons; m; m = m->next)
        showhide(m->stack);
    if (m) {
        arrangemon(m);
        restack(m);
    } else for (m = mons; m; m = m->next)
        arrangemon(m);
}

I don't think I have to dig any further...


Now I think I need to implement some sort of an if sentantce in the C code to prevent selmon->nmaster to increase too much, but I am a bit confused. Can anyone help?

2

There are 2 best solutions below

0
On

Nobody answered before I could figure this one out myself. Problem is that Suckless team never implemented any kind of mechanism to count a number of opened windows (they call them clients). This is why I added a int nclients; member to struct Monitor:

struct Monitor {
    char ltsymbol[16];
    float mfact;
    int nmaster;
    int nclients;
    int num;
    int by;               /* bar geometry */
    int mx, my, mw, mh;   /* screen size */
    int wx, wy, ww, wh;   /* window area  */
    unsigned int seltags;
    unsigned int sellt;
    unsigned int tagset[2];
    int showbar;
    int topbar;
    Client *clients;
    Client *sel;
    Client *stack;
    Monitor *next;
    Window barwin;
    const Layout *lt[2];
};

And then I made sure it is initialized to 0 at boot time by adding m->nclients = 0; in createmon() function which I guessed is ran at the beginning:

Monitor *
createmon(void)
{
    Monitor *m;

    m = ecalloc(1, sizeof(Monitor));
    m->tagset[0] = m->tagset[1] = 1;
    m->mfact = mfact;
    m->nmaster = nmaster;
    m->nclients = 0;
    m->showbar = showbar;
    m->topbar = topbar;
    m->gappx = gappx;
    m->lt[0] = &layouts[0];
    m->lt[1] = &layouts[1 % LENGTH(layouts)];
    strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
    return m;
}

Then I made sure that my counter nclients is increased when new window appears. I added ++selmon->nclients; and arrange(selmon); (to be able to move clients to stack/master imediately after you close one of them) statement at the beginning of the spawn() function:

void
spawn(const Arg *arg)
{
    ++selmon->nclients;
    arrange(selmon);
    if (arg->v == dmenucmd)
        dmenumon[0] = '0' + selmon->num;
    if (fork() == 0) {
        if (dpy)
            close(ConnectionNumber(dpy));
        setsid();
        execvp(((char **)arg->v)[0], (char **)arg->v);
        fprintf(stderr, "dwm: execvp %s", ((char **)arg->v)[0]);
        perror(" failed");
        exit(EXIT_SUCCESS);
    }
}

Counter should be decreased when window is closed. This is why I added a --selmon->nclients; and arrange(selmon); (to be able to move clients to stack/master imediately after you close one of them) at the top of the killclient() function:

void
killclient(const Arg *arg)
{
    --selmon->nclients;
    arrange(selmon);
    if (!selmon->sel)
        return;
    if (!sendevent(selmon->sel->win, wmatom[WMDelete], NoEventMask, wmatom[WMDelete], CurrentTime, 0 , 0, 0)) {
        XGrabServer(dpy);
        XSetErrorHandler(xerrordummy);
        XSetCloseDownMode(dpy, DestroyAll);
        XKillClient(dpy, selmon->sel->win);
        XSync(dpy, False);
        XSetErrorHandler(xerror);
        XUngrabServer(dpy);
    }
}

Now that the counter was set up, I could use it to rewrite incnmaster() function like this:

void
incnmaster(const Arg *arg)
{
    if((arg->i > 0) && (selmon->nmaster < selmon->nclients)){
        ++selmon->nmaster;
    }
    if((arg->i < 0) && (selmon->nmaster > 0)){
        --selmon->nmaster;
    }
    arrange(selmon);

}

Pay attention. My DWM is a bit patched, so some lines might be a bit different than yours, but just stick to the same philosophy and you can fix this.

This solution partialy works. It only fails to work when I:

A. use dmenu

dmenu when started can (a) open a client or (b) do nothing. In case (a) everything works as expected, but in case (b) nmaster and nclients become out of sync again.

So for example if I do scenario (b) once and use CTRL+i endless times, I will have to use CTRL+d once and nothing will happen, but if I use it once more one window is moved from master to stack area.

B. run any kind of windowed application from a terminal

It looks like DWM can't keep track of windows that are run from a terminal and treats them in a wrong way... In this case as well nmaster and nclients become out of sync.


Does anyone know if there is any other function besides spawn that is executed when any kind of window is opened?

This is still not solved!

1
On

Why are you holding number of clients when it's linked list? You cant obtain number of clients on demand. Similar code can be found monocle count patch. If you really have to keep that count yourself (for performance reasons), I would look at any place where is Client list modified by dwm and project that modification to counter.

Structure Client contains pointer to a "next" Client, implementation may depend on whenever you want to use multihead support, but using code similar to Client* c = nexttiled(c->next), where first reference can be obtained from Monitor by calling Client* c = nexttiled(monitor->clients). It you count these in loop that should be enough.

If you want to still keep count yourself, I would find functions within dwm.c working with Client (detach, attach, ...) and find which are modifying list where you can increment/decrement counter based on executed operation.