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