dwm-dynamicswallow-20240320-061e9fe.diff - sites - public wiki contents of suck… | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
dwm-dynamicswallow-20240320-061e9fe.diff (29463B) | |
--- | |
1 From 20175007156c243ee577e0aa518a05282950e766 Mon Sep 17 00:00:00 2001 | |
2 From: visil <[email protected]> | |
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}/m… | |
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, … | |
43 { "Firefox", NULL, NULL, 1 << 8, 0, … | |
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.0… | |
52 @@ -85,6 +89,7 @@ static const Key keys[] = { | |
53 { MODKEY, XK_period, focusmon, {.i … | |
54 { MODKEY|ShiftMask, XK_comma, tagmon, {.i … | |
55 { MODKEY|ShiftMask, XK_period, tagmon, {.i … | |
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, movemou… | |
62 { ClkClientWin, MODKEY, Button2, togglef… | |
63 { ClkClientWin, MODKEY, Button3, resizem… | |
64 + { ClkClientWin, MODKEY|ShiftMask, Button1, swalmouse, … | |
65 { ClkTagBar, 0, Button1, view, … | |
66 { ClkTagBar, 0, Button3, togglev… | |
67 { ClkTagBar, MODKEY, Button1, tag, … | |
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} ${FREETYPELIB… | |
73 | |
74 # flags | |
75 CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_XOPEN_SOURCE=700L -DVERSI… | |
76 -#CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} | |
77 -CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os ${… | |
78 +CFLAGS = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS} | |
79 +#CFLAGS = -std=c99 -pedantic -Wall -Wno-deprecated-declarations -Os $… | |
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 }; /* defaul… | |
98 enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, | |
99 ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ | |
100 +enum { ClientRegular = 1, ClientSwallowee, ClientSwallower }; /* client… | |
101 | |
102 typedef union { | |
103 int i; | |
104 @@ -95,6 +96,7 @@ struct Client { | |
105 int isfixed, isfloating, isurgent, neverfocus, oldstate, isfull… | |
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 em… | |
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 we… | |
126 + * without the swallow having been consumed. 'decay' keeps trac… | |
127 + * remaining "charges". */ | |
128 + int decay; | |
129 + | |
130 + /* The swallower, i.e. the client which will swallow the next m… | |
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, con… | |
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]->a… | |
192 @@ -617,7 +655,13 @@ configurerequest(XEvent *e) | |
193 XMoveResizeWindow(dpy, c->win, c->x, c-… | |
194 } else | |
195 configure(c); | |
196 - } else { | |
197 + break; | |
198 + case ClientSwallower: | |
199 + /* Reject any move/resize requests for swallowers and c… | |
200 + * refusal to client via a synthetic ConfigureNotify (I… | |
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, ter… | |
234 + * swallow. This cuts off the swallow chain after the c… | |
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 ? SchemeS… | |
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, segment… | |
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], se… | |
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 == C… | |
311 + && (typeswee == ClientRegular || typesw… | |
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_… | |
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 t… | |
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 swallow… | |
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… | |
422 + * be set if swal() is called from swalmanage(). 'swer' and 'swee' must… | |
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 swallo… | |
436 + * swallow another window is ambiguous and is thus avoided alto… | |
437 + * contrast, a swallowee can swallow in a well-defined manner b… | |
438 + * to the head of the swallow chain. */ | |
439 + if (!manage) | |
440 + swalunreg(swer); | |
441 + | |
442 + /* Disable fullscreen prior to swallow. Swallows involving full… | |
443 + * windows produces quirky artefacts such as fullscreen termina… | |
444 + * pseudo-fullscreen windows. */ | |
445 + setfullscreen(swer, 0); | |
446 + setfullscreen(swee, 0); | |
447 + | |
448 + /* Swap swallowee into client and focus lists. Keeps current fo… | |
449 + * the swer (which gets unmapped) is focused in which case the … | |
450 + * receive focus. */ | |
451 + detach(swee); | |
452 + for (pc = &swer->mon->clients; *pc && *pc != swer; pc = &(*pc)-… | |
453 + *pc = swee; | |
454 + swee->next = swer->next; | |
455 + detachstack(swee); | |
456 + for (pc = &swer->mon->stack; *pc && *pc != swer; pc = &(*pc)->s… | |
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… | |
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]->arran… | |
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 't… | |
492 + * shall point null-terminated strings and must not be NULL. If an alre… | |
493 + * existing swallow instance targets 'c' its filters are updated and no… | |
494 + * swallow instance is created. 'c' may be ClientRegular or ClientSwall… | |
495 + * Complement to swalrm(). | |
496 + */ | |
497 +void | |
498 +swalreg(Client *c, const char *class, const char *inst, const char *tit… | |
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 afte… | |
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 r… | |
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 t… | |
560 + * may be used to perform the swallow. The following lines are … | |
561 + * minimal implementation of manage() with a few chunks delegat… | |
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|Pr… | |
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, 3… | |
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 it… | |
584 + * name, instance name and window title. Returns NULL if none is found.… | |
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|ButtonReleas… | |
625 + GrabModeAsync, None, cursor[CurSwal]->cursor, CurrentTi… | |
626 + return; | |
627 + | |
628 + do { | |
629 + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedi… | |
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 … | |
645 + * movements. */ | |
646 + XCheckMaskEvent(dpy, EnterWindowMask, &ev); | |
647 +} | |
648 + | |
649 +/* | |
650 + * Delete swallow instance swallows and free its resources. Complement … | |
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 s… | |
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 nee… | |
682 + break; | |
683 + } | |
684 + } | |
685 +} | |
686 + | |
687 +/* | |
688 + * Stop an active swallow of swallowed client 'swee' and remap the swal… | |
689 + * If 'swee' is a swallower itself 'root' must point the root client of… | |
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… | |
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]->arran… | |
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].… | |
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[NetClientLis… | |
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[NetC… | |
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 clie… | |
805 + * no client is found NULL is written to 'pc' and zero is returned. If … | |
806 + * is found and is a swallower (ClientSwallower) and proot is not NULL … | |
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 in… | |
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 … | |
860 + # an error while others silently evaluate the expressio… | |
861 + if ! wid=$(printf '%d' "$1" 2>/dev/null) || [ "$wid" -l… | |
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")SWALLO… | |
877 + Register window $($myprintf "\033[3m")SWALLOWER$($myprintf … | |
878 + match the $($myprintf "\033[3m")CLASS$($myprintf "\033[0m")… | |
879 + string-matching. An omitted filter will match anything. | |
880 + | |
881 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
882 + Deregister queued swallow for window $($myprintf "\033[3m")… | |
883 + | |
884 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
885 + Perform immediate swallow of window $($myprintf "\033[3m")S… | |
886 + | |
887 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
888 + Stop swallow of window $($myprintf "\033[3m")SWALLOWEE$($my… | |
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" "$widswe… | |
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… | |
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; } || … | |
937 + -i) [ -n "$2" ] && { instance="$2"; shift 2; } … | |
938 + -t) [ -n "$2" ] && { title="$2"; shift 2; } || … | |
939 + "") break ;; | |
940 + *) usage 1 ;; | |
941 + esac | |
942 + done | |
943 + widswer="$arg1" | |
944 + if name="$(printf "${PREFIX}swalreg${SEP}%u${SEP}%s${SE… | |
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" "$… | |
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 wr… | |
979 + * the end of every segment. The beginning of every segment is written … | |
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 |