[wiki] [sites] Simplified the tab-related code and Fixed bugs in tab-pretag. || kanishk

From: <git_AT_suckless.org>
Date: Tue, 12 May 2026 17:07:19 +0200

commit 4ca4b788838bf083e781d92ee55eed0b1045f20e
Author: kanishk <saini07kanishk_AT_gmail.com>
Date: Tue May 12 20:32:48 2026 +0530

     Simplified the tab-related code and Fixed bugs in tab-pretag.
    
    Fixed bugs in the pertag-tab integration that caused the tab patch to behave
      like the normal tab patch without proper pertag support. Simplified the
      mode logic and which also helped in toggle of the tab bar .

diff --git a/dwm.suckless.org/patches/tab/dwm-tab-20260512-44dbc68.diff b/dwm.suckless.org/patches/tab/dwm-tab-20260512-44dbc68.diff
new file mode 100644
index 00000000..e6cccc4c
--- /dev/null
+++ b/dwm.suckless.org/patches/tab/dwm-tab-20260512-44dbc68.diff
_AT_@ -0,0 +1,517 @@
+From d72a4be8178a9704753e9c5c00f083e499e001ac Mon Sep 17 00:00:00 2001
+From: kanishk <saini07kanishk_AT_gmail.com>
+Date: Tue, 12 May 2026 16:55:03 +0530
+Subject: [PATCH] tab feature
+
+---
+ config.def.h | 7 ++
+ dwm.c | 234 ++++++++++++++++++++++++++++++++++++++++++++++-----
+ 2 files changed, 219 insertions(+), 22 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 81c3fc0..2aba0a4 100644
+--- a/config.def.h
++++ b/config.def.h
+_AT_@ -5,8 +5,13 @@ static const unsigned int borderpx = 1; /* border pixel of windows */
+ static const unsigned int snap = 32; /* snap pixel */
+ static const int showbar = 1; /* 0 means no bar */
+ static const int topbar = 1; /* 0 means bottom bar */
++static const int showtab = 1; /* 0 means no tabbar */
++static const int alltab = 0; /* 0 means no tabbar in all layouts */
++static const int toptab = 0; /* 1 means top tab bar */
+ static const char *fonts[] = { "monospace:size=10" };
+ static const char dmenufont[] = "monospace:size=10";
++
++
+ static const char col_gray1[] = "#222222";
+ static const char col_gray2[] = "#444444";
+ static const char col_gray3[] = "#bbbbbb";
+_AT_@ -66,6 +71,7 @@ static const Key keys[] = {
+ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
+ { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
+ { MODKEY, XK_b, togglebar, {0} },
++ { MODKEY, XK_w, toggletab, {0} }, // tabmodes
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+_AT_@ -113,5 +119,6 @@ static const Button buttons[] = {
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
++ { ClkTabBar, 0, Button1, focuswin, {0} }, // tab mode
+ };
+
+diff --git a/dwm.c b/dwm.c
+index ab3a84c..18628ef 100644
+--- a/dwm.c
++++ b/dwm.c
+_AT_@ -56,6 +56,8 @@
+ #define TAGMASK ((1 << LENGTH(tags)) - 1)
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
++
++
+ /* enums */
+ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+ enum { SchemeNorm, SchemeSel }; /* color schemes */
+_AT_@ -63,7 +65,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
+ NetWMFullscreen, NetActiveWindow, NetWMWindowType,
+ NetWMWindowTypeDialog, NetClientList, 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 {
+_AT_@ -110,24 +112,32 @@ typedef struct {
+ void (*arrange)(Monitor *);
+ } Layout;
+
++#define MAXTABS 50 // tab
++
+ 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];
+ int showbar;
++ int showtab;// tab
+ int topbar;
++ int toptab;// tab
+ Client *clients;
+ Client *sel;
+ Client *stack;
+ Monitor *next;
+ Window barwin;
++ Window tabwin;// tab
++ int ntabs;// tab
++ int tab_widths[MAXTABS];// tab
+ const Layout *lt[2];
+ };
+
+_AT_@ -162,12 +172,15 @@ static void detachstack(Client *c);
+ static Monitor *dirtomon(int dir);
+ static void drawbar(Monitor *m);
+ static void drawbars(void);
++static void drawtab(Monitor *m);// tab
++static void drawtabs(void);// tab
+ 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);// tab
+ static Atom getatomprop(Client *c, Atom prop);
+ static int getrootptr(int *x, int *y);
+ static long getstate(Window w);
+_AT_@ -205,6 +218,7 @@ static void setup(void);
+ static void seturgent(Client *c, int urg);
+ static void showhide(Client *c);
+ static void spawn(const Arg *arg);
++static void toggletab(const Arg *arg);// tab
+ static void tag(const Arg *arg);
+ static void tagmon(const Arg *arg);
+ static void tile(Monitor *m);
+_AT_@ -239,6 +253,7 @@ static char stext[256];
+ static int screen;
+ static int sw, sh; /* X display screen geometry width, height */
+ static int bh; /* bar height */
++static int th = 0; /* tab bar geometry */// tab
+ static int lrpad; /* sum of left and right padding for text */
+ static int (*xerrorxlib)(Display *, XErrorEvent *);
+ static unsigned int numlockmask = 0;
+_AT_@ -395,6 +410,8 @@ arrange(Monitor *m)
+ void
+ arrangemon(Monitor *m)
+ {
++ updatebarpos(selmon);//tab//tab
++ XMoveResizeWindow(dpy, selmon->tabwin, selmon->wx, selmon->ty, selmon->ww, th);//tab
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
+ if (m->lt[m->sellt]->arrange)
+ m->lt[m->sellt]->arrange(m);
+_AT_@ -440,11 +457,28 @@ buttonpress(XEvent *e)
+ arg.ui = 1 << i;
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol))
+ click = ClkLtSymbol;
+- else if (ev->x > selmon->ww - (int)TEXTW(stext) + lrpad - 2)
++ else if (ev->x > selmon->ww - (int)TEXTW(stext))
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+- } else if ((c = wintoclient(ev->window))) {
++ }
++ if(ev->window == selmon->tabwin) {//tab
++ i = 0; x = 0;//tab
++ for(c = selmon->clients; c; c = c->next){//tab
++ if(!ISVISIBLE(c)) continue;//tab
++ x += selmon->tab_widths[i];//tab
++ if (ev->x > x)//tab
++ ++i;//tab
++ else//tab
++ break;//tab
++ if(i >= m->ntabs) break;//tab
++ }//tab
++ if(c) {//tab
++ click = ClkTabBar;//tab
++ arg.ui = i;//tab
++ }
++ }
++ else if((c = wintoclient(ev->window))) {
+ focus(c);
+ restack(selmon);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+_AT_@ -452,8 +486,9 @@ buttonpress(XEvent *e)
+ }
+ 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)){//tab
++ buttons[i].func(((click == ClkTagBar || click == ClkTabBar) && buttons[i].arg.i == 0) ? &arg : &buttons[i].arg);//tab
++ }
+ }
+
+ void
+_AT_@ -508,6 +543,8 @@ cleanupmon(Monitor *mon)
+ }
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
++ XUnmapWindow(dpy, mon->tabwin);//tab
++ XDestroyWindow(dpy, mon->tabwin);//tab
+ free(mon);
+ }
+
+_AT_@ -639,7 +676,10 @@ createmon(void)
+ m->mfact = mfact;
+ m->nmaster = nmaster;
+ m->showbar = showbar;
++ m->showtab = showtab;//tab
+ m->topbar = topbar;
++ m->toptab = toptab;//tab
++ m->ntabs = 0;//tab
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % LENGTH(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
+_AT_@ -756,6 +796,106 @@ 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)
+ {
+_AT_@ -781,8 +921,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);//tab
++ }
+ }
+
+ void
+_AT_@ -808,6 +950,7 @@ focus(Client *c)
+ }
+ selmon->sel = c;
+ drawbars();
++ drawtabs();//tab
+ }
+
+ /* there are some broken focus acquiring clients needing extra handling */
+_AT_@ -860,18 +1003,31 @@ 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 format;
++ int di;
+ unsigned long nitems, dl;
+ unsigned char *p = NULL;
+ Atom da, atom = None;
+
+ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM,
+- &da, &format, &nitems, &dl, &p) == Success && p) {
+- if (nitems > 0 && format == 32)
+- atom = *(long *)p;
++ &da, &di, &nitems, &dl, &p) == Success && p) {
++ if (nitems > 0)
++ atom = *(Atom *)p;
+ XFree(p);
+ }
+ return atom;
+_AT_@ -897,10 +1053,10 @@ getstate(Window w)
+ Atom real;
+
+ if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState],
+- &real, &format, &n, &extra, &p) != Success)
++ &real, &format, &n, &extra, (unsigned char **)&p) != Success)
+ return -1;
+- if (n != 0 && format == 32)
+- result = *(long *)p;
++ if (n != 0)
++ result = *p;
+ XFree(p);
+ return result;
+ }
+_AT_@ -1243,12 +1399,14 @@ propertynotify(XEvent *e)
+ case XA_WM_HINTS:
+ updatewmhints(c);
+ drawbars();
++ drawtabs();//tab
+ break;
+ }
+ if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
+ updatetitle(c);
+ if (c == c->mon->sel)
+ drawbar(c->mon);
++ drawtab(c->mon);//tab
+ }
+ if (ev->atom == netatom[NetWMWindowType])
+ updatewindowtype(c);
+_AT_@ -1362,6 +1520,7 @@ restack(Monitor *m)
+ XWindowChanges wc;
+
+ drawbar(m);
++ drawtab(m);
+ if (!m->sel)
+ return;
+ if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
+_AT_@ -1376,7 +1535,8 @@ restack(Monitor *m)
+ }
+ }
+ XSync(dpy, False);
+- while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
++ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
++ ;
+ }
+
+ void
+_AT_@ -1429,8 +1589,6 @@ sendmon(Client *c, Monitor *m)
+ c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
+ attach(c);
+ attachstack(c);
+- if (c->isfullscreen)
+- resizeclient(c, m->mx, m->my, m->mw, m->mh);
+ focus(NULL);
+ arrange(NULL);
+ }
+_AT_@ -1472,10 +1630,12 @@ sendevent(Client *c, Atom proto)
+ void
+ setfocus(Client *c)
+ {
+- if (!c->neverfocus)
++ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+- XChangeProperty(dpy, root, netatom[NetActiveWindow], XA_WINDOW, 32,
+- PropModeReplace, (unsigned char *)&c->win, 1);
++ XChangeProperty(dpy, root, netatom[NetActiveWindow],
++ XA_WINDOW, 32, PropModeReplace,
++ (unsigned char *) &(c->win), 1);
++ }
+ sendevent(c, wmatom[WMTakeFocus]);
+ }
+
+_AT_@ -1563,6 +1723,7 @@ setup(void)
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+ bh = drw->fonts->h + 2;
++ th = bh;//tab
+ updategeom();
+ /* init atoms */
+ utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+_AT_@ -1721,6 +1882,13 @@ togglebar(const Arg *arg)
+ arrange(selmon);
+ }
+
++ void
++toggletab(const Arg *arg)
++{
++ selmon->showtab=!selmon->showtab;
++ arrange(selmon);
++}
++
+ void
+ togglefloating(const Arg *arg)
+ {
+_AT_@ -1833,6 +2001,11 @@ updatebars(void)
+ CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa);
+ XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor);
+ 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);
+ }
+ }
+_AT_@ -1840,14 +2013,31 @@ updatebars(void)
+ void
+ updatebarpos(Monitor *m)
+ {
++ Client *c;//tab
++ int nvis = 0;//tab
+ 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;//tab
++ } else {//tab
+ m->by = -bh;
++ }
++
++ for(c = m->clients; c; c = c->next) {//tab
++ if(ISVISIBLE(c)) ++nvis;//tab
++ }//tab
++//this check condition to show the tabbar coditions showtab_always and others.
++// if(m->showtab == showtab_always || ((m->showtab == showtab_auto) && (nvis > 1) && (m->lt[m->sellt]->arrange == monocle)))
++ if((m->showtab==1 && alltab==1) || (m->showtab== 1 && ( nvis >1) && (m->lt[m->sellt]->arrange==monocle )) ){//tab
++ m->wh -= th;//tab
++ m->ty = m->toptab ? m->wy : m->wy + m->wh;//tab
++ if ( m->toptab )//tab
++ m->wy += th;//tab
++ } else {//tab
++ m->ty = -th;//tab
++ }//tab
+ }
+
+ void
+_AT_@ -2085,7 +2275,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)// condition for tab
+ return m;
+ if ((c = wintoclient(w)))
+ return c->mon;
+--
+2.54.0
+
diff --git a/dwm.suckless.org/patches/tab/dwm-tab_pertag-20260512-44dbc68.diff b/dwm.suckless.org/patches/tab/dwm-tab_pertag-20260512-44dbc68.diff
new file mode 100644
index 00000000..a3aed1e8
--- /dev/null
+++ b/dwm.suckless.org/patches/tab/dwm-tab_pertag-20260512-44dbc68.diff
_AT_@ -0,0 +1,553 @@
+From d7c08095ed378b72710dfb61808fd78ca9384bfb Mon Sep 17 00:00:00 2001
+From: kanishk <saini07kanishk_AT_gmail.com>
+Date: Tue, 12 May 2026 18:16:49 +0530
+Subject: [PATCH] tab with pertag
+
+---
+ config.def.h | 7 ++
+ dwm.c | 241 ++++++++++++++++++++++++++++++++++++++++++++++-----
+ 2 files changed, 226 insertions(+), 22 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 81c3fc0..925979b 100644
+--- a/config.def.h
++++ b/config.def.h
+_AT_@ -5,8 +5,13 @@ static const unsigned int borderpx = 1; /* border pixel of windows */
+ static const unsigned int snap = 32; /* snap pixel */
+ static const int showbar = 1; /* 0 means no bar */
+ static const int topbar = 1; /* 0 means bottom bar */
++static const int showtab = 1; /* 0 means no tabbar */
++static const int alltab = 0; /* 0 means no tabbar for all layouts */
++static const int toptab = 0; /* 1 means top tab bar */
+ static const char *fonts[] = { "monospace:size=10" };
+ static const char dmenufont[] = "monospace:size=10";
++
++
+ static const char col_gray1[] = "#222222";
+ static const char col_gray2[] = "#444444";
+ static const char col_gray3[] = "#bbbbbb";
+_AT_@ -66,6 +71,7 @@ static const Key keys[] = {
+ { MODKEY, XK_p, spawn, {.v = dmenucmd } },
+ { MODKEY|ShiftMask, XK_Return, spawn, {.v = termcmd } },
+ { MODKEY, XK_b, togglebar, {0} },
++ { MODKEY, XK_w, toggletab, {0} }, // tabmodes
+ { MODKEY, XK_j, focusstack, {.i = +1 } },
+ { MODKEY, XK_k, focusstack, {.i = -1 } },
+ { MODKEY, XK_i, incnmaster, {.i = +1 } },
+_AT_@ -113,5 +119,6 @@ static const Button buttons[] = {
+ { ClkTagBar, 0, Button3, toggleview, {0} },
+ { ClkTagBar, MODKEY, Button1, tag, {0} },
+ { ClkTagBar, MODKEY, Button3, toggletag, {0} },
++ { ClkTabBar, 0, Button1, focuswin, {0} }, // tab mode
+ };
+
+diff --git a/dwm.c b/dwm.c
+index fc3365c..cd9c6a6 100644
+--- a/dwm.c
++++ b/dwm.c
+_AT_@ -56,6 +56,8 @@
+ #define TAGMASK ((1 << LENGTH(tags)) - 1)
+ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad)
+
++
++
+ /* enums */
+ enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
+ enum { SchemeNorm, SchemeSel }; /* color schemes */
+_AT_@ -63,7 +65,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
+ NetWMFullscreen, NetActiveWindow, NetWMWindowType,
+ NetWMWindowTypeDialog, NetClientList, 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 {
+_AT_@ -110,6 +112,8 @@ typedef struct {
+ void (*arrange)(Monitor *);
+ } Layout;
+
++#define MAXTABS 50 // tab
++
+ typedef struct Pertag Pertag;
+ struct Monitor {
+ char ltsymbol[16];
+_AT_@ -117,18 +121,24 @@ struct Monitor {
+ 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];
+ int showbar;
++ int showtab;// tab
+ int topbar;
++ int toptab;// tab
+ Client *clients;
+ Client *sel;
+ Client *stack;
+ Monitor *next;
+ Window barwin;
++ Window tabwin;// tab
++ int ntabs;// tab
++ int tab_widths[MAXTABS];// tab
+ const Layout *lt[2];
+ Pertag *pertag;
+ };
+_AT_@ -164,12 +174,15 @@ static void detachstack(Client *c);
+ static Monitor *dirtomon(int dir);
+ static void drawbar(Monitor *m);
+ static void drawbars(void);
++static void drawtab(Monitor *m);// tab
++static void drawtabs(void);// tab
+ 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);// tab
+ static Atom getatomprop(Client *c, Atom prop);
+ static int getrootptr(int *x, int *y);
+ static long getstate(Window w);
+_AT_@ -207,6 +220,7 @@ static void setup(void);
+ static void seturgent(Client *c, int urg);
+ static void showhide(Client *c);
+ static void spawn(const Arg *arg);
++static void toggletab(const Arg *arg);// tab
+ static void tag(const Arg *arg);
+ static void tagmon(const Arg *arg);
+ static void tile(Monitor *m);
+_AT_@ -241,6 +255,7 @@ static char stext[256];
+ static int screen;
+ static int sw, sh; /* X display screen geometry width, height */
+ static int bh; /* bar height */
++static int th = 0; /* tab bar geometry */// tab
+ static int lrpad; /* sum of left and right padding for text */
+ static int (*xerrorxlib)(Display *, XErrorEvent *);
+ static unsigned int numlockmask = 0;
+_AT_@ -279,6 +294,7 @@ struct Pertag {
+ unsigned int sellts[LENGTH(tags) + 1]; /* selected layouts */
+ const Layout *ltidxs[LENGTH(tags) + 1][2]; /* matrix of tags and layouts indexes */
+ int showbars[LENGTH(tags) + 1]; /* display bar for the current tag */
++ int showtabs[LENGTH(tags) + 1]; /* display tab for the current tag */
+ };
+
+ /* compile-time check if all tags fit into an unsigned int bit array. */
+_AT_@ -406,6 +422,8 @@ arrange(Monitor *m)
+ void
+ arrangemon(Monitor *m)
+ {
++ updatebarpos(selmon);//tab
++ XMoveResizeWindow(dpy, selmon->tabwin, selmon->wx, selmon->ty, selmon->ww, th);//tab
+ strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, sizeof m->ltsymbol);
+ if (m->lt[m->sellt]->arrange)
+ m->lt[m->sellt]->arrange(m);
+_AT_@ -451,11 +469,28 @@ buttonpress(XEvent *e)
+ arg.ui = 1 << i;
+ } else if (ev->x < x + TEXTW(selmon->ltsymbol))
+ click = ClkLtSymbol;
+- else if (ev->x > selmon->ww - (int)TEXTW(stext) + lrpad - 2)
++ else if (ev->x > selmon->ww - (int)TEXTW(stext))
+ click = ClkStatusText;
+ else
+ click = ClkWinTitle;
+- } else if ((c = wintoclient(ev->window))) {
++ }
++ if(ev->window == selmon->tabwin) {//tab
++ i = 0; x = 0;//tab
++ for(c = selmon->clients; c; c = c->next){//tab
++ if(!ISVISIBLE(c)) continue;//tab
++ x += selmon->tab_widths[i];//tab
++ if (ev->x > x)//tab
++ ++i;//tab
++ else//tab
++ break;//tab
++ if(i >= m->ntabs) break;//tab
++ }//tab
++ if(c) {//tab
++ click = ClkTabBar;//tab
++ arg.ui = i;//tab
++ }
++ }
++ else if((c = wintoclient(ev->window))) {
+ focus(c);
+ restack(selmon);
+ XAllowEvents(dpy, ReplayPointer, CurrentTime);
+_AT_@ -463,8 +498,9 @@ buttonpress(XEvent *e)
+ }
+ 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)){//tab
++ buttons[i].func(((click == ClkTagBar || click == ClkTabBar) && buttons[i].arg.i == 0) ? &arg : &buttons[i].arg);//tab
++ }
+ }
+
+ void
+_AT_@ -519,6 +555,8 @@ cleanupmon(Monitor *mon)
+ }
+ XUnmapWindow(dpy, mon->barwin);
+ XDestroyWindow(dpy, mon->barwin);
++ XUnmapWindow(dpy, mon->tabwin);//tab
++ XDestroyWindow(dpy, mon->tabwin);//tab
+ free(mon);
+ }
+
+_AT_@ -651,7 +689,10 @@ createmon(void)
+ m->mfact = mfact;
+ m->nmaster = nmaster;
+ m->showbar = showbar;
++ m->showtab = showtab;//tab
+ m->topbar = topbar;
++ m->toptab = toptab;//tab
++ m->ntabs = 0;//tab
+ m->lt[0] = &layouts[0];
+ m->lt[1] = &layouts[1 % LENGTH(layouts)];
+ strncpy(m->ltsymbol, layouts[0].symbol, sizeof m->ltsymbol);
+_AT_@ -667,6 +708,7 @@ createmon(void)
+ m->pertag->sellts[i] = m->sellt;
+
+ m->pertag->showbars[i] = m->showbar;
++ m->pertag->showtabs[i] = m->showtab;
+ }
+
+ return m;
+_AT_@ -782,6 +824,106 @@ 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)
+ {
+_AT_@ -807,8 +949,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);//tab
++ }
+ }
+
+ void
+_AT_@ -834,6 +978,7 @@ focus(Client *c)
+ }
+ selmon->sel = c;
+ drawbars();
++ drawtabs();//tab
+ }
+
+ /* there are some broken focus acquiring clients needing extra handling */
+_AT_@ -886,18 +1031,31 @@ 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 format;
++ int di;
+ unsigned long nitems, dl;
+ unsigned char *p = NULL;
+ Atom da, atom = None;
+
+ if (XGetWindowProperty(dpy, c->win, prop, 0L, sizeof atom, False, XA_ATOM,
+- &da, &format, &nitems, &dl, &p) == Success && p) {
+- if (nitems > 0 && format == 32)
+- atom = *(long *)p;
++ &da, &di, &nitems, &dl, &p) == Success && p) {
++ if (nitems > 0)
++ atom = *(Atom *)p;
+ XFree(p);
+ }
+ return atom;
+_AT_@ -923,10 +1081,10 @@ getstate(Window w)
+ Atom real;
+
+ if (XGetWindowProperty(dpy, w, wmatom[WMState], 0L, 2L, False, wmatom[WMState],
+- &real, &format, &n, &extra, &p) != Success)
++ &real, &format, &n, &extra, (unsigned char **)&p) != Success)
+ return -1;
+- if (n != 0 && format == 32)
+- result = *(long *)p;
++ if (n != 0)
++ result = *p;
+ XFree(p);
+ return result;
+ }
+_AT_@ -1269,12 +1427,14 @@ propertynotify(XEvent *e)
+ case XA_WM_HINTS:
+ updatewmhints(c);
+ drawbars();
++ drawtabs();//tab
+ break;
+ }
+ if (ev->atom == XA_WM_NAME || ev->atom == netatom[NetWMName]) {
+ updatetitle(c);
+ if (c == c->mon->sel)
+ drawbar(c->mon);
++ drawtab(c->mon);//tab
+ }
+ if (ev->atom == netatom[NetWMWindowType])
+ updatewindowtype(c);
+_AT_@ -1388,6 +1548,7 @@ restack(Monitor *m)
+ XWindowChanges wc;
+
+ drawbar(m);
++ drawtab(m);
+ if (!m->sel)
+ return;
+ if (m->sel->isfloating || !m->lt[m->sellt]->arrange)
+_AT_@ -1402,7 +1563,8 @@ restack(Monitor *m)
+ }
+ }
+ XSync(dpy, False);
+- while (XCheckMaskEvent(dpy, EnterWindowMask, &ev));
++ while (XCheckMaskEvent(dpy, EnterWindowMask, &ev))
++ ;
+ }
+
+ void
+_AT_@ -1455,8 +1617,6 @@ sendmon(Client *c, Monitor *m)
+ c->tags = m->tagset[m->seltags]; /* assign tags of target monitor */
+ attach(c);
+ attachstack(c);
+- if (c->isfullscreen)
+- resizeclient(c, m->mx, m->my, m->mw, m->mh);
+ focus(NULL);
+ arrange(NULL);
+ }
+_AT_@ -1498,10 +1658,12 @@ sendevent(Client *c, Atom proto)
+ void
+ setfocus(Client *c)
+ {
+- if (!c->neverfocus)
++ if (!c->neverfocus) {
+ XSetInputFocus(dpy, c->win, RevertToPointerRoot, CurrentTime);
+- XChangeProperty(dpy, root, netatom[NetActiveWindow], XA_WINDOW, 32,
+- PropModeReplace, (unsigned char *)&c->win, 1);
++ XChangeProperty(dpy, root, netatom[NetActiveWindow],
++ XA_WINDOW, 32, PropModeReplace,
++ (unsigned char *) &(c->win), 1);
++ }
+ sendevent(c, wmatom[WMTakeFocus]);
+ }
+
+_AT_@ -1589,6 +1751,7 @@ setup(void)
+ die("no fonts could be loaded.");
+ lrpad = drw->fonts->h;
+ bh = drw->fonts->h + 2;
++ th = bh;//tab
+ updategeom();
+ /* init atoms */
+ utf8string = XInternAtom(dpy, "UTF8_STRING", False);
+_AT_@ -1747,6 +1910,13 @@ togglebar(const Arg *arg)
+ arrange(selmon);
+ }
+
++ void
++toggletab(const Arg *arg)
++{
++ selmon->showtab= selmon->pertag->showtabs[selmon->pertag->curtag]=!selmon->showtab;
++ arrange(selmon);
++}
++
+ void
+ togglefloating(const Arg *arg)
+ {
+_AT_@ -1807,6 +1977,9 @@ toggleview(const Arg *arg)
+ if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag])
+ togglebar(NULL);
+
++ if (selmon->showtab != selmon->pertag->showtabs[selmon->pertag->curtag])
++ toggletab(NULL);
++
+ focus(NULL);
+ arrange(selmon);
+ }
+_AT_@ -1883,6 +2056,11 @@ updatebars(void)
+ CWOverrideRedirect|CWBackPixmap|CWEventMask, &wa);
+ XDefineCursor(dpy, m->barwin, cursor[CurNormal]->cursor);
+ 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);
+ }
+ }
+_AT_@ -1890,14 +2068,31 @@ updatebars(void)
+ void
+ updatebarpos(Monitor *m)
+ {
++ Client *c;//tab
++ int nvis = 0;//tab
+ 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;//tab
++ } else {//tab
+ m->by = -bh;
++ }
++
++ for(c = m->clients; c; c = c->next) {//tab
++ if(ISVISIBLE(c)) ++nvis;//tab
++ }//tab
++//this check condition to show the tabbar coditions showtab_always and others.
++// if(m->showtab == showtab_always || ((m->showtab == showtab_auto) && (nvis > 1) && (m->lt[m->sellt]->arrange == monocle)))
++ if((m->showtab==1 && alltab==1) || (m->showtab== 1 && ( nvis >1) && (m->lt[m->sellt]->arrange==monocle )) ){//tab
++ m->wh -= th;//tab
++ m->ty = m->toptab ? m->wy : m->wy + m->wh;//tab
++ if ( m->toptab )//tab
++ m->wy += th;//tab
++ } else {//tab
++ m->ty = -th;//tab
++ }//tab
+ }
+
+ void
+_AT_@ -2134,6 +2329,8 @@ view(const Arg *arg)
+ if (selmon->showbar != selmon->pertag->showbars[selmon->pertag->curtag])
+ togglebar(NULL);
+
++ if (selmon->showtab != selmon->pertag->showbars[selmon->pertag->curtag])
++ toggletab(NULL);
+ focus(NULL);
+ arrange(selmon);
+ }
+_AT_@ -2161,7 +2358,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)// condition for tab
+ return m;
+ if ((c = wintoclient(w)))
+ return c->mon;
+--
+2.54.0
+
diff --git a/dwm.suckless.org/patches/tab/index.md b/dwm.suckless.org/patches/tab/index.md
index 548dc8e2..9adb891e 100644
--- a/dwm.suckless.org/patches/tab/index.md
+++ b/dwm.suckless.org/patches/tab/index.md
_AT_@ -1,6 +1,63 @@
 tab
 ===
 
+Description
+-----------
+This patch transforms the monocle layout into a tabbed layout whenever more
+than one window is present in the current view. The tabs are displayed in a
+dedicated bar at either the top or bottom of the screen.
+
+In addition to monocle mode, the tab bar can also be enabled for all layouts
+through configuration options in config.h or config.def.h.
+
+
+Usage
+-----
+Navigation between windows can be performed using the usual Modkey-j and
+Modkey-k keybindings, or by clicking directly on a window tab.
+
+The tab bar can be toggled using Modkey-w by default. This keybinding can be
+changed through the configuration options in config.h or config.def.h.
+
+
+Installation
+------------
+
+* Make sure the directory where you build dwm does not contain a config.h file;
+* Apply the patch;
+* Run make and make install.
+
+
+Configuration
+--------------
+
+* To change the position of the tab bar use :-
+```c
+static const int toptab = 0; /* 1 means top tab bar */
+```
+
+* To use tab bar for all layouts :-
+
+```c
+static const int alltab = 0; /* 0 means no tabbar for all layouts */
+```
+
+
+Download
+--------
+
+* Tab patch alone
+ * [dwm-tab-20260512-44dbc68.diff](dwm-tab-20260512-44dbc68.diff)
+
+* Tab patch with pertag support;
+ apply it on top of an already patched pertag build.
+ * [dwm-tab_pertag-20260512-44dbc68.diff](dwm-tab_pertag-20260512-44dbc68.diff)
+
+
+
+check below for the original code.
+
+
 Description
 -----------
 Transforms the monocle layout into a ''tabbed'' layout if more than one window
_AT_@ -124,6 +181,10 @@ Download
 
 Change log
 ----------
+* Fixed bugs in the pertag-tab integration that caused the tab patch to behave
+ like the normal tab patch without proper pertag support. These issues are
+ resolved in dwm-tab_pertag-20260512-44dbc68.diff.
+* Simplified the tab-related code. Simplified the three tab mode.
 * Add i3 like tabs that cover whole screen width
 * Fixed the standalone tab patch not applying to the current git master.
 * **v2b** Fixed in the pertag-tab patch the support for per-tag default layout
_AT_@ -136,6 +197,7 @@ Change log
 
 Authors
 -------
+* Kanishk Saini - `<saini07kanishk_AT_gmail.com>`
 * Philippe Gras - `<philippe dot gras at free dot fr>`
 * Varun Iyer (6.2 ver) - `<varun_iyer_AT_protonmail.com>`
 * howoz - `<howoz_AT_airmail.cc>`
Received on Tue May 12 2026 - 17:07:19 CEST

This archive was generated by hypermail 2.3.0 : Tue May 12 2026 - 17:12:52 CEST