|
@@ -123,6 +123,7 @@ enum {
|
|
|
}; /* default atoms */
|
|
|
enum {
|
|
|
ClkTagBar,
|
|
|
+ ClkTabBar,
|
|
|
ClkLtSymbol,
|
|
|
ClkStatusText,
|
|
|
ClkWinTitle,
|
|
@@ -177,12 +178,15 @@ typedef struct {
|
|
|
void (*arrange)(Monitor *);
|
|
|
} Layout;
|
|
|
|
|
|
+#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 */
|
|
|
int gappih; /* horizontal gap between windows */
|
|
@@ -194,13 +198,16 @@ struct Monitor {
|
|
|
unsigned int sellt;
|
|
|
unsigned int tagset[2];
|
|
|
unsigned int colorfultag;
|
|
|
- int showbar;
|
|
|
- int topbar;
|
|
|
+ int showbar, showtab;
|
|
|
+ int topbar, toptab;
|
|
|
Client *clients;
|
|
|
Client *sel;
|
|
|
Client *stack;
|
|
|
Monitor *next;
|
|
|
Window barwin;
|
|
|
+ Window tabwin;
|
|
|
+ int ntabs;
|
|
|
+ int tab_widths[MAXTABS];
|
|
|
const Layout *lt[2];
|
|
|
};
|
|
|
|
|
@@ -247,12 +254,15 @@ static void dragcfact(const Arg *arg);
|
|
|
static void drawbar(Monitor *m);
|
|
|
static void drawbars(void);
|
|
|
static int drawstatusbar(Monitor *m, int bh, char *text);
|
|
|
+static void drawtab(Monitor *m);
|
|
|
+static void drawtabs(void);
|
|
|
static void enternotify(XEvent *e);
|
|
|
static void expose(XEvent *e);
|
|
|
static void focus(Client *c);
|
|
|
static void focusin(XEvent *e);
|
|
|
static void focusmon(const Arg *arg);
|
|
|
static void focusstack(const Arg *arg);
|
|
|
+static void focuswin(const Arg *arg);
|
|
|
static Atom getatomprop(Client *c, Atom prop);
|
|
|
static int getrootptr(int *x, int *y);
|
|
|
static long getstate(Window w);
|
|
@@ -302,6 +312,7 @@ static void sighup(int unused);
|
|
|
static void sigterm(int unused);
|
|
|
static void spawn(const Arg *arg);
|
|
|
static Monitor *systraytomon(Monitor *m);
|
|
|
+static void tabmode(const Arg *arg);
|
|
|
static void tag(const Arg *arg);
|
|
|
static void tagmon(const Arg *arg);
|
|
|
static void togglebar(const Arg *arg);
|
|
@@ -341,6 +352,7 @@ static char stext[1024];
|
|
|
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 lrpad; /* sum of left and right padding for text */
|
|
|
static int vp; /* vertical padding for bar */
|
|
|
static int sp; /* side padding for bar */
|
|
@@ -497,6 +509,8 @@ void arrange(Monitor *m) {
|
|
|
}
|
|
|
|
|
|
void arrangemon(Monitor *m) {
|
|
|
+ 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);
|
|
@@ -540,7 +554,24 @@ void buttonpress(XEvent *e) {
|
|
|
click = ClkStatusText;
|
|
|
else
|
|
|
click = ClkWinTitle;
|
|
|
- } else if ((c = wintoclient(ev->window))) {
|
|
|
+ } // wut?
|
|
|
+ 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);
|
|
|
restack(selmon);
|
|
|
XAllowEvents(dpy, ReplayPointer, CurrentTime);
|
|
@@ -551,7 +582,8 @@ void buttonpress(XEvent *e) {
|
|
|
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);
|
|
|
+ (click == ClkTagBar || click == ClkTabBar) && buttons[i].arg.i == 0 ? &arg : &buttons[i].arg);
|
|
|
+
|
|
|
}
|
|
|
|
|
|
void checkotherwm(void) {
|
|
@@ -605,6 +637,8 @@ void cleanupmon(Monitor *mon) {
|
|
|
}
|
|
|
XUnmapWindow(dpy, mon->barwin);
|
|
|
XDestroyWindow(dpy, mon->barwin);
|
|
|
+ XUnmapWindow(dpy, mon->tabwin);
|
|
|
+ XDestroyWindow(dpy, mon->tabwin);
|
|
|
free(mon);
|
|
|
}
|
|
|
|
|
@@ -787,7 +821,10 @@ Monitor *createmon(void) {
|
|
|
m->mfact = mfact;
|
|
|
m->nmaster = nmaster;
|
|
|
m->showbar = showbar;
|
|
|
+ m->showtab = showtab;
|
|
|
m->topbar = topbar;
|
|
|
+ m->toptab = toptab;
|
|
|
+ m->ntabs = 0;
|
|
|
m->colorfultag = colorfultag ? colorfultag : 0;
|
|
|
m->gappih = gappih;
|
|
|
m->gappiv = gappiv;
|
|
@@ -1343,6 +1380,106 @@ void drawbars(void) {
|
|
|
drawbar(m);
|
|
|
}
|
|
|
|
|
|
+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) {
|
|
|
+ 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;
|
|
|
+ int x = 0;
|
|
|
+ int w = 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;
|
|
|
+ w = m->tab_widths[i];
|
|
|
+ drw_setscheme(drw, scheme[(c == m->sel) ? SchemeSel : SchemeNorm]);
|
|
|
+ drw_text(drw, x, 0, w, th, 0, c->name, 0);
|
|
|
+ x += w;
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+
|
|
|
+ drw_setscheme(drw, scheme[SchemeNorm]);
|
|
|
+
|
|
|
+ /* cleans interspace between window names and current viewed tag label */
|
|
|
+ w = m->ww - view_info_w - x;
|
|
|
+ drw_text(drw, x, 0, w, th, 0, "", 0);
|
|
|
+
|
|
|
+ /* view info */
|
|
|
+ x += w;
|
|
|
+ w = view_info_w;
|
|
|
+ drw_text(drw, x, 0, w, th, 0, view_info, 0);
|
|
|
+
|
|
|
+ drw_map(drw, m->tabwin, 0, 0, m->ww, th);
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
void enternotify(XEvent *e) {
|
|
|
Client *c;
|
|
|
Monitor *m;
|
|
@@ -1394,6 +1531,7 @@ void focus(Client *c) {
|
|
|
}
|
|
|
selmon->sel = c;
|
|
|
drawbars();
|
|
|
+ drawtabs();
|
|
|
}
|
|
|
|
|
|
/* there are some broken focus acquiring clients needing extra handling */
|
|
@@ -1442,6 +1580,19 @@ void 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;
|
|
|
unsigned long dl;
|
|
@@ -1849,12 +2000,14 @@ void 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);
|
|
@@ -1997,6 +2150,7 @@ void restack(Monitor *m) {
|
|
|
XWindowChanges wc;
|
|
|
|
|
|
drawbar(m);
|
|
|
+ drawtab(m);
|
|
|
if (!m->sel)
|
|
|
return;
|
|
|
if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
|
|
@@ -2241,6 +2395,7 @@ void setup(void) {
|
|
|
die("no fonts could be loaded.");
|
|
|
lrpad = drw->fonts->h;
|
|
|
bh = drw->fonts->h + 2 + vertpadbar + borderpx * 2;
|
|
|
+ th = bh - vertpadbar - borderpx * 2;
|
|
|
updategeom();
|
|
|
sp = sidepad;
|
|
|
vp = (topbar == 1) ? vertpad : -vertpad;
|
|
@@ -2370,6 +2525,16 @@ void spawn(const Arg *arg) {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+void
|
|
|
+tabmode(const Arg *arg)
|
|
|
+{
|
|
|
+ if(arg && arg->i >= 0)
|
|
|
+ selmon->showtab = arg->ui % showtab_nmodes;
|
|
|
+ else
|
|
|
+ selmon->showtab = (selmon->showtab + 1 ) % showtab_nmodes;
|
|
|
+ arrange(selmon);
|
|
|
+}
|
|
|
+
|
|
|
void tag(const Arg *arg) {
|
|
|
if (selmon->sel && arg->ui & TAGMASK) {
|
|
|
selmon->sel->tags = arg->ui & TAGMASK;
|
|
@@ -2515,19 +2680,42 @@ void updatebars(void) {
|
|
|
if (showsystray && m == systraytomon(m))
|
|
|
XMapRaised(dpy, systray->win);
|
|
|
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]->cursor);
|
|
|
+ XMapRaised(dpy, m->tabwin);
|
|
|
XSetClassHint(dpy, m->barwin, &ch);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
void updatebarpos(Monitor *m) {
|
|
|
+ Client *c;
|
|
|
+ int nvis = 0;
|
|
|
+
|
|
|
m->wy = m->my;
|
|
|
m->wh = m->mh;
|
|
|
if (m->showbar) {
|
|
|
m->wh = m->wh - vertpad - bh;
|
|
|
m->by = m->topbar ? m->wy : m->wy + m->wh + vertpad;
|
|
|
- m->wy = m->topbar ? m->wy + bh + vp : m->wy;
|
|
|
+ if (m->topbar)
|
|
|
+ m->wy += bh + vp;
|
|
|
} else
|
|
|
m->by = -bh - vp;
|
|
|
+
|
|
|
+ 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;
|
|
|
+ }
|
|
|
}
|
|
|
|
|
|
void updateclientlist() {
|
|
@@ -2872,7 +3060,7 @@ Monitor *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;
|