dwm-dynamicswallow-20210221-61bb8b2.diff - sites - public wiki contents of suckless.org
 (HTM) git clone git://git.suckless.org/sites
 (DIR) Log
 (DIR) Files
 (DIR) Refs
       ---
       dwm-dynamicswallow-20210221-61bb8b2.diff (29490B)
       ---
            1 From ac7558081ea5f576ef2ed09c1817d2722baa92fd Mon Sep 17 00:00:00 2001
            2 From: =?UTF-8?q?Stanislaw=20H=C3=BCll?= <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