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