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