diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | config.def.h | 38 | ||||
-rw-r--r-- | config.h | 38 | ||||
-rw-r--r-- | dwm.c | 588 | ||||
-rw-r--r-- | dwmswallow | 120 | ||||
-rw-r--r-- | patches/dwm-dynamicswallow-20240320-061e9fe.diff | 1020 | ||||
-rw-r--r-- | patches/dwm-swallow-6.3.diff | 412 | ||||
-rw-r--r-- | util.c | 29 | ||||
-rw-r--r-- | util.h | 1 |
9 files changed, 1790 insertions, 459 deletions
@@ -34,12 +34,15 @@ install: all mkdir -p ${DESTDIR}${PREFIX}/bin cp -f dwm ${DESTDIR}${PREFIX}/bin chmod 755 ${DESTDIR}${PREFIX}/bin/dwm + cp -f dwmswallow ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwmswallow mkdir -p ${DESTDIR}${MANPREFIX}/man1 sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 uninstall: rm -f ${DESTDIR}${PREFIX}/bin/dwm\ + ${DESTDIR}${MANPREFIX}/bin/dwmswallow\ ${DESTDIR}${MANPREFIX}/man1/dwm.1 .PHONY: all clean dist install uninstall diff --git a/config.def.h b/config.def.h index f837b4b..c7d1401 100644 --- a/config.def.h +++ b/config.def.h @@ -7,18 +7,27 @@ static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ static const char *fonts[] = { "terminus:size=13" }; static const char dmenufont[] = "terminus:size=13"; -static const char col_gray1[] = "#222222"; -static const char col_gray2[] = "#444444"; -static const char col_gray3[] = "#bbbbbb"; -static const char col_gray4[] = "#eeeeee"; -static const char col_cyan[] = "#005577"; + +static const char norm_fg[] = "#ddbebe"; +static const char norm_bg[] = "#000000"; +static const char norm_border[] = "#9a8585"; + +static const char sel_fg[] = "#ddbebe"; +static const char sel_bg[] = "#C16A7E"; +static const char sel_border[] = "#ddbebe"; + +static const char urg_fg[] = "#ddbebe"; +static const char urg_bg[] = "#9F5869"; +static const char urg_border[] = "#9F5869"; + static const char *colors[][3] = { - /* fg bg border */ - [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, - [SchemeSel] = { col_gray4, col_cyan, col_cyan }, - [SchemeHid] = { col_cyan, col_gray1, col_cyan }, + /* fg bg border */ + [SchemeNorm] = { norm_fg, norm_bg, norm_border }, // unfocused wins + [SchemeSel] = { sel_fg, sel_bg, sel_border }, // the focused win + [SchemeHid] = { urg_fg, urg_bg, urg_border }, }; + /* tagging */ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; @@ -32,6 +41,10 @@ static const Rule rules[] = { { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, { "St", NULL, "st", 0, 0, -1 }, }; +/* window swallowing */ +static const int swaldecay = 3; +static const int swalretroactive = 1; +static const char swalsymbol[] = "👅"; /* layout(s) */ static const float mfact = 0.45; /* factor of master area size [0.05..0.95] */ @@ -59,7 +72,7 @@ static const Layout layouts[] = { /* commands */ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ -static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", norm_bg, "-nf", norm_fg, "-sb", sel_bg, "-sf", sel_fg, NULL }; static const char *termcmd[] = { "st", NULL }; static const Key keys[] = { @@ -71,7 +84,6 @@ static const Key keys[] = { { MODKEY, XK_k, focusstackvis, {.i = -1 } }, { MODKEY|ShiftMask, XK_j, focusstackhid, {.i = +1 } }, { MODKEY|ShiftMask, XK_k, focusstackhid, {.i = -1 } }, - { MODKEY, XK_i, incnmaster, {.i = +1 } }, { MODKEY, XK_h, setmfact, {.f = -0.05} }, { MODKEY, XK_l, setmfact, {.f = +0.05} }, { MODKEY|ShiftMask, XK_q, killclient, {0} }, @@ -86,8 +98,7 @@ static const Key keys[] = { { MODKEY, XK_period, focusmon, {.i = +1 } }, { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, - { MODKEY, XK_s, show, {0} }, - { MODKEY|ShiftMask, XK_s, showall, {0} }, + { MODKEY, XK_s, showall, {0} }, { MODKEY, XK_h, hide, {0} }, TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) @@ -113,6 +124,7 @@ static const Button buttons[] = { { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkClientWin, MODKEY|ShiftMask, Button1, swalmouse, {0} }, { ClkTagBar, 0, Button1, view, {0} }, { ClkTagBar, 0, Button3, toggleview, {0} }, { ClkTagBar, MODKEY, Button1, tag, {0} }, @@ -7,18 +7,27 @@ static const int showbar = 1; /* 0 means no bar */ static const int topbar = 1; /* 0 means bottom bar */ static const char *fonts[] = { "terminus:size=13" }; static const char dmenufont[] = "terminus:size=13"; -static const char col_gray1[] = "#222222"; -static const char col_gray2[] = "#444444"; -static const char col_gray3[] = "#bbbbbb"; -static const char col_gray4[] = "#eeeeee"; -static const char col_cyan[] = "#005577"; + +static const char norm_fg[] = "#ddbebe"; +static const char norm_bg[] = "#000000"; +static const char norm_border[] = "#9a8585"; + +static const char sel_fg[] = "#ddbebe"; +static const char sel_bg[] = "#C16A7E"; +static const char sel_border[] = "#ddbebe"; + +static const char urg_fg[] = "#ddbebe"; +static const char urg_bg[] = "#9F5869"; +static const char urg_border[] = "#9F5869"; + static const char *colors[][3] = { - /* fg bg border */ - [SchemeNorm] = { col_gray3, col_gray1, col_gray2 }, - [SchemeSel] = { col_gray4, col_cyan, col_cyan }, - [SchemeHid] = { col_cyan, col_gray1, col_cyan }, + /* fg bg border */ + [SchemeNorm] = { norm_fg, norm_bg, norm_border }, // unfocused wins + [SchemeSel] = { sel_fg, sel_bg, sel_border }, // the focused win + [SchemeHid] = { urg_fg, urg_bg, urg_border }, }; + /* tagging */ static const char *tags[] = { "1", "2", "3", "4", "5", "6", "7", "8", "9" }; @@ -32,6 +41,10 @@ static const Rule rules[] = { { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, { "St", NULL, "st", 0, 0, -1 }, }; +/* window swallowing */ +static const int swaldecay = 3; +static const int swalretroactive = 1; +static const char swalsymbol[] = "👅"; /* layout(s) */ static const float mfact = 0.45; /* factor of master area size [0.05..0.95] */ @@ -59,7 +72,7 @@ static const Layout layouts[] = { /* commands */ static char dmenumon[2] = "0"; /* component of dmenucmd, manipulated in spawn() */ -static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", col_gray1, "-nf", col_gray3, "-sb", col_cyan, "-sf", col_gray4, NULL }; +static const char *dmenucmd[] = { "dmenu_run", "-m", dmenumon, "-fn", dmenufont, "-nb", norm_bg, "-nf", norm_fg, "-sb", sel_bg, "-sf", sel_fg, NULL }; static const char *termcmd[] = { "st", NULL }; static const Key keys[] = { @@ -71,7 +84,6 @@ static const Key keys[] = { { MODKEY, XK_k, focusstackvis, {.i = -1 } }, { MODKEY|ShiftMask, XK_j, focusstackhid, {.i = +1 } }, { MODKEY|ShiftMask, XK_k, focusstackhid, {.i = -1 } }, - { MODKEY, XK_i, incnmaster, {.i = +1 } }, { MODKEY, XK_h, setmfact, {.f = -0.05} }, { MODKEY, XK_l, setmfact, {.f = +0.05} }, { MODKEY|ShiftMask, XK_q, killclient, {0} }, @@ -86,8 +98,7 @@ static const Key keys[] = { { MODKEY, XK_period, focusmon, {.i = +1 } }, { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, - { MODKEY, XK_s, show, {0} }, - { MODKEY|ShiftMask, XK_s, showall, {0} }, + { MODKEY, XK_s, showall, {0} }, { MODKEY, XK_h, hide, {0} }, TAGKEYS( XK_1, 0) TAGKEYS( XK_2, 1) @@ -113,6 +124,7 @@ static const Button buttons[] = { { ClkClientWin, MODKEY, Button1, movemouse, {0} }, { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, + { ClkClientWin, MODKEY|ShiftMask, Button1, swalmouse, {0} }, { ClkTagBar, 0, Button1, view, {0} }, { ClkTagBar, 0, Button3, toggleview, {0} }, { ClkTagBar, MODKEY, Button1, tag, {0} }, @@ -58,7 +58,7 @@ #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) /* enums */ -enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ +enum { CurNormal, CurResize, CurMove, CurSwal, CurLast }; /* cursor */ enum { SchemeNorm, SchemeSel, SchemeHid }; /* color schemes */ enum { NetSupported, NetWMName, NetWMState, NetWMCheck, NetWMFullscreen, NetActiveWindow, NetWMWindowType, @@ -66,6 +66,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMCheck, enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ +enum { ClientRegular = 1, ClientSwallowee, ClientSwallower }; /* client types */ typedef union { int i; @@ -96,6 +97,7 @@ struct Client { int issteam; Client *next; Client *snext; + Client *swer; /* swallower of client, NULL by default */ Monitor *mon; Window win; }; @@ -144,6 +146,27 @@ typedef struct { int isfloating; int monitor; } Rule; +typedef struct Swallow Swallow; +struct Swallow { + /* Window class name, instance name (WM_CLASS) and title + * (WM_NAME/_NET_WM_NAME, latter preferred if it exists). An empty string + * implies a wildcard as per strstr(). */ + char class[256]; + char inst[256]; + char title[256]; + + /* Used to delete swallow instance after 'swaldecay' windows were mapped + * without the swallow having been consumed. 'decay' keeps track of the + * remaining "charges". */ + int decay; + + /* The swallower, i.e. the client which will swallow the next mapped window + * whose filters match the above properties. */ + Client *client; + + /* Linked list of registered swallow instances. */ + Swallow *next; +}; /* function declarations */ static void applyrules(Client *c); @@ -169,6 +192,7 @@ static void drawbar(Monitor *m); static void drawbars(void); static void enternotify(XEvent *e); static void expose(XEvent *e); +static int fakesignal(void); static void focus(Client *c); static void focusin(XEvent *e); static void focusmon(const Arg *arg); @@ -217,6 +241,16 @@ static void showall(const Arg *arg); static void showwin(Client *c); static void showhide(Client *c); static void spawn(const Arg *arg); +static void swal(Client *swer, Client *swee, int manage); +static void swalreg(Client *c, const char* class, const char* inst, const char* title); +static void swaldecayby(int decayby); +static void swalmanage(Swallow *s, Window w, XWindowAttributes *wa); +static Swallow *swalmatch(Window w); +static void swalmouse(const Arg *arg); +static void swalrm(Swallow *s); +static void swalunreg(Client *c); +static void swalstop(Client *c, Client *root); +static void swalstopsel(const Arg *unused); static void tag(const Arg *arg); static void tagmon(const Arg *arg); static void tile(Monitor *m); @@ -241,6 +275,7 @@ static void updatewmhints(Client *c); static void view(const Arg *arg); static void warp(const Client *c); static Client *wintoclient(Window w); +static int wintoclient2(Window w, Client **pc, Client **proot); static Monitor *wintomon(Window w); static int xerror(Display *dpy, XErrorEvent *ee); static int xerrordummy(Display *dpy, XErrorEvent *ee); @@ -279,6 +314,7 @@ static Clr **scheme; static Display *dpy; static Drw *drw; static Monitor *mons, *selmon; +static Swallow *swallows; static Window root, wmcheckwin; /* configuration, allows nested code to access above variables */ @@ -617,7 +653,9 @@ configurerequest(XEvent *e) XConfigureRequestEvent *ev = &e->xconfigurerequest; XWindowChanges wc; - if ((c = wintoclient(ev->window))) { + switch (wintoclient2(ev->window, &c, NULL)) { + case ClientRegular: /* fallthrough */ + case ClientSwallowee: if (ev->value_mask & CWBorderWidth) c->bw = ev->border_width; else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { @@ -650,7 +688,13 @@ configurerequest(XEvent *e) XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); } else configure(c); - } else { + break; + case ClientSwallower: + /* Reject any move/resize requests for swallowers and communicate + * refusal to client via a synthetic ConfigureNotify (ICCCM 4.1.5). */ + configure(c); + break; + default: wc.x = ev->x; wc.y = ev->y; wc.width = ev->width; @@ -659,6 +703,7 @@ configurerequest(XEvent *e) wc.sibling = ev->above; wc.stack_mode = ev->detail; XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); + break; } XSync(dpy, False); } @@ -683,11 +728,30 @@ createmon(void) void destroynotify(XEvent *e) { - Client *c; + Client *c, *swee, *root; XDestroyWindowEvent *ev = &e->xdestroywindow; - if ((c = wintoclient(ev->window))) + switch (wintoclient2(ev->window, &c, &root)) { + case ClientRegular: unmanage(c, 1); + break; + case ClientSwallowee: + swalstop(c, NULL); + unmanage(c, 1); + break; + case ClientSwallower: + /* If the swallower is swallowed by another client, terminate the + * swallow. This cuts off the swallow chain after the client. */ + swalstop(c, root); + + /* Cut off the swallow chain before the client. */ + for (swee = root; swee->swer != c; swee = swee->swer); + swee->swer = NULL; + + free(c); + updateclientlist(); + break; + } } void @@ -769,6 +833,12 @@ drawbar(Monitor *m) drw_setscheme(drw, scheme[SchemeNorm]); x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + /* Draw swalsymbol next to ltsymbol. */ + if (m->sel && m->sel->swer) { + w = TEXTW(swalsymbol); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, swalsymbol, 0); + } + if ((w = m->ww - tw - x) > bh) { if (n > 0) { int remainder = w % n; @@ -841,6 +911,81 @@ expose(XEvent *e) drawbar(m); } +int +fakesignal(void) +{ + /* Command syntax: <PREFIX><COMMAND>[<SEP><ARG>]... */ + static const char sep[] = "###"; + static const char prefix[] = "#!"; + + size_t numsegments, numargs; + char rootname[256]; + char *segments[16] = {0}; + + /* Get root name, split by separator and find the prefix */ + if (!gettextprop(root, XA_WM_NAME, rootname, sizeof(rootname)) + || strncmp(rootname, prefix, sizeof(prefix) - 1)) { + return 0; + } + numsegments = split(rootname + sizeof(prefix) - 1, sep, segments, sizeof(segments)); + numargs = numsegments - 1; /* number of arguments to COMMAND */ + + if (!strcmp(segments[0], "swalreg")) { + /* Params: windowid, [class], [instance], [title] */ + Window w; + Client *c; + + if (numargs >= 1) { + w = strtoul(segments[1], NULL, 0); + switch (wintoclient2(w, &c, NULL)) { + case ClientRegular: /* fallthrough */ + case ClientSwallowee: + swalreg(c, segments[2], segments[3], segments[4]); + break; + } + } + } + else if (!strcmp(segments[0], "swal")) { + /* Params: swallower's windowid, swallowee's window-id */ + Client *swer, *swee; + Window winswer, winswee; + int typeswer, typeswee; + + if (numargs >= 2) { + winswer = strtoul(segments[1], NULL, 0); + typeswer = wintoclient2(winswer, &swer, NULL); + winswee = strtoul(segments[2], NULL, 0); + typeswee = wintoclient2(winswee, &swee, NULL); + if ((typeswer == ClientRegular || typeswer == ClientSwallowee) + && (typeswee == ClientRegular || typeswee == ClientSwallowee)) + swal(swer, swee, 0); + } + } + else if (!strcmp(segments[0], "swalunreg")) { + /* Params: swallower's windowid */ + Client *swer; + Window winswer; + + if (numargs == 1) { + winswer = strtoul(segments[1], NULL, 0); + if ((swer = wintoclient(winswer))) + swalunreg(swer); + } + } + else if (!strcmp(segments[0], "swalstop")) { + /* Params: swallowee's windowid */ + Client *swee; + Window winswee; + + if (numargs == 1) { + winswee = strtoul(segments[1], NULL, 0); + if ((swee = wintoclient(winswee))) + swalstop(swee, NULL); + } + } + return 1; +} + void focus(Client *c) { @@ -1221,13 +1366,35 @@ mappingnotify(XEvent *e) void maprequest(XEvent *e) { + Client *c, *swee, *root; static XWindowAttributes wa; XMapRequestEvent *ev = &e->xmaprequest; + Swallow *s; if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) return; - if (!wintoclient(ev->window)) - manage(ev->window, &wa); + switch (wintoclient2(ev->window, &c, &root)) { + case ClientRegular: /* fallthrough */ + case ClientSwallowee: + /* Regulars and swallowees are always mapped. Nothing to do. */ + break; + case ClientSwallower: + /* Remapping a swallower will simply stop the swallow. */ + for (swee = root; swee->swer != c; swee = swee->swer); + swalstop(swee, root); + break; + default: + /* No client is managing the window. See if any swallows match. */ + if ((s = swalmatch(ev->window))) + swalmanage(s, ev->window, &wa); + else + manage(ev->window, &wa); + break; + } + + /* Reduce decay counter of all swallow instances. */ + if (swaldecay) + swaldecayby(1); } void @@ -1343,11 +1510,13 @@ propertynotify(XEvent *e) { Client *c; Window trans; + Swallow *s; XPropertyEvent *ev = &e->xproperty; - if ((ev->window == root) && (ev->atom == XA_WM_NAME)) - updatestatus(); - else if (ev->state == PropertyDelete) + if ((ev->window == root) && (ev->atom == XA_WM_NAME)) { + if (!fakesignal()) + updatestatus(); + } else if (ev->state == PropertyDelete) return; /* ignore */ else if ((c = wintoclient(ev->window))) { switch(ev->atom) { @@ -1369,6 +1538,9 @@ propertynotify(XEvent *e) updatetitle(c); if (c == c->mon->sel) drawbar(c->mon); + if (swalretroactive && (s = swalmatch(c->win))) { + swal(s->client, c, 0); + } } if (ev->atom == netatom[NetWMWindowType]) updatewindowtype(c); @@ -1715,6 +1887,7 @@ setup(void) cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); cursor[CurResize] = drw_cur_create(drw, XC_sizing); cursor[CurMove] = drw_cur_create(drw, XC_fleur); + cursor[CurSwal] = drw_cur_create(drw, XC_bottom_side); /* init appearance */ scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); for (i = 0; i < LENGTH(colors); i++) @@ -1834,6 +2007,326 @@ spawn(const Arg *arg) } } +/* + * Perform immediate swallow of client 'swee' by client 'swer'. 'manage' shall + * be set if swal() is called from swalmanage(). 'swer' and 'swee' must be + * regular or swallowee, but not swallower. + */ +void +swal(Client *swer, Client *swee, int manage) +{ + Client *c, **pc; + int sweefocused = selmon->sel == swee; + + /* No self-swallowing! */ + if (swer == swee) + return; + + /* Remove any swallows registered for the swer. Asking a swallower to + * swallow another window is ambiguous and is thus avoided altogether. In + * contrast, a swallowee can swallow in a well-defined manner by attaching + * to the head of the swallow chain. */ + if (!manage) + swalunreg(swer); + + /* Disable fullscreen prior to swallow. Swallows involving fullscreen + * windows produces quirky artefacts such as fullscreen terminals or tiled + * pseudo-fullscreen windows. */ + setfullscreen(swer, 0); + setfullscreen(swee, 0); + + /* Swap swallowee into client and focus lists. Keeps current focus unless + * the swer (which gets unmapped) is focused in which case the swee will + * receive focus. */ + detach(swee); + for (pc = &swer->mon->clients; *pc && *pc != swer; pc = &(*pc)->next); + *pc = swee; + swee->next = swer->next; + detachstack(swee); + for (pc = &swer->mon->stack; *pc && *pc != swer; pc = &(*pc)->snext); + *pc = swee; + swee->snext = swer->snext; + swee->mon = swer->mon; + if (sweefocused) { + detachstack(swee); + attachstack(swee); + selmon = swer->mon; + } + swee->tags = swer->tags; + swee->isfloating = swer->isfloating; + for (c = swee; c->swer; c = c->swer); + c->swer = swer; + + /* Configure geometry params obtained from patches (e.g. cfacts) here. */ + // swee->cfact = swer->cfact; + + /* ICCCM 4.1.3.1 */ + setclientstate(swer, WithdrawnState); + if (manage) + setclientstate(swee, NormalState); + + if (swee->isfloating || !swee->mon->lt[swee->mon->sellt]->arrange) + XRaiseWindow(dpy, swee->win); + resize(swee, swer->x, swer->y, swer->w, swer->h, 0); + + focus(NULL); + arrange(NULL); + if (manage) + XMapWindow(dpy, swee->win); + XUnmapWindow(dpy, swer->win); + restack(swer->mon); +} + +/* + * Register a future swallow with swallower 'c'. 'class', 'inst' and 'title' + * shall point null-terminated strings and must not be NULL. If an already + * existing swallow instance targets 'c' its filters are updated and no new + * swallow instance is created. 'c' may be ClientRegular or ClientSwallowee. + * Complement to swalrm(). + */ +void +swalreg(Client *c, const char *class, const char *inst, const char *title) +{ + Swallow *s; + + if (!c) + return; + + /* Update existing swallow */ + for (s = swallows; s; s = s->next) { + if (s->client == c) { + strncpy(s->class, class, sizeof(s->class) - 1); + strncpy(s->inst, inst, sizeof(s->inst) - 1); + strncpy(s->title, title, sizeof(s->title) - 1); + s->decay = swaldecay; + + /* Only one swallow per client. May return after first hit. */ + return; + } + } + + s = ecalloc(1, sizeof(Swallow)); + s->decay = swaldecay; + s->client = c; + strncpy(s->class, class, sizeof(s->class) - 1); + strncpy(s->inst, inst, sizeof(s->inst) - 1); + strncpy(s->title, title, sizeof(s->title) - 1); + + s->next = swallows; + swallows = s; +} + +/* + * Decrease decay counter of all registered swallows by 'decayby' and remove any + * swallow instances whose counter is less than or equal to zero. + */ +void +swaldecayby(int decayby) +{ + Swallow *s, *t; + + for (s = swallows; s; s = t) { + s->decay -= decayby; + t = s->next; + if (s->decay <= 0) + swalrm(s); + } +} + +/* + * Window configuration and client setup for new windows which are to be + * swallowed immediately. Pendant to manage() for such windows. + */ +void +swalmanage(Swallow *s, Window w, XWindowAttributes *wa) +{ + Client *swee, *swer; + XWindowChanges wc; + + swer = s->client; + swalrm(s); + + /* Perform bare minimum setup of a client for window 'w' such that swal() + * may be used to perform the swallow. The following lines are basically a + * minimal implementation of manage() with a few chunks delegated to + * swal(). */ + swee = ecalloc(1, sizeof(Client)); + swee->win = w; + swee->mon = swer->mon; + swee->oldbw = wa->border_width; + swee->bw = borderpx; + attach(swee); + attachstack(swee); + updatetitle(swee); + updatesizehints(swee); + XSelectInput(dpy, swee->win, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); + wc.border_width = swee->bw; + XConfigureWindow(dpy, swee->win, CWBorderWidth, &wc); + grabbuttons(swee, 0); + XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(swee->win), 1); + + swal(swer, swee, 1); +} + +/* + * Return swallow instance which targets window 'w' as determined by its class + * name, instance name and window title. Returns NULL if none is found. Pendant + * to wintoclient(). + */ +Swallow * +swalmatch(Window w) +{ + XClassHint ch = { NULL, NULL }; + Swallow *s = NULL; + char title[sizeof(s->title)]; + + XGetClassHint(dpy, w, &ch); + if (!gettextprop(w, netatom[NetWMName], title, sizeof(title))) + gettextprop(w, XA_WM_NAME, title, sizeof(title)); + + for (s = swallows; s; s = s->next) { + if ((!ch.res_class || strstr(ch.res_class, s->class)) + && (!ch.res_name || strstr(ch.res_name, s->inst)) + && (title[0] == '\0' || strstr(title, s->title))) + break; + } + + if (ch.res_class) + XFree(ch.res_class); + if (ch.res_name) + XFree(ch.res_name); + return s; +} + +/* + * Interactive drag-and-drop swallow. + */ +void +swalmouse(const Arg *arg) +{ + Client *swer, *swee; + XEvent ev; + + if (!(swee = selmon->sel)) + return; + + if (XGrabPointer(dpy, root, False, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, + GrabModeAsync, None, cursor[CurSwal]->cursor, CurrentTime) != GrabSuccess) + return; + + do { + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); + switch(ev.type) { + case ConfigureRequest: /* fallthrough */ + case Expose: /* fallthrough */ + case MapRequest: + handler[ev.type](&ev); + break; + } + } while (ev.type != ButtonRelease); + XUngrabPointer(dpy, CurrentTime); + + if ((swer = wintoclient(ev.xbutton.subwindow)) + && swer != swee) + swal(swer, swee, 0); + + /* Remove accumulated pending EnterWindow events caused by the mouse + * movements. */ + XCheckMaskEvent(dpy, EnterWindowMask, &ev); +} + +/* + * Delete swallow instance swallows and free its resources. Complement to + * swalreg(). If NULL is passed all registered swallows are deleted. + */ +void +swalrm(Swallow *s) +{ + Swallow *t, **ps; + + if (s) { + for (ps = &swallows; *ps && *ps != s; ps = &(*ps)->next); + *ps = s->next; + free(s); + } + else { + for(s = swallows; s; s = t) { + t = s->next; + free(s); + } + swallows = NULL; + } +} + +/* + * Removes swallow instance targeting 'c' if it exists. Complement to swalreg(). + */ +void +swalunreg(Client *c) { Swallow *s; + + for (s = swallows; s; s = s->next) { + if (c == s->client) { + swalrm(s); + /* Max. 1 registered swallow per client. No need to continue. */ + break; + } + } +} + +/* + * Stop an active swallow of swallowed client 'swee' and remap the swallower. + * If 'swee' is a swallower itself 'root' must point the root client of the + * swallow chain containing 'swee'. + */ +void +swalstop(Client *swee, Client *root) +{ + Client *swer; + + if (!swee || !(swer = swee->swer)) + return; + + swee->swer = NULL; + root = root ? root : swee; + swer->mon = root->mon; + swer->tags = root->tags; + swer->next = root->next; + root->next = swer; + swer->snext = root->snext; + root->snext = swer; + swer->isfloating = swee->isfloating; + + /* Configure geometry params obtained from patches (e.g. cfacts) here. */ + // swer->cfact = 1.0; + + /* If swer is not in tiling mode reuse swee's geometry. */ + if (swer->isfloating || !root->mon->lt[root->mon->sellt]->arrange) { + XRaiseWindow(dpy, swer->win); + resize(swer, swee->x, swee->y, swee->w, swee->h, 0); + } + + /* Override swer's border scheme which may be using SchemeSel. */ + XSetWindowBorder(dpy, swer->win, scheme[SchemeNorm][ColBorder].pixel); + + /* ICCCM 4.1.3.1 */ + setclientstate(swer, NormalState); + + XMapWindow(dpy, swer->win); + focus(NULL); + arrange(swer->mon); +} + +/* + * Stop active swallow for currently selected client. + */ +void +swalstopsel(const Arg *unused) +{ + if (selmon->sel) + swalstop(selmon->sel, NULL); +} + void tag(const Arg *arg) { @@ -1991,12 +2484,24 @@ unmapnotify(XEvent *e) { Client *c; XUnmapEvent *ev = &e->xunmap; + int type; - if ((c = wintoclient(ev->window))) { - if (ev->send_event) - setclientstate(c, WithdrawnState); - else - unmanage(c, 0); + type = wintoclient2(ev->window, &c, NULL); + if (type && ev->send_event) { + setclientstate(c, WithdrawnState); + return; + } + switch (type) { + case ClientRegular: + unmanage(c, 0); + break; + case ClientSwallowee: + swalstop(c, NULL); + unmanage(c, 0); + break; + case ClientSwallower: + /* Swallowers are never mapped. Nothing to do. */ + break; } } @@ -2038,15 +2543,19 @@ updatebarpos(Monitor *m) void updateclientlist(void) { - Client *c; + Client *c, *d; Monitor *m; XDeleteProperty(dpy, root, netatom[NetClientList]); - for (m = mons; m; m = m->next) - for (c = m->clients; c; c = c->next) - XChangeProperty(dpy, root, netatom[NetClientList], - XA_WINDOW, 32, PropModeAppend, - (unsigned char *) &(c->win), 1); + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + for (d = c; d; d = d->swer) { + XChangeProperty(dpy, root, netatom[NetClientList], + XA_WINDOW, 32, PropModeAppend, + (unsigned char *) &(c->win), 1); + } + } + } } int @@ -2282,6 +2791,43 @@ wintoclient(Window w) return NULL; } +/* + * Writes client managing window 'w' into 'pc' and returns type of client. If + * no client is found NULL is written to 'pc' and zero is returned. If a client + * is found and is a swallower (ClientSwallower) and proot is not NULL the root + * client of the swallow chain is written to 'proot'. + */ +int +wintoclient2(Window w, Client **pc, Client **proot) +{ + Monitor *m; + Client *c, *d; + + for (m = mons; m; m = m->next) { + for (c = m->clients; c; c = c->next) { + if (c->win == w) { + *pc = c; + if (c->swer) + return ClientSwallowee; + else + return ClientRegular; + } + else { + for (d = c->swer; d; d = d->swer) { + if (d->win == w) { + if (proot) + *proot = c; + *pc = d; + return ClientSwallower; + } + } + } + } + } + *pc = NULL; + return 0; +} + Monitor * wintomon(Window w) { diff --git a/dwmswallow b/dwmswallow new file mode 100644 index 0000000..eaab3bb --- /dev/null +++ b/dwmswallow @@ -0,0 +1,120 @@ +#!/usr/bin/env sh + +# Separator and command prefix, as defined in dwm.c:fakesignal() +SEP='###' +PREFIX='#!' + +# Asserts that all arguments are valid X11 window IDs, i.e. positive integers. +# For the purpose of this script 0 is declared invalid. +is_winid() { + while :; do + # Given input incompatible to %d, some implementations of printf return + # an error while others silently evaluate the expression to 0. + if ! wid=$(printf '%d' "$1" 2>/dev/null) || [ "$wid" -le 0 ]; then + return 1 + fi + + [ -n "$2" ] && shift || break + done +} + +# Prints usage help. If "$1" is provided, function exits script after +# execution. +usage() { + [ -t 1 ] && myprintf=printf || myprintf=true + msg="$(cat <<-EOF + dwm window swallowing command-line interface. Usage: + + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER [-c CLASS] [-i INSTANCE] [-t TITLE]$($myprintf "\033[0m") + Register window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m") to swallow the next future window whose attributes + match the $($myprintf "\033[3m")CLASS$($myprintf "\033[0m") name, $($myprintf "\033[3m")INSTANCE$($myprintf "\033[0m") name and window $($myprintf "\033[3m")TITLE$($myprintf "\033[0m") filters using basic + string-matching. An omitted filter will match anything. + + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER -d$($myprintf "\033[0m") + Deregister queued swallow for window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m"). Inverse of above signature. + + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER SWALLOWEE$($myprintf "\033[0m") + Perform immediate swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m") by window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m"). + + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWEE -s$($myprintf "\033[0m") + Stop swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m"). Inverse of the above signature. Visible + windows only. + + $($myprintf "\033[1m")dwmswallow -h$($myprintf "\033[0m") + Show this usage information. + EOF + )" + + if [ -n "$1" ]; then + echo "$msg" >&2 + exit "$1" + else + echo "$msg" + fi +} + +# Determine number of leading positional arguments +arg1="$1" # save for later +arg2="$2" # save for later +num_pargs=0 +while :; do + case "$1" in + -*|"") break ;; + *) num_pargs=$((num_pargs + 1)); shift ;; + esac +done + +case "$num_pargs" in +1) + ! is_winid "$arg1" && usage 1 + + widswer="$arg1" + if [ "$1" = "-d" ] && [ "$#" -eq 1 ]; then + if name="$(printf "${PREFIX}swalunreg${SEP}%u" "$widswer" 2>/dev/null)"; then + xsetroot -name "$name" + else + usage 1 + fi + elif [ "$1" = "-s" ] && [ "$#" -eq 1 ]; then + widswee="$arg1" + if name="$(printf "${PREFIX}swalstop${SEP}%u" "$widswee" 2>/dev/null)"; then + xsetroot -name "$name" + else + usage 1 + fi + else + while :; do + case "$1" in + -c) [ -n "$2" ] && { class="$2"; shift 2; } || usage 1 ;; + -i) [ -n "$2" ] && { instance="$2"; shift 2; } || usage 1 ;; + -t) [ -n "$2" ] && { title="$2"; shift 2; } || usage 1 ;; + "") break ;; + *) usage 1 ;; + esac + done + widswer="$arg1" + if name="$(printf "${PREFIX}swalreg${SEP}%u${SEP}%s${SEP}%s${SEP}%s" "$widswer" "$class" "$instance" "$title" 2>/dev/null)"; then + xsetroot -name "$name" + else + usage 1 + fi + fi + ;; +2) + ! is_winid "$arg1" "$arg2" || [ -n "$1" ] && usage 1 + + widswer="$arg1" + widswee="$arg2" + if name="$(printf "${PREFIX}swal${SEP}%u${SEP}%u" "$widswer" "$widswee" 2>/dev/null)"; then + xsetroot -name "$name" + else + usage 1 + fi + ;; +*) + if [ "$arg1" = "-h" ] && [ $# -eq 1 ]; then + usage + else + usage 1 + fi +esac diff --git a/patches/dwm-dynamicswallow-20240320-061e9fe.diff b/patches/dwm-dynamicswallow-20240320-061e9fe.diff new file mode 100644 index 0000000..4cdc5a3 --- /dev/null +++ b/patches/dwm-dynamicswallow-20240320-061e9fe.diff @@ -0,0 +1,1020 @@ +From 20175007156c243ee577e0aa518a05282950e766 Mon Sep 17 00:00:00 2001 +From: visil <workregor@mail.ru> +Date: Wed, 20 Mar 2024 17:48:47 +0300 +Subject: [PATCH] Dynamic swallowing patch for 6.5 + +--- + Makefile | 3 + + config.def.h | 6 + + config.mk | 4 +- + dwm.c | 588 +++++++++++++++++++++++++++++++++++++++++++++++++-- + dwmswallow | 120 +++++++++++ + util.c | 29 +++ + util.h | 1 + + 7 files changed, 728 insertions(+), 23 deletions(-) + create mode 100644 dwmswallow + +diff --git a/Makefile b/Makefile +index ffa69b4..67aa239 100644 +--- a/Makefile ++++ b/Makefile +@@ -34,12 +34,15 @@ install: all + mkdir -p ${DESTDIR}${PREFIX}/bin + cp -f dwm ${DESTDIR}${PREFIX}/bin + chmod 755 ${DESTDIR}${PREFIX}/bin/dwm ++ cp -f dwmswallow ${DESTDIR}${PREFIX}/bin ++ chmod 755 ${DESTDIR}${PREFIX}/bin/dwmswallow + mkdir -p ${DESTDIR}${MANPREFIX}/man1 + sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1 + chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1 + + uninstall: + rm -f ${DESTDIR}${PREFIX}/bin/dwm\ ++ ${DESTDIR}${MANPREFIX}/bin/dwmswallow\ + ${DESTDIR}${MANPREFIX}/man1/dwm.1 + + .PHONY: all clean dist install uninstall +diff --git a/config.def.h b/config.def.h +index 9efa774..bf94c30 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -30,6 +30,10 @@ static const Rule rules[] = { + { "Gimp", NULL, NULL, 0, 1, -1 }, + { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, + }; ++/* window swallowing */ ++static const int swaldecay = 3; ++static const int swalretroactive = 1; ++static const char swalsymbol[] = "👅"; + + /* layout(s) */ + static const float mfact = 0.55; /* factor of master area size [0.05..0.95] */ +@@ -85,6 +89,7 @@ static const Key keys[] = { + { MODKEY, XK_period, focusmon, {.i = +1 } }, + { MODKEY|ShiftMask, XK_comma, tagmon, {.i = -1 } }, + { MODKEY|ShiftMask, XK_period, tagmon, {.i = +1 } }, ++ { MODKEY, XK_u, swalstopsel, {0} }, + TAGKEYS( XK_1, 0) + TAGKEYS( XK_2, 1) + TAGKEYS( XK_3, 2) +@@ -108,6 +113,7 @@ static const Button buttons[] = { + { ClkClientWin, MODKEY, Button1, movemouse, {0} }, + { ClkClientWin, MODKEY, Button2, togglefloating, {0} }, + { ClkClientWin, MODKEY, Button3, resizemouse, {0} }, ++ { ClkClientWin, MODKEY|ShiftMask, Button1, swalmouse, {0} }, + { ClkTagBar, 0, Button1, view, {0} }, + { ClkTagBar, 0, Button3, toggleview, {0} }, + { ClkTagBar, MODKEY, Button1, tag, {0} }, +diff --git a/config.mk b/config.mk +index 8efca9a..6a1883c 100644 +--- a/config.mk ++++ b/config.mk +@@ -27,8 +27,8 @@ LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} + + # flags + CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} +-#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} +-CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} ++CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} ++#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${INCS} ${CPPFLAGS} + LDFLAGS = ${LIBS} + + # Solaris +diff --git a/dwm.c b/dwm.c +index f1d86b2..c01cf41 100644 +--- a/dwm.c ++++ b/dwm.c +@@ -58,7 +58,7 @@ + #define TEXTW(X) (drw_fontset_getwidth(drw, (X)) + lrpad) + + /* enums */ +-enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */ ++enum { CurNormal, CurResize, CurMove, CurSwal, CurLast }; /* cursor */ + enum { SchemeNorm, SchemeSel }; /* color schemes */ + enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + NetWMFullscreen, NetActiveWindow, NetWMWindowType, +@@ -66,6 +66,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMCheck, + enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */ + enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, + ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ ++enum { ClientRegular = 1, ClientSwallowee, ClientSwallower }; /* client types */ + + typedef union { + int i; +@@ -95,6 +96,7 @@ struct Client { + int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; + Client *next; + Client *snext; ++ Client *swer; /* swallower of client, NULL by default */ + Monitor *mon; + Window win; + }; +@@ -140,6 +142,27 @@ typedef struct { + int isfloating; + int monitor; + } Rule; ++typedef struct Swallow Swallow; ++struct Swallow { ++ /* Window class name, instance name (WM_CLASS) and title ++ * (WM_NAME/_NET_WM_NAME, latter preferred if it exists). An empty string ++ * implies a wildcard as per strstr(). */ ++ char class[256]; ++ char inst[256]; ++ char title[256]; ++ ++ /* Used to delete swallow instance after 'swaldecay' windows were mapped ++ * without the swallow having been consumed. 'decay' keeps track of the ++ * remaining "charges". */ ++ int decay; ++ ++ /* The swallower, i.e. the client which will swallow the next mapped window ++ * whose filters match the above properties. */ ++ Client *client; ++ ++ /* Linked list of registered swallow instances. */ ++ Swallow *next; ++}; + + /* function declarations */ + static void applyrules(Client *c); +@@ -165,6 +188,7 @@ static void drawbar(Monitor *m); + static void drawbars(void); + static void enternotify(XEvent *e); + static void expose(XEvent *e); ++static int fakesignal(void); + static void focus(Client *c); + static void focusin(XEvent *e); + static void focusmon(const Arg *arg); +@@ -206,6 +230,16 @@ static void setup(void); + static void seturgent(Client *c, int urg); + static void showhide(Client *c); + static void spawn(const Arg *arg); ++static void swal(Client *swer, Client *swee, int manage); ++static void swalreg(Client *c, const char* class, const char* inst, const char* title); ++static void swaldecayby(int decayby); ++static void swalmanage(Swallow *s, Window w, XWindowAttributes *wa); ++static Swallow *swalmatch(Window w); ++static void swalmouse(const Arg *arg); ++static void swalrm(Swallow *s); ++static void swalunreg(Client *c); ++static void swalstop(Client *c, Client *root); ++static void swalstopsel(const Arg *unused); + static void tag(const Arg *arg); + static void tagmon(const Arg *arg); + static void tile(Monitor *m); +@@ -228,6 +262,7 @@ static void updatewindowtype(Client *c); + static void updatewmhints(Client *c); + static void view(const Arg *arg); + static Client *wintoclient(Window w); ++static int wintoclient2(Window w, Client **pc, Client **proot); + static Monitor *wintomon(Window w); + static int xerror(Display *dpy, XErrorEvent *ee); + static int xerrordummy(Display *dpy, XErrorEvent *ee); +@@ -266,6 +301,7 @@ static Clr **scheme; + static Display *dpy; + static Drw *drw; + static Monitor *mons, *selmon; ++static Swallow *swallows; + static Window root, wmcheckwin; + + /* configuration, allows nested code to access above variables */ +@@ -586,7 +622,9 @@ configurerequest(XEvent *e) + XConfigureRequestEvent *ev = &e->xconfigurerequest; + XWindowChanges wc; + +- if ((c = wintoclient(ev->window))) { ++ switch (wintoclient2(ev->window, &c, NULL)) { ++ case ClientRegular: /* fallthrough */ ++ case ClientSwallowee: + if (ev->value_mask & CWBorderWidth) + c->bw = ev->border_width; + else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) { +@@ -617,7 +655,13 @@ configurerequest(XEvent *e) + XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); + } else + configure(c); +- } else { ++ break; ++ case ClientSwallower: ++ /* Reject any move/resize requests for swallowers and communicate ++ * refusal to client via a synthetic ConfigureNotify (ICCCM 4.1.5). */ ++ configure(c); ++ break; ++ default: + wc.x = ev->x; + wc.y = ev->y; + wc.width = ev->width; +@@ -626,6 +670,7 @@ configurerequest(XEvent *e) + wc.sibling = ev->above; + wc.stack_mode = ev->detail; + XConfigureWindow(dpy, ev->window, ev->value_mask, &wc); ++ break; + } + XSync(dpy, False); + } +@@ -650,11 +695,30 @@ createmon(void) + void + destroynotify(XEvent *e) + { +- Client *c; ++ Client *c, *swee, *root; + XDestroyWindowEvent *ev = &e->xdestroywindow; + +- if ((c = wintoclient(ev->window))) ++ switch (wintoclient2(ev->window, &c, &root)) { ++ case ClientRegular: + unmanage(c, 1); ++ break; ++ case ClientSwallowee: ++ swalstop(c, NULL); ++ unmanage(c, 1); ++ break; ++ case ClientSwallower: ++ /* If the swallower is swallowed by another client, terminate the ++ * swallow. This cuts off the swallow chain after the client. */ ++ swalstop(c, root); ++ ++ /* Cut off the swallow chain before the client. */ ++ for (swee = root; swee->swer != c; swee = swee->swer); ++ swee->swer = NULL; ++ ++ free(c); ++ updateclientlist(); ++ break; ++ } + } + + void +@@ -734,6 +798,12 @@ drawbar(Monitor *m) + drw_setscheme(drw, scheme[SchemeNorm]); + x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0); + ++ /* Draw swalsymbol next to ltsymbol. */ ++ if (m->sel && m->sel->swer) { ++ w = TEXTW(swalsymbol); ++ x = drw_text(drw, x, 0, w, bh, lrpad / 2, swalsymbol, 0); ++ } ++ + if ((w = m->ww - tw - x) > bh) { + if (m->sel) { + drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]); +@@ -786,6 +856,81 @@ expose(XEvent *e) + drawbar(m); + } + ++int ++fakesignal(void) ++{ ++ /* Command syntax: <PREFIX><COMMAND>[<SEP><ARG>]... */ ++ static const char sep[] = "###"; ++ static const char prefix[] = "#!"; ++ ++ size_t numsegments, numargs; ++ char rootname[256]; ++ char *segments[16] = {0}; ++ ++ /* Get root name, split by separator and find the prefix */ ++ if (!gettextprop(root, XA_WM_NAME, rootname, sizeof(rootname)) ++ || strncmp(rootname, prefix, sizeof(prefix) - 1)) { ++ return 0; ++ } ++ numsegments = split(rootname + sizeof(prefix) - 1, sep, segments, sizeof(segments)); ++ numargs = numsegments - 1; /* number of arguments to COMMAND */ ++ ++ if (!strcmp(segments[0], "swalreg")) { ++ /* Params: windowid, [class], [instance], [title] */ ++ Window w; ++ Client *c; ++ ++ if (numargs >= 1) { ++ w = strtoul(segments[1], NULL, 0); ++ switch (wintoclient2(w, &c, NULL)) { ++ case ClientRegular: /* fallthrough */ ++ case ClientSwallowee: ++ swalreg(c, segments[2], segments[3], segments[4]); ++ break; ++ } ++ } ++ } ++ else if (!strcmp(segments[0], "swal")) { ++ /* Params: swallower's windowid, swallowee's window-id */ ++ Client *swer, *swee; ++ Window winswer, winswee; ++ int typeswer, typeswee; ++ ++ if (numargs >= 2) { ++ winswer = strtoul(segments[1], NULL, 0); ++ typeswer = wintoclient2(winswer, &swer, NULL); ++ winswee = strtoul(segments[2], NULL, 0); ++ typeswee = wintoclient2(winswee, &swee, NULL); ++ if ((typeswer == ClientRegular || typeswer == ClientSwallowee) ++ && (typeswee == ClientRegular || typeswee == ClientSwallowee)) ++ swal(swer, swee, 0); ++ } ++ } ++ else if (!strcmp(segments[0], "swalunreg")) { ++ /* Params: swallower's windowid */ ++ Client *swer; ++ Window winswer; ++ ++ if (numargs == 1) { ++ winswer = strtoul(segments[1], NULL, 0); ++ if ((swer = wintoclient(winswer))) ++ swalunreg(swer); ++ } ++ } ++ else if (!strcmp(segments[0], "swalstop")) { ++ /* Params: swallowee's windowid */ ++ Client *swee; ++ Window winswee; ++ ++ if (numargs == 1) { ++ winswee = strtoul(segments[1], NULL, 0); ++ if ((swee = wintoclient(winswee))) ++ swalstop(swee, NULL); ++ } ++ } ++ return 1; ++} ++ + void + focus(Client *c) + { +@@ -1101,13 +1246,35 @@ mappingnotify(XEvent *e) + void + maprequest(XEvent *e) + { ++ Client *c, *swee, *root; + static XWindowAttributes wa; + XMapRequestEvent *ev = &e->xmaprequest; ++ Swallow *s; + + if (!XGetWindowAttributes(dpy, ev->window, &wa) || wa.override_redirect) + return; +- if (!wintoclient(ev->window)) +- manage(ev->window, &wa); ++ switch (wintoclient2(ev->window, &c, &root)) { ++ case ClientRegular: /* fallthrough */ ++ case ClientSwallowee: ++ /* Regulars and swallowees are always mapped. Nothing to do. */ ++ break; ++ case ClientSwallower: ++ /* Remapping a swallower will simply stop the swallow. */ ++ for (swee = root; swee->swer != c; swee = swee->swer); ++ swalstop(swee, root); ++ break; ++ default: ++ /* No client is managing the window. See if any swallows match. */ ++ if ((s = swalmatch(ev->window))) ++ swalmanage(s, ev->window, &wa); ++ else ++ manage(ev->window, &wa); ++ break; ++ } ++ ++ /* Reduce decay counter of all swallow instances. */ ++ if (swaldecay) ++ swaldecayby(1); + } + + void +@@ -1223,11 +1390,13 @@ propertynotify(XEvent *e) + { + Client *c; + Window trans; ++ Swallow *s; + XPropertyEvent *ev = &e->xproperty; + +- if ((ev->window == root) && (ev->atom == XA_WM_NAME)) +- updatestatus(); +- else if (ev->state == PropertyDelete) ++ if ((ev->window == root) && (ev->atom == XA_WM_NAME)) { ++ if (!fakesignal()) ++ updatestatus(); ++ } else if (ev->state == PropertyDelete) + return; /* ignore */ + else if ((c = wintoclient(ev->window))) { + switch(ev->atom) { +@@ -1249,6 +1418,9 @@ propertynotify(XEvent *e) + updatetitle(c); + if (c == c->mon->sel) + drawbar(c->mon); ++ if (swalretroactive && (s = swalmatch(c->win))) { ++ swal(s->client, c, 0); ++ } + } + if (ev->atom == netatom[NetWMWindowType]) + updatewindowtype(c); +@@ -1583,6 +1755,7 @@ setup(void) + cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr); + cursor[CurResize] = drw_cur_create(drw, XC_sizing); + cursor[CurMove] = drw_cur_create(drw, XC_fleur); ++ cursor[CurSwal] = drw_cur_create(drw, XC_bottom_side); + /* init appearance */ + scheme = ecalloc(LENGTH(colors), sizeof(Clr *)); + for (i = 0; i < LENGTH(colors); i++) +@@ -1666,6 +1839,326 @@ spawn(const Arg *arg) + } + } + ++/* ++ * Perform immediate swallow of client 'swee' by client 'swer'. 'manage' shall ++ * be set if swal() is called from swalmanage(). 'swer' and 'swee' must be ++ * regular or swallowee, but not swallower. ++ */ ++void ++swal(Client *swer, Client *swee, int manage) ++{ ++ Client *c, **pc; ++ int sweefocused = selmon->sel == swee; ++ ++ /* No self-swallowing! */ ++ if (swer == swee) ++ return; ++ ++ /* Remove any swallows registered for the swer. Asking a swallower to ++ * swallow another window is ambiguous and is thus avoided altogether. In ++ * contrast, a swallowee can swallow in a well-defined manner by attaching ++ * to the head of the swallow chain. */ ++ if (!manage) ++ swalunreg(swer); ++ ++ /* Disable fullscreen prior to swallow. Swallows involving fullscreen ++ * windows produces quirky artefacts such as fullscreen terminals or tiled ++ * pseudo-fullscreen windows. */ ++ setfullscreen(swer, 0); ++ setfullscreen(swee, 0); ++ ++ /* Swap swallowee into client and focus lists. Keeps current focus unless ++ * the swer (which gets unmapped) is focused in which case the swee will ++ * receive focus. */ ++ detach(swee); ++ for (pc = &swer->mon->clients; *pc && *pc != swer; pc = &(*pc)->next); ++ *pc = swee; ++ swee->next = swer->next; ++ detachstack(swee); ++ for (pc = &swer->mon->stack; *pc && *pc != swer; pc = &(*pc)->snext); ++ *pc = swee; ++ swee->snext = swer->snext; ++ swee->mon = swer->mon; ++ if (sweefocused) { ++ detachstack(swee); ++ attachstack(swee); ++ selmon = swer->mon; ++ } ++ swee->tags = swer->tags; ++ swee->isfloating = swer->isfloating; ++ for (c = swee; c->swer; c = c->swer); ++ c->swer = swer; ++ ++ /* Configure geometry params obtained from patches (e.g. cfacts) here. */ ++ // swee->cfact = swer->cfact; ++ ++ /* ICCCM 4.1.3.1 */ ++ setclientstate(swer, WithdrawnState); ++ if (manage) ++ setclientstate(swee, NormalState); ++ ++ if (swee->isfloating || !swee->mon->lt[swee->mon->sellt]->arrange) ++ XRaiseWindow(dpy, swee->win); ++ resize(swee, swer->x, swer->y, swer->w, swer->h, 0); ++ ++ focus(NULL); ++ arrange(NULL); ++ if (manage) ++ XMapWindow(dpy, swee->win); ++ XUnmapWindow(dpy, swer->win); ++ restack(swer->mon); ++} ++ ++/* ++ * Register a future swallow with swallower 'c'. 'class', 'inst' and 'title' ++ * shall point null-terminated strings and must not be NULL. If an already ++ * existing swallow instance targets 'c' its filters are updated and no new ++ * swallow instance is created. 'c' may be ClientRegular or ClientSwallowee. ++ * Complement to swalrm(). ++ */ ++void ++swalreg(Client *c, const char *class, const char *inst, const char *title) ++{ ++ Swallow *s; ++ ++ if (!c) ++ return; ++ ++ /* Update existing swallow */ ++ for (s = swallows; s; s = s->next) { ++ if (s->client == c) { ++ strncpy(s->class, class, sizeof(s->class) - 1); ++ strncpy(s->inst, inst, sizeof(s->inst) - 1); ++ strncpy(s->title, title, sizeof(s->title) - 1); ++ s->decay = swaldecay; ++ ++ /* Only one swallow per client. May return after first hit. */ ++ return; ++ } ++ } ++ ++ s = ecalloc(1, sizeof(Swallow)); ++ s->decay = swaldecay; ++ s->client = c; ++ strncpy(s->class, class, sizeof(s->class) - 1); ++ strncpy(s->inst, inst, sizeof(s->inst) - 1); ++ strncpy(s->title, title, sizeof(s->title) - 1); ++ ++ s->next = swallows; ++ swallows = s; ++} ++ ++/* ++ * Decrease decay counter of all registered swallows by 'decayby' and remove any ++ * swallow instances whose counter is less than or equal to zero. ++ */ ++void ++swaldecayby(int decayby) ++{ ++ Swallow *s, *t; ++ ++ for (s = swallows; s; s = t) { ++ s->decay -= decayby; ++ t = s->next; ++ if (s->decay <= 0) ++ swalrm(s); ++ } ++} ++ ++/* ++ * Window configuration and client setup for new windows which are to be ++ * swallowed immediately. Pendant to manage() for such windows. ++ */ ++void ++swalmanage(Swallow *s, Window w, XWindowAttributes *wa) ++{ ++ Client *swee, *swer; ++ XWindowChanges wc; ++ ++ swer = s->client; ++ swalrm(s); ++ ++ /* Perform bare minimum setup of a client for window 'w' such that swal() ++ * may be used to perform the swallow. The following lines are basically a ++ * minimal implementation of manage() with a few chunks delegated to ++ * swal(). */ ++ swee = ecalloc(1, sizeof(Client)); ++ swee->win = w; ++ swee->mon = swer->mon; ++ swee->oldbw = wa->border_width; ++ swee->bw = borderpx; ++ attach(swee); ++ attachstack(swee); ++ updatetitle(swee); ++ updatesizehints(swee); ++ XSelectInput(dpy, swee->win, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask); ++ wc.border_width = swee->bw; ++ XConfigureWindow(dpy, swee->win, CWBorderWidth, &wc); ++ grabbuttons(swee, 0); ++ XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend, ++ (unsigned char *) &(swee->win), 1); ++ ++ swal(swer, swee, 1); ++} ++ ++/* ++ * Return swallow instance which targets window 'w' as determined by its class ++ * name, instance name and window title. Returns NULL if none is found. Pendant ++ * to wintoclient(). ++ */ ++Swallow * ++swalmatch(Window w) ++{ ++ XClassHint ch = { NULL, NULL }; ++ Swallow *s = NULL; ++ char title[sizeof(s->title)]; ++ ++ XGetClassHint(dpy, w, &ch); ++ if (!gettextprop(w, netatom[NetWMName], title, sizeof(title))) ++ gettextprop(w, XA_WM_NAME, title, sizeof(title)); ++ ++ for (s = swallows; s; s = s->next) { ++ if ((!ch.res_class || strstr(ch.res_class, s->class)) ++ && (!ch.res_name || strstr(ch.res_name, s->inst)) ++ && (title[0] == '\0' || strstr(title, s->title))) ++ break; ++ } ++ ++ if (ch.res_class) ++ XFree(ch.res_class); ++ if (ch.res_name) ++ XFree(ch.res_name); ++ return s; ++} ++ ++/* ++ * Interactive drag-and-drop swallow. ++ */ ++void ++swalmouse(const Arg *arg) ++{ ++ Client *swer, *swee; ++ XEvent ev; ++ ++ if (!(swee = selmon->sel)) ++ return; ++ ++ if (XGrabPointer(dpy, root, False, ButtonPressMask|ButtonReleaseMask, GrabModeAsync, ++ GrabModeAsync, None, cursor[CurSwal]->cursor, CurrentTime) != GrabSuccess) ++ return; ++ ++ do { ++ XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev); ++ switch(ev.type) { ++ case ConfigureRequest: /* fallthrough */ ++ case Expose: /* fallthrough */ ++ case MapRequest: ++ handler[ev.type](&ev); ++ break; ++ } ++ } while (ev.type != ButtonRelease); ++ XUngrabPointer(dpy, CurrentTime); ++ ++ if ((swer = wintoclient(ev.xbutton.subwindow)) ++ && swer != swee) ++ swal(swer, swee, 0); ++ ++ /* Remove accumulated pending EnterWindow events caused by the mouse ++ * movements. */ ++ XCheckMaskEvent(dpy, EnterWindowMask, &ev); ++} ++ ++/* ++ * Delete swallow instance swallows and free its resources. Complement to ++ * swalreg(). If NULL is passed all registered swallows are deleted. ++ */ ++void ++swalrm(Swallow *s) ++{ ++ Swallow *t, **ps; ++ ++ if (s) { ++ for (ps = &swallows; *ps && *ps != s; ps = &(*ps)->next); ++ *ps = s->next; ++ free(s); ++ } ++ else { ++ for(s = swallows; s; s = t) { ++ t = s->next; ++ free(s); ++ } ++ swallows = NULL; ++ } ++} ++ ++/* ++ * Removes swallow instance targeting 'c' if it exists. Complement to swalreg(). ++ */ ++void ++swalunreg(Client *c) { Swallow *s; ++ ++ for (s = swallows; s; s = s->next) { ++ if (c == s->client) { ++ swalrm(s); ++ /* Max. 1 registered swallow per client. No need to continue. */ ++ break; ++ } ++ } ++} ++ ++/* ++ * Stop an active swallow of swallowed client 'swee' and remap the swallower. ++ * If 'swee' is a swallower itself 'root' must point the root client of the ++ * swallow chain containing 'swee'. ++ */ ++void ++swalstop(Client *swee, Client *root) ++{ ++ Client *swer; ++ ++ if (!swee || !(swer = swee->swer)) ++ return; ++ ++ swee->swer = NULL; ++ root = root ? root : swee; ++ swer->mon = root->mon; ++ swer->tags = root->tags; ++ swer->next = root->next; ++ root->next = swer; ++ swer->snext = root->snext; ++ root->snext = swer; ++ swer->isfloating = swee->isfloating; ++ ++ /* Configure geometry params obtained from patches (e.g. cfacts) here. */ ++ // swer->cfact = 1.0; ++ ++ /* If swer is not in tiling mode reuse swee's geometry. */ ++ if (swer->isfloating || !root->mon->lt[root->mon->sellt]->arrange) { ++ XRaiseWindow(dpy, swer->win); ++ resize(swer, swee->x, swee->y, swee->w, swee->h, 0); ++ } ++ ++ /* Override swer's border scheme which may be using SchemeSel. */ ++ XSetWindowBorder(dpy, swer->win, scheme[SchemeNorm][ColBorder].pixel); ++ ++ /* ICCCM 4.1.3.1 */ ++ setclientstate(swer, NormalState); ++ ++ XMapWindow(dpy, swer->win); ++ focus(NULL); ++ arrange(swer->mon); ++} ++ ++/* ++ * Stop active swallow for currently selected client. ++ */ ++void ++swalstopsel(const Arg *unused) ++{ ++ if (selmon->sel) ++ swalstop(selmon->sel, NULL); ++} ++ + void + tag(const Arg *arg) + { +@@ -1806,12 +2299,24 @@ unmapnotify(XEvent *e) + { + Client *c; + XUnmapEvent *ev = &e->xunmap; ++ int type; + +- if ((c = wintoclient(ev->window))) { +- if (ev->send_event) +- setclientstate(c, WithdrawnState); +- else +- unmanage(c, 0); ++ type = wintoclient2(ev->window, &c, NULL); ++ if (type && ev->send_event) { ++ setclientstate(c, WithdrawnState); ++ return; ++ } ++ switch (type) { ++ case ClientRegular: ++ unmanage(c, 0); ++ break; ++ case ClientSwallowee: ++ swalstop(c, NULL); ++ unmanage(c, 0); ++ break; ++ case ClientSwallower: ++ /* Swallowers are never mapped. Nothing to do. */ ++ break; + } + } + +@@ -1853,15 +2358,19 @@ updatebarpos(Monitor *m) + void + updateclientlist() + { +- Client *c; ++ Client *c, *d; + Monitor *m; + + XDeleteProperty(dpy, root, netatom[NetClientList]); +- for (m = mons; m; m = m->next) +- for (c = m->clients; c; c = c->next) +- XChangeProperty(dpy, root, netatom[NetClientList], +- XA_WINDOW, 32, PropModeAppend, +- (unsigned char *) &(c->win), 1); ++ for (m = mons; m; m = m->next) { ++ for (c = m->clients; c; c = c->next) { ++ for (d = c; d; d = d->swer) { ++ XChangeProperty(dpy, root, netatom[NetClientList], ++ XA_WINDOW, 32, PropModeAppend, ++ (unsigned char *) &(c->win), 1); ++ } ++ } ++ } + } + + int +@@ -2075,6 +2584,43 @@ wintoclient(Window w) + return NULL; + } + ++/* ++ * Writes client managing window 'w' into 'pc' and returns type of client. If ++ * no client is found NULL is written to 'pc' and zero is returned. If a client ++ * is found and is a swallower (ClientSwallower) and proot is not NULL the root ++ * client of the swallow chain is written to 'proot'. ++ */ ++int ++wintoclient2(Window w, Client **pc, Client **proot) ++{ ++ Monitor *m; ++ Client *c, *d; ++ ++ for (m = mons; m; m = m->next) { ++ for (c = m->clients; c; c = c->next) { ++ if (c->win == w) { ++ *pc = c; ++ if (c->swer) ++ return ClientSwallowee; ++ else ++ return ClientRegular; ++ } ++ else { ++ for (d = c->swer; d; d = d->swer) { ++ if (d->win == w) { ++ if (proot) ++ *proot = c; ++ *pc = d; ++ return ClientSwallower; ++ } ++ } ++ } ++ } ++ } ++ *pc = NULL; ++ return 0; ++} ++ + Monitor * + wintomon(Window w) + { +diff --git a/dwmswallow b/dwmswallow +new file mode 100644 +index 0000000..eaab3bb +--- /dev/null ++++ b/dwmswallow +@@ -0,0 +1,120 @@ ++#!/usr/bin/env sh ++ ++# Separator and command prefix, as defined in dwm.c:fakesignal() ++SEP='###' ++PREFIX='#!' ++ ++# Asserts that all arguments are valid X11 window IDs, i.e. positive integers. ++# For the purpose of this script 0 is declared invalid. ++is_winid() { ++ while :; do ++ # Given input incompatible to %d, some implementations of printf return ++ # an error while others silently evaluate the expression to 0. ++ if ! wid=$(printf '%d' "$1" 2>/dev/null) || [ "$wid" -le 0 ]; then ++ return 1 ++ fi ++ ++ [ -n "$2" ] && shift || break ++ done ++} ++ ++# Prints usage help. If "$1" is provided, function exits script after ++# execution. ++usage() { ++ [ -t 1 ] && myprintf=printf || myprintf=true ++ msg="$(cat <<-EOF ++ dwm window swallowing command-line interface. Usage: ++ ++ $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER [-c CLASS] [-i INSTANCE] [-t TITLE]$($myprintf "\033[0m") ++ Register window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m") to swallow the next future window whose attributes ++ match the $($myprintf "\033[3m")CLASS$($myprintf "\033[0m") name, $($myprintf "\033[3m")INSTANCE$($myprintf "\033[0m") name and window $($myprintf "\033[3m")TITLE$($myprintf "\033[0m") filters using basic ++ string-matching. An omitted filter will match anything. ++ ++ $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER -d$($myprintf "\033[0m") ++ Deregister queued swallow for window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m"). Inverse of above signature. ++ ++ $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER SWALLOWEE$($myprintf "\033[0m") ++ Perform immediate swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m") by window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m"). ++ ++ $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWEE -s$($myprintf "\033[0m") ++ Stop swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m"). Inverse of the above signature. Visible ++ windows only. ++ ++ $($myprintf "\033[1m")dwmswallow -h$($myprintf "\033[0m") ++ Show this usage information. ++ EOF ++ )" ++ ++ if [ -n "$1" ]; then ++ echo "$msg" >&2 ++ exit "$1" ++ else ++ echo "$msg" ++ fi ++} ++ ++# Determine number of leading positional arguments ++arg1="$1" # save for later ++arg2="$2" # save for later ++num_pargs=0 ++while :; do ++ case "$1" in ++ -*|"") break ;; ++ *) num_pargs=$((num_pargs + 1)); shift ;; ++ esac ++done ++ ++case "$num_pargs" in ++1) ++ ! is_winid "$arg1" && usage 1 ++ ++ widswer="$arg1" ++ if [ "$1" = "-d" ] && [ "$#" -eq 1 ]; then ++ if name="$(printf "${PREFIX}swalunreg${SEP}%u" "$widswer" 2>/dev/null)"; then ++ xsetroot -name "$name" ++ else ++ usage 1 ++ fi ++ elif [ "$1" = "-s" ] && [ "$#" -eq 1 ]; then ++ widswee="$arg1" ++ if name="$(printf "${PREFIX}swalstop${SEP}%u" "$widswee" 2>/dev/null)"; then ++ xsetroot -name "$name" ++ else ++ usage 1 ++ fi ++ else ++ while :; do ++ case "$1" in ++ -c) [ -n "$2" ] && { class="$2"; shift 2; } || usage 1 ;; ++ -i) [ -n "$2" ] && { instance="$2"; shift 2; } || usage 1 ;; ++ -t) [ -n "$2" ] && { title="$2"; shift 2; } || usage 1 ;; ++ "") break ;; ++ *) usage 1 ;; ++ esac ++ done ++ widswer="$arg1" ++ if name="$(printf "${PREFIX}swalreg${SEP}%u${SEP}%s${SEP}%s${SEP}%s" "$widswer" "$class" "$instance" "$title" 2>/dev/null)"; then ++ xsetroot -name "$name" ++ else ++ usage 1 ++ fi ++ fi ++ ;; ++2) ++ ! is_winid "$arg1" "$arg2" || [ -n "$1" ] && usage 1 ++ ++ widswer="$arg1" ++ widswee="$arg2" ++ if name="$(printf "${PREFIX}swal${SEP}%u${SEP}%u" "$widswer" "$widswee" 2>/dev/null)"; then ++ xsetroot -name "$name" ++ else ++ usage 1 ++ fi ++ ;; ++*) ++ if [ "$arg1" = "-h" ] && [ $# -eq 1 ]; then ++ usage ++ else ++ usage 1 ++ fi ++esac +diff --git a/util.c b/util.c +index 96b82c9..3af7b6c 100644 +--- a/util.c ++++ b/util.c +@@ -24,6 +24,35 @@ die(const char *fmt, ...) + + exit(1); + } ++/* ++ * Splits a string into segments according to a separator. A '\0' is written to ++ * the end of every segment. The beginning of every segment is written to ++ * 'pbegin'. Only the first 'maxcount' segments will be written if ++ * maxcount > 0. Inspired by python's split. ++ * ++ * Used exclusively by fakesignal() to split arguments. ++ */ ++size_t ++split(char *s, const char* sep, char **pbegin, size_t maxcount) { ++ ++ char *p, *q; ++ const size_t seplen = strlen(sep); ++ size_t count = 0; ++ ++ maxcount = maxcount == 0 ? (size_t)-1 : maxcount; ++ p = s; ++ while ((q = strstr(p, sep)) != NULL && count < maxcount) { ++ pbegin[count] = p; ++ *q = '\0'; ++ p = q + seplen; ++ count++; ++ } ++ if (count < maxcount) { ++ pbegin[count] = p; ++ count++; ++ } ++ return count; ++} + + void * + ecalloc(size_t nmemb, size_t size) +diff --git a/util.h b/util.h +index f633b51..670345f 100644 +--- a/util.h ++++ b/util.h +@@ -6,3 +6,4 @@ + + void die(const char *fmt, ...); + void *ecalloc(size_t nmemb, size_t size); ++size_t split(char *s, const char* sep, char **pbegin, size_t maxcount); +-- +2.44.0 + diff --git a/patches/dwm-swallow-6.3.diff b/patches/dwm-swallow-6.3.diff deleted file mode 100644 index 47586a0..0000000 --- a/patches/dwm-swallow-6.3.diff +++ /dev/null @@ -1,412 +0,0 @@ -From 0cf9a007511f7dfd7dd94171b172562ebac9b6d5 Mon Sep 17 00:00:00 2001 -From: Tom Schwindl <schwindl@posteo.de> -Date: Sat, 10 Sep 2022 12:51:09 +0200 -Subject: [PATCH] 6.3 swallow patch - ---- - config.def.h | 9 +- - config.mk | 3 +- - dwm.c | 235 +++++++++++++++++++++++++++++++++++++++++++++++++-- - 3 files changed, 237 insertions(+), 10 deletions(-) - -diff --git a/config.def.h b/config.def.h -index 061ad662f82a..0b2b8ffd30d5 100644 ---- a/config.def.h -+++ b/config.def.h -@@ -3,6 +3,7 @@ - /* appearance */ - static const unsigned int borderpx = 1; /* border pixel of windows */ - static const unsigned int snap = 32; /* snap pixel */ -+static const int swallowfloating = 0; /* 1 means swallow floating windows by default */ - static const int showbar = 1; /* 0 means no bar */ - static const int topbar = 1; /* 0 means bottom bar */ - static const char *fonts[] = { "monospace:size=10" }; -@@ -26,9 +27,11 @@ static const Rule rules[] = { - * WM_CLASS(STRING) = instance, class - * WM_NAME(STRING) = title - */ -- /* class instance title tags mask isfloating monitor */ -- { "Gimp", NULL, NULL, 0, 1, -1 }, -- { "Firefox", NULL, NULL, 1 << 8, 0, -1 }, -+ /* class instance title tags mask isfloating isterminal noswallow monitor */ -+ { "Gimp", NULL, NULL, 0, 1, 0, 0, -1 }, -+ { "Firefox", NULL, NULL, 1 << 8, 0, 0, -1, -1 }, -+ { "St", NULL, NULL, 0, 0, 1, 0, -1 }, -+ { NULL, NULL, "Event Tester", 0, 0, 0, 1, -1 }, /* xev */ - }; - - /* layout(s) */ -diff --git a/config.mk b/config.mk -index 81c493ef4aff..52d1ebf30bec 100644 ---- a/config.mk -+++ b/config.mk -@@ -20,10 +20,11 @@ FREETYPEINC = /usr/include/freetype2 - # OpenBSD (uncomment) - #FREETYPEINC = ${X11INC}/freetype2 - #MANPREFIX = ${PREFIX}/man -+#KVMLIB = -lkvm - - # includes and libs - INCS = -I${X11INC} -I${FREETYPEINC} --LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -+LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS} -lX11-xcb -lxcb -lxcb-res ${KVMLIB} - - # flags - CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L -DVERSION=\"${VERSION}\" ${XINERAMAFLAGS} -diff --git a/dwm.c b/dwm.c -index e5efb6a22806..e68294b6b679 100644 ---- a/dwm.c -+++ b/dwm.c -@@ -40,6 +40,12 @@ - #include <X11/extensions/Xinerama.h> - #endif /* XINERAMA */ - #include <X11/Xft/Xft.h> -+#include <X11/Xlib-xcb.h> -+#include <xcb/res.h> -+#ifdef __OpenBSD__ -+#include <sys/sysctl.h> -+#include <kvm.h> -+#endif /* __OpenBSD */ - - #include "drw.h" - #include "util.h" -@@ -92,9 +98,11 @@ struct Client { - int basew, baseh, incw, inch, maxw, maxh, minw, minh, hintsvalid; - int bw, oldbw; - unsigned int tags; -- int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen; -+ int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen, isterminal, noswallow; -+ pid_t pid; - Client *next; - Client *snext; -+ Client *swallowing; - Monitor *mon; - Window win; - }; -@@ -138,6 +146,8 @@ typedef struct { - const char *title; - unsigned int tags; - int isfloating; -+ int isterminal; -+ int noswallow; - int monitor; - } Rule; - -@@ -235,6 +245,12 @@ static int xerrordummy(Display *dpy, XErrorEvent *ee); - static int xerrorstart(Display *dpy, XErrorEvent *ee); - static void zoom(const Arg *arg); - -+static pid_t getparentprocess(pid_t p); -+static int isdescprocess(pid_t p, pid_t c); -+static Client *swallowingclient(Window w); -+static Client *termforwin(const Client *c); -+static pid_t winpid(Window w); -+ - /* variables */ - static const char broken[] = "broken"; - static char stext[256]; -@@ -269,6 +285,8 @@ static Drw *drw; - static Monitor *mons, *selmon; - static Window root, wmcheckwin; - -+static xcb_connection_t *xcon; -+ - /* configuration, allows nested code to access above variables */ - #include "config.h" - -@@ -298,6 +316,8 @@ applyrules(Client *c) - && (!r->class || strstr(class, r->class)) - && (!r->instance || strstr(instance, r->instance))) - { -+ c->isterminal = r->isterminal; -+ c->noswallow = r->noswallow; - c->isfloating = r->isfloating; - c->tags |= r->tags; - for (m = mons; m && m->num != r->monitor; m = m->next); -@@ -416,6 +436,53 @@ attachstack(Client *c) - c->mon->stack = c; - } - -+void -+swallow(Client *p, Client *c) -+{ -+ -+ if (c->noswallow || c->isterminal) -+ return; -+ if (c->noswallow && !swallowfloating && c->isfloating) -+ return; -+ -+ detach(c); -+ detachstack(c); -+ -+ setclientstate(c, WithdrawnState); -+ XUnmapWindow(dpy, p->win); -+ -+ p->swallowing = c; -+ c->mon = p->mon; -+ -+ Window w = p->win; -+ p->win = c->win; -+ c->win = w; -+ updatetitle(p); -+ XMoveResizeWindow(dpy, p->win, p->x, p->y, p->w, p->h); -+ arrange(p->mon); -+ configure(p); -+ updateclientlist(); -+} -+ -+void -+unswallow(Client *c) -+{ -+ c->win = c->swallowing->win; -+ -+ free(c->swallowing); -+ c->swallowing = NULL; -+ -+ /* unfullscreen the client */ -+ setfullscreen(c, 0); -+ updatetitle(c); -+ arrange(c->mon); -+ XMapWindow(dpy, c->win); -+ XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h); -+ setclientstate(c, NormalState); -+ focus(NULL); -+ arrange(c->mon); -+} -+ - void - buttonpress(XEvent *e) - { -@@ -656,6 +723,9 @@ destroynotify(XEvent *e) - - if ((c = wintoclient(ev->window))) - unmanage(c, 1); -+ -+ else if ((c = swallowingclient(ev->window))) -+ unmanage(c->swallowing, 1); - } - - void -@@ -1022,12 +1092,13 @@ killclient(const Arg *arg) - void - manage(Window w, XWindowAttributes *wa) - { -- Client *c, *t = NULL; -+ Client *c, *t = NULL, *term = NULL; - Window trans = None; - XWindowChanges wc; - - c = ecalloc(1, sizeof(Client)); - c->win = w; -+ c->pid = winpid(w); - /* geometry */ - c->x = c->oldx = wa->x; - c->y = c->oldy = wa->y; -@@ -1042,6 +1113,7 @@ manage(Window w, XWindowAttributes *wa) - } else { - c->mon = selmon; - applyrules(c); -+ term = termforwin(c); - } - - if (c->x + WIDTH(c) > c->mon->wx + c->mon->ww) -@@ -1076,6 +1148,8 @@ manage(Window w, XWindowAttributes *wa) - c->mon->sel = c; - arrange(c->mon); - XMapWindow(dpy, c->win); -+ if (term) -+ swallow(term, c); - focus(NULL); - } - -@@ -1763,6 +1837,20 @@ unmanage(Client *c, int destroyed) - Monitor *m = c->mon; - XWindowChanges wc; - -+ if (c->swallowing) { -+ unswallow(c); -+ return; -+ } -+ -+ Client *s = swallowingclient(c->win); -+ if (s) { -+ free(s->swallowing); -+ s->swallowing = NULL; -+ arrange(m); -+ focus(NULL); -+ return; -+ } -+ - detach(c); - detachstack(c); - if (!destroyed) { -@@ -1778,9 +1866,12 @@ unmanage(Client *c, int destroyed) - XUngrabServer(dpy); - } - free(c); -- focus(NULL); -- updateclientlist(); -- arrange(m); -+ -+ if (!s) { -+ arrange(m); -+ focus(NULL); -+ updateclientlist(); -+ } - } - - void -@@ -2044,6 +2135,136 @@ view(const Arg *arg) - arrange(selmon); - } - -+pid_t -+winpid(Window w) -+{ -+ -+ pid_t result = 0; -+ -+#ifdef __linux__ -+ xcb_res_client_id_spec_t spec = {0}; -+ spec.client = w; -+ spec.mask = XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID; -+ -+ xcb_generic_error_t *e = NULL; -+ xcb_res_query_client_ids_cookie_t c = xcb_res_query_client_ids(xcon, 1, &spec); -+ xcb_res_query_client_ids_reply_t *r = xcb_res_query_client_ids_reply(xcon, c, &e); -+ -+ if (!r) -+ return (pid_t)0; -+ -+ xcb_res_client_id_value_iterator_t i = xcb_res_query_client_ids_ids_iterator(r); -+ for (; i.rem; xcb_res_client_id_value_next(&i)) { -+ spec = i.data->spec; -+ if (spec.mask & XCB_RES_CLIENT_ID_MASK_LOCAL_CLIENT_PID) { -+ uint32_t *t = xcb_res_client_id_value_value(i.data); -+ result = *t; -+ break; -+ } -+ } -+ -+ free(r); -+ -+ if (result == (pid_t)-1) -+ result = 0; -+ -+#endif /* __linux__ */ -+ -+#ifdef __OpenBSD__ -+ Atom type; -+ int format; -+ unsigned long len, bytes; -+ unsigned char *prop; -+ pid_t ret; -+ -+ if (XGetWindowProperty(dpy, w, XInternAtom(dpy, "_NET_WM_PID", 0), 0, 1, False, AnyPropertyType, &type, &format, &len, &bytes, &prop) != Success || !prop) -+ return 0; -+ -+ ret = *(pid_t*)prop; -+ XFree(prop); -+ result = ret; -+ -+#endif /* __OpenBSD__ */ -+ return result; -+} -+ -+pid_t -+getparentprocess(pid_t p) -+{ -+ unsigned int v = 0; -+ -+#ifdef __linux__ -+ FILE *f; -+ char buf[256]; -+ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p); -+ -+ if (!(f = fopen(buf, "r"))) -+ return 0; -+ -+ fscanf(f, "%*u %*s %*c %u", &v); -+ fclose(f); -+#endif /* __linux__*/ -+ -+#ifdef __OpenBSD__ -+ int n; -+ kvm_t *kd; -+ struct kinfo_proc *kp; -+ -+ kd = kvm_openfiles(NULL, NULL, NULL, KVM_NO_FILES, NULL); -+ if (!kd) -+ return 0; -+ -+ kp = kvm_getprocs(kd, KERN_PROC_PID, p, sizeof(*kp), &n); -+ v = kp->p_ppid; -+#endif /* __OpenBSD__ */ -+ -+ return (pid_t)v; -+} -+ -+int -+isdescprocess(pid_t p, pid_t c) -+{ -+ while (p != c && c != 0) -+ c = getparentprocess(c); -+ -+ return (int)c; -+} -+ -+Client * -+termforwin(const Client *w) -+{ -+ Client *c; -+ Monitor *m; -+ -+ if (!w->pid || w->isterminal) -+ return NULL; -+ -+ for (m = mons; m; m = m->next) { -+ for (c = m->clients; c; c = c->next) { -+ if (c->isterminal && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid)) -+ return c; -+ } -+ } -+ -+ return NULL; -+} -+ -+Client * -+swallowingclient(Window w) -+{ -+ Client *c; -+ Monitor *m; -+ -+ for (m = mons; m; m = m->next) { -+ for (c = m->clients; c; c = c->next) { -+ if (c->swallowing && c->swallowing->win == w) -+ return c; -+ } -+ } -+ -+ return NULL; -+} -+ - Client * - wintoclient(Window w) - { -@@ -2133,10 +2354,12 @@ main(int argc, char *argv[]) - fputs("warning: no locale support\n", stderr); - if (!(dpy = XOpenDisplay(NULL))) - die("dwm: cannot open display"); -+ if (!(xcon = XGetXCBConnection(dpy))) -+ die("dwm: cannot get xcb connection\n"); - checkotherwm(); - setup(); - #ifdef __OpenBSD__ -- if (pledge("stdio rpath proc exec", NULL) == -1) -+ if (pledge("stdio rpath proc exec ps", NULL) == -1) - die("pledge"); - #endif /* __OpenBSD__ */ - scan(); --- -2.37.2 - @@ -25,6 +25,35 @@ die(const char *fmt, ...) exit(1); } +/* + * Splits a string into segments according to a separator. A '\0' is written to + * the end of every segment. The beginning of every segment is written to + * 'pbegin'. Only the first 'maxcount' segments will be written if + * maxcount > 0. Inspired by python's split. + * + * Used exclusively by fakesignal() to split arguments. + */ +size_t +split(char *s, const char* sep, char **pbegin, size_t maxcount) { + + char *p, *q; + const size_t seplen = strlen(sep); + size_t count = 0; + + maxcount = maxcount == 0 ? (size_t)-1 : maxcount; + p = s; + while ((q = strstr(p, sep)) != NULL && count < maxcount) { + pbegin[count] = p; + *q = '\0'; + p = q + seplen; + count++; + } + if (count < maxcount) { + pbegin[count] = p; + count++; + } + return count; +} void * ecalloc(size_t nmemb, size_t size) @@ -7,3 +7,4 @@ void die(const char *fmt, ...); void *ecalloc(size_t nmemb, size_t size); +size_t split(char *s, const char* sep, char **pbegin, size_t maxcount); |