dwm-dynamicswallow-20240320-061e9fe.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
       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