sites

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

dwm-dynamicswallow-6.4.diff (29049B)


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