dwm-dynamicswallow-20210221-61bb8b2.diff - sites - public wiki contents of suck… | |
git clone git://git.suckless.org/sites | |
Log | |
Files | |
Refs | |
--- | |
dwm-dynamicswallow-20210221-61bb8b2.diff (29490B) | |
--- | |
1 From ac7558081ea5f576ef2ed09c1817d2722baa92fd Mon Sep 17 00:00:00 2001 | |
2 From: =?UTF-8?q?Stanislaw=20H=C3=BCll?= <[email protected]> | |
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}/m… | |
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, … | |
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.0… | |
51 static const int nmaster = 1; /* number of clients in master are… | |
52 @@ -84,6 +89,7 @@ static 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 @@ -107,6 +113,7 @@ static Button buttons[] = { | |
61 { ClkClientWin, MODKEY, Button1, movemou… | |
62 { ClkClientWin, MODKEY, Button2, togglef… | |
63 { ClkClientWin, MODKEY, Button3, resizem… | |
64 + { ClkClientWin, MODKEY|ShiftMask, Button1, swalmou… | |
65 { ClkTagBar, 0, Button1, view, … | |
66 { ClkTagBar, 0, Button3, togglev… | |
67 { ClkTagBar, MODKEY, Button1, tag, … | |
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 }; /* defaul… | |
83 enum { ClkTagBar, ClkLtSymbol, ClkStatusText, ClkWinTitle, | |
84 ClkClientWin, ClkRootWin, ClkLast }; /* clicks */ | |
85 +enum { ClientRegular = 1, ClientSwallowee, ClientSwallower }; /* client… | |
86 | |
87 typedef union { | |
88 int i; | |
89 @@ -95,6 +96,7 @@ struct Client { | |
90 int isfixed, isfloating, isurgent, neverfocus, oldstate, isfull… | |
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 em… | |
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 we… | |
111 + * without the swallow having been consumed. 'decay' keeps trac… | |
112 + * remaining "charges". */ | |
113 + int decay; | |
114 + | |
115 + /* The swallower, i.e. the client which will swallow the next m… | |
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, in… | |
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, con… | |
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]->a… | |
179 + } else if (c->isfloating || !selmon->lt[selmon->sellt]-… | |
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-… | |
185 } else | |
186 configure(c); | |
187 - } else { | |
188 + break; | |
189 + case ClientSwallower: | |
190 + /* Reject any move/resize requests for swallowers and c… | |
191 + * refusal to client via a synthetic ConfigureNotify (I… | |
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, ter… | |
225 + * swallow. This cuts off the swallow chain after the c… | |
226 + swalstop(c, root); | |
227 + | |
228 + /* Cut off the swallow chain before the client. */ | |
229 + for (swee = root; swee->swallowedby != c; swee = swee->… | |
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 ? SchemeS… | |
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, segment… | |
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], se… | |
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 == C… | |
302 + && (typeswee == ClientRegular || typesw… | |
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 t… | |
353 + break; | |
354 + case ClientSwallower: | |
355 + /* Remapping a swallower will simply stop the swallow. … | |
356 + for (swee = root; swee->swallowedby != c; swee = swee->… | |
357 + swalstop(swee, root); | |
358 + break; | |
359 + default: | |
360 + /* No client is managing the window. See if any swallow… | |
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… | |
415 + * be set if swal() is called from swalmanage(). 'swer' and 'swee' must… | |
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 swallo… | |
425 + * swallow another window is ambiguous and is thus avoided alto… | |
426 + * contrast, a swallowee can swallow in a well-defined manner b… | |
427 + * to the head of the swallow chain. */ | |
428 + if (!manage) | |
429 + swalunreg(swer); | |
430 + | |
431 + /* Disable fullscreen prior to swallow. Swallows involving full… | |
432 + * windows produces quirky artefacts such as fullscreen termina… | |
433 + * pseudo-fullscreen windows. */ | |
434 + setfullscreen(swer, 0); | |
435 + setfullscreen(swee, 0); | |
436 + | |
437 + /* Swap swallowee into client and focus lists. Keeps current fo… | |
438 + * the swer (which gets unmapped) is focused in which case the … | |
439 + * receive focus. */ | |
440 + detach(swee); | |
441 + for (pc = &swer->mon->clients; *pc && *pc != swer; pc = &(*pc)-… | |
442 + *pc = swee; | |
443 + swee->next = swer->next; | |
444 + detachstack(swee); | |
445 + for (pc = &swer->mon->stack; *pc && *pc != swer; pc = &(*pc)->s… | |
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… | |
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]->arran… | |
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 't… | |
481 + * shall point null-terminated strings or be NULL, implying a wildcard.… | |
482 + * already existing swallow instance targets 'c' its filters are update… | |
483 + * new swallow instance is created. 'c' may be ClientRegular or ClientS… | |
484 + * Complement to swalrm(). | |
485 + */ | |
486 +void swalreg(Client *c, const char *class, const char *inst, const char… | |
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->clas… | |
497 + else | |
498 + s->class[0] = '\0'; | |
499 + if (inst) | |
500 + strncpy(s->inst, inst, sizeof(s->inst) … | |
501 + else | |
502 + s->inst[0] = '\0'; | |
503 + if (title) | |
504 + strncpy(s->title, title, sizeof(s->titl… | |
505 + else | |
506 + s->title[0] = '\0'; | |
507 + s->decay = swaldecay; | |
508 + | |
509 + /* Only one swallow per client. May return afte… | |
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 r… | |
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 t… | |
559 + * may be used to perform the swallow. The following lines are … | |
560 + * minimal implementation of manage() with a few chunks delegat… | |
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|Pr… | |
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, 3… | |
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 it… | |
583 + * name, instance name and window title. Returns NULL if none is found.… | |
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|ButtonReleas… | |
624 + GrabModeAsync, None, cursor[CurSwal]->cursor, CurrentTi… | |
625 + return; | |
626 + | |
627 + do { | |
628 + XMaskEvent(dpy, MOUSEMASK|ExposureMask|SubstructureRedi… | |
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 … | |
644 + * movements. */ | |
645 + XCheckMaskEvent(dpy, EnterWindowMask, &ev); | |
646 +} | |
647 + | |
648 +/* | |
649 + * Delete swallow instance swallows and free its resources. Complement … | |
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 s… | |
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 nee… | |
680 + break; | |
681 + } | |
682 + } | |
683 +} | |
684 + | |
685 +/* | |
686 + * Stop an active swallow of swallowed client 'swee' and remap the swal… | |
687 + * If 'swee' is a swallower itself 'root' must point the root client of… | |
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… | |
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]->arran… | |
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].… | |
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[NetClientLis… | |
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[NetC… | |
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 clie… | |
816 + * no client is found NULL is written to 'pc' and zero is returned. If … | |
817 + * is found and is a swallower (ClientSwallower) and proot is not NULL … | |
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->swal… | |
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 in… | |
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 … | |
871 + # an error while others silently evaluate the expressio… | |
872 + if ! wid=$(printf '%d' "$1" 2>/dev/null) || [ "$wid" -l… | |
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")SWALLO… | |
888 + Register window $($myprintf "\033[3m")SWALLOWER$($myprintf … | |
889 + match the $($myprintf "\033[3m")CLASS$($myprintf "\033[0m")… | |
890 + string-matching. An omitted filter will match anything. | |
891 + | |
892 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
893 + Deregister queued swallow for window $($myprintf "\033[3m")… | |
894 + | |
895 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
896 + Perform immediate swallow of window $($myprintf "\033[3m")S… | |
897 + | |
898 + $($myprintf "\033[1m")dwmswallow $($myprintf "\033[3m")SWALLO… | |
899 + Stop swallow of window $($myprintf "\033[3m")SWALLOWEE$($my… | |
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" "$widswe… | |
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… | |
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; } || … | |
948 + -i) [ -n "$2" ] && { instance="$2"; shift 2; } … | |
949 + -t) [ -n "$2" ] && { title="$2"; shift 2; } || … | |
950 + "") break ;; | |
951 + *) usage 1 ;; | |
952 + esac | |
953 + done | |
954 + widswer="$arg1" | |
955 + if name="$(printf "${PREFIX}swalreg${SEP}%u${SEP}%s${SE… | |
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" "$… | |
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 wr… | |
991 + * the end of every segment. The beginning of every segment is written … | |
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 |