dwm-dynamicswallow-20210221-61bb8b2.diff - sites - public wiki contents of suckless.org
(HTM) git clone git://git.suckless.org/sites
(DIR) Log
(DIR) Files
(DIR) 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