diff --git a/config.def.h b/config.def.h index 77ff358..666b9c0 100644 --- a/config.def.h +++ b/config.def.h @@ -12,6 +12,13 @@ static const unsigned int borderpx = 1; /* border pixel of windows */ static const unsigned int snap = 32; /* snap pixel */ static const Bool showbar = True; /* False means no bar */ static const Bool topbar = True; /* False means bottom bar */ +/* Display modes of the tab bar: never shown, always shown, shown only in */ +/* monocle mode in presence of several windows. */ +/* Modes after showtab_nmodes are disabled */ +enum showtab_modes { showtab_never, showtab_auto, showtab_nmodes, showtab_always}; +static const int showtab = showtab_auto; /* Default tab bar show mode */ +static const Bool toptab = False; /* False means bottom tab bar */ + /* tagging */ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; @@ -25,7 +32,7 @@ static const Rule rules[] = { /* layout(s) */ 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 */ +static const Bool resizehints = False; /* True means respect size hints in tiled resizals */ static const Layout layouts[] = { /* symbol arrange function */ @@ -54,6 +61,7 @@ static Key keys[] = { { MODKEY, XK_p, spawn, {.v = dmenucmd } }, { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } }, { MODKEY, XK_b, togglebar, {0} }, + { MODKEY, XK_w, tabmode, {-1} }, { MODKEY, XK_j, focusstack, {.i = +1 } }, { MODKEY, XK_k, focusstack, {.i = -1 } }, { MODKEY, XK_i, incnmaster, {.i = +1 } }, @@ -101,5 +109,6 @@ static Button buttons[] = { { ClkTagBar, 0, Button3, toggleview, {0} }, { ClkTagBar, MODKEY, Button1, tag, {0} }, { ClkTagBar, MODKEY, Button3, toggletag, {0} }, + { ClkTabBar, 0, Button1, focuswin, {0} }, }; diff --git a/dwm.1 b/dwm.1 index 5268a06..d213208 100644 --- a/dwm.1 +++ b/dwm.1 @@ -19,14 +19,22 @@ layout applied. Windows are grouped by tags. Each window can be tagged with one or multiple tags. Selecting certain tags displays all windows with these tags. .P -Each screen contains a small status bar which displays all available tags, the -layout, the title of the focused window, and the text read from the root window -name property, if the screen is focused. A floating window is indicated with an -empty square and a maximised floating window is indicated with a filled square -before the windows title. The selected tags are indicated with a different -color. The tags of the focused window are indicated with a filled square in the -top left corner. The tags which are applied to one or more windows are -indicated with an empty square in the top left corner. +Each screen contains two small status bars. +.P +One bar displays all available tags, the layout, the title of the focused +window, and the text read from the root window name property, if the screen is +focused. A floating window is indicated with an empty square and a maximised +floating window is indicated with a filled square before the windows title. The +selected tags are indicated with a different color. The tags of the focused +window are indicated with a filled square in the top left corner. The tags +which are applied to one or more windows are indicated with an empty square in +the top left corner. +.P +Another bar contains a tab for each window of the current view and allows +navigation from window to window, especially in the monocle mode. The different +display modes of this bar are described under the Mod1\-w Keybord command +section. When a single tag is selected, that tag is recalled in the left corner +of the tab bar. .P dwm draws a small border around windows to indicate the focus state. .SH OPTIONS @@ -43,7 +51,8 @@ command. .TP .B Button1 click on a tag label to display all windows with that tag, click on the layout -label toggles between tiled and floating layout. +label toggles between tiled and floating layout, click on a window name in the +tab bar brings focus to that window. .TP .B Button3 click on a tag label adds/removes all windows with that tag to/from the view. @@ -104,6 +113,12 @@ Increase master area size. .B Mod1\-h Decrease master area size. .TP +.B Mod1\-w +Cycle over the tab bar display modes: never displayed, always displayed, +displayed only in monocle mode when the view contains than one window (auto +mode). Some display modes can be disabled in the configuration, config.h. In +the default configuration only "always" and "auto" display modes are enabled. +.TP .B Mod1\-Return Zooms/cycles focused window to/from master area (tiled layouts only). .TP diff --git a/dwm.c b/dwm.c index 1d78655..33eb637 100644 --- a/dwm.c +++ b/dwm.c @@ -44,7 +44,7 @@ #define BUTTONMASK (ButtonPressMask|ButtonReleaseMask) #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))) + * 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 LENGTH(X) (sizeof X / sizeof X[0]) #define MAX(A, B) ((A) > (B) ? (A) : (B)) @@ -62,7 +62,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMFullscreen, NetActiveWindow, NetWMWindowType, NetWMWindowTypeDialog, NetLast }; /* EWMH atoms */ enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ -enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, +enum { ClkTagBar, ClkTabBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ typedef union { @@ -102,6 +102,7 @@ typedef struct { unsigned long norm[ColLast]; unsigned long sel[ColLast]; Drawable drawable; + Drawable tabdrawable; GC gc; struct { int ascent; @@ -124,25 +125,36 @@ typedef struct { void (*arrange)(Monitor *); } Layout; +typedef struct Pertag Pertag; + +#define MAXTABS 50 + struct Monitor { char ltsymbol[16]; float mfact; int nmaster; int num; int by; /* bar geometry */ + int ty; /* tab bar geometry */ int mx, my, mw, mh; /* screen size */ int wx, wy, ww, wh; /* window area */ unsigned int seltags; unsigned int sellt; unsigned int tagset[2]; Bool showbar; + Bool showtab; Bool topbar; + Bool toptab; Client *clients; Client *sel; Client *stack; Monitor *next; Window barwin; + Window tabwin; + int ntabs; + int tab_widths[MAXTABS]; const Layout *lt[2]; + Pertag *pertag; }; typedef struct { @@ -178,11 +190,15 @@ static void die(const char *errstr, ...); static Monitor *dirtomon(int dir); static void drawbar(Monitor *m); static void drawbars(void); +static void drawtab(Monitor *m); +static void drawtabs(void); static void drawsquare(Bool filled, Bool empty, Bool invert, unsigned long col[ColLast]); -static void drawtext(const char *text, unsigned long col[ColLast], Bool invert); +static void drawtext(Drawable drawable, const char *text, unsigned long col[ColLast], Bool invert); +//static void drawtabtext(const char *text, unsigned long col[ColLast], Bool invert); static void enternotify(XEvent *e); static void expose(XEvent *e); static void focus(Client *c); +static void focuswin(const Arg* arg); static void focusin(XEvent *e); static void focusmon(const Arg *arg); static void focusstack(const Arg *arg); @@ -229,6 +245,7 @@ static void tagmon(const Arg *arg); static int textnw(const char *text, unsigned int len); static void tile(Monitor *); static void togglebar(const Arg *arg); +static void tabmode(const Arg *arg); static void togglefloating(const Arg *arg); static void toggletag(const Arg *arg); static void toggleview(const Arg *arg); @@ -258,6 +275,7 @@ static char stext[256]; static int screen; static int sw, sh; /* X display screen geometry width, height */ static int bh, blw = 0; /* bar geometry */ +static int th = 0; /* tab bar geometry */ static int (*xerrorxlib)(Display *, XErrorEvent *); static unsigned int numlockmask = 0; static void (*handler[LASTEvent]) (XEvent *) = { @@ -287,6 +305,16 @@ static Window root; /* configuration, allows nested code to access above variables */ #include "config.h" +struct Pertag { + unsigned int curtag, prevtag; /* current and previous tag */ + int nmasters[LENGTH(tags) + 1]; /* number of windows in master area */ + float mfacts[LENGTH(tags) + 1]; /* mfacts per tag */ + unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */ + const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */ + Bool showbars[LENGTH(tags) + 1]; /* display bar for the current tag */ + Bool showtabs[LENGTH(tags) + 1]; /* display tab bar for the current tag */ +}; + /* compile-time check if all tags fit into an unsigned int bit array. */ struct NumTags { char limitexceeded[LENGTH(tags) > 31 ? -1 : 1]; }; @@ -405,6 +433,10 @@ arrange(Monitor *m) { void arrangemon(Monitor *m) { + /* Following two lines needed for the auto mode of the tab bar */ + updatebarpos(m); + XMoveResizeWindow(dpy, m->tabwin, m->wx, m->ty, m->ww, th); + strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol); if(m->lt[m->sellt]->arrange) m->lt[m->sellt]->arrange(m); @@ -454,14 +486,32 @@ buttonpress(XEvent *e) { else click = ClkWinTitle; } + if(ev->window == selmon->tabwin) { + i = 0; x = 0; + for(c = selmon->clients; c; c = c->next){ + if(!ISVISIBLE(c)) continue; + x += selmon->tab_widths[i]; + if (ev->x > x) + ++i; + else + break; + if(i >= m->ntabs) break; + } + if(c) { + click = ClkTabBar; + arg.ui = i; + } + } else if((c = wintoclient(ev->window))) { focus(c); click = ClkClientWin; } for(i = 0; i < LENGTH(buttons); i++) if(click == buttons[i].click && buttons[i].func && buttons[i].button == ev->button - && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)) - buttons[i].func(click == ClkTagBar && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg); + && CLEANMASK(buttons[i].mask) == CLEANMASK(ev->state)){ + buttons[i].func(((click == ClkTagBar || click == ClkTabBar) + && buttons[i].arg.i == 0) ? &arg : &buttons[i].arg); + } } void @@ -491,6 +541,7 @@ cleanup(void) { XFreeFont(dpy, dc.font.xfont); XUngrabKey(dpy, AnyKey, AnyModifier, root); XFreePixmap(dpy, dc.drawable); + XFreePixmap(dpy, dc.tabdrawable); XFreeGC(dpy, dc.gc); XFreeCursor(dpy, cursor[CurNormal]); XFreeCursor(dpy, cursor[CurResize]); @@ -513,6 +564,8 @@ cleanupmon(Monitor *mon) { } XUnmapWindow(dpy, mon->barwin); XDestroyWindow(dpy, mon->barwin); + XUnmapWindow(dpy, mon->tabwin); + XDestroyWindow(dpy, mon->tabwin); free(mon); } @@ -538,7 +591,7 @@ clientmessage(XEvent *e) { if(cme->message_type == netatom[NetWMState]) { if(cme->data.l[1] == netatom[NetWMFullscreen] || cme->data.l[2] == netatom[NetWMFullscreen]) setfullscreen(c, (cme->data.l[0] == 1 /* _NET_WM_STATE_ADD */ - || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); + || (cme->data.l[0] == 2 /* _NET_WM_STATE_TOGGLE */ && !c->isfullscreen))); } else if(cme->message_type == netatom[NetActiveWindow]) { if(!ISVISIBLE(c)) { @@ -581,9 +634,13 @@ configurenotify(XEvent *e) { if(dc.drawable != 0) XFreePixmap(dpy, dc.drawable); dc.drawable = XCreatePixmap(dpy, root, sw, bh, DefaultDepth(dpy, screen)); + if(dc.tabdrawable != 0) + XFreePixmap(dpy, dc.tabdrawable); + dc.tabdrawable = XCreatePixmap(dpy, root, sw, th, DefaultDepth(dpy, screen)); updatebars(); - for(m = mons; m; m = m->next) + for(m = mons; m; m = m->next){ XMoveResizeWindow(dpy, m->barwin, m->wx, m->by, m->ww, bh); + } focus(NULL); arrange(NULL); } @@ -646,6 +703,7 @@ configurerequest(XEvent *e) { Monitor * createmon(void) { Monitor *m; + int i; if(!(m = (Monitor *)calloc(1, sizeof(Monitor)))) die("fatal: could not malloc() %u bytes\n", sizeof(Monitor)); @@ -653,10 +711,34 @@ createmon(void) { m->mfact = mfact; m->nmaster = nmaster; m->showbar = showbar; + m->showtab = showtab; m->topbar = topbar; + m->toptab = toptab; + m->ntabs = 0; m->lt[0] = &layouts[0]; m->lt[1] = &layouts[1 % LENGTH(layouts)]; strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol); + if(!(m->pertag = (Pertag *)calloc(1, sizeof(Pertag)))) + die("fatal: could not malloc() %u bytes\n", sizeof(Pertag)); + m->pertag->curtag = m->pertag->prevtag = 1; + for(i=0; i <= LENGTH(tags); i++) { + /* init nmaster */ + m->pertag->nmasters[i] = m->nmaster; + + /* init mfacts */ + m->pertag->mfacts[i] = m->mfact; + + /* init layouts */ + m->pertag->ltidxs[i][0] = m->lt[0]; + m->pertag->ltidxs[i][1] = m->lt[1]; + m->pertag->sellts[i] = m->sellt; + + /* init showbar */ + m->pertag->showbars[i] = m->showbar; + + /* init showtab */ + m->pertag->showtabs[i] = m->showtab; + } return m; } @@ -731,13 +813,13 @@ drawbar(Monitor *m) { for(i = 0; i < LENGTH(tags); i++) { dc.w = TEXTW(tags[i]); col = m->tagset[m->seltags] & 1 << i ? dc.sel : dc.norm; - drawtext(tags[i], col, urg & 1 << i); + drawtext(dc.drawable, tags[i], col, urg & 1 << i); drawsquare(m == selmon && selmon->sel && selmon->sel->tags & 1 << i, - occ & 1 << i, urg & 1 << i, col); + occ & 1 << i, urg & 1 << i, col); dc.x += dc.w; } dc.w = blw = TEXTW(m->ltsymbol); - drawtext(m->ltsymbol, dc.norm, False); + drawtext(dc.drawable, m->ltsymbol, dc.norm, False); dc.x += dc.w; x = dc.x; if(m == selmon) { /* status is only drawn on selected monitor */ @@ -747,19 +829,20 @@ drawbar(Monitor *m) { dc.x = x; dc.w = m->ww - x; } - drawtext(stext, dc.norm, False); + drawtext(dc.drawable, stext, dc.norm, False); } else dc.x = m->ww; if((dc.w = dc.x - x) > bh) { dc.x = x; if(m->sel) { - col = m == selmon ? dc.sel : dc.norm; - drawtext(m->sel->name, col, False); + // col = m == selmon ? dc.sel : dc.norm; + // drawtext(dc.drawable, m->sel->name, col, False); + drawtext(dc.drawable, m->sel->name, dc.norm, False); drawsquare(m->sel->isfixed, m->sel->isfloating, False, col); } else - drawtext(NULL, dc.norm, False); + drawtext(dc.drawable, NULL, dc.norm, False); } XCopyArea(dpy, dc.drawable, m->barwin, dc.gc, 0, 0, m->ww, bh, 0, 0); XSync(dpy, False); @@ -774,6 +857,104 @@ drawbars(void) { } void +drawtabs(void) { + Monitor *m; + + for(m = mons; m; m = m->next) + drawtab(m); +} + +static int +cmpint(const void *p1, const void *p2) { + /* The actual arguments to this function are "pointers to + pointers to char", but strcmp(3) arguments are "pointers + to char", hence the following cast plus dereference */ + return *((int*) p1) > * (int*) p2; +} + + +void +drawtab(Monitor *m) { + unsigned long *col; + Client *c; + int i; + int itag = -1; + char view_info[50]; + int view_info_w = 0; + int sorted_label_widths[MAXTABS]; + int tot_width; + int maxsize = bh; + dc.x = 0; + + //view_info: indicate the tag which is displayed in the view + for(i = 0; i < LENGTH(tags); ++i){ + if((selmon->tagset[selmon->seltags] >> i) & 1) { + if(itag >=0){ //more than one tag selected + itag = -1; + break; + } + itag = i; + } + } + if(0 <= itag && itag < LENGTH(tags)){ + snprintf(view_info, sizeof view_info, "[%s]", tags[itag]); + } else { + strncpy(view_info, "[...]", sizeof view_info); + } + view_info[sizeof(view_info) - 1 ] = 0; + view_info_w = TEXTW(view_info); + tot_width = view_info_w; + + /* Calculates number of labels and their width */ + m->ntabs = 0; + for(c = m->clients; c; c = c->next){ + if(!ISVISIBLE(c)) continue; + m->tab_widths[m->ntabs] = TEXTW(c->name); + tot_width += m->tab_widths[m->ntabs]; + ++m->ntabs; + if(m->ntabs >= MAXTABS) break; + } + + if(tot_width > m->ww){ //not enough space to display the labels, they need to be truncated + memcpy(sorted_label_widths, m->tab_widths, sizeof(int) * m->ntabs); + qsort(sorted_label_widths, m->ntabs, sizeof(int), cmpint); + tot_width = view_info_w; + for(i = 0; i < m->ntabs; ++i){ + if(tot_width + (m->ntabs - i) * sorted_label_widths[i] > m->ww) + break; + tot_width += sorted_label_widths[i]; + } + maxsize = (m->ww - tot_width) / (m->ntabs - i); + } else{ + maxsize = m->ww; + } + i = 0; + for(c = m->clients; c; c = c->next){ + if(!ISVISIBLE(c)) continue; + if(i >= m->ntabs) break; + if(m->tab_widths[i] > maxsize) m->tab_widths[i] = maxsize; + dc.w = m->tab_widths[i]; + col = (c == m->sel) ? dc.sel : dc.norm; + drawtext(dc.tabdrawable, c->name, col, 0); + dc.x += dc.w; + ++i; + } + + /* cleans interspace between window names and current viewed tag label */ + dc.w = m->ww - view_info_w - dc.x; + drawtext(dc.tabdrawable, NULL, dc.norm, 0); + + /* view info */ + dc.x += dc.w; + dc.w = view_info_w; + drawtext(dc.tabdrawable, view_info, dc.norm, 0); + + XCopyArea(dpy, dc.tabdrawable, m->tabwin, dc.gc, 0, 0, m->ww, th, 0, 0); + XSync(dpy, False); +} + + +void drawsquare(Bool filled, Bool empty, Bool invert, unsigned long col[ColLast]) { int x; @@ -785,13 +966,14 @@ drawsquare(Bool filled, Bool empty, Bool invert, unsigned long col[ColLast]) { XDrawRectangle(dpy, dc.drawable, dc.gc, dc.x+1, dc.y+1, x, x); } + void -drawtext(const char *text, unsigned long col[ColLast], Bool invert) { +drawtext(Drawable drawable, const char *text, unsigned long col[ColLast], Bool invert) { char buf[256]; int i, x, y, h, len, olen; XSetForeground(dpy, dc.gc, col[invert ? ColFG : ColBG]); - XFillRectangle(dpy, dc.drawable, dc.gc, dc.x, dc.y, dc.w, dc.h); + XFillRectangle(dpy, drawable, dc.gc, dc.x, dc.y, dc.w, dc.h); if(!text) return; olen = strlen(text); @@ -807,11 +989,12 @@ drawtext(const char *text, unsigned long col[ColLast], Bool invert) { for(i = len; i && i > len - 3; buf[--i] = '.'); XSetForeground(dpy, dc.gc, col[invert ? ColBG : ColFG]); if(dc.font.set) - XmbDrawString(dpy, dc.drawable, dc.font.set, dc.gc, x, y, buf, len); + XmbDrawString(dpy, drawable, dc.font.set, dc.gc, x, y, buf, len); else - XDrawString(dpy, dc.drawable, dc.gc, x, y, buf, len); + XDrawString(dpy, drawable, dc.gc, x, y, buf, len); } + void enternotify(XEvent *e) { Client *c; @@ -836,8 +1019,10 @@ expose(XEvent *e) { Monitor *m; XExposeEvent *ev = &e->xexpose; - if(ev->count == 0 && (m = wintomon(ev->window))) + if(ev->count == 0 && (m = wintomon(ev->window))){ drawbar(m); + drawtab(m); + } } void @@ -862,6 +1047,7 @@ focus(Client *c) { XSetInputFocus(dpy, root, RevertToPointerRoot, CurrentTime); selmon->sel = c; drawbars(); + drawtabs(); } void @@ -911,6 +1097,19 @@ focusstack(const Arg *arg) { } } +void +focuswin(const Arg* arg){ + int iwin = arg->i; + Client* c = NULL; + for(c = selmon->clients; c && (iwin || !ISVISIBLE(c)) ; c = c->next){ + if(ISVISIBLE(c)) --iwin; + }; + if(c) { + focus(c); + restack(selmon); + } +} + Atom getatomprop(Client *c, Atom prop) { int di; @@ -919,7 +1118,7 @@ getatomprop(Client *c, Atom prop) { Atom da, atom = None; if(XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM, - &da, &di, &dl, &dl, &p) == Success && p) { + &da, &di, &dl, &dl, &p) == Success && p) { atom = *(Atom *)p; XFree(p); } @@ -954,7 +1153,7 @@ getstate(Window w) { Atom real; if(XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState], - &real, &format, &n, &extra, (unsigned char **)&p) != Success) + &real, &format, &n, &extra, (unsigned char **)&p) != Success) return -1; if(n != 0) result = *p; @@ -999,13 +1198,13 @@ grabbuttons(Client *c, Bool focused) { if(buttons[i].click == ClkClientWin) for(j = 0; j < LENGTH(modifiers); j++) XGrabButton(dpy, buttons[i].button, - buttons[i].mask | modifiers[j], - c->win, False, BUTTONMASK, - GrabModeAsync, GrabModeSync, None, None); + buttons[i].mask | modifiers[j], + c->win, False, BUTTONMASK, + GrabModeAsync, GrabModeSync, None, None); } else XGrabButton(dpy, AnyButton, AnyModifier, c->win, False, - BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); + BUTTONMASK, GrabModeAsync, GrabModeSync, None, None); } } @@ -1028,7 +1227,7 @@ grabkeys(void) { void incnmaster(const Arg *arg) { - selmon->nmaster = MAX(selmon->nmaster + arg->i, 0); + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag] = MAX(selmon->nmaster + arg->i, 0); arrange(selmon); } @@ -1139,7 +1338,7 @@ manage(Window w, XWindowAttributes *wa) { c->x = MAX(c->x, c->mon->mx); /* only fix client y-offset, if the client center might cover the bar */ c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= c->mon->wx) - && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); + && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : c->mon->my); c->bw = borderpx; wc.border_width = c->bw; @@ -1311,12 +1510,14 @@ propertynotify(XEvent *e) { case XA_WM_HINTS: updatewmhints(c); drawbars(); + drawtabs(); break; } if(ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) { updatetitle(c); if(c == c->mon->sel) drawbar(c->mon); + drawtab(c->mon); } if(ev->atom == netatom[NetWMWindowType]) updatewindowtype(c); @@ -1375,7 +1576,7 @@ resizemouse(const Arg *arg) { ocx = c->x; ocy = c->y; if(XGrabPointer(dpy, root, False, MOUSEMASK, GrabModeAsync, GrabModeAsync, - None, cursor[CurResize], CurrentTime) != GrabSuccess) + None, cursor[CurResize], CurrentTime) != GrabSuccess) return; XWarpPointer(dpy, None, c->win, 0, 0, 0, 0, c->w + c->bw - 1, c->h + c->bw - 1); do { @@ -1418,6 +1619,7 @@ restack(Monitor *m) { XWindowChanges wc; drawbar(m); + drawtab(m); if(!m->sel) return; if(m->sel->isfloating || !m->lt[m->sellt]->arrange) @@ -1529,7 +1731,7 @@ void setfullscreen(Client *c, Bool fullscreen) { if(fullscreen) { XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, - PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); + PropModeReplace, (unsigned char*)&netatom[NetWMFullscreen], 1); c->isfullscreen = True; c->oldstate = c->isfloating; c->oldbw = c->bw; @@ -1540,7 +1742,7 @@ setfullscreen(Client *c, Bool fullscreen) { } else { XChangeProperty(dpy, c->win, netatom[NetWMState], XA_ATOM, 32, - PropModeReplace, (unsigned char*)0, 0); + PropModeReplace, (unsigned char*)0, 0); c->isfullscreen = False; c->isfloating = c->oldstate; c->bw = c->oldbw; @@ -1555,10 +1757,13 @@ setfullscreen(Client *c, Bool fullscreen) { void setlayout(const Arg *arg) { - if(!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) - selmon->sellt ^= 1; + if(!arg || !arg->v || arg->v != selmon->lt[selmon->sellt]) { + selmon->pertag->sellts[selmon->pertag->curtag] ^= 1; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + } if(arg && arg->v) - selmon->lt[selmon->sellt] = (Layout *)arg->v; + selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt] = (Layout *)arg->v; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; strncpy(selmon->ltsymbol, selmon->lt[selmon->sellt]->symbol, sizeof selmon->ltsymbol); if(selmon->sel) arrange(selmon); @@ -1576,7 +1781,7 @@ setmfact(const Arg *arg) { f = arg->f < 1.0 ? arg->f + selmon->mfact : arg->f - 1.0; if(f < 0.1 || f > 0.9) return; - selmon->mfact = f; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag] = f; arrange(selmon); } @@ -1594,6 +1799,7 @@ setup(void) { sw = DisplayWidth(dpy, screen); sh = DisplayHeight(dpy, screen); bh = dc.h = dc.font.height + 2; + th = bh; updategeom(); /* init atoms */ wmatom[WMProtocols] = XInternAtom(dpy, "WM_PROTOCOLS", False); @@ -1619,6 +1825,7 @@ setup(void) { dc.sel[ColBG] = getcolor(selbgcolor); dc.sel[ColFG] = getcolor(selfgcolor); dc.drawable = XCreatePixmap(dpy, root, DisplayWidth(dpy, screen), bh, DefaultDepth(dpy, screen)); + dc.tabdrawable = XCreatePixmap(dpy, root, DisplayWidth(dpy, screen), th, DefaultDepth(dpy, screen)); dc.gc = XCreateGC(dpy, root, 0, NULL); XSetLineAttributes(dpy, dc.gc, 1, LineSolid, CapButt, JoinMiter); if(!dc.font.set) @@ -1632,7 +1839,7 @@ setup(void) { /* select for events */ wa.cursor = cursor[CurNormal]; wa.event_mask = SubstructureRedirectMask|SubstructureNotifyMask|ButtonPressMask|PointerMotionMask - |EnterWindowMask|LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; + |EnterWindowMask|LeaveWindowMask|StructureNotifyMask|PropertyChangeMask; XChangeWindowAttributes(dpy, root, CWEventMask|CWCursor, &wa); XSelectInput(dpy, root, wa.event_mask); grabkeys(); @@ -1729,13 +1936,24 @@ tile(Monitor *m) { void togglebar(const Arg *arg) { - selmon->showbar = !selmon->showbar; + selmon->showbar = selmon->pertag->showbars[selmon->pertag->curtag] = !selmon->showbar; updatebarpos(selmon); XMoveResizeWindow(dpy, selmon->barwin, selmon->wx, selmon->by, selmon->ww, bh); arrange(selmon); } void +tabmode(const Arg *arg) { + if(arg && arg->i >= 0) + selmon->showtab = arg->ui % showtab_nmodes; + else + selmon->showtab = (selmon->showtab + 1 ) % showtab_nmodes; + selmon->pertag->showtabs[selmon->pertag->curtag] = selmon->showtab; + arrange(selmon); +} + + +void togglefloating(const Arg *arg) { if(!selmon->sel) return; @@ -1763,9 +1981,31 @@ toggletag(const Arg *arg) { void toggleview(const Arg *arg) { unsigned int newtagset = selmon->tagset[selmon->seltags] ^ (arg->ui & TAGMASK); + int i; if(newtagset) { + if(newtagset == ~0) { + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = 0; + } + /* test if the user did not select the same tag */ + if(!(newtagset & 1 << (selmon->pertag->curtag - 1))) { + selmon->pertag->prevtag = selmon->pertag->curtag; + for (i=0; !(newtagset & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } selmon->tagset[selmon->seltags] = newtagset; + + /* apply settings for this view */ + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + if (selmon->showtab != selmon->pertag->showtabs[selmon->pertag->curtag]) + tabmode(NULL); focus(NULL); arrange(selmon); } @@ -1828,24 +2068,47 @@ updatebars(void) { }; for(m = mons; m; m = m->next) { m->barwin = XCreateWindow(dpy, root, m->wx, m->by, m->ww, bh, 0, DefaultDepth(dpy, screen), - CopyFromParent, DefaultVisual(dpy, screen), - CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); XDefineCursor(dpy, m->barwin, cursor[CurNormal]); XMapRaised(dpy, m->barwin); + m->tabwin = XCreateWindow(dpy, root, m->wx, m->ty, m->ww, th, 0, DefaultDepth(dpy, screen), + CopyFromParent, DefaultVisual(dpy, screen), + CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa); + XDefineCursor(dpy, m->tabwin, cursor[CurNormal]); + XMapRaised(dpy, m->tabwin); } } void updatebarpos(Monitor *m) { + Client *c; + int nvis = 0; + m->wy = m->my; m->wh = m->mh; if(m->showbar) { m->wh -= bh; m->by = m->topbar ? m->wy : m->wy + m->wh; - m->wy = m->topbar ? m->wy + bh : m->wy; - } - else + if ( m->topbar ) + m->wy += bh; + } else { m->by = -bh; + } + + for(c = m->clients; c; c = c->next){ + if(ISVISIBLE(c)) ++nvis; + } + + if(m->showtab == showtab_always + || ((m->showtab == showtab_auto) && (nvis > 1) && (m->lt[m->sellt]->arrange == monocle))){ + m->wh -= th; + m->ty = m->toptab ? m->wy : m->wy + m->wh; + if ( m->toptab ) + m->wy += th; + } else { + m->ty = -th; + } } Bool @@ -1992,7 +2255,7 @@ updatesizehints(Client *c) { else c->maxa = c->mina = 0.0; c->isfixed = (c->maxw && c->minw && c->maxh && c->minh - && c->maxw == c->minw && c->maxh == c->minh); + && c->maxw == c->minw && c->maxh == c->minh); } void @@ -2043,11 +2306,35 @@ updatewmhints(Client *c) { void view(const Arg *arg) { + int i; + unsigned int tmptag; + if((arg->ui & TAGMASK) == selmon->tagset[selmon->seltags]) return; selmon->seltags ^= 1; /* toggle sel tagset */ - if(arg->ui & TAGMASK) + if(arg->ui & TAGMASK) { + selmon->pertag->prevtag = selmon->pertag->curtag; selmon->tagset[selmon->seltags] = arg->ui & TAGMASK; + if(arg->ui == ~0) + selmon->pertag->curtag = 0; + else { + for (i=0; !(arg->ui & 1 << i); i++) ; + selmon->pertag->curtag = i + 1; + } + } else { + tmptag = selmon->pertag->prevtag; + selmon->pertag->prevtag = selmon->pertag->curtag; + selmon->pertag->curtag = tmptag; + } + selmon->nmaster = selmon->pertag->nmasters[selmon->pertag->curtag]; + selmon->mfact = selmon->pertag->mfacts[selmon->pertag->curtag]; + selmon->sellt = selmon->pertag->sellts[selmon->pertag->curtag]; + selmon->lt[selmon->sellt] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt]; + selmon->lt[selmon->sellt^1] = selmon->pertag->ltidxs[selmon->pertag->curtag][selmon->sellt^1]; + if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag]) + togglebar(NULL); + if (selmon->showtab != selmon->pertag->showtabs[selmon->pertag->curtag]) + tabmode(NULL); focus(NULL); arrange(selmon); } @@ -2073,7 +2360,7 @@ wintomon(Window w) { if(w == root && getrootptr(&x, &y)) return recttomon(x, y, 1, 1); for(m = mons; m; m = m->next) - if(w == m->barwin) + if(w == m->barwin || w == m->tabwin) return m; if((c = wintoclient(w))) return c->mon;