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