Author: Jan Christoph Ebersbach URL: http://dwm.suckless.org/patches/single_tagset This patch addresses the multi-monitor setup. Instead of having separate tags for every monitor there is just one list of tags for all monitors. Instead of moving windows from one monitor to the other, the desired tag from the other monitor can just be selected and all windows will be drawn on the current monitor. Several deep changes needed to be made: 1. Macro ISVISIBLE expects a second parameter, the monitor 2. Monitor->clients and Monitor->stack were moved to the global variable Clientlist cl. All monitors refer to this one list. 3. A new method attachclients was added. When changing between tags this function ensures that all clients are pointing to the right monitor. Please be aware that this patch probably breaks any other patch! Index: dwm/dwm.c =================================================================== --- dwm/dwm.c.orig 2014-02-09 15:24:26.156117354 +0100 +++ dwm/dwm.c 2014-02-09 15:24:26.148117354 +0100 @@ -48,7 +48,7 @@ #define CLEANMASK(mask) (mask & ~(numlockmask|LockMask) & (ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask)) #define INTERSECT(x,y,w,h,m) (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - MAX((x),(m)->wx)) \ * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - MAX((y),(m)->wy))) -#define ISVISIBLE(C) ((C->tags & C->mon->tagset[C->mon->seltags])) +#define ISVISIBLE(C, M) ((C->tags & M->tagset[M->seltags])) #define LENGTH(X) (sizeof X / sizeof X[0]) #define MOUSEMASK (BUTTONMASK|PointerMotionMask) #define WIDTH(X) ((X)->w + 2 * (X)->bw) @@ -110,6 +110,7 @@ void (*arrange)(Monitor *); } Layout; +typedef struct Clientlist Clientlist; struct Monitor { char ltsymbol[16]; float mfact; @@ -123,9 +124,8 @@ unsigned int tagset[2]; Bool showbar; Bool topbar; - Client *clients; + Clientlist *cl; Client *sel; - Client *stack; Monitor *next; Window barwin; const Layout *lt[2]; @@ -140,12 +140,18 @@ int monitor; } Rule; +struct Clientlist { + Client *clients; + Client *stack; +}; + /* function declarations */ static void applyrules(Client *c); static Bool applysizehints(Client *c, int *x, int *y, int *w, int *h, Bool interact); static void arrange(Monitor *m); static void arrangemon(Monitor *m); static void attach(Client *c); +static void attachclients(Monitor *m); static void attachstack(Client *c); static void buttonpress(XEvent *e); static void checkotherwm(void); @@ -183,7 +189,7 @@ static void monocle(Monitor *m); static void motionnotify(XEvent *e); static void movemouse(const Arg *arg); -static Client *nexttiled(Client *c); +static Client *nexttiled(Client *c, Monitor *m); static void pop(Client *); static void propertynotify(XEvent *e); static void quit(const Arg *arg); @@ -266,6 +272,7 @@ static Fnt *fnt; static Monitor *mons, *selmon; static Window root; +static Clientlist *cl; /* configuration, allows nested code to access above variables */ #include "config.h" @@ -296,7 +303,7 @@ { c->isfloating = r->isfloating; c->tags |= r->tags; - for(m = mons; m && m->num != r->monitor; m = m->next); + for(m = mons; m && (m->tagset[m->seltags] & c->tags) == 0; m = m->next) ; if(m) c->mon = m; } @@ -377,9 +384,9 @@ void arrange(Monitor *m) { if(m) - showhide(m->stack); + showhide(m->cl->stack); else for(m = mons; m; m = m->next) - showhide(m->stack); + showhide(m->cl->stack); if(m) { arrangemon(m); restack(m); @@ -396,14 +403,47 @@ void attach(Client *c) { - c->next = c->mon->clients; - c->mon->clients = c; + c->next = c->mon->cl->clients; + c->mon->cl->clients = c; +} + +void +attachclients(Monitor *m) { + /* attach clients to the specified monitor */ + Monitor *tm; + Client *c; + unsigned int utags = 0; + Bool rmons = False; + if(!m) + return; + + /* collect information about the tags in use */ + for(tm = mons; tm; tm = tm->next) + if(tm != m) + utags |= m->tagset[m->seltags]; + + for(c = m->cl->clients; c; c = c->next) + if(ISVISIBLE(c, m)) { + /* if client is also visible on other tags that are displayed on + * other monitors, remove these tags */ + if(c->tags & utags) { + c->tags = c->tags & m->tagset[m->seltags]; + rmons = True; + } + unfocus(c, True); + c->mon = m; + } + + if(rmons) + for(tm = mons; tm; tm = tm->next) + if(tm != m) + arrange(tm); } void attachstack(Client *c) { - c->snext = c->mon->stack; - c->mon->stack = c; + c->snext = c->mon->cl->stack; + c->mon->cl->stack = c; } void @@ -466,8 +506,8 @@ view(&a); selmon->lt[selmon->sellt] = &foo; for(m = mons; m; m = m->next) - while(m->stack) - unmanage(m->stack, False); + while(m->cl->stack) + unmanage(m->cl->stack, False); XUngrabKey(dpy, AnyKey, AnyModifier, root); while(mons) cleanupmon(mons); @@ -527,7 +567,7 @@ || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); } else if(cme->message_type == netatom[NetActiveWindow]) { - if(!ISVISIBLE(c)) { + if(!ISVISIBLE(c, c->mon)) { c->mon->seltags ^= 1; c->mon->tagset[c->mon->seltags] = c->tags; } @@ -609,7 +649,7 @@ c->y = m->my + (m->mh / 2 - HEIGHT(c) / 2); /* center in y direction */ if((ev->value_mask & (CWX|CWY)) && !(ev->value_mask & (CWWidth|CWHeight))) configure(c); - if(ISVISIBLE(c)) + if(ISVISIBLE(c, m)) XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); } else @@ -630,11 +670,18 @@ Monitor * createmon(void) { - Monitor *m; + Monitor *m, *tm; + int i; if(!(m = (Monitor *)calloc(1, sizeof(Monitor)))) die("fatal: could not malloc() %u bytes\n", sizeof(Monitor)); - m->tagset[0] = m->tagset[1] = 1; + m->cl = cl; + /* reassing tags when creating a new monitor */ + for(i=1, tm = mons; tm; tm = tm->next, i++) { + tm->seltags ^= 1; + tm->tagset[tm->seltags] = i; + } + m->tagset[0] = m->tagset[1] = i; m->mfact = mfact; m->nmaster = nmaster; m->showbar = showbar; @@ -658,7 +705,7 @@ detach(Client *c) { Client **tc; - for(tc = &c->mon->clients; *tc && *tc != c; tc = &(*tc)->next); + for(tc = &c->mon->cl->clients; *tc && *tc != c; tc = &(*tc)->next); *tc = c->next; } @@ -666,11 +713,11 @@ detachstack(Client *c) { Client **tc, *t; - for(tc = &c->mon->stack; *tc && *tc != c; tc = &(*tc)->snext); + for(tc = &c->mon->cl->stack; *tc && *tc != c; tc = &(*tc)->snext); *tc = c->snext; if(c == c->mon->sel) { - for(t = c->mon->stack; t && !ISVISIBLE(t); t = t->snext); + for(t = c->mon->cl->stack; t && !ISVISIBLE(t, c->mon); t = t->snext); c->mon->sel = t; } } @@ -696,7 +743,7 @@ unsigned int i, occ = 0, urg = 0; Client *c; - for(c = m->clients; c; c = c->next) { + for(c = m->cl->clients; c; c = c->next) { occ |= c->tags; if(c->isurgent) urg |= c->tags; @@ -779,8 +826,8 @@ void focus(Client *c) { - if(!c || !ISVISIBLE(c)) - for(c = selmon->stack; c && !ISVISIBLE(c); c = c->snext); + if(!c || !ISVISIBLE(c, selmon)) + for(c = selmon->cl->stack; c && !ISVISIBLE(c, selmon); c = c->snext); /* was if(selmon->sel) */ if(selmon->sel && selmon->sel != c) unfocus(selmon->sel, False); @@ -832,17 +879,17 @@ if(!selmon->sel) return; if(arg->i > 0) { - for(c = selmon->sel->next; c && !ISVISIBLE(c); c = c->next); + for(c = selmon->sel->next; c && !ISVISIBLE(c, selmon); c = c->next); if(!c) - for(c = selmon->clients; c && !ISVISIBLE(c); c = c->next); + for(c = selmon->cl->clients; c && !ISVISIBLE(c, selmon); c = c->next); } else { - for(i = selmon->clients; i != selmon->sel; i = i->next) - if(ISVISIBLE(i)) + for(i = selmon->cl->clients; i != selmon->sel; i = i->next) + if(ISVISIBLE(i, selmon)) c = i; if(!c) for(; i; i = i->next) - if(ISVISIBLE(i)) + if(ISVISIBLE(i, selmon)) c = i; } if(c) { @@ -1092,12 +1139,12 @@ unsigned int n = 0; Client *c; - for(c = m->clients; c; c = c->next) - if(ISVISIBLE(c)) + for(c = m->cl->clients; c; c = c->next) + if(ISVISIBLE(c, m)) n++; if(n > 0) /* override layout symbol */ snprintf(m->ltsymbol, sizeof m->ltsymbol, "[%d]", n); - for(c = nexttiled(m->clients); c; c = nexttiled(c->next)) + for(c = nexttiled(m->cl->clients, m); c; c = nexttiled(c->next, m)) resize(c, m->wx, m->wy, m->ww - 2 * c->bw, m->wh - 2 * c->bw, False); } @@ -1175,8 +1222,8 @@ } Client * -nexttiled(Client *c) { - for(; c && (c->isfloating || !ISVISIBLE(c)); c = c->next); +nexttiled(Client *c, Monitor *m) { + for(; c && (c->isfloating || !ISVISIBLE(c, m)); c = c->next); return c; } @@ -1328,8 +1375,8 @@ if(m->lt[m->sellt]->arrange) { wc.stack_mode = Below; wc.sibling = m->barwin; - for(c = m->stack; c; c = c->snext) - if(!c->isfloating && ISVISIBLE(c)) { + for(c = m->cl->stack; c; c = c->snext) + if(!c->isfloating && ISVISIBLE(c, m)) { XConfigureWindow(dpy, c->win, CWSibling|CWStackMode, &wc); wc.sibling = c->win; } @@ -1379,7 +1426,6 @@ if(c->mon == m) return; unfocus(c, True); - detach(c); detachstack(c); c->mon = m; c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */ @@ -1501,6 +1547,8 @@ sw = DisplayWidth(dpy, screen); sh = DisplayHeight(dpy, screen); bh = fnt->h + 2; + if(!(cl = (Clientlist *)calloc(1, sizeof(Clientlist)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Clientlist)); drw = drw_create(dpy, screen, root, sw, sh); drw_setfont(drw, fnt); updategeom(); @@ -1549,9 +1597,11 @@ showhide(Client *c) { if(!c) return; - if(ISVISIBLE(c)) { /* show clients top down */ + if(ISVISIBLE(c, c->mon)) { /* show clients top down */ XMoveWindow(dpy, c->win, c->x, c->y); if((!c->mon->lt[c->mon->sellt]->arrange || c->isfloating) && !c->isfullscreen) + if(c->isfloating) + keepfloatingposition(c); resize(c, c->x, c->y, c->w, c->h, False); showhide(c->snext); } @@ -1585,7 +1635,22 @@ void tag(const Arg *arg) { + Monitor *m; + unsigned int newtags; if(selmon->sel && arg->ui & TAGMASK) { + newtags = arg->ui & TAGMASK; + for(m = mons; m; m = m->next) + /* if tag is visible on another monitor, move client to the new monitor */ + if(m != selmon && m->tagset[m->seltags] & newtags) { + /* prevent moving client to all tags (MODKEY-Shift-0) when multiple monitors are connected */ + if(newtags & selmon->tagset[selmon->seltags]) + return; + selmon->sel->tags = newtags; + selmon->sel->mon = m; + arrange(m); + break; + } + /* workaround in case just one monitor is connected */ selmon->sel->tags = arg->ui & TAGMASK; focus(NULL); arrange(selmon); @@ -1604,7 +1669,7 @@ unsigned int i, n, h, mw, my, ty; Client *c; - for(n = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), n++); + for(n = 0, c = nexttiled(m->cl->clients, m); c; c = nexttiled(c->next, m), n++); if(n == 0) return; @@ -1612,7 +1677,7 @@ mw = m->nmaster ? m->ww * m->mfact : 0; else mw = m->ww; - for(i = my = ty = 0, c = nexttiled(m->clients); c; c = nexttiled(c->next), i++) + for(i = my = ty = 0, c = nexttiled(m->cl->clients, m); c; c = nexttiled(c->next, m), i++) if(i < m->nmaster) { h = (m->wh - my) / (MIN(n, m->nmaster) - i); resize(c, m->wx, m->wy + my, mw - (2*c->bw), h - (2*c->bw), False); @@ -1648,12 +1713,17 @@ void toggletag(const Arg *arg) { + Monitor *m; unsigned int newtags; if(!selmon->sel) return; newtags = selmon->sel->tags ^ (arg->ui & TAGMASK); if(newtags) { + /* prevent adding tags that are in use on other monitors */ + for(m = mons; m; m = m->next) + if(m != selmon && newtags & m->tagset[m->seltags]) + return; selmon->sel->tags = newtags; focus(NULL); arrange(selmon); @@ -1662,9 +1732,14 @@ void toggleview(const Arg *arg) { + Monitor *m; unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); if(newtagset) { + /* prevent displaying the same tags on multiple monitors */ + for(m = mons; m; m = m->next) + if(m != selmon && newtagset & m->tagset[m->seltags]) + return; selmon->tagset[selmon->seltags] = newtagset; focus(NULL); arrange(selmon); @@ -1703,6 +1778,7 @@ XUngrabServer(dpy); } free(c); + attachclients(selmon); focus(NULL); updateclientlist(); arrange(m); @@ -1760,7 +1836,7 @@ XDeleteProperty(dpy, root, netatom[NetClientList]); for(m = mons; m; m = m->next) - for(c = m->clients; c; c = c->next) + for(c = m->cl->clients; c; c = c->next) XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, (unsigned char *) &(c->win), 1); @@ -1790,8 +1866,10 @@ if(n <= nn) { for(i = 0; i < (nn - n); i++) { /* new monitors available */ for(m = mons; m && m->next; m = m->next); - if(m) + if(m) { m->next = createmon(); + attachclients(m->next); + } else mons = createmon(); } @@ -1812,17 +1890,13 @@ else { /* less monitors available nn < n */ for(i = nn; i < n; i++) { for(m = mons; m && m->next; m = m->next); - while(m->clients) { - dirty = True; - c = m->clients; - m->clients = c->next; - detachstack(c); - c->mon = mons; - attach(c); - attachstack(c); - } if(m == selmon) selmon = mons; + for(c = m->cl->clients; c; c = c->next) { + dirty = True; + if(c->mon == m) + c->mon = selmon; + } cleanupmon(m); } } @@ -1960,11 +2034,31 @@ void view(const Arg *arg) { + Monitor *m; + unsigned int newtagset = selmon->tagset[selmon->seltags ^ 1]; if((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) return; + + /* swap tags when trying to display a tag from another monitor */ + if(arg->ui & TAGMASK) + newtagset = arg->ui & TAGMASK; + for(m = mons; m; m = m->next) + if(m != selmon && newtagset & m->tagset[m->seltags]) { + /* prevent displaying all tags (MODKEY-0) when multiple monitors + * are connected */ + if(newtagset & selmon->tagset[selmon->seltags]) + return; + m->seltags ^= 1; + m->tagset[m->seltags] = selmon->tagset[selmon->seltags]; + attachclients(m); + arrange(m); + break; + } + selmon->seltags ^= 1; /* toggle sel tagset */ if(arg->ui & TAGMASK) selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + attachclients(selmon); focus(NULL); arrange(selmon); } @@ -1975,7 +2069,7 @@ Monitor *m; for(m = mons; m; m = m->next) - for(c = m->clients; c; c = c->next) + for(c = m->cl->clients; c; c = c->next) if(c->win == w) return c; return NULL; @@ -2037,8 +2131,8 @@ if(!selmon->lt[selmon->sellt]->arrange || (selmon->sel && selmon->sel->isfloating)) return; - if(c == nexttiled(selmon->clients)) - if(!c || !(c = nexttiled(c->next))) + if(c == nexttiled(selmon->cl->clients, selmon)) + if(!c || !(c = nexttiled(c->next, selmon))) return; pop(c); } Index: dwm/config.def.h =================================================================== --- dwm/config.def.h.orig 2014-02-09 15:24:26.156117354 +0100 +++ dwm/config.def.h 2014-02-09 15:24:26.148117354 +0100 @@ -54,6 +54,7 @@ static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", font, "-nb", normbgcolor, "-nf", normfgcolor, "-sb", selbgcolor, "-sf", selfgcolor, NULL }; static const char *termcmd[] = { "st", NULL }; +#include "keepfloatingposition.c" static Key keys[] = { /* modifier key function argument */ { MODKEY, XK_p, spawn, {.v = dmenucmd } }, Index: dwm/keepfloatingposition.c =================================================================== --- /dev/null 1970-01-01 00:00:00.000000000 +0000 +++ dwm/keepfloatingposition.c 2014-02-09 15:24:26.148117354 +0100 @@ -0,0 +1,30 @@ +static void +keepfloatingposition(Client *c) { + Monitor *m; + int cmmx = c->mon->mx; + int cmmy = c->mon->my; + int cmmw = c->mon->mw; + int cmmh = c->mon->mh; + int mmx, mmy; + if(!(cmmx <= c->x && + cmmx + cmmw - 1 >= c->x && + cmmy <= c->y && + cmmy + cmmh - 1 >= c->y)) + for(m = mons; m; m = m->next) { + mmx = m->mx; + mmy = m->my; + if(mmx <= c->x && + mmx + m->mw - 1 >= c->x && + mmy <= c->y && + mmy + m->mh - 1 >= c->y) { + c->x = c->x - mmx + cmmx; + c->y = c->y - mmy + cmmy; + if(c->x + c->w + 2 * c->bw > cmmx + cmmw - 1) + c->x -= c->x + c->w + 2 * c->bw - cmmx - cmmw; + if(c->y + c->h + 2 * c->bw > cmmy + cmmh - 1) + c->y -= c->y + c->h + 2 * c->bw - cmmy - cmmh; + resizeclient(c, c->x, c->y, c->w, c->h); + break; + } + } +}