sites

public wiki contents of suckless.org
git clone git://git.suckless.org/sites
Log | Files | Refs

dwm-dynamicswallow-20210221-61bb8b2.diff (29490B)


      1 From ac7558081ea5f576ef2ed09c1817d2722baa92fd Mon Sep 17 00:00:00 2001
      2 From: =?UTF-8?q?Stanislaw=20H=C3=BCll?= <st@nislaw.de>
      3 Date: Sun, 21 Feb 2021 10:27:10 +0100
      4 Subject: [PATCH] dynamicswallow patch
      5 
      6 ---
      7  Makefile     |   3 +
      8  config.def.h |   7 +
      9  dwm.c        | 602 +++++++++++++++++++++++++++++++++++++++++++++++++--
     10  dwmswallow   | 120 ++++++++++
     11  util.c       |  30 +++
     12  util.h       |   1 +
     13  6 files changed, 740 insertions(+), 23 deletions(-)
     14  create mode 100755 dwmswallow
     15 
     16 diff --git a/Makefile b/Makefile
     17 index 77bcbc0..8bd79c8 100644
     18 --- a/Makefile
     19 +++ b/Makefile
     20 @@ -40,12 +40,15 @@ install: all
     21  	mkdir -p ${DESTDIR}${PREFIX}/bin
     22  	cp -f dwm ${DESTDIR}${PREFIX}/bin
     23  	chmod 755 ${DESTDIR}${PREFIX}/bin/dwm
     24 +	cp -f dwmswallow ${DESTDIR}${PREFIX}/bin
     25 +	chmod 755 ${DESTDIR}${PREFIX}/bin/dwmswallow
     26  	mkdir -p ${DESTDIR}${MANPREFIX}/man1
     27  	sed "s/VERSION/${VERSION}/g" < dwm.1 > ${DESTDIR}${MANPREFIX}/man1/dwm.1
     28  	chmod 644 ${DESTDIR}${MANPREFIX}/man1/dwm.1
     29  
     30  uninstall:
     31  	rm -f ${DESTDIR}${PREFIX}/bin/dwm\
     32 +		${DESTDIR}${MANPREFIX}/bin/dwmswallow\
     33  		${DESTDIR}${MANPREFIX}/man1/dwm.1
     34  
     35  .PHONY: all options clean dist install uninstall
     36 diff --git a/config.def.h b/config.def.h
     37 index 1c0b587..39e07a8 100644
     38 --- a/config.def.h
     39 +++ b/config.def.h
     40 @@ -31,6 +31,11 @@ static const Rule rules[] = {
     41  	{ "Firefox",  NULL,       NULL,       1 << 8,       0,           -1 },
     42  };
     43  
     44 +/* window swallowing */
     45 +static const int swaldecay = 3;
     46 +static const int swalretroactive = 1;
     47 +static const char swalsymbol[] = "👅";
     48 +
     49  /* layout(s) */
     50  static const float mfact     = 0.55; /* factor of master area size [0.05..0.95] */
     51  static const int nmaster     = 1;    /* number of clients in master area */
     52 @@ -84,6 +89,7 @@ static Key keys[] = {
     53  	{ MODKEY,                       XK_period, focusmon,       {.i = +1 } },
     54  	{ MODKEY|ShiftMask,             XK_comma,  tagmon,         {.i = -1 } },
     55  	{ MODKEY|ShiftMask,             XK_period, tagmon,         {.i = +1 } },
     56 +	{ MODKEY,                       XK_u,      swalstopsel,    {0} },
     57  	TAGKEYS(                        XK_1,                      0)
     58  	TAGKEYS(                        XK_2,                      1)
     59  	TAGKEYS(                        XK_3,                      2)
     60 @@ -107,6 +113,7 @@ static Button buttons[] = {
     61  	{ ClkClientWin,         MODKEY,         Button1,        movemouse,      {0} },
     62  	{ ClkClientWin,         MODKEY,         Button2,        togglefloating, {0} },
     63  	{ ClkClientWin,         MODKEY,         Button3,        resizemouse,    {0} },
     64 +	{ ClkClientWin,         MODKEY|ShiftMask, Button1,      swalmouse,      {0} },
     65  	{ ClkTagBar,            0,              Button1,        view,           {0} },
     66  	{ ClkTagBar,            0,              Button3,        toggleview,     {0} },
     67  	{ ClkTagBar,            MODKEY,         Button1,        tag,            {0} },
     68 diff --git a/dwm.c b/dwm.c
     69 index 664c527..390ef30 100644
     70 --- a/dwm.c
     71 +++ b/dwm.c
     72 @@ -58,7 +58,7 @@
     73  #define TEXTW(X)                (drw_fontset_getwidth(drw, (X)) + lrpad)
     74  
     75  /* enums */
     76 -enum { CurNormal, CurResize, CurMove, CurLast }; /* cursor */
     77 +enum { CurNormal, CurResize, CurMove, CurSwal, CurLast }; /* cursor */
     78  enum { SchemeNorm, SchemeSel }; /* color schemes */
     79  enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
     80         NetWMFullscreen, NetActiveWindow, NetWMWindowType,
     81 @@ -66,6 +66,7 @@ enum { NetSupported, NetWMName, NetWMState, NetWMCheck,
     82  enum { WMProtocols, WMDelete, WMState, WMTakeFocus, WMLast }; /* default atoms */
     83  enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle,
     84         ClkClientWin, ClkRootWin, ClkLast }; /* clicks */
     85 +enum { ClientRegular = 1, ClientSwallowee, ClientSwallower }; /* client types */
     86  
     87  typedef union {
     88  	int i;
     89 @@ -95,6 +96,7 @@ struct Client {
     90  	int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
     91  	Client *next;
     92  	Client *snext;
     93 +	Client *swallowedby;
     94  	Monitor *mon;
     95  	Window win;
     96  };
     97 @@ -141,6 +143,28 @@ typedef struct {
     98  	int monitor;
     99  } Rule;
    100  
    101 +typedef struct Swallow Swallow;
    102 +struct Swallow {
    103 +	/* Window class name, instance name (WM_CLASS) and title
    104 +	 * (WM_NAME/_NET_WM_NAME, latter preferred if it exists). An empty string
    105 +	 * implies a wildcard as per strstr(). */
    106 +	char class[256];
    107 +	char inst[256];
    108 +	char title[256];
    109 +
    110 +	/* Used to delete swallow instance after 'swaldecay' windows were mapped
    111 +	 * without the swallow having been consumed. 'decay' keeps track of the
    112 +	 * remaining "charges". */
    113 +	int decay;
    114 +
    115 +	/* The swallower, i.e. the client which will swallow the next mapped window
    116 +	 * whose filters match the above properties. */
    117 +	Client *client;
    118 +
    119 +	/* Linked list of registered swallow instances. */
    120 +	Swallow *next;
    121 +};
    122 +
    123  /* function declarations */
    124  static void applyrules(Client *c);
    125  static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int interact);
    126 @@ -165,6 +189,7 @@ static void drawbar(Monitor *m);
    127  static void drawbars(void);
    128  static void enternotify(XEvent *e);
    129  static void expose(XEvent *e);
    130 +static int fakesignal(void);
    131  static void focus(Client *c);
    132  static void focusin(XEvent *e);
    133  static void focusmon(const Arg *arg);
    134 @@ -207,6 +232,16 @@ static void seturgent(Client *c, int urg);
    135  static void showhide(Client *c);
    136  static void sigchld(int unused);
    137  static void spawn(const Arg *arg);
    138 +static void swal(Client *swer, Client *swee, int manage);
    139 +static void swalreg(Client *c, const char* class, const char* inst, const char* title);
    140 +static void swaldecayby(int decayby);
    141 +static void swalmanage(Swallow *s, Window w, XWindowAttributes *wa);
    142 +static Swallow *swalmatch(Window w);
    143 +static void swalmouse(const Arg *arg);
    144 +static void swalrm(Swallow *s);
    145 +static void swalunreg(Client *c);
    146 +static void swalstop(Client *c, Client *root);
    147 +static void swalstopsel(const Arg *unused);
    148  static void tag(const Arg *arg);
    149  static void tagmon(const Arg *arg);
    150  static void tile(Monitor *);
    151 @@ -229,6 +264,7 @@ static void updatewindowtype(Client *c);
    152  static void updatewmhints(Client *c);
    153  static void view(const Arg *arg);
    154  static Client *wintoclient(Window w);
    155 +static int wintoclient2(Window w, Client **pc, Client **proot);
    156  static Monitor *wintomon(Window w);
    157  static int xerror(Display *dpy, XErrorEvent *ee);
    158  static int xerrordummy(Display *dpy, XErrorEvent *ee);
    159 @@ -267,6 +303,7 @@ static Clr **scheme;
    160  static Display *dpy;
    161  static Drw *drw;
    162  static Monitor *mons, *selmon;
    163 +static Swallow *swallows;
    164  static Window root, wmcheckwin;
    165  
    166  /* configuration, allows nested code to access above variables */
    167 @@ -584,10 +621,12 @@ configurerequest(XEvent *e)
    168  	XConfigureRequestEvent *ev = &e->xconfigurerequest;
    169  	XWindowChanges wc;
    170  
    171 -	if ((c = wintoclient(ev->window))) {
    172 -		if (ev->value_mask & CWBorderWidth)
    173 +	switch (wintoclient2(ev->window, &c, NULL)) {
    174 +	case ClientRegular: /* fallthrough */
    175 +	case ClientSwallowee:
    176 +		if (ev->value_mask & CWBorderWidth) {
    177  			c->bw = ev->border_width;
    178 -		else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
    179 +		} else if (c->isfloating || !selmon->lt[selmon->sellt]->arrange) {
    180  			m = c->mon;
    181  			if (ev->value_mask & CWX) {
    182  				c->oldx = c->x;
    183 @@ -615,7 +654,13 @@ configurerequest(XEvent *e)
    184  				XMoveResizeWindow(dpy, c->win, c->x, c->y, c->w, c->h);
    185  		} else
    186  			configure(c);
    187 -	} else {
    188 +		break;
    189 +	case ClientSwallower:
    190 +		/* Reject any move/resize requests for swallowers and communicate
    191 +		 * refusal to client via a synthetic ConfigureNotify (ICCCM 4.1.5). */
    192 +		configure(c);
    193 +		break;
    194 +	default:
    195  		wc.x = ev->x;
    196  		wc.y = ev->y;
    197  		wc.width = ev->width;
    198 @@ -624,6 +669,7 @@ configurerequest(XEvent *e)
    199  		wc.sibling = ev->above;
    200  		wc.stack_mode = ev->detail;
    201  		XConfigureWindow(dpy, ev->window, ev->value_mask, &wc);
    202 +		break;
    203  	}
    204  	XSync(dpy, False);
    205  }
    206 @@ -648,11 +694,30 @@ createmon(void)
    207  void
    208  destroynotify(XEvent *e)
    209  {
    210 -	Client *c;
    211 +	Client *c, *swee, *root;
    212  	XDestroyWindowEvent *ev = &e->xdestroywindow;
    213  
    214 -	if ((c = wintoclient(ev->window)))
    215 +	switch (wintoclient2(ev->window, &c, &root)) {
    216 +	case ClientRegular:
    217 +		unmanage(c, 1);
    218 +		break;
    219 +	case ClientSwallowee:
    220 +		swalstop(c, NULL);
    221  		unmanage(c, 1);
    222 +		break;
    223 +	case ClientSwallower:
    224 +		/* If the swallower is swallowed by another client, terminate the
    225 +		 * swallow. This cuts off the swallow chain after the client. */
    226 +		swalstop(c, root);
    227 +
    228 +		/* Cut off the swallow chain before the client. */
    229 +		for (swee = root; swee->swallowedby != c; swee = swee->swallowedby);
    230 +		swee->swallowedby = NULL;
    231 +
    232 +		free(c);
    233 +		updateclientlist();
    234 +		break;
    235 +	}
    236  }
    237  
    238  void
    239 @@ -729,6 +794,12 @@ drawbar(Monitor *m)
    240  	drw_setscheme(drw, scheme[SchemeNorm]);
    241  	x = drw_text(drw, x, 0, w, bh, lrpad / 2, m->ltsymbol, 0);
    242  
    243 +	/* Draw swalsymbol next to ltsymbol. */
    244 +	if (m->sel && m->sel->swallowedby) {
    245 +		w = TEXTW(swalsymbol);
    246 +		x = drw_text(drw, x, 0, w, bh, lrpad / 2, swalsymbol, 0);
    247 +	}
    248 +
    249  	if ((w = m->ww - tw - x) > bh) {
    250  		if (m->sel) {
    251  			drw_setscheme(drw, scheme[m == selmon ? SchemeSel : SchemeNorm]);
    252 @@ -781,6 +852,81 @@ expose(XEvent *e)
    253  		drawbar(m);
    254  }
    255  
    256 +int
    257 +fakesignal(void)
    258 +{
    259 +	/* Command syntax: <PREFIX><COMMAND>[<SEP><ARG>]... */
    260 +	static const char sep[] = "###";
    261 +	static const char prefix[] = "#!";
    262 +
    263 +	size_t numsegments, numargs;
    264 +	char rootname[256];
    265 +	char *segments[16] = {0};
    266 +
    267 +	/* Get root name, split by separator and find the prefix */
    268 +	if (!gettextprop(root, XA_WM_NAME, rootname, sizeof(rootname))
    269 +		|| strncmp(rootname, prefix, sizeof(prefix) - 1)) {
    270 +		return 0;
    271 +	}
    272 +	numsegments = split(rootname + sizeof(prefix) - 1, sep, segments, sizeof(segments));
    273 +	numargs = numsegments - 1; /* number of arguments to COMMAND */
    274 +
    275 +	if (!strcmp(segments[0], "swalreg")) {
    276 +		/* Params: windowid, [class], [instance], [title] */
    277 +		Window w;
    278 +		Client *c;
    279 +
    280 +		if (numargs >= 1) {
    281 +			w = strtoul(segments[1], NULL, 0);
    282 +			switch (wintoclient2(w, &c, NULL)) {
    283 +			case ClientRegular: /* fallthrough */
    284 +			case ClientSwallowee:
    285 +				swalreg(c, segments[2], segments[3], segments[4]);
    286 +				break;
    287 +			}
    288 +		}
    289 +	}
    290 +	else if (!strcmp(segments[0], "swal")) {
    291 +		/* Params: swallower's windowid, swallowee's window-id */
    292 +		Client *swer, *swee;
    293 +		Window winswer, winswee;
    294 +		int typeswer, typeswee;
    295 +
    296 +		if (numargs >= 2) {
    297 +			winswer = strtoul(segments[1], NULL, 0);
    298 +			typeswer = wintoclient2(winswer, &swer, NULL);
    299 +			winswee = strtoul(segments[2], NULL, 0);
    300 +			typeswee = wintoclient2(winswee, &swee, NULL);
    301 +			if ((typeswer == ClientRegular || typeswer == ClientSwallowee)
    302 +				&& (typeswee == ClientRegular || typeswee == ClientSwallowee))
    303 +				swal(swer, swee, 0);
    304 +		}
    305 +	}
    306 +	else if (!strcmp(segments[0], "swalunreg")) {
    307 +		/* Params: swallower's windowid */
    308 +		Client *swer;
    309 +		Window winswer;
    310 +
    311 +		if (numargs == 1) {
    312 +			winswer = strtoul(segments[1], NULL, 0);
    313 +			if ((swer = wintoclient(winswer)))
    314 +				swalunreg(swer);
    315 +		}
    316 +	}
    317 +	else if (!strcmp(segments[0], "swalstop")) {
    318 +		/* Params: swallowee's windowid */
    319 +		Client *swee;
    320 +		Window winswee;
    321 +
    322 +		if (numargs == 1) {
    323 +			winswee = strtoul(segments[1], NULL, 0);
    324 +			if ((swee = wintoclient(winswee)))
    325 +				swalstop(swee, NULL);
    326 +		}
    327 +	}
    328 +	return 1;
    329 +}
    330 +
    331  void
    332  focus(Client *c)
    333  {
    334 @@ -1090,15 +1236,37 @@ mappingnotify(XEvent *e)
    335  void
    336  maprequest(XEvent *e)
    337  {
    338 +	Client *c, *swee, *root;
    339  	static XWindowAttributes wa;
    340  	XMapRequestEvent *ev = &e->xmaprequest;
    341 +	Swallow *s;
    342  
    343  	if (!XGetWindowAttributes(dpy, ev->window, &wa))
    344  		return;
    345  	if (wa.override_redirect)
    346  		return;
    347 -	if (!wintoclient(ev->window))
    348 -		manage(ev->window, &wa);
    349 +	switch (wintoclient2(ev->window, &c, &root)) {
    350 +	case ClientRegular: /* fallthrough */
    351 +	case ClientSwallowee:
    352 +		/* Regulars and swallowees are always mapped. Nothing to do. */
    353 +		break;
    354 +	case ClientSwallower:
    355 +		/* Remapping a swallower will simply stop the swallow. */
    356 +		for (swee = root; swee->swallowedby != c; swee = swee->swallowedby);
    357 +		swalstop(swee, root);
    358 +		break;
    359 +	default:
    360 +		/* No client is managing the window. See if any swallows match. */
    361 +		if ((s = swalmatch(ev->window)))
    362 +			swalmanage(s, ev->window, &wa);
    363 +		else
    364 +			manage(ev->window, &wa);
    365 +		break;
    366 +	}
    367 +
    368 +	/* Reduce decay counter of all swallow instances. */
    369 +	if (swaldecay)
    370 +		swaldecayby(1);
    371  }
    372  
    373  void
    374 @@ -1214,11 +1382,13 @@ propertynotify(XEvent *e)
    375  {
    376  	Client *c;
    377  	Window trans;
    378 +	Swallow *s;
    379  	XPropertyEvent *ev = &e->xproperty;
    380  
    381 -	if ((ev->window == root) && (ev->atom == XA_WM_NAME))
    382 -		updatestatus();
    383 -	else if (ev->state == PropertyDelete)
    384 +	if ((ev->window == root) && (ev->atom == XA_WM_NAME)) {
    385 +		if (!fakesignal())
    386 +			updatestatus();
    387 +	} else if (ev->state == PropertyDelete)
    388  		return; /* ignore */
    389  	else if ((c = wintoclient(ev->window))) {
    390  		switch(ev->atom) {
    391 @@ -1240,6 +1410,9 @@ propertynotify(XEvent *e)
    392  			updatetitle(c);
    393  			if (c == c->mon->sel)
    394  				drawbar(c->mon);
    395 +			if (swalretroactive && (s = swalmatch(c->win))) {
    396 +				swal(s->client, c, 0);
    397 +			}
    398  		}
    399  		if (ev->atom == netatom[NetWMWindowType])
    400  			updatewindowtype(c);
    401 @@ -1567,6 +1740,7 @@ setup(void)
    402  	cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
    403  	cursor[CurResize] = drw_cur_create(drw, XC_sizing);
    404  	cursor[CurMove] = drw_cur_create(drw, XC_fleur);
    405 +	cursor[CurSwal] = drw_cur_create(drw, XC_bottom_side);
    406  	/* init appearance */
    407  	scheme = ecalloc(LENGTH(colors), sizeof(Clr *));
    408  	for (i = 0; i < LENGTH(colors); i++)
    409 @@ -1653,6 +1827,331 @@ spawn(const Arg *arg)
    410  	}
    411  }
    412  
    413 +/*
    414 + * Perform immediate swallow of client 'swee' by client 'swer'. 'manage' shall
    415 + * be set if swal() is called from swalmanage(). 'swer' and 'swee' must be
    416 + * regular or swallowee, but not swallower.
    417 + */
    418 +void
    419 +swal(Client *swer, Client *swee, int manage)
    420 +{
    421 +	Client *c, **pc;
    422 +	int sweefocused = selmon->sel == swee;
    423 +
    424 +	/* Remove any swallows registered for the swer. Asking a swallower to
    425 +	 * swallow another window is ambiguous and is thus avoided altogether. In
    426 +	 * contrast, a swallowee can swallow in a well-defined manner by attaching
    427 +	 * to the head of the swallow chain. */
    428 +	if (!manage)
    429 +		swalunreg(swer);
    430 +
    431 +	/* Disable fullscreen prior to swallow. Swallows involving fullscreen
    432 +	 * windows produces quirky artefacts such as fullscreen terminals or tiled
    433 +	 * pseudo-fullscreen windows. */
    434 +	setfullscreen(swer, 0);
    435 +	setfullscreen(swee, 0);
    436 +
    437 +	/* Swap swallowee into client and focus lists. Keeps current focus unless
    438 +	 * the swer (which gets unmapped) is focused in which case the swee will
    439 +	 * receive focus. */
    440 +	detach(swee);
    441 +	for (pc = &swer->mon->clients; *pc && *pc != swer; pc = &(*pc)->next);
    442 +	*pc = swee;
    443 +	swee->next = swer->next;
    444 +	detachstack(swee);
    445 +	for (pc = &swer->mon->stack; *pc && *pc != swer; pc = &(*pc)->snext);
    446 +	*pc = swee;
    447 +	swee->snext = swer->snext;
    448 +	swee->mon = swer->mon;
    449 +	if (sweefocused) {
    450 +		detachstack(swee);
    451 +		attachstack(swee);
    452 +		selmon = swer->mon;
    453 +	}
    454 +	swee->tags = swer->tags;
    455 +	swee->isfloating = swer->isfloating;
    456 +	for (c = swee; c->swallowedby; c = c->swallowedby);
    457 +	c->swallowedby = swer;
    458 +
    459 +	/* Configure geometry params obtained from patches (e.g. cfacts) here. */
    460 +	// swee->cfact = swer->cfact;
    461 +
    462 +	/* ICCCM 4.1.3.1 */
    463 +	setclientstate(swer, WithdrawnState);
    464 +	if (manage)
    465 +		setclientstate(swee, NormalState);
    466 +
    467 +	if (swee->isfloating || !swee->mon->lt[swee->mon->sellt]->arrange)
    468 +		XRaiseWindow(dpy, swee->win);
    469 +	resize(swee, swer->x, swer->y, swer->w, swer->h, 0);
    470 +
    471 +	focus(NULL);
    472 +	arrange(NULL);
    473 +	if (manage)
    474 +		XMapWindow(dpy, swee->win);
    475 +	XUnmapWindow(dpy, swer->win);
    476 +	restack(swer->mon);
    477 +}
    478 +
    479 +/*
    480 + * Register a future swallow with swallower. 'c' 'class', 'inst' and 'title'
    481 + * shall point null-terminated strings or be NULL, implying a wildcard. If an
    482 + * already existing swallow instance targets 'c' its filters are updated and no
    483 + * new swallow instance is created. 'c' may be ClientRegular or ClientSwallowee.
    484 + * Complement to swalrm().
    485 + */
    486 +void swalreg(Client *c, const char *class, const char *inst, const char *title)
    487 +{
    488 +	Swallow *s;
    489 +
    490 +	if (!c)
    491 +		return;
    492 +
    493 +	for (s = swallows; s; s = s->next) {
    494 +		if (s->client == c) {
    495 +			if (class)
    496 +				strncpy(s->class, class, sizeof(s->class) - 1);
    497 +			else
    498 +				s->class[0] = '\0';
    499 +			if (inst)
    500 +				strncpy(s->inst, inst, sizeof(s->inst) - 1);
    501 +			else
    502 +				s->inst[0] = '\0';
    503 +			if (title)
    504 +				strncpy(s->title, title, sizeof(s->title) - 1);
    505 +			else
    506 +				s->title[0] = '\0';
    507 +			s->decay = swaldecay;
    508 +
    509 +			/* Only one swallow per client. May return after first hit. */
    510 +			return;
    511 +		}
    512 +	}
    513 +
    514 +	s = ecalloc(1, sizeof(Swallow));
    515 +	s->decay = swaldecay;
    516 +	s->client = c;
    517 +	if (class)
    518 +		strncpy(s->class, class, sizeof(s->class) - 1);
    519 +	if (inst)
    520 +		strncpy(s->inst, inst, sizeof(s->inst) - 1);
    521 +	if (title)
    522 +		strncpy(s->title, title, sizeof(s->title) - 1);
    523 +
    524 +	s->next = swallows;
    525 +	swallows = s;
    526 +}
    527 +
    528 +/*
    529 + * Decrease decay counter of all registered swallows by 'decayby' and remove any
    530 + * swallow instances whose counter is less than or equal to zero.
    531 + */
    532 +void
    533 +swaldecayby(int decayby)
    534 +{
    535 +	Swallow *s, *t;
    536 +
    537 +	for (s = swallows; s; s = t) {
    538 +		s->decay -= decayby;
    539 +		t = s->next;
    540 +		if (s->decay <= 0)
    541 +			swalrm(s);
    542 +	}
    543 +}
    544 +
    545 +/*
    546 + * Window configuration and client setup for new windows which are to be
    547 + * swallowed immediately. Pendant to manage() for such windows.
    548 + */
    549 +void
    550 +swalmanage(Swallow *s, Window w, XWindowAttributes *wa)
    551 +{
    552 +	Client *swee, *swer;
    553 +	XWindowChanges wc;
    554 +
    555 +	swer = s->client;
    556 +	swalrm(s);
    557 +
    558 +	/* Perform bare minimum setup of a client for window 'w' such that swal()
    559 +	 * may be used to perform the swallow. The following lines are basically a
    560 +	 * minimal implementation of manage() with a few chunks delegated to
    561 +	 * swal(). */
    562 +	swee = ecalloc(1, sizeof(Client));
    563 +	swee->win = w;
    564 +	swee->mon = swer->mon;
    565 +	swee->oldbw = wa->border_width;
    566 +	swee->bw = borderpx;
    567 +	attach(swee);
    568 +	attachstack(swee);
    569 +	updatetitle(swee);
    570 +	updatesizehints(swee);
    571 +	XSelectInput(dpy, swee->win, EnterWindowMask|FocusChangeMask|PropertyChangeMask|StructureNotifyMask);
    572 +	wc.border_width = swee->bw;
    573 +	XConfigureWindow(dpy, swee->win, CWBorderWidth, &wc);
    574 +	grabbuttons(swee, 0);
    575 +	XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, PropModeAppend,
    576 +		(unsigned char *) &(swee->win), 1);
    577 +
    578 +	swal(swer, swee, 1);
    579 +}
    580 +
    581 +/*
    582 + * Return swallow instance which targets window 'w' as determined by its class
    583 + * name, instance name and window title. Returns NULL if none is found. Pendant
    584 + * to wintoclient().
    585 + */
    586 +Swallow *
    587 +swalmatch(Window w)
    588 +{
    589 +	XClassHint ch = { NULL, NULL };
    590 +	Swallow *s = NULL;
    591 +	char title[sizeof(s->title)];
    592 +
    593 +	XGetClassHint(dpy, w, &ch);
    594 +	if (!gettextprop(w, netatom[NetWMName], title, sizeof(title)))
    595 +		gettextprop(w, XA_WM_NAME, title, sizeof(title));
    596 +
    597 +	for (s = swallows; s; s = s->next) {
    598 +		if ((!ch.res_class || strstr(ch.res_class, s->class))
    599 +			&& (!ch.res_name || strstr(ch.res_name, s->inst))
    600 +			&& (title[0] == '\0' || strstr(title, s->title)))
    601 +			break;
    602 +	}
    603 +
    604 +	if (ch.res_class)
    605 +		XFree(ch.res_class);
    606 +	if (ch.res_name)
    607 +		XFree(ch.res_name);
    608 +	return s;
    609 +}
    610 +
    611 +/*
    612 + * Interactive drag-and-drop swallow.
    613 + */
    614 +void
    615 +swalmouse(const Arg *arg)
    616 +{
    617 +	Client *swer, *swee;
    618 +	XEvent ev;
    619 +
    620 +	if (!(swee = selmon->sel))
    621 +		return;
    622 +
    623 +	if (XGrabPointer(dpy, root, False, ButtonPressMask|ButtonReleaseMask, GrabModeAsync,
    624 +		GrabModeAsync, None, cursor[CurSwal]->cursor, CurrentTime) != GrabSuccess)
    625 +		return;
    626 +
    627 +	do {
    628 +		XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedirectMask, &ev);
    629 +		switch(ev.type) {
    630 +		case ConfigureRequest: /* fallthrough */
    631 +		case Expose: /* fallthrough */
    632 +		case MapRequest:
    633 +			handler[ev.type](&ev);
    634 +			break;
    635 +		}
    636 +	} while (ev.type != ButtonRelease);
    637 +	XUngrabPointer(dpy, CurrentTime);
    638 +
    639 +	if ((swer = wintoclient(ev.xbutton.subwindow))
    640 +		&& swer != swee)
    641 +		swal(swer, swee, 0);
    642 +
    643 +	/* Remove accumulated pending EnterWindow events caused by the mouse
    644 +	 * movements. */
    645 +	XCheckMaskEvent(dpy, EnterWindowMask, &ev);
    646 +}
    647 +
    648 +/*
    649 + * Delete swallow instance swallows and free its resources. Complement to
    650 + * swalreg(). If NULL is passed all swallows are deleted from.
    651 + */
    652 +void
    653 +swalrm(Swallow *s)
    654 +{
    655 +	Swallow *t, **ps;
    656 +
    657 +	if (s) {
    658 +		for (ps = &swallows; *ps && *ps != s; ps = &(*ps)->next);
    659 +		*ps = s->next;
    660 +		free(s);
    661 +	}
    662 +	else {
    663 +		for(s = swallows; s; s = t) {
    664 +			t = s->next;
    665 +			free(s);
    666 +		}
    667 +		swallows = NULL;
    668 +	}
    669 +}
    670 +
    671 +/*
    672 + * Removes swallow instance targeting 'c' if it exists. Complement to swalreg().
    673 + */
    674 +void swalunreg(Client *c) { Swallow *s;
    675 +
    676 +	for (s = swallows; s; s = s->next) {
    677 +		if (c == s->client) {
    678 +			swalrm(s);
    679 +			/* Max. 1 registered swallow per client. No need to continue. */
    680 +			break;
    681 +		}
    682 +	}
    683 +}
    684 +
    685 +/*
    686 + * Stop an active swallow of swallowed client 'swee' and remap the swallower.
    687 + * If 'swee' is a swallower itself 'root' must point the root client of the
    688 + * swallow chain containing 'swee'.
    689 + */
    690 +void
    691 +swalstop(Client *swee, Client *root)
    692 +{
    693 +	Client *swer;
    694 +
    695 +	if (!swee || !(swer = swee->swallowedby))
    696 +		return;
    697 +
    698 +	swee->swallowedby = NULL;
    699 +	root = root ? root : swee;
    700 +	swer->mon = root->mon;
    701 +	swer->tags = root->tags;
    702 +	swer->next = root->next;
    703 +	root->next = swer;
    704 +	swer->snext = root->snext;
    705 +	root->snext = swer;
    706 +	swer->isfloating = swee->isfloating;
    707 +
    708 +	/* Configure geometry params obtained from patches (e.g. cfacts) here. */
    709 +	// swer->cfact = 1.0;
    710 +
    711 +	/* If swer is not in tiling mode reuse swee's geometry. */
    712 +	if (swer->isfloating || !root->mon->lt[root->mon->sellt]->arrange) {
    713 +		XRaiseWindow(dpy, swer->win);
    714 +		resize(swer, swee->x, swee->y, swee->w, swee->h, 0);
    715 +	}
    716 +
    717 +	/* Override swer's border scheme which may be using SchemeSel. */
    718 +	XSetWindowBorder(dpy, swer->win, scheme[SchemeNorm][ColBorder].pixel);
    719 +
    720 +	/* ICCCM 4.1.3.1 */
    721 +	setclientstate(swer, NormalState);
    722 +
    723 +	XMapWindow(dpy, swer->win);
    724 +	focus(NULL);
    725 +	arrange(swer->mon);
    726 +}
    727 +
    728 +/*
    729 + * Stop active swallow for currently selected client.
    730 + */
    731 +void
    732 +swalstopsel(const Arg *unused)
    733 +{
    734 +	if (selmon->sel)
    735 +		swalstop(selmon->sel, NULL);
    736 +}
    737 +
    738  void
    739  tag(const Arg *arg)
    740  {
    741 @@ -1768,6 +2267,9 @@ unmanage(Client *c, int destroyed)
    742  	Monitor *m = c->mon;
    743  	XWindowChanges wc;
    744  
    745 +	/* Remove all swallow instances targeting client. */
    746 +	swalunreg(c);
    747 +
    748  	detach(c);
    749  	detachstack(c);
    750  	if (!destroyed) {
    751 @@ -1790,14 +2292,27 @@ unmanage(Client *c, int destroyed)
    752  void
    753  unmapnotify(XEvent *e)
    754  {
    755 +
    756  	Client *c;
    757  	XUnmapEvent *ev = &e->xunmap;
    758 +	int type;
    759  
    760 -	if ((c = wintoclient(ev->window))) {
    761 -		if (ev->send_event)
    762 -			setclientstate(c, WithdrawnState);
    763 -		else
    764 -			unmanage(c, 0);
    765 +	type = wintoclient2(ev->window, &c, NULL);
    766 +	if (type && ev->send_event) {
    767 +		setclientstate(c, WithdrawnState);
    768 +		return;
    769 +	}
    770 +	switch (type) {
    771 +	case ClientRegular:
    772 +		unmanage(c, 0);
    773 +		break;
    774 +	case ClientSwallowee:
    775 +		swalstop(c, NULL);
    776 +		unmanage(c, 0);
    777 +		break;
    778 +	case ClientSwallower:
    779 +		/* Swallowers are never mapped. Nothing to do. */
    780 +		break;
    781  	}
    782  }
    783  
    784 @@ -1839,15 +2354,19 @@ updatebarpos(Monitor *m)
    785  void
    786  updateclientlist()
    787  {
    788 -	Client *c;
    789 +	Client *c, *d;
    790  	Monitor *m;
    791  
    792  	XDeleteProperty(dpy, root, netatom[NetClientList]);
    793 -	for (m = mons; m; m = m->next)
    794 -		for (c = m->clients; c; c = c->next)
    795 -			XChangeProperty(dpy, root, netatom[NetClientList],
    796 -				XA_WINDOW, 32, PropModeAppend,
    797 -				(unsigned char *) &(c->win), 1);
    798 +	for (m = mons; m; m = m->next) {
    799 +		for (c = m->clients; c; c = c->next) {
    800 +			for (d = c; d; d = d->swallowedby) {
    801 +				XChangeProperty(dpy, root, netatom[NetClientList],
    802 +					XA_WINDOW, 32, PropModeAppend,
    803 +					(unsigned char *) &(c->win), 1);
    804 +			}
    805 +		}
    806 +	}
    807  }
    808  
    809  int
    810 @@ -2060,6 +2579,43 @@ wintoclient(Window w)
    811  	return NULL;
    812  }
    813  
    814 +/*
    815 + * Writes client managing window 'w' into 'pc' and returns type of client. If
    816 + * no client is found NULL is written to 'pc' and zero is returned. If a client
    817 + * is found and is a swallower (ClientSwallower) and proot is not NULL the root
    818 + * client of the swallow chain is written to 'proot'.
    819 + */
    820 +int
    821 +wintoclient2(Window w, Client **pc, Client **proot)
    822 +{
    823 +	Monitor *m;
    824 +	Client *c, *d;
    825 +
    826 +	for (m = mons; m; m = m->next) {
    827 +		for (c = m->clients; c; c = c->next) {
    828 +			if (c->win == w) {
    829 +				*pc = c;
    830 +				if (c->swallowedby)
    831 +					return ClientSwallowee;
    832 +				else
    833 +					return ClientRegular;
    834 +			}
    835 +			else {
    836 +				for (d = c->swallowedby; d; d = d->swallowedby) {
    837 +					if (d->win == w) {
    838 +						if (proot)
    839 +							*proot = c;
    840 +						*pc = d;
    841 +						return ClientSwallower;
    842 +					}
    843 +				}
    844 +			}
    845 +		}
    846 +	}
    847 +	*pc = NULL;
    848 +	return 0;
    849 +}
    850 +
    851  Monitor *
    852  wintomon(Window w)
    853  {
    854 diff --git a/dwmswallow b/dwmswallow
    855 new file mode 100755
    856 index 0000000..9400606
    857 --- /dev/null
    858 +++ b/dwmswallow
    859 @@ -0,0 +1,120 @@
    860 +#!/usr/bin/env sh
    861 +
    862 +# Separator and command prefix, as defined in dwm.c:fakesignal()
    863 +SEP='###'
    864 +PREFIX='#!'
    865 +
    866 +# Asserts that all arguments are valid X11 window IDs, i.e. positive integers.
    867 +# For the purpose of this script 0 is declared invalid aswe
    868 +is_winid() {
    869 +	while :; do
    870 +		# Given input incompatible to %d, some implementations of printf return
    871 +		# an error while others silently evaluate the expression to 0.
    872 +		if ! wid=$(printf '%d' "$1" 2>/dev/null) || [ "$wid" -le 0 ]; then
    873 +			return 1
    874 +		fi
    875 +
    876 +		[ -n "$2" ] && shift || break
    877 +	done
    878 +}
    879 +
    880 +# Prints usage help. If "$1" is provided, function exits script after
    881 +# execution.
    882 +usage() {
    883 +	[ -t 1 ] && myprintf=printf || myprintf=true
    884 +	msg="$(cat <<-EOF
    885 +	dwm window swallowing command-line interface. Usage:
    886 +
    887 +	  $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER [-c CLASS] [-i INSTANCE] [-t TITLE]$($myprintf "\033[0m")
    888 +	    Register window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m") to swallow the next future window whose attributes
    889 +	    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
    890 +	    string-matching. An omitted filter will match anything.
    891 +
    892 +	  $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER -d$($myprintf "\033[0m")
    893 +	    Deregister queued swallow for window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m"). Inverse of above signature.
    894 +
    895 +	  $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWER SWALLOWEE$($myprintf "\033[0m")
    896 +	    Perform immediate swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m") by window $($myprintf "\033[3m")SWALLOWER$($myprintf "\033[0m").
    897 +
    898 +	  $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLOWEE -s$($myprintf "\033[0m")
    899 +	    Stop swallow of window $($myprintf "\033[3m")SWALLOWEE$($myprintf "\033[0m"). Inverse of the above signature. Visible
    900 +	    windows only.
    901 +
    902 +	  $($myprintf "\033[1m")dwmswallow -h$($myprintf "\033[0m")
    903 +	    Show this usage information.
    904 +	EOF
    905 +	)"
    906 +
    907 +	if [ -n "$1" ]; then
    908 +		echo "$msg" >&2
    909 +		exit "$1"
    910 +	else
    911 +		echo "$msg"
    912 +	fi
    913 +}
    914 +
    915 +# Determine number of leading positional arguments
    916 +arg1="$1" # save for later
    917 +arg2="$2" # save for later
    918 +num_pargs=0
    919 +while :; do
    920 +	case "$1" in
    921 +	-*|"") break ;;
    922 +	*) num_pargs=$((num_pargs + 1)); shift ;;
    923 +	esac
    924 +done
    925 +
    926 +case "$num_pargs" in
    927 +1)
    928 +	! is_winid "$arg1" && usage 1
    929 +
    930 +	widswer="$arg1"
    931 +	if [ "$1" = "-d" ] && [ "$#" -eq 1 ]; then
    932 +		if name="$(printf "${PREFIX}swalunreg${SEP}%u" "$widswer" 2>/dev/null)"; then
    933 +			xsetroot -name "$name"
    934 +		else
    935 +			usage 1
    936 +		fi
    937 +	elif [ "$1" = "-s" ] && [ "$#" -eq 1 ]; then
    938 +		widswee="$arg1"
    939 +		if name="$(printf "${PREFIX}swalstop${SEP}%u" "$widswee" 2>/dev/null)"; then
    940 +			xsetroot -name "$name"
    941 +		else
    942 +			usage 1
    943 +		fi
    944 +	else
    945 +		while :; do
    946 +			case "$1" in
    947 +			-c) [ -n "$2" ] && { class="$2"; shift 2; } || usage 1 ;;
    948 +			-i) [ -n "$2" ] && { instance="$2"; shift 2; } || usage 1 ;;
    949 +			-t) [ -n "$2" ] && { title="$2"; shift 2; } || usage 1 ;;
    950 +			"") break ;;
    951 +			*)	usage 1 ;;
    952 +			esac
    953 +		done
    954 +		widswer="$arg1"
    955 +		if name="$(printf "${PREFIX}swalreg${SEP}%u${SEP}%s${SEP}%s${SEP}%s" "$widswer" "$class" "$instance" "$title" 2>/dev/null)"; then
    956 +			xsetroot -name "$name"
    957 +		else
    958 +			usage 1
    959 +		fi
    960 +	fi
    961 +	;;
    962 +2)
    963 +	! is_winid "$arg1" "$arg2" || [ -n "$1" ] && usage 1
    964 +
    965 +	widswer="$arg1"
    966 +	widswee="$arg2"
    967 +	if name="$(printf "${PREFIX}swal${SEP}%u${SEP}%u" "$widswer" "$widswee" 2>/dev/null)"; then
    968 +		xsetroot -name "$name"
    969 +	else
    970 +		usage 1
    971 +	fi
    972 +	;;
    973 +*)
    974 +	if [ "$arg1" = "-h" ] && [ $# -eq 1 ]; then
    975 +		usage
    976 +	else
    977 +		usage 1
    978 +	fi
    979 +esac
    980 diff --git a/util.c b/util.c
    981 index fe044fc..1f51877 100644
    982 --- a/util.c
    983 +++ b/util.c
    984 @@ -33,3 +33,33 @@ die(const char *fmt, ...) {
    985  
    986  	exit(1);
    987  }
    988 +
    989 +/*
    990 + * Splits a string into segments according to a separator. A '\0' is written to
    991 + * the end of every segment. The beginning of every segment is written to
    992 + * 'pbegin'. Only the first 'maxcount' segments will be written if
    993 + * maxcount > 0. Inspired by python's split.
    994 + *
    995 + * Used exclusively by fakesignal() to split arguments.
    996 + */
    997 +size_t
    998 +split(char *s, const char* sep, char **pbegin, size_t maxcount) {
    999 +
   1000 +	char *p, *q;
   1001 +	const size_t seplen = strlen(sep);
   1002 +	size_t count = 0;
   1003 +
   1004 +	maxcount = maxcount == 0 ? (size_t)-1 : maxcount;
   1005 +	p = s;
   1006 +	while ((q = strstr(p, sep)) != NULL && count < maxcount) {
   1007 +		pbegin[count] = p;
   1008 +		*q = '\0';
   1009 +		p = q + seplen;
   1010 +		count++;
   1011 +	}
   1012 +	if (count < maxcount) {
   1013 +		pbegin[count] = p;
   1014 +		count++;
   1015 +	}
   1016 +	return count;
   1017 +}
   1018 diff --git a/util.h b/util.h
   1019 index f633b51..670345f 100644
   1020 --- a/util.h
   1021 +++ b/util.h
   1022 @@ -6,3 +6,4 @@
   1023  
   1024  void die(const char *fmt, ...);
   1025  void *ecalloc(size_t nmemb, size_t size);
   1026 +size_t split(char *s, const char* sep, char **pbegin, size_t maxcount);
   1027 -- 
   1028 2.25.1
   1029