Hi,

I'm newbie in dwm, so I felt the luck of desktops grid like in other Window Managers like XFCE or OpenBox. And I couldn't find any solution for multiple rows of tags, so I decided to make it by myself.

In addition I got assumed to xxkb's functionality, but it have some problems with dwm, described here:
http://lists.suckless.org/dwm/0808/6471.html
So I decided to implement this functionality directly in dwm. While implementation I had some problems with applications like guake which have hide-restore behaviour. Unfortunately, dwm removes information about any unmapping client so I had to store list of xkb info separately.

I lived with this two patches for a week and they seems to work nice, but I think they need to be checked.

P.S. xkb patch have small conflict with systemtray patch which easy to solve manually.

P.P.S Sorry for my English.


diff --git a/config.def.h b/config.def.h
index 875885b..d6670dc 100644
--- a/config.def.h
+++ b/config.def.h
@@ -16,6 +16,22 @@ static const Bool topbar            = True;     /* False means bottom bar */
 /* tagging */
 static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" };
 
+/* grid of tags */
+#define DRAWCLASSICTAGS             1 << 0
+#define DRAWTAGGRID                 1 << 1
+
+#define SWITCHTAG_UP                1 << 0
+#define SWITCHTAG_DOWN              1 << 1
+#define SWITCHTAG_LEFT              1 << 2
+#define SWITCHTAG_RIGHT             1 << 3
+#define SWITCHTAG_TOGGLETAG         1 << 4
+#define SWITCHTAG_TAG               1 << 5
+#define SWITCHTAG_VIEW              1 << 6
+#define SWITCHTAG_TOGGLEVIEW        1 << 7
+
+static const unsigned int drawtagmask = DRAWTAGGRID; /* | DRAWCLASSICTAGS to show classic row of tags */
+static const int tagrows = 3;
+
 static const Rule rules[] = {
 	/* xprop(1):
 	 *	WM_CLASS(STRING) = instance, class
@@ -89,6 +105,16 @@ static Key keys[] = {
 	TAGKEYS(                        XK_8,                      7)
 	TAGKEYS(                        XK_9,                      8)
 	{ MODKEY|ShiftMask,             XK_q,      quit,           {0} },
+
+    { MODKEY|ControlMask,           XK_Up,     switchtag,      { .ui = SWITCHTAG_UP     | SWITCHTAG_VIEW } },
+    { MODKEY|ControlMask,           XK_Down,   switchtag,      { .ui = SWITCHTAG_DOWN   | SWITCHTAG_VIEW } },
+    { MODKEY|ControlMask,           XK_Right,  switchtag,      { .ui = SWITCHTAG_RIGHT  | SWITCHTAG_VIEW } },
+    { MODKEY|ControlMask,           XK_Left,   switchtag,      { .ui = SWITCHTAG_LEFT   | SWITCHTAG_VIEW } },
+
+    { MODKEY|Mod4Mask,              XK_Up,     switchtag,      { .ui = SWITCHTAG_UP     | SWITCHTAG_TAG | SWITCHTAG_VIEW } },
+    { MODKEY|Mod4Mask,              XK_Down,   switchtag,      { .ui = SWITCHTAG_DOWN   | SWITCHTAG_TAG | SWITCHTAG_VIEW } },
+    { MODKEY|Mod4Mask,              XK_Right,  switchtag,      { .ui = SWITCHTAG_RIGHT  | SWITCHTAG_TAG | SWITCHTAG_VIEW } },
+    { MODKEY|Mod4Mask,              XK_Left,   switchtag,      { .ui = SWITCHTAG_LEFT   | SWITCHTAG_TAG | SWITCHTAG_VIEW } },
 };
 
 /* button definitions */
diff --git a/dwm.c b/dwm.c
index 1bbb4b3..e211b74 100644
--- a/dwm.c
+++ b/dwm.c
@@ -163,6 +163,7 @@ static void detachstack(Client *c);
 static Monitor *dirtomon(int dir);
 static void drawbar(Monitor *m);
 static void drawbars(void);
+static void drawtaggrid(Monitor *m, int *x_pos, unsigned int occ);
 static void enternotify(XEvent *e);
 static void expose(XEvent *e);
 static void focus(Client *c);
@@ -205,6 +206,7 @@ static void setup(void);
 static void showhide(Client *c);
 static void sigchld(int unused);
 static void spawn(const Arg *arg);
+static void switchtag(const Arg *arg);
 static void tag(const Arg *arg);
 static void tagmon(const Arg *arg);
 static void tile(Monitor *);
@@ -702,6 +704,7 @@ drawbar(Monitor *m) {
 			urg |= c->tags;
 	}
 	x = 0;
+    if (drawtagmask & DRAWCLASSICTAGS)
 	for(i = 0; i < LENGTH(tags); i++) {
 		w = TEXTW(tags[i]);
 		drw_setscheme(drw, m->tagset[m->seltags] & 1 << i ? &scheme[SchemeSel] : &scheme[SchemeNorm]);
@@ -710,6 +713,9 @@ drawbar(Monitor *m) {
 		           occ & 1 << i, urg & 1 << i);
 		x += w;
 	}
+    if (drawtagmask & DRAWTAGGRID) {
+        drawtaggrid(m,&x,occ);
+    }
 	w = blw = TEXTW(m->ltsymbol);
 	drw_setscheme(drw, &scheme[SchemeNorm]);
 	drw_text(drw, x, 0, w, bh, m->ltsymbol, 0);
@@ -748,6 +754,47 @@ drawbars(void) {
 	for(m = mons; m; m = m->next)
 		drawbar(m);
 }
+void drawtaggrid(Monitor *m, int *x_pos, unsigned int occ)
+{
+    unsigned int x, y, h, max_x, columns;
+    int invert, i,j, k;
+
+    h = bh / tagrows;
+    x = max_x = *x_pos;
+    y = 0;
+    columns = LENGTH(tags) / tagrows + ((LENGTH(tags) % tagrows > 0) ? 1 : 0);
+
+    /* Firstly we will fill the borders of squares */
+    XSetForeground(drw->dpy, drw->gc, scheme[SchemeNorm].bg->rgb);
+    XFillRectangle(dpy, drw->drawable, drw->gc, x, y, h*columns + 1, bh);
+
+    /* We well draw LENGTH(tags) squares in tagraws raws. */
+	for(j = 0,  i= 0; j < tagrows; j++) {
+        x = *x_pos;
+        for (k = 0; k < columns && i < LENGTH(tags); k++, i++) {
+		    invert = m->tagset[m->seltags] & 1 << i ? 0 : 1;
+
+            /* Select active color for current square */
+            XSetForeground(drw->dpy, drw->gc, !invert ? scheme[SchemeSel].bg->rgb :
+                                scheme[SchemeNorm].fg->rgb);
+            XFillRectangle(dpy, drw->drawable, drw->gc, x+1, y+1, h-1, h-1);
+
+            /* Mark square if tag has client */
+            if (occ & 1 << i) {
+                XSetForeground(drw->dpy, drw->gc, !invert ? scheme[SchemeSel].fg->rgb :
+                                scheme[SchemeNorm].bg->rgb);
+                XFillRectangle(dpy, drw->drawable, drw->gc, x + 1, y + 1,
+                               h / 2, h / 2);
+            }
+		    x += h;
+            if (x > max_x) {
+                max_x = x;
+            }
+        }
+        y += h;
+	}
+    *x_pos = max_x + 1;
+}
 
 void
 enternotify(XEvent *e) {
@@ -1582,6 +1629,81 @@ spawn(const Arg *arg) {
 		exit(EXIT_SUCCESS);
 	}
 }
+void switchtag(const Arg *arg)
+{
+    unsigned int columns;
+    unsigned int new_tagset = 0;
+    unsigned int pos, i;
+    int col, row;
+    Arg new_arg;
+
+    columns = LENGTH(tags) / tagrows + ((LENGTH(tags) % tagrows > 0) ? 1 : 0);
+
+    for (i = 0; i < LENGTH(tags); ++i) {
+        if (!(selmon->tagset[selmon->seltags] & 1 << i)) {
+            continue;
+        }
+        pos = i;
+        row = pos / columns;
+        col = pos % columns;
+        if (arg->ui & SWITCHTAG_UP) {     /* UP */
+            row --;
+            if (row < 0) {
+                row = tagrows - 1;
+            }
+            do {
+                pos = row * columns + col;
+                row --;
+            } while (pos >= LENGTH(tags));
+        }
+        if (arg->ui & SWITCHTAG_DOWN) {     /* DOWN */
+            row ++;
+            if (row >= tagrows) {
+                row = 0;
+            }
+            pos = row * columns + col;
+            if (pos >= LENGTH(tags)) {
+                row = 0;
+            }
+            pos = row * columns + col;
+        }
+        if (arg->ui & SWITCHTAG_LEFT) {     /* LEFT */
+            col --;
+            if (col < 0) {
+                col = columns - 1;
+            }
+            do {
+                pos = row * columns + col;
+                col --;
+            } while (pos >= LENGTH(tags));
+        }
+        if (arg->ui & SWITCHTAG_RIGHT) {     /* RIGHT */
+            col ++;
+            if (col >= columns) {
+                col = 0;
+            }
+            pos = row * columns + col;
+            if (pos >= LENGTH(tags)) {
+                col = 0;
+                pos = row * columns + col;
+            }
+        }
+        new_tagset |= 1 << pos;
+    }
+    new_arg.ui = new_tagset;
+    if (arg->ui & SWITCHTAG_TOGGLETAG) {
+        toggletag(&new_arg);
+    }
+    if (arg->ui & SWITCHTAG_TAG) {
+        tag(&new_arg);
+    }
+    if (arg->ui & SWITCHTAG_VIEW) {
+        view (&new_arg);
+    }
+    if (arg->ui & SWITCHTAG_TOGGLEVIEW) {
+        toggleview (&new_arg);
+    }
+}
 
 void
 tag(const Arg *arg) {
diff --git a/config.def.h b/config.def.h
index 875885b..2dd24c6 100644
--- a/config.def.h
+++ b/config.def.h
@@ -31,6 +31,13 @@ static const float mfact      = 0.55; /* factor of master area size [0.05..0.95]
 static const int nmaster      = 1;    /* number of clients in master area */
 static const Bool resizehints = True; /* True means respect size hints in tiled resizals */
 
+/* xkb frontend */
+static const Bool showxkb         = True; /* False means no xkb layout text */
+static const char *xkb_layouts [] = {
+    "en",
+    "ru",
+};
+
 static const Layout layouts[] = {
 	/* symbol     arrange function */
 	{ "[]=",      tile },    /* first entry is default */
diff --git a/dwm.c b/dwm.c
index 1bbb4b3..35bd4d7 100644
--- a/dwm.c
+++ b/dwm.c
@@ -36,6 +36,7 @@
 #include <X11/Xlib.h>
 #include <X11/Xproto.h>
 #include <X11/Xutil.h>
+#include <X11/XKBlib.h>
 #ifdef XINERAMA
 #include <X11/extensions/Xinerama.h>
 #endif /* XINERAMA */
@@ -83,6 +84,7 @@ typedef struct {
 
 typedef struct Monitor Monitor;
 typedef struct Client Client;
+typedef struct XkbInfo XkbInfo;
 struct Client {
 	char name[256];
 	float mina, maxa;
@@ -96,6 +98,13 @@ struct Client {
 	Client *snext;
 	Monitor *mon;
 	Window win;
+    XkbInfo *xkb;
+};
+struct XkbInfo {
+    XkbInfo *next;
+    XkbInfo *prev;
+    int group;
+    Window w;
 };
 
 typedef struct {
@@ -165,6 +174,7 @@ static void drawbar(Monitor *m);
 static void drawbars(void);
 static void enternotify(XEvent *e);
 static void expose(XEvent *e);
+static XkbInfo *findxkb(Window w);
 static void focus(Client *c);
 static void focusin(XEvent *e);
 static void focusmon(const Arg *arg);
@@ -231,6 +241,7 @@ static Monitor *wintomon(Window w);
 static int xerror(Display *dpy, XErrorEvent *ee);
 static int xerrordummy(Display *dpy, XErrorEvent *ee);
 static int xerrorstart(Display *dpy, XErrorEvent *ee);
+static void xkbeventnotify(XEvent *e);
 static void zoom(const Arg *arg);
 
 /* variables */
@@ -241,6 +252,7 @@ static int sw, sh;           /* X display screen geometry width, height */
 static int bh, blw = 0;      /* bar geometry */
 static int (*xerrorxlib)(Display *, XErrorEvent *);
 static unsigned int numlockmask = 0;
+static int xkbEventType = 0;
 static void (*handler[LASTEvent]) (XEvent *) = {
 	[ButtonPress] = buttonpress,
 	[ClientMessage] = clientmessage,
@@ -266,6 +278,8 @@ static Drw *drw;
 static Fnt *fnt;
 static Monitor *mons, *selmon;
 static Window root;
+static XkbInfo xkbGlobal;
+static XkbInfo *xkbSaved = NULL;
 
 /* configuration, allows nested code to access above variables */
 #include "config.h"
@@ -693,6 +707,7 @@ dirtomon(int dir) {
 void
 drawbar(Monitor *m) {
 	int x, xx, w;
+    int ww = 0;
 	unsigned int i, occ = 0, urg = 0;
 	Client *c;
 
@@ -718,14 +733,23 @@ drawbar(Monitor *m) {
 	if(m == selmon) { /* status is only drawn on selected monitor */
 		w = TEXTW(stext);
 		x = m->ww - w;
+        if (showxkb) {
+            ww = TEXTW(xkb_layouts[xkbGlobal.group]);
+            x -= ww;
+        }
 		if(x < xx) {
 			x = xx;
 			w = m->ww - xx;
 		}
 		drw_text(drw, x, 0, w, bh, stext, 0);
+        if (showxkb) {
+            drw_setscheme(drw, &scheme[SchemeNorm]);
+            drw_text(drw, x+w, 0, ww, bh, xkb_layouts[xkbGlobal.group], 0);
+        }
 	}
 	else
 		x = m->ww;
+
 	if((w = x - xx) > bh) {
 		x = xx;
 		if(m->sel) {
@@ -777,6 +801,18 @@ expose(XEvent *e) {
 		drawbar(m);
 }
 
+XkbInfo *
+findxkb(Window w)
+{
+    XkbInfo *xkb;
+    for (xkb = xkbSaved; xkb != NULL; xkb=xkb->next) {
+        if (xkb->w == w) {
+            return xkb;
+        }
+    }
+    return NULL;
+}
+
 void
 focus(Client *c) {
 	if(!c || !ISVISIBLE(c))
@@ -1008,6 +1044,7 @@ manage(Window w, XWindowAttributes *wa) {
 	Client *c, *t = NULL;
 	Window trans = None;
 	XWindowChanges wc;
+    XkbInfo *xkb;
 
 	if(!(c = calloc(1, sizeof(Client))))
 		die("fatal: could not malloc() %u bytes\n", sizeof(Client));
@@ -1038,6 +1075,24 @@ manage(Window w, XWindowAttributes *wa) {
 	           && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my);
 	c->bw = borderpx;
 
+    /* Set current xkb state */
+    xkb = findxkb(c->win);
+    if (xkb == NULL) {
+        xkb = malloc(sizeof *xkb);
+        if (xkb == NULL) {
+            die("fatal: could not malloc() %u bytes\n", sizeof *xkb);
+        }
+        xkb->group = xkbGlobal.group;
+        xkb->w = c->win;
+        xkb->next = xkbSaved;
+        if (xkbSaved != NULL) {
+            xkbSaved->prev = xkb;
+        }
+        xkb->prev = NULL;
+        xkbSaved = xkb;
+    }
+    c->xkb = xkb;
+
 	wc.border_width = c->bw;
 	XConfigureWindow(dpy, w, CWBorderWidth, &wc);
 	XSetWindowBorder(dpy, w, scheme[SchemeNorm].border->rgb);
@@ -1344,8 +1399,14 @@ run(void) {
 	/* main event loop */
 	XSync(dpy, False);
 	while(running && !XNextEvent(dpy, &ev))
+    {
+        if(ev.type == xkbEventType) {
+            xkbeventnotify(&ev);
+            continue;
+        }
 		if(handler[ev.type])
 			handler[ev.type](&ev); /* call handler */
+    }
 }
 
 void
@@ -1428,6 +1489,7 @@ setfocus(Client *c) {
 		XChangeProperty(dpy, root, netatom[NetActiveWindow],
  		                XA_WINDOW, 32, PropModeReplace,
  		                (unsigned char *) &(c->win), 1);
+        XkbLockGroup(dpy, XkbUseCoreKbd, c->xkb->group);
 	}
 	sendevent(c, wmatom[WMTakeFocus]);
 }
@@ -1490,6 +1552,7 @@ setmfact(const Arg *arg) {
 void
 setup(void) {
 	XSetWindowAttributes wa;
+    XkbStateRec xkbstate;
 
 	/* clean up any zombies immediately */
 	sigchld(0);
@@ -1541,6 +1604,16 @@ setup(void) {
 	                |EnterWindowMask|LeaveWindowMask|StructureNotifyMask|PropertyChangeMask;
 	XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa);
 	XSelectInput(dpy, root, wa.event_mask);
+
+    /* get xkb extension info, events and current state */
+    if (!XkbQueryExtension(dpy, NULL, &xkbEventType, NULL, NULL, NULL)) {
+		fputs("warning: can not query xkb extension\n", stderr);
+    }
+    XkbSelectEventDetails(dpy, XkbUseCoreKbd, XkbStateNotify,
+                          XkbAllStateComponentsMask, XkbGroupStateMask);
+    XkbGetState(dpy, XkbUseCoreKbd, &xkbstate);
+    xkbGlobal.group = xkbstate.locked_group;
+
 	grabkeys();
 	focus(NULL);
 }
@@ -1687,6 +1760,7 @@ void
 unmanage(Client *c, Bool destroyed) {
 	Monitor *m = c->mon;
 	XWindowChanges wc;
+    XkbInfo *xkb;
 
 	/* The server grab construct avoids race conditions. */
 	detach(c);
@@ -1702,6 +1776,18 @@ unmanage(Client *c, Bool destroyed) {
 		XSetErrorHandler(xerror);
 		XUngrabServer(dpy);
 	}
+    else {
+        xkb = findxkb(c->win);
+        if (xkb != NULL) {
+            if (xkb->prev) {
+                xkb->prev->next = xkb->next;
+            }
+            if (xkb->next) {
+                xkb->next->prev = xkb->prev;
+            }
+            free(xkb);
+        }
+    }
 	free(c);
 	focus(NULL);
 	updateclientlist();
@@ -2030,6 +2116,23 @@ xerrorstart(Display *dpy, XErrorEvent *ee) {
 	return -1;
 }
 
+void xkbeventnotify(XEvent *e)
+{
+    XkbEvent *ev;
+
+    ev = (XkbEvent *) e;
+    switch (ev->any.xkb_type) {
+    case XkbStateNotify:
+        xkbGlobal.group = ev->state.locked_group;
+        if (selmon != NULL && selmon->sel != NULL) {
+            selmon->sel->xkb->group = xkbGlobal.group;
+        }
+        if (showxkb) {
+            drawbars();
+        }
+        break;
+    }
+}
 void
 zoom(const Arg *arg) {
 	Client *c = selmon->sel;

Reply via email to