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;