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