monsterwm

Personal build of monsterwm
git clone git://git.gormless.xyz/monsterwm.git
Log | Files | Refs | README | LICENSE

monsterwm.c (49193B)


      1 /* see license for copyright and license */
      2 
      3 #include <stdlib.h>
      4 #include <stdio.h>
      5 #include <err.h>
      6 #include <stdarg.h>
      7 #include <unistd.h>
      8 #include <string.h>
      9 #include <signal.h>
     10 #include <sys/wait.h>
     11 #include <X11/Xutil.h>
     12 #include <X11/XKBlib.h>
     13 #include <X11/Xproto.h>
     14 #include <X11/Xatom.h>
     15 
     16 #define LENGTH(x)       (sizeof(x)/sizeof(*x))
     17 #define CLEANMASK(mask) (mask & ~(numlockmask | LockMask))
     18 #define BUTTONMASK      ButtonPressMask|ButtonReleaseMask
     19 #define ISFFT(c)        (c->isfull || c->isfloat || c->istrans)
     20 #define ROOTMASK        SubstructureRedirectMask|ButtonPressMask|SubstructureNotifyMask|PropertyChangeMask
     21 
     22 enum { RESIZE, MOVE };
     23 enum { TILE, MONOCLE, BSTACK, GRID, FLOAT, MODES };
     24 enum { WM_PROTOCOLS, WM_DELETE_WINDOW, WM_COUNT };
     25 enum { NET_SUPPORTED, NET_FULLSCREEN, NET_WM_STATE, NET_ACTIVE, NET_COUNT };
     26 
     27 /**
     28  * argument structure to be passed to function by config.h
     29  * com - function pointer ~ the command to run
     30  * i   - an integer to indicate different states
     31  * v   - any type argument
     32  */
     33 typedef union {
     34     const char** com;
     35     const int i;
     36     const void *v;
     37 } Arg;
     38 
     39 /**
     40  * a key struct represents a combination of
     41  * mod    - a modifier mask
     42  * keysym - and the key pressed
     43  * func   - the function to be triggered because of the above combo
     44  * arg    - the argument to the function
     45  */
     46 typedef struct {
     47     unsigned int mod;
     48     KeySym keysym;
     49     void (*func)(const Arg *);
     50     const Arg arg;
     51 } Key;
     52 
     53 /**
     54  * a button struct represents a combination of
     55  * mask   - a modifier mask
     56  * button - and the mouse button pressed
     57  * func   - the function to be triggered because of the above combo
     58  * arg    - the argument to the function
     59  */
     60 typedef struct {
     61     unsigned int mask, button;
     62     void (*func)(const Arg *);
     63     const Arg arg;
     64 } Button;
     65 
     66 /**
     67  * define behavior of certain applications
     68  * configured in config.h
     69  *
     70  * class   - the class or name of the instance
     71  * desktop - what desktop it should be spawned at
     72  * follow  - whether to change desktop focus to the specified desktop
     73  */
     74 typedef struct {
     75     const char *class;
     76     const int desktop;
     77     const Bool follow, floating;
     78 } AppRule;
     79 
     80 /* exposed function prototypes sorted alphabetically */
     81 static void change_desktop(const Arg *arg);
     82 static void client_to_desktop(const Arg *arg);
     83 static void focusurgent();
     84 static void killclient();
     85 static void last_desktop();
     86 static void move_down();
     87 static void move_up();
     88 static void moveresize(const Arg *arg);
     89 static void mousemotion(const Arg *arg);
     90 static void next_win();
     91 static void prev_win();
     92 static void quit(const Arg *arg);
     93 static void resize_master(const Arg *arg);
     94 static void resize_stack(const Arg *arg);
     95 static void rotate(const Arg *arg);
     96 static void rotate_filled(const Arg *arg);
     97 static void spawn(const Arg *arg);
     98 static void swap_master();
     99 static void switch_mode(const Arg *arg);
    100 static void togglepanel();
    101 
    102 #include "config.h"
    103 
    104 /**
    105  * a client is a wrapper to a window that additionally
    106  * holds some properties for that window
    107  *
    108  * next    - the client after this one, or NULL if the current is the last client
    109  * isurgn  - set when the window received an urgent hint
    110  * isfull  - set when the window is fullscreen
    111  * isfloat - set when the window is floating
    112  * istrans - set when the window is transient
    113  * win     - the window this client is representing
    114  *
    115  * istrans is separate from isfloat as floating windows can be reset to
    116  * their tiling positions, while the transients will always be floating
    117  */
    118 typedef struct Client {
    119     struct Client *next;
    120     Bool isurgn, isfull, isfloat, istrans;
    121     Window win;
    122 } Client;
    123 
    124 /**
    125  * properties of each desktop
    126  *
    127  * masz - the size of the master area
    128  * sasz - additional size of the first stack window area
    129  * mode - the desktop's tiling layout mode
    130  * head - the start of the client list
    131  * curr - the currently highlighted window
    132  * prev - the client that previously had focus
    133  * sbar - the visibility status of the panel/statusbar
    134  */
    135 typedef struct {
    136     int mode, masz, sasz;
    137     Client *head, *curr, *prev;
    138     Bool sbar;
    139 } Desktop;
    140 
    141 /* hidden function prototypes sorted alphabetically */
    142 static Client* addwindow(Window w, Desktop *d);
    143 static void buttonpress(XEvent *e);
    144 static void cleanup(void);
    145 static void clientmessage(XEvent *e);
    146 static void configurerequest(XEvent *e);
    147 static void deletewindow(Window w);
    148 static void desktopinfo(void);
    149 static void destroynotify(XEvent *e);
    150 static void enternotify(XEvent *e);
    151 static void focus(Client *c, Desktop *d);
    152 static void focusin(XEvent *e);
    153 static unsigned long getcolor(const char* color, const int screen);
    154 static void grabbuttons(Client *c);
    155 static void grabkeys(void);
    156 static void grid(int x, int y, int w, int h, const Desktop *d);
    157 static void keypress(XEvent *e);
    158 static void maprequest(XEvent *e);
    159 static void monocle(int x, int y, int w, int h, const Desktop *d);
    160 static Client* prevclient(Client *c, Desktop *d);
    161 static void propertynotify(XEvent *e);
    162 static void removeclient(Client *c, Desktop *d);
    163 static void run(void);
    164 static void setfullscreen(Client *c, Desktop *d, Bool fullscrn);
    165 static void setup(void);
    166 static void sigchld(int sig);
    167 static void stack(int x, int y, int w, int h, const Desktop *d);
    168 static void tile(Desktop *d);
    169 static void unmapnotify(XEvent *e);
    170 static Bool wintoclient(Window w, Client **c, Desktop **d);
    171 static int xerror(Display *dis, XErrorEvent *ee);
    172 static int xerrorstart(Display *dis, XErrorEvent *ee);
    173 
    174 /**
    175  * global variables
    176  *
    177  * running      - whether the wm is accepting and processing more events
    178  * wh           - screen height
    179  * ww           - screen width
    180  * dis          - the display aka dpy
    181  * root         - the root window
    182  * wmatoms      - array holding atoms for ICCCM support
    183  * netatoms     - array holding atoms for EWMH support
    184  * desktops     - array of managed desktops
    185  * currdeskidx  - which desktop is currently active
    186  */
    187 static Bool running = True;
    188 static int wh, ww, currdeskidx, prevdeskidx, retval;
    189 static unsigned int numlockmask, win_unfocus, win_focus;
    190 static Display *dis;
    191 static Window root;
    192 static Atom wmatoms[WM_COUNT], netatoms[NET_COUNT];
    193 static Desktop desktops[DESKTOPS];
    194 
    195 /**
    196  * array of event handlers
    197  *
    198  * when a new event is received,
    199  * call the appropriate handler function
    200  */
    201 static void (*events[LASTEvent])(XEvent *e) = {
    202     [KeyPress]         = keypress,     [EnterNotify]    = enternotify,
    203     [MapRequest]       = maprequest,   [ClientMessage]  = clientmessage,
    204     [ButtonPress]      = buttonpress,  [DestroyNotify]  = destroynotify,
    205     [UnmapNotify]      = unmapnotify,  [PropertyNotify] = propertynotify,
    206     [ConfigureRequest] = configurerequest,    [FocusIn] = focusin,
    207 };
    208 
    209 /**
    210  * array of layout handlers
    211  *
    212  * x - the start position in the x axis to place clients
    213  * y - the start position in the y axis to place clients
    214  * w - available width  that windows have to expand
    215  * h - available height that windows have to expand
    216  * d - the desktop to tile its clients
    217  */
    218 static void (*layout[MODES])(int x, int y, int w, int h, const Desktop *d) = {
    219     [TILE] = stack, [BSTACK] = stack, [GRID] = grid, [MONOCLE] = monocle,
    220 };
    221 
    222 /**
    223  * add the given window to the given desktop
    224  *
    225  * create a new client to hold the new window
    226  *
    227  * if there is no head at the given desktop
    228  * add the window as the head
    229  * otherwise if ATTACH_ASIDE is not set,
    230  * add the window as the last client
    231  * otherwise add the window as head
    232  */
    233 Client* addwindow(Window w, Desktop *d) {
    234     Client *c = NULL, *t = prevclient(d->head, d);
    235     if (!(c = (Client *)calloc(1, sizeof(Client)))) err(EXIT_FAILURE, "cannot allocate client");
    236     if (!d->head) d->head = c;
    237     else if (!ATTACH_ASIDE) { c->next = d->head; d->head = c; }
    238     else if (t) t->next = c; else d->head->next = c;
    239 
    240     XSelectInput(dis, (c->win = w), PropertyChangeMask|FocusChangeMask|(FOLLOW_MOUSE?EnterWindowMask:0));
    241     return c;
    242 }
    243 
    244 /**
    245  * on the press of a key binding (see grabkeys)
    246  * call the appropriate handler
    247  */
    248 void buttonpress(XEvent *e) {
    249     Desktop *d = NULL; Client *c = NULL;
    250     Bool w = wintoclient(e->xbutton.window, &c, &d);
    251 
    252     if (w && CLICK_TO_FOCUS && c != d->curr && e->xbutton.button == FOCUS_BUTTON) focus(c, d);
    253 
    254     for (unsigned int i = 0; i < LENGTH(buttons); i++)
    255         if (CLEANMASK(buttons[i].mask) == CLEANMASK(e->xbutton.state) &&
    256             buttons[i].func && buttons[i].button == e->xbutton.button) {
    257             if (c && d->curr != c) focus(c, d);
    258             buttons[i].func(&(buttons[i].arg));
    259         }
    260 }
    261 
    262 /**
    263  * focus another desktop
    264  *
    265  * to avoid flickering (esp. monocle mode):
    266  * first map the new windows
    267  * first the current window and then all other
    268  * then unmap the old windows
    269  * first all others then the current
    270  */
    271 void change_desktop(const Arg *arg) {
    272     if (arg->i == currdeskidx || arg->i < 0 || arg->i >= DESKTOPS) return;
    273     Desktop *d = &desktops[(prevdeskidx = currdeskidx)], *n = &desktops[(currdeskidx = arg->i)];
    274     if (n->curr) XMapWindow(dis, n->curr->win);
    275     for (Client *c = n->head; c; c = c->next) XMapWindow(dis, c->win);
    276     XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = SubstructureNotifyMask});
    277     for (Client *c = d->head; c; c = c->next) if (c != d->curr) XUnmapWindow(dis, c->win);
    278     if (d->curr) XUnmapWindow(dis, d->curr->win);
    279     XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.event_mask = ROOTMASK});
    280     if (n->head) { tile(n); focus(n->curr, n); }
    281     desktopinfo();
    282 }
    283 
    284 /**
    285  * remove all windows in all desktops by sending a delete window message
    286  */
    287 void cleanup(void) {
    288     Window root_return, parent_return, *children;
    289     unsigned int nchildren;
    290 
    291     XUngrabKey(dis, AnyKey, AnyModifier, root);
    292     XQueryTree(dis, root, &root_return, &parent_return, &children, &nchildren);
    293     for (unsigned int i = 0; i < nchildren; i++) deletewindow(children[i]);
    294     if (children) XFree(children);
    295     XSync(dis, False);
    296 }
    297 
    298 /**
    299  * move the current focused client to another desktop
    300  *
    301  * add the current client as the last on the new desktop
    302  * then remove it from the current desktop
    303  */
    304 void client_to_desktop(const Arg *arg) {
    305     if (arg->i == currdeskidx || arg->i < 0 || arg->i >= DESKTOPS || !desktops[currdeskidx].curr) return;
    306     Desktop *d = &desktops[currdeskidx], *n = &desktops[arg->i];
    307     Client *c = d->curr, *p = prevclient(d->curr, d), *l = prevclient(n->head, n);
    308 
    309     /* unlink current client from current desktop */
    310     if (d->head == c || !p) d->head = c->next; else p->next = c->next;
    311     c->next = NULL;
    312     XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = SubstructureNotifyMask});
    313     if (XUnmapWindow(dis, c->win)) focus(d->prev, d);
    314     XChangeWindowAttributes(dis, root, CWEventMask, &(XSetWindowAttributes){.event_mask = ROOTMASK});
    315     if (!(c->isfloat || c->istrans) || (d->head && !d->head->next)) tile(d);
    316 
    317     /* link client to new desktop and make it the current */
    318     focus(l ? (l->next = c):n->head ? (n->head->next = c):(n->head = c), n);
    319 
    320     if (FOLLOW_WINDOW) change_desktop(arg); else desktopinfo();
    321 }
    322 
    323 /**
    324  * receive and process client messages
    325  *
    326  * check if window wants to change its state to fullscreen,
    327  * or if the window want to become active/focused
    328  *
    329  * to change the state of a mapped window, a client MUST
    330  * send a _NET_WM_STATE client message to the root window
    331  * message_type must be _NET_WM_STATE
    332  *   data.l[0] is the action to be taken
    333  *   data.l[1] is the property to alter three actions:
    334  *   - remove/unset _NET_WM_STATE_REMOVE=0
    335  *   - add/set _NET_WM_STATE_ADD=1,
    336  *   - toggle _NET_WM_STATE_TOGGLE=2
    337  *
    338  * to request to become active, a client should send a
    339  * message of _NET_ACTIVE_WINDOW type. when such a message
    340  * is received and a client holding that window exists,
    341  * the window becomes the current active focused window
    342  * on its desktop.
    343  */
    344 void clientmessage(XEvent *e) {
    345     Desktop *d = NULL; Client *c = NULL;
    346     if (!wintoclient(e->xclient.window, &c, &d)) return;
    347 
    348     if (e->xclient.message_type        == netatoms[NET_WM_STATE] && (
    349         (unsigned)e->xclient.data.l[1] == netatoms[NET_FULLSCREEN]
    350      || (unsigned)e->xclient.data.l[2] == netatoms[NET_FULLSCREEN])) {
    351         setfullscreen(c, d, (e->xclient.data.l[0] == 1 || (e->xclient.data.l[0] == 2 && !c->isfull)));
    352         if (!(c->isfloat || c->istrans) || !d->head->next) tile(d);
    353     } else if (e->xclient.message_type == netatoms[NET_ACTIVE]) focus(c, d);
    354 }
    355 
    356 /**
    357  * configure a window's size, position, border width, and stacking order.
    358  *
    359  * windows usually have a prefered size (width, height) and position (x, y),
    360  * and sometimes borer with (border_width) and stacking order (above, detail).
    361  * a configure request attempts to reconfigure those properties for a window.
    362  *
    363  * we don't really care about those values, because a tiling wm will impose
    364  * its own values for those properties.
    365  * however the requested values must be set initially for some windows,
    366  * otherwise the window will misbehave or even crash (see gedit, geany, gvim).
    367  *
    368  * some windows depend on the number of columns and rows to set their
    369  * size, and not on pixels (terminals, consoles, some editors etc).
    370  * normally those clients when tiled and respecting the prefered size
    371  * will create gaps around them (window_hints).
    372  * however, clients are tiled to match the wm's prefered size,
    373  * not respecting those prefered values.
    374  *
    375  * some windows implement window manager functions themselves.
    376  * that is windows explicitly steal focus, or manage subwindows,
    377  * or move windows around w/o the window manager's help, etc..
    378  * to disallow this behavior, we 'tile()' the desktop to which
    379  * the window that sent the configure request belongs.
    380  */
    381 void configurerequest(XEvent *e) {
    382     XConfigureRequestEvent *ev = &e->xconfigurerequest;
    383     XWindowChanges wc = { ev->x, ev->y,  ev->width, ev->height, ev->border_width, ev->above, ev->detail };
    384     if (XConfigureWindow(dis, ev->window, ev->value_mask, &wc)) XSync(dis, False);
    385     Desktop *d = NULL; Client *c = NULL;
    386     if (wintoclient(ev->window, &c, &d)) tile(d);
    387 }
    388 
    389 /**
    390  * clients receiving a WM_DELETE_WINDOW message should behave as if
    391  * the user selected "delete window" from a hypothetical menu and
    392  * also perform any confirmation dialog with the user.
    393  */
    394 void deletewindow(Window w) {
    395     XEvent ev = { .type = ClientMessage };
    396     ev.xclient.window = w;
    397     ev.xclient.format = 32;
    398     ev.xclient.message_type = wmatoms[WM_PROTOCOLS];
    399     ev.xclient.data.l[0]    = wmatoms[WM_DELETE_WINDOW];
    400     ev.xclient.data.l[1]    = CurrentTime;
    401     XSendEvent(dis, w, False, NoEventMask, &ev);
    402 }
    403 
    404 /**
    405  * output info about the desktops on standard output stream
    406  *
    407  * the information is formatted as a space separated line
    408  * where each token contains information about a desktop.
    409  * each token is a formatted as ':' separated string of values.
    410  * the values are:
    411  *   - the desktop number/id
    412  *   - the desktop's client count
    413  *   - the desktop's tiling layout mode/id
    414  *   - whether the desktop is the current focused (1) or not (0)
    415  *   - whether any client in that desktop has received an urgent hint
    416  *
    417  * once the info is collected, immediately flush the stream
    418  */
    419 void desktopinfo(void) {
    420     Desktop *d = NULL;
    421     Client *c = NULL;
    422     Bool urgent = False;
    423 
    424     for (int w = 0, i = 0; i < DESKTOPS; i++, w = 0, urgent = False) {
    425         for (d = &desktops[i], c = d->head; c; urgent |= c->isurgn, ++w, c = c->next);
    426         printf("%d:%d:%d:%d:%d%c", i, w, d->mode, i == currdeskidx, urgent, i == DESKTOPS-1 ? '\n':' ');
    427     }
    428     fflush(stdout);
    429 }
    430 
    431 /**
    432  * generated whenever a client application destroys a window
    433  *
    434  * a destroy notification is received when a window is being closed
    435  * on receival, remove the client that held that window
    436  */
    437 void destroynotify(XEvent *e) {
    438     Desktop *d = NULL; Client *c = NULL;
    439     if (wintoclient(e->xdestroywindow.window, &c, &d)) removeclient(c, d);
    440 }
    441 
    442 /**
    443  * when the mouse enters a window's borders, that window,
    444  * if has set notifications of such events (EnterWindowMask)
    445  * will notify that the pointer entered its region
    446  * and will get focus if FOLLOW_MOUSE is set in the config.
    447  */
    448 void enternotify(XEvent *e) {
    449     Desktop *d = NULL; Client *c = NULL, *p = NULL;
    450 
    451     if (!FOLLOW_MOUSE || (e->xcrossing.mode != NotifyNormal && e->xcrossing.detail == NotifyInferior)
    452         || !wintoclient(e->xcrossing.window, &c, &d) || e->xcrossing.window == d->curr->win) return;
    453 
    454     if ((p = d->prev))
    455         XChangeWindowAttributes(dis, p->win, CWEventMask, &(XSetWindowAttributes){.do_not_propagate_mask = EnterWindowMask});
    456     focus(c, d);
    457     if (p) XChangeWindowAttributes(dis, p->win, CWEventMask, &(XSetWindowAttributes){.event_mask = EnterWindowMask});
    458 }
    459 
    460 /**
    461  * 1. set current/active/focused and previously focused client
    462  *    in other words, manage curr and prev references
    463  * 2. restack clients
    464  * 3. highlight borders and set active window property
    465  * 4. give input focus to the current/active/focused client
    466  */
    467 void focus(Client *c, Desktop *d) {
    468     /* update references to prev and curr,
    469      * previously focused and currently focused clients.
    470      *
    471      * if there are no clients (!head) or the new client
    472      * is NULL, then delete the _NET_ACTIVE_WINDOW property
    473      *
    474      * if the new client is the prev client then
    475      *  - either the current client was removed
    476      *    and thus focus(prev) was called
    477      *  - or the previous from current is prev
    478      *    ie, two consecutive clients were focused
    479      *    and then prev_win() was called, to focus
    480      *    the previous from current client, which
    481      *    happens to be prev (curr == c->next).
    482      * (below: h:head p:prev c:curr)
    483      *
    484      * [h]->[p]->[c]->NULL   ===>   [h|p]->[c]->NULL
    485      *            ^ remove current
    486      *
    487      * [h]->[p]->[c]->NULL   ===>   [h]->[c]->[p]->NULL
    488      *       ^ prev_win swaps prev and curr
    489      *
    490      * in the first case we need to update prev reference,
    491      * choice here is to set it to the previous from the
    492      * new current client.
    493      * the second case is handled as any other case, the
    494      * current client is now the previously focused (prev = curr)
    495      * and the new current client is now curr (curr = c)
    496      *
    497      * references should only change when the current
    498      * client is different from the one given to focus.
    499      *
    500      * the new client should never be NULL, except if,
    501      * there is no other client on the workspace (!head).
    502      * prev and curr always point to different clients.
    503      *
    504      * NOTICE: remove client can remove any client,
    505      * not just the current (curr). Thus, if prev is
    506      * removed, its reference needs to be updated.
    507      * That is handled by removeclient() function.
    508      * All other reference changes for curr and prev
    509      * should and are handled here.
    510      */
    511     if (!d->head || !c) { /* no clients - no active window - nothing to do */
    512         XDeleteProperty(dis, root, netatoms[NET_ACTIVE]);
    513         d->curr = d->prev = NULL;
    514         return;
    515     } else if (d->prev == c && d->curr != c->next) { d->prev = prevclient((d->curr = c), d);
    516     } else if (d->curr != c) { d->prev = d->curr; d->curr = c; }
    517 
    518     /* restack clients
    519      *
    520      * stack order is based on client properties.
    521      * from top to bottom:
    522      *  - current when floating or transient
    523      *  - floating or trancient windows
    524      *  - current when tiled
    525      *  - current when fullscreen
    526      *  - fullscreen windows
    527      *  - tiled windows
    528      *
    529      * num of n:all fl:fullscreen ft:floating/transient windows
    530      */
    531     int n = 0, fl = 0, ft = 0;
    532     for (c = d->head; c; c = c->next, ++n) if (ISFFT(c)) { fl++; if (!c->isfull) ft++; }
    533     Window w[n];
    534     w[(d->curr->isfloat || d->curr->istrans) ? 0:ft] = d->curr->win;
    535     for (fl += !ISFFT(d->curr) ? 1:0, c = d->head; c; c = c->next) {
    536         XSetWindowBorder(dis, c->win, c == d->curr ? win_focus:win_unfocus);
    537         /*
    538          * a window should have borders in any case, except if
    539          *  - the window is fullscreen
    540          *  - the window is not floating or transient and
    541          *      - the mode is MONOCLE or,
    542          *      - it is the only window on screen
    543          */
    544         XSetWindowBorderWidth(dis, c->win, c->isfull || (!ISFFT(c) &&
    545             (d->mode == MONOCLE || !d->head->next)) ? 0:BORDER_WIDTH);
    546         if (c != d->curr) w[c->isfull ? --fl:ISFFT(c) ? --ft:--n] = c->win;
    547         if (CLICK_TO_FOCUS || c == d->curr) grabbuttons(c);
    548     }
    549     XRestackWindows(dis, w, LENGTH(w));
    550 
    551     XSetInputFocus(dis, d->curr->win, RevertToPointerRoot, CurrentTime);
    552     XChangeProperty(dis, root, netatoms[NET_ACTIVE], XA_WINDOW, 32,
    553                     PropModeReplace, (unsigned char *)&d->curr->win, 1);
    554 
    555     XSync(dis, False);
    556 }
    557 
    558 /**
    559  * dont give focus to any client except current.
    560  * some apps explicitly call XSetInputFocus (see
    561  * tabbed, chromium), resulting in loss of input
    562  * focuse (mouse/kbd) from the current focused
    563  * client.
    564  *
    565  * this gives focus back to the current selected
    566  * client, by the user, through the wm.
    567  */
    568 void focusin(XEvent *e) {
    569     Desktop *d = &desktops[currdeskidx];
    570     if (d->curr && d->curr->win != e->xfocus.window) focus(d->curr, d);
    571 }
    572 
    573 /**
    574  * find and focus the first client that received an urgent hint
    575  * first look in the current desktop then on other desktops
    576  */
    577 void focusurgent(void) {
    578     Client *c = NULL;
    579     int d = -1;
    580     for (c = desktops[currdeskidx].head; c && !c->isurgn; c = c->next);
    581     while (!c && d < DESKTOPS-1) for (c = desktops[++d].head; c && !c->isurgn; c = c->next);
    582     if (c) { if (d != -1) change_desktop(&(Arg){.i = d}); focus(c, &desktops[currdeskidx]); }
    583 }
    584 
    585 /**
    586  * get a pixel with the requested color to
    587  * fill some window area (such as borders)
    588  */
    589 unsigned long getcolor(const char* color, const int screen) {
    590     XColor c; Colormap map = DefaultColormap(dis, screen);
    591     if (!XAllocNamedColor(dis, map, color, &c, &c)) err(EXIT_FAILURE, "cannot allocate color");
    592     return c.pixel;
    593 }
    594 
    595 /**
    596  * register button bindings to be notified of
    597  * when they occur.
    598  * the wm listens to those button bindings and
    599  * calls an appropriate handler when a binding
    600  * occurs (see buttonpress).
    601  */
    602 void grabbuttons(Client *c) {
    603     unsigned int b, m, modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
    604 
    605     for (m = 0; CLICK_TO_FOCUS && m < LENGTH(modifiers); m++)
    606         if (c != desktops[currdeskidx].curr) XGrabButton(dis, FOCUS_BUTTON, modifiers[m],
    607                 c->win, False, BUTTONMASK, GrabModeAsync, GrabModeAsync, None, None);
    608         else XUngrabButton(dis, FOCUS_BUTTON, modifiers[m], c->win);
    609 
    610     for (b = 0, m = 0; b < LENGTH(buttons); b++, m = 0) while (m < LENGTH(modifiers))
    611         XGrabButton(dis, buttons[b].button, buttons[b].mask|modifiers[m++], c->win,
    612                       False, BUTTONMASK, GrabModeAsync, GrabModeAsync, None, None);
    613 }
    614 
    615 /**
    616  * register key bindings to be notified of
    617  * when they occur.
    618  * the wm listens to those key bindings and
    619  * calls an appropriate handler when a binding
    620  * occurs (see keypressed).
    621  */
    622 void grabkeys(void) {
    623     KeyCode code;
    624     XUngrabKey(dis, AnyKey, AnyModifier, root);
    625     unsigned int k, m, modifiers[] = { 0, LockMask, numlockmask, numlockmask|LockMask };
    626 
    627     for (k = 0, m = 0; k < LENGTH(keys); k++, m = 0)
    628         while ((code = XKeysymToKeycode(dis, keys[k].keysym)) && m < LENGTH(modifiers))
    629             XGrabKey(dis, code, keys[k].mod|modifiers[m++], root, True, GrabModeAsync, GrabModeAsync);
    630 }
    631 
    632 /**
    633  * grid mode / grid layout
    634  * arrange windows in a grid aka fair
    635  */
    636 void grid(int x, int y, int w, int h, const Desktop *d) {
    637     int n = 0, cols = 0, cn = 0, rn = 0, i = -1;
    638     for (Client *c = d->head; c; c = c->next) if (!ISFFT(c)) ++n;
    639     for (cols = 0; cols <= n/2; cols++) if (cols*cols >= n) break; /* emulate square root */
    640     if (n == 0) return; else if (n == 5) cols = 2;
    641 
    642     int rows = n/cols, ch = h - USELESSGAP, cw = (w - USELESSGAP)/(cols ? cols:1);
    643     for (Client *c = d->head; c; c = c->next) {
    644         if (ISFFT(c)) continue; else ++i;
    645         if (i/rows + 1 > cols - n%cols) rows = n/cols + 1;
    646         XMoveResizeWindow(dis, c->win, x + cn*cw + USELESSGAP, y + rn*ch/rows + USELESSGAP,
    647                    cw - 2*BORDER_WIDTH - USELESSGAP, ch/rows - 2*BORDER_WIDTH - USELESSGAP);
    648         if (++rn >= rows) { rn = 0; cn++; }
    649     }
    650 }
    651 
    652 /**
    653  * on the press of a key binding (see grabkeys)
    654  * call the appropriate handler
    655  */
    656 void keypress(XEvent *e) {
    657     KeySym keysym = XkbKeycodeToKeysym(dis, e->xkey.keycode, 0, 0);
    658     for (unsigned int i = 0; i < LENGTH(keys); i++)
    659         if (keysym == keys[i].keysym && CLEANMASK(keys[i].mod) == CLEANMASK(e->xkey.state))
    660             if (keys[i].func) keys[i].func(&keys[i].arg);
    661 }
    662 
    663 /**
    664  * explicitly kill the current client - close the highlighted window
    665  * if the client accepts WM_DELETE_WINDOW requests send a delete message
    666  * otherwise forcefully kill and remove the client
    667  */
    668 void killclient(void) {
    669     Desktop *d = &desktops[currdeskidx];
    670     if (!d->curr) return;
    671 
    672     Atom *prot = NULL; int n = -1;
    673     if (XGetWMProtocols(dis, d->curr->win, &prot, &n))
    674         while(--n >= 0 && prot[n] != wmatoms[WM_DELETE_WINDOW]);
    675     if (n < 0) { XKillClient(dis, d->curr->win); removeclient(d->curr, d); }
    676     else deletewindow(d->curr->win);
    677     if (prot) XFree(prot);
    678 }
    679 
    680 /**
    681  * focus the previously/last focused desktop
    682  */
    683 void last_desktop(void) {
    684     change_desktop(&(Arg){.i = prevdeskidx});
    685 }
    686 
    687 /**
    688  * a map request is received when a window wants to display itself.
    689  * if the window has override_redirect flag set,
    690  * then it should not be handled by the wm.
    691  * if the window already has a client then there is nothing to do.
    692  *
    693  * match window class and/or install name against an app rule.
    694  * create a new client for the window and add it to the appropriate desktop.
    695  * set the floating, transient and fullscreen state of the client.
    696  * if the desktop in which the window is to be spawned is the current desktop
    697  * then display/map the window, else, if follow is set, focus the new desktop.
    698  */
    699 void maprequest(XEvent *e) {
    700     Desktop *d = NULL; Client *c = NULL;
    701     Window w = e->xmaprequest.window;
    702     XWindowAttributes wa = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
    703     if (wintoclient(w, &c, &d) || (XGetWindowAttributes(dis, w, &wa) && wa.override_redirect)) return;
    704 
    705     XClassHint ch = {0, 0};
    706     Bool follow = False, floating = False;
    707     int newdsk = currdeskidx;
    708 
    709     if (XGetClassHint(dis, w, &ch)) for (unsigned int i = 0; i < LENGTH(rules); i++)
    710         if (strstr(ch.res_class, rules[i].class) || strstr(ch.res_name, rules[i].class)) {
    711             if (rules[i].desktop >= 0 && rules[i].desktop < DESKTOPS) newdsk = rules[i].desktop;
    712             follow = rules[i].follow, floating = rules[i].floating;
    713             break;
    714         }
    715     if (ch.res_class) XFree(ch.res_class);
    716     if (ch.res_name) XFree(ch.res_name);
    717 
    718     c = addwindow(w, (d = &desktops[newdsk])); /* from now on, use c->win */
    719     c->istrans = XGetTransientForHint(dis, c->win, &w);
    720     if ((c->isfloat = (floating || d->mode == FLOAT)) && !c->istrans)
    721         XMoveWindow(dis, c->win, (ww - wa.width)/2, (wh - wa.height)/2);
    722 
    723     int i; unsigned long l; unsigned char *state = NULL; Atom a;
    724     if (XGetWindowProperty(dis, c->win, netatoms[NET_WM_STATE], 0L, sizeof a,
    725                 False, XA_ATOM, &a, &i, &l, &l, &state) == Success && state)
    726         setfullscreen(c, d, (*(Atom *)state == netatoms[NET_FULLSCREEN]));
    727     if (state) XFree(state);
    728 
    729     if (currdeskidx == newdsk) { if (!ISFFT(c)) tile(d); XMapWindow(dis, c->win); }
    730     else if (follow) change_desktop(&(Arg){.i = newdsk});
    731     focus(c, d);
    732 
    733     if (!follow) desktopinfo();
    734 }
    735 
    736 /**
    737  * handle resize and positioning of a window with the pointer.
    738  *
    739  * grab the pointer and get it's current position.
    740  * now, all pointer movement events will be reported until it is ungrabbed.
    741  *
    742  * while the mouse is pressed, grab interesting events (see button press,
    743  * button release, pointer motion).
    744  * on on pointer movement resize or move the window under the curson.
    745  * also handle map requests and configure requests.
    746  *
    747  * finally, on ButtonRelease, ungrab the poitner.
    748  * event handling is passed back to run() function.
    749  *
    750  * once a window has been moved or resized, it's marked as floating.
    751  */
    752 void mousemotion(const Arg *arg) {
    753     Desktop *d = &desktops[currdeskidx];
    754     XWindowAttributes wa;
    755     XEvent ev;
    756 
    757     if (!d->curr || !XGetWindowAttributes(dis, d->curr->win, &wa)) return;
    758 
    759     if (arg->i == RESIZE) XWarpPointer(dis, d->curr->win, d->curr->win, 0, 0, 0, 0, --wa.width, --wa.height);
    760     int rx, ry, c, xw, yh; unsigned int v; Window w;
    761     if (!XQueryPointer(dis, root, &w, &w, &rx, &ry, &c, &c, &v) || w != d->curr->win) return;
    762 
    763     if (XGrabPointer(dis, root, False, BUTTONMASK|PointerMotionMask, GrabModeAsync,
    764                      GrabModeAsync, None, None, CurrentTime) != GrabSuccess) return;
    765 
    766     if (!d->curr->isfloat && !d->curr->istrans) { d->curr->isfloat = True; tile(d); focus(d->curr, d); }
    767 
    768     /* init rectangle properties */
    769     XGCValues gv = { .function = GXinvert, .subwindow_mode = IncludeInferiors, .line_width = BORDER_WIDTH };
    770     GC gc = XCreateGC(dis, root, GCFunction|GCSubwindowMode|GCLineWidth, &gv);
    771 
    772     /* draw rectangle */
    773     if (arg->i == MOVE) XDrawRectangle(dis, root, gc, xw = wa.x, yh = wa.y, wa.width, wa.height);
    774     else XDrawRectangle(dis, root, gc, wa.x, wa.y, xw = wa.width, yh = wa.height);
    775 
    776     do {
    777         XMaskEvent(dis, BUTTONMASK|PointerMotionMask|SubstructureRedirectMask, &ev);
    778         if (ev.type == MotionNotify) {
    779             /* clear rectangle from prev position */
    780             if (arg->i == MOVE) XDrawRectangle(dis, root, gc, xw, yh, wa.width, wa.height);
    781             else if (arg->i == RESIZE) XDrawRectangle(dis, root, gc, wa.x, wa.y, xw, yh);
    782 
    783             xw = (arg->i == MOVE ? wa.x:wa.width)  + ev.xmotion.x - rx;
    784             yh = (arg->i == MOVE ? wa.y:wa.height) + ev.xmotion.y - ry;
    785 
    786         /* draw rectangle in new position */
    787 	if (arg->i == MOVE) XDrawRectangle(dis, root, gc, xw, yh, wa.width, wa.height);
    788 	else if (arg->i == RESIZE) XDrawRectangle(dis, root, gc, wa.x, wa.y, xw, yh);
    789         } else if (ev.type == ConfigureRequest || ev.type == MapRequest) events[ev.type](&ev);
    790     } while (ev.type != ButtonRelease);
    791 
    792      /* clear rectangle from last position */
    793     if (arg->i == MOVE) XDrawRectangle(dis, root, gc, xw, yh, wa.width, wa.height);
    794     else if (arg->i == RESIZE) XDrawRectangle(dis, root, gc, wa.x, wa.y, xw, yh);
    795 
    796     /* actually move/resize the window to the new position/size */
    797     if (arg->i == RESIZE) XResizeWindow(dis, d->curr->win,
    798             xw > MINWSZ ? xw:wa.width, yh > MINWSZ ? yh:wa.height);
    799     else if (arg->i == MOVE) XMoveWindow(dis, d->curr->win, xw, yh);
    800 
    801     XUngrabPointer(dis, CurrentTime);
    802 }
    803 
    804 /**
    805  * monocle aka max aka fullscreen mode/layout
    806  * each window should cover all the available screen space
    807  */
    808 void monocle(int x, int y, int w, int h, const Desktop *d) {
    809     for (Client *c = d->head; c; c = c->next) if (!ISFFT(c))
    810         XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP, w - 2*USELESSGAP, h - 2*USELESSGAP);
    811 }
    812 
    813 /**
    814  * swap positions of current and next from current clients
    815  */
    816 void move_down(void) {
    817     Desktop *d = &desktops[currdeskidx];
    818     if (!d->curr || !d->head->next) return;
    819     /* p is previous, c is current, n is next, if current is head n is last */
    820     Client *p = prevclient(d->curr, d), *n = (d->curr->next) ? d->curr->next:d->head;
    821     /*
    822      * if c is head, swapping with n should update head to n
    823      * [c]->[n]->..  ==>  [n]->[c]->..
    824      *  ^head              ^head
    825      *
    826      * else there is a previous client and p->next should be what's after c
    827      * ..->[p]->[c]->[n]->..  ==>  ..->[p]->[n]->[c]->..
    828      */
    829     if (d->curr == d->head) d->head = n; else p->next = d->curr->next;
    830     /*
    831      * if c is the last client, c will be the current head
    832      * [n]->..->[p]->[c]->NULL  ==>  [c]->[n]->..->[p]->NULL
    833      *  ^head                         ^head
    834      * else c will take the place of n, so c-next will be n->next
    835      * ..->[p]->[c]->[n]->..  ==>  ..->[p]->[n]->[c]->..
    836      */
    837     d->curr->next = (d->curr->next) ? n->next:n;
    838     /*
    839      * if c was swapped with n then they now point to the same ->next. n->next should be c
    840      * ..->[p]->[c]->[n]->..  ==>  ..->[p]->[n]->..  ==>  ..->[p]->[n]->[c]->..
    841      *                                        [c]-^
    842      *
    843      * else c is the last client and n is head,
    844      * so c will be move to be head, no need to update n->next
    845      * [n]->..->[p]->[c]->NULL  ==>  [c]->[n]->..->[p]->NULL
    846      *  ^head                         ^head
    847      */
    848     if (d->curr->next == n->next) n->next = d->curr; else d->head = d->curr;
    849     if (!d->curr->isfloat && !d->curr->istrans) tile(d);
    850 }
    851 
    852 /**
    853  * swap positions of current and previous from current clients
    854  */
    855 void move_up(void) {
    856     Desktop *d = &desktops[currdeskidx];
    857     if (!d->curr || !d->head->next) return;
    858     /* p is previous from current or last if current is head */
    859     Client *pp = NULL, *p = prevclient(d->curr, d);
    860     /* pp is previous from p, or null if current is head and thus p is last */
    861     if (p->next) for (pp = d->head; pp && pp->next != p; pp = pp->next);
    862     /*
    863      * if p has a previous client then the next client should be current (current is c)
    864      * ..->[pp]->[p]->[c]->..  ==>  ..->[pp]->[c]->[p]->..
    865      *
    866      * if p doesn't have a previous client, then p might be head, so head must change to c
    867      * [p]->[c]->..  ==>  [c]->[p]->..
    868      *  ^head              ^head
    869      * if p is not head, then c is head (and p is last), so the new head is next of c
    870      * [c]->[n]->..->[p]->NULL  ==>  [n]->..->[p]->[c]->NULL
    871      *  ^head         ^last           ^head         ^last
    872      */
    873     if (pp) pp->next = d->curr; else d->head = (d->curr == d->head) ? d->curr->next:d->curr;
    874     /*
    875      * next of p should be next of c
    876      * ..->[pp]->[p]->[c]->[n]->..  ==>  ..->[pp]->[c]->[p]->[n]->..
    877      * except if c was head (now c->next is head), so next of p should be c
    878      * [c]->[n]->..->[p]->NULL  ==>  [n]->..->[p]->[c]->NULL
    879      *  ^head         ^last           ^head         ^last
    880      */
    881     p->next = (d->curr->next == d->head) ? d->curr:d->curr->next;
    882     /*
    883      * next of c should be p
    884      * ..->[pp]->[p]->[c]->[n]->..  ==>  ..->[pp]->[c]->[p]->[n]->..
    885      * except if c was head (now c->next is head), so c is must be last
    886      * [c]->[n]->..->[p]->NULL  ==>  [n]->..->[p]->[c]->NULL
    887      *  ^head         ^last           ^head         ^last
    888      */
    889     d->curr->next = (d->curr->next == d->head) ? NULL:p;
    890     if (!d->curr->isfloat && !d->curr->istrans) tile(d);
    891 }
    892 
    893 /**
    894  * move and resize a window with the keyboard
    895  */
    896 void moveresize(const Arg *arg) {
    897     Desktop *d = &desktops[currdeskidx];
    898     XWindowAttributes wa;
    899     if (!d->curr || !XGetWindowAttributes(dis, d->curr->win, &wa)) return;
    900     if (!d->curr->isfloat && !d->curr->istrans) { d->curr->isfloat = True; tile(d); focus(d->curr, d); }
    901     XMoveResizeWindow(dis, d->curr->win, wa.x + ((int *)arg->v)[0], wa.y + ((int *)arg->v)[1],
    902                                 wa.width + ((int *)arg->v)[2], wa.height + ((int *)arg->v)[3]);
    903 }
    904 
    905 /**
    906  * cyclic focus the next window
    907  * if the window is the last on stack, focus head
    908  */
    909 void next_win(void) {
    910     Desktop *d = &desktops[currdeskidx];
    911     if (d->curr && d->head->next) focus(d->curr->next ? d->curr->next:d->head, d);
    912 }
    913 
    914 /**
    915  * get the previous client from the given
    916  * if no such client, return NULL
    917  */
    918 Client* prevclient(Client *c, Desktop *d) {
    919     Client *p = NULL;
    920     if (c && d->head && d->head->next) for (p = d->head; p->next && p->next != c; p = p->next);
    921     return p;
    922 }
    923 
    924 /**
    925  * cyclic focus the previous window
    926  * if the window is head, focus the last stack window
    927  */
    928 void prev_win(void) {
    929     Desktop *d = &desktops[currdeskidx];
    930     if (d->curr && d->head->next) focus(prevclient(d->curr, d), d);
    931 }
    932 
    933 /**
    934  * set unrgent hint for a window
    935  */
    936 void propertynotify(XEvent *e) {
    937     Desktop *d = NULL; Client *c = NULL;
    938     if (e->xproperty.atom != XA_WM_HINTS || !wintoclient(e->xproperty.window, &c, &d)) return;
    939 
    940     XWMHints *wmh = XGetWMHints(dis, c->win);
    941     c->isurgn = (c != desktops[currdeskidx].curr && wmh && (wmh->flags & XUrgencyHint));
    942 
    943     if (wmh) XFree(wmh);
    944     desktopinfo();
    945 }
    946 
    947 /**
    948  * to quit just stop receiving events
    949  * run is stopped and control is back to main
    950  */
    951 void quit(const Arg *arg) {
    952     retval = arg->i;
    953     running = False;
    954 }
    955 
    956 /**
    957  * remove the specified client from the given desktop
    958  *
    959  * if c was the previous client, previous must be updated.
    960  * if c was the current client, current must be updated.
    961  */
    962 void removeclient(Client *c, Desktop *d) {
    963     Client **p = NULL;
    964     for (p = &d->head; *p && (*p != c); p = &(*p)->next);
    965     if (!*p) return; else *p = c->next;
    966     if (c == d->prev && !(d->prev = prevclient(d->curr, d))) d->prev = d->head;
    967     if (c == d->curr || (d->head && !d->head->next)) focus(d->prev, d);
    968     if (!(c->isfloat || c->istrans) || (d->head && !d->head->next)) tile(d);
    969     free(c);
    970     desktopinfo();
    971 }
    972 
    973 /**
    974  * resize the master size
    975  * we should check for window size limits for both master and
    976  * stack clients. the size of a window can't be less than MINWSZ
    977  */
    978 void resize_master(const Arg *arg) {
    979     Desktop *d = &desktops[currdeskidx];
    980     int msz = (d->mode == BSTACK ? wh:ww) * MASTER_SIZE + (d->masz += arg->i);
    981     if (msz >= MINWSZ && (d->mode == BSTACK ? wh:ww) - msz >= MINWSZ) tile(d);
    982     else d->masz -= arg->i; /* reset master area size */
    983 }
    984 
    985 /**
    986  * resize the first stack window
    987  */
    988 void resize_stack(const Arg *arg) {
    989     desktops[currdeskidx].sasz += arg->i;
    990     tile(&desktops[currdeskidx]);
    991 }
    992 
    993 /**
    994  * jump and focus the next or previous desktop
    995  */
    996 void rotate(const Arg *arg) {
    997     change_desktop(&(Arg){.i = (DESKTOPS + currdeskidx + arg->i) % DESKTOPS});
    998 }
    999 
   1000 /**
   1001  * jump and focus the next non-empty desktop
   1002  */
   1003 void rotate_filled(const Arg *arg) {
   1004     int n = arg->i;
   1005     while (n < DESKTOPS && !desktops[(DESKTOPS + currdeskidx + n) % DESKTOPS].head) (n += arg->i);
   1006     change_desktop(&(Arg){.i = (DESKTOPS + currdeskidx + n) % DESKTOPS});
   1007 }
   1008 
   1009 /**
   1010  * main event loop
   1011  * on receival of an event call the appropriate handler
   1012  */
   1013 void run(void) {
   1014     XEvent ev;
   1015     while(running && !XNextEvent(dis, &ev)) if (events[ev.type]) events[ev.type](&ev);
   1016 }
   1017 
   1018 /**
   1019  * set the fullscreen state of a client
   1020  *
   1021  * if a client gets fullscreen resize it
   1022  * to cover all screen space.
   1023  * the border should be zero (0).
   1024  *
   1025  * if a client is reset from fullscreen,
   1026  * the border should be BORDER_WIDTH,
   1027  * except if no other client is on that desktop.
   1028  */
   1029 void setfullscreen(Client *c, Desktop *d, Bool fullscrn) {
   1030     if (fullscrn != c->isfull) XChangeProperty(dis, c->win,
   1031             netatoms[NET_WM_STATE], XA_ATOM, 32, PropModeReplace, (unsigned char*)
   1032             ((c->isfull = fullscrn) ? &netatoms[NET_FULLSCREEN]:0), fullscrn);
   1033     if (fullscrn) XMoveResizeWindow(dis, c->win, 0, 0, ww, wh + PANEL_HEIGHT);
   1034     XSetWindowBorderWidth(dis, c->win, (c->isfull || !d->head->next ? 0:BORDER_WIDTH));
   1035 }
   1036 
   1037 /**
   1038  * set initial values
   1039  */
   1040 void setup(void) {
   1041     sigchld(0);
   1042 
   1043     /* screen and root window */
   1044     const int screen = DefaultScreen(dis);
   1045     root = RootWindow(dis, screen);
   1046 
   1047     /* screen width and height */
   1048     ww = XDisplayWidth(dis,  screen);
   1049     wh = XDisplayHeight(dis, screen) - PANEL_HEIGHT;
   1050 
   1051     /* initialize mode and panel visibility for each desktop */
   1052     for (unsigned int d = 0; d < DESKTOPS; d++)
   1053         desktops[d] = (Desktop){ .mode = DEFAULT_MODE, .sbar = SHOW_PANEL };
   1054 
   1055     /* get color for focused and unfocused client borders */
   1056     win_focus = getcolor(FOCUS, screen);
   1057     win_unfocus = getcolor(UNFOCUS, screen);
   1058 
   1059     /* set numlockmask */
   1060     XModifierKeymap *modmap = XGetModifierMapping(dis);
   1061     for (int k = 0; k < 8; k++) for (int j = 0; j < modmap->max_keypermod; j++)
   1062         if (modmap->modifiermap[modmap->max_keypermod*k + j] == XKeysymToKeycode(dis, XK_Num_Lock))
   1063             numlockmask = (1 << k);
   1064     XFreeModifiermap(modmap);
   1065 
   1066     /* set up atoms for dialog/notification windows */
   1067     wmatoms[WM_PROTOCOLS]     = XInternAtom(dis, "WM_PROTOCOLS",     False);
   1068     wmatoms[WM_DELETE_WINDOW] = XInternAtom(dis, "WM_DELETE_WINDOW", False);
   1069     netatoms[NET_SUPPORTED]   = XInternAtom(dis, "_NET_SUPPORTED",   False);
   1070     netatoms[NET_WM_STATE]    = XInternAtom(dis, "_NET_WM_STATE",    False);
   1071     netatoms[NET_ACTIVE]      = XInternAtom(dis, "_NET_ACTIVE_WINDOW",       False);
   1072     netatoms[NET_FULLSCREEN]  = XInternAtom(dis, "_NET_WM_STATE_FULLSCREEN", False);
   1073 
   1074     /* propagate EWMH support */
   1075     XChangeProperty(dis, root, netatoms[NET_SUPPORTED], XA_ATOM, 32,
   1076               PropModeReplace, (unsigned char *)netatoms, NET_COUNT);
   1077 
   1078     /* set the appropriate error handler
   1079      * try an action that will cause an error if another wm is active
   1080      * wait until events are processed to process the error from the above action
   1081      * if all is good set the generic error handler */
   1082     XSetErrorHandler(xerrorstart);
   1083     /* set masks for reporting events handled by the wm */
   1084     XSelectInput(dis, root, ROOTMASK);
   1085     XSync(dis, False);
   1086     XSetErrorHandler(xerror);
   1087     XSync(dis, False);
   1088 
   1089     grabkeys();
   1090     if (DEFAULT_DESKTOP >= 0 && DEFAULT_DESKTOP < DESKTOPS) change_desktop(&(Arg){.i = DEFAULT_DESKTOP});
   1091 }
   1092 
   1093 void sigchld(__attribute__((unused)) int sig) {
   1094     if (signal(SIGCHLD, sigchld) != SIG_ERR) while(0 < waitpid(-1, NULL, WNOHANG));
   1095     else err(EXIT_FAILURE, "cannot install SIGCHLD handler");
   1096 }
   1097 
   1098 /**
   1099  * execute a command
   1100  */
   1101 void spawn(const Arg *arg) {
   1102     if (fork()) return;
   1103     if (dis) close(ConnectionNumber(dis));
   1104     setsid();
   1105     execvp((char*)arg->com[0], (char**)arg->com);
   1106     err(EXIT_SUCCESS, "execvp %s", (char *)arg->com[0]);
   1107 }
   1108 
   1109 /**
   1110  * tile or common tiling aka v-stack mode/layout
   1111  * bstack or bottom stack aka h-stack mode/layout
   1112  */
   1113 void stack(int x, int y, int w, int h, const Desktop *d) {
   1114     Client *c = NULL, *t = NULL; Bool b = (d->mode == BSTACK);
   1115     int n = 0, p = 0, z = (b ? w:h), ma = (b ? h:w) * MASTER_SIZE + d->masz;
   1116 
   1117     /* count stack windows and grab first non-floating, non-fullscreen window */
   1118     for (t = d->head; t; t = t->next) if (!ISFFT(t)) { if (c) ++n; else c = t; }
   1119 
   1120     /* if there is only one window (c && !n), it should cover the available screen space
   1121      * if there is only one stack window, then we don't care about growth
   1122      * if more than one stack windows (n > 1) adjustments may be needed.
   1123      *
   1124      *   - p is the num of pixels than remain when spliting the
   1125      *       available width/height to the number of windows
   1126      *   - z is each client's height/width
   1127      *
   1128      *      ----------  --.    ----------------------.
   1129      *      |   |----| }--|--> sasz                  }--> first client will have
   1130      *      |   | 1s |    |                          |    z+p+sasz height/width.
   1131      *      | M |----|-.  }--> screen height (h)  ---'
   1132      *      |   | 2s | }--|--> client height (z)    two stack clients on tile mode
   1133      *      -----------' -'                         ::: ascii art by c00kiemon5ter
   1134      *
   1135      * what we do is, remove the sasz from the screen height/width and then
   1136      * divide that space with the windows on the stack so all windows have
   1137      * equal height/width: z = (z - sasz)/n
   1138      *
   1139      * sasz was left out (subtrackted), to later be added to the first client
   1140      * height/width. before we do that, there will be cases when the num of
   1141      * windows cannot be perfectly divided with the available screen height/width.
   1142      * for example: 100px scr. height, and 3 stack windows: 100/3 = 33,3333..
   1143      * so we get that remaining space and merge it to sasz: p = (z - sasz) % n + sasz
   1144      *
   1145      * in the end, we know each client's height/width (z), and how many pixels
   1146      * should be added to the first stack client (p) so that it satisfies sasz,
   1147      * and also, does not result in gaps created on the bottom of the screen.
   1148      */
   1149     if (c && !n) XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP,
   1150            w - 2*(BORDER_WIDTH + USELESSGAP), h - 2*(BORDER_WIDTH + USELESSGAP));
   1151     if (!c || !n) return; else if (n > 1) { p = (z - d->sasz)%n + d->sasz; z = (z - d->sasz)/n; }
   1152 
   1153     /* tile the first non-floating, non-fullscreen window to cover the master area */
   1154     if (b) XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP,
   1155     w - 2*(BORDER_WIDTH + USELESSGAP), ma - 2*(BORDER_WIDTH + USELESSGAP));
   1156     else   XMoveResizeWindow(dis, c->win, x + USELESSGAP, y + USELESSGAP,
   1157     ma - 2*(BORDER_WIDTH + USELESSGAP), h - 2*(BORDER_WIDTH + USELESSGAP));
   1158 
   1159     /* tile the next non-floating, non-fullscreen (and first) stack window adding p */
   1160     for (c = c->next; c && ISFFT(c); c = c->next);
   1161     int ch = z - 2*BORDER_WIDTH - USELESSGAP, cw = (b ? h:w) - 2*BORDER_WIDTH - ma - USELESSGAP;
   1162     if (b) XMoveResizeWindow(dis, c->win, x += USELESSGAP, y += ma, ch - USELESSGAP + p, cw);
   1163     else   XMoveResizeWindow(dis, c->win, x += ma, y += USELESSGAP, cw, ch - USELESSGAP + p);
   1164 
   1165     /* tile the rest of the non-floating, non-fullscreen stack windows */
   1166     for (b ? (x += z+p-USELESSGAP):(y += z+p-USELESSGAP), c = c->next; c; c = c->next) {
   1167         if (ISFFT(c)) continue;
   1168         if (b) { XMoveResizeWindow(dis, c->win, x, y, ch, cw); x += z; }
   1169         else   { XMoveResizeWindow(dis, c->win, x, y, cw, ch); y += z; }
   1170     }
   1171 }
   1172 
   1173 /**
   1174  * swap master window with current.
   1175  * if current is head swap with next
   1176  * if current is not head, then head
   1177  * is behind us, so move_up until we
   1178  * are the head
   1179  */
   1180 void swap_master(void) {
   1181     Desktop *d = &desktops[currdeskidx];
   1182     if (!d->curr || !d->head->next) return;
   1183     if (d->curr == d->head) move_down();
   1184     else while (d->curr != d->head) move_up();
   1185     focus(d->head, d);
   1186 }
   1187 
   1188 /**
   1189  * switch tiling mode/layout
   1190  *
   1191  * if mode is reselected reset all floating clients
   1192  * if mode is FLOAT set all clients floating
   1193  */
   1194 void switch_mode(const Arg *arg) {
   1195     Desktop *d = &desktops[currdeskidx];
   1196     if (d->mode != arg->i) d->mode = arg->i;
   1197     else if (d->mode != FLOAT) for (Client *c = d->head; c; c = c->next) c->isfloat = False;
   1198     if (d->head) { tile(d); focus(d->curr, d); }
   1199     desktopinfo();
   1200 }
   1201 
   1202 /**
   1203  * tile clients of the given desktop with the desktop's mode/layout
   1204  * call the tiling handler fucntion taking account the panel height
   1205  */
   1206 void tile(Desktop *d) {
   1207     if (!d->head || d->mode == FLOAT) return; /* nothing to arange */
   1208     layout[d->head->next ? d->mode:MONOCLE](0, TOP_PANEL && d->sbar ? PANEL_HEIGHT:0,
   1209                                                   ww, wh + (d->sbar ? 0:PANEL_HEIGHT), d);
   1210 }
   1211 
   1212 /**
   1213  * toggle visibility state of the panel/bar
   1214  */
   1215 void togglepanel(void) {
   1216     desktops[currdeskidx].sbar = !desktops[currdeskidx].sbar;
   1217     tile(&desktops[currdeskidx]);
   1218 }
   1219 
   1220 /**
   1221  * windows that request to unmap should lose their client
   1222  * so invisible windows do not exist on screen
   1223  */
   1224 void unmapnotify(XEvent *e) {
   1225     Desktop *d = NULL; Client *c = NULL;
   1226     if (wintoclient(e->xunmap.window, &c, &d)) removeclient(c, d);
   1227 }
   1228 
   1229 /**
   1230  * find to which client and desktop the given window belongs to
   1231  */
   1232 Bool wintoclient(Window w, Client **c, Desktop **d) {
   1233     for (int i = 0; i < DESKTOPS && !*c; i++)
   1234         for (*d = &desktops[i], *c = (*d)->head; *c && (*c)->win != w; *c = (*c)->next);
   1235     return (*c != NULL);
   1236 }
   1237 
   1238 /**
   1239  * There's no way to check accesses to destroyed windows,
   1240  * thus those cases are ignored (especially on UnmapNotify's).
   1241  */
   1242 int xerror(__attribute__((unused)) Display *dis, XErrorEvent *ee) {
   1243     if ((ee->error_code == BadAccess   && (ee->request_code == X_GrabKey
   1244                                        ||  ee->request_code == X_GrabButton))
   1245     || (ee->error_code  == BadMatch    && (ee->request_code == X_SetInputFocus
   1246                                        ||  ee->request_code == X_ConfigureWindow))
   1247     || (ee->error_code  == BadDrawable && (ee->request_code == X_PolyFillRectangle
   1248     || ee->request_code == X_CopyArea  ||  ee->request_code == X_PolySegment
   1249                                        ||  ee->request_code == X_PolyText8))
   1250     || ee->error_code   == BadWindow) return 0;
   1251     err(EXIT_FAILURE, "xerror: request: %d code: %d", ee->request_code, ee->error_code);
   1252 }
   1253 
   1254 /**
   1255  * error handler function to display an appropriate error message
   1256  * when the window manager initializes (see setup - XSetErrorHandler)
   1257  */
   1258 int xerrorstart(__attribute__((unused)) Display *dis, __attribute__((unused)) XErrorEvent *ee) {
   1259     errx(EXIT_FAILURE, "xerror: another window manager is already running");
   1260 }
   1261 
   1262 int main(int argc, char *argv[]) {
   1263     if (argc == 2 && !strncmp(argv[1], "-v", 3))
   1264         errx(EXIT_SUCCESS, "version: %s - by c00kiemon5ter >:3 omnomnomnom", VERSION);
   1265     else if (argc != 1) errx(EXIT_FAILURE, "usage: man monsterwm");
   1266     if (!(dis = XOpenDisplay(NULL))) errx(EXIT_FAILURE, "cannot open display");
   1267     setup();
   1268     desktopinfo(); /* zero out every desktop on (re)start */
   1269     run();
   1270     cleanup();
   1271     XCloseDisplay(dis);
   1272     return retval;
   1273 }
   1274 
   1275 /* vim: set expandtab ts=4 sts=4 sw=4 : */