commit 97b34849b8179f4d669569b2756a0e45415457dd
Author: Fishhh <[email protected]>
Date:   Sun Dec 15 13:09:29 2024 +0100

    [dwm][patch][betterswallow] Add patch
    
    This patch is yet another take on swallowing, this time with a PID-based
    mechanism for detecting swallowed windows and the simple IPC mechanism of
    a ClientMessage.
    
    The PID-based mechanism is, in my opinion, significantly better than the
    crude string matching mechanisms of `dynamicswallow`, not to mention the
    `devour` program which just unmaps the active window.
    
    Since IPC is handled with a ClientMessage, the code for IPC is
    significantly simpler than alternative approaches and is entirely
    self-contained in this patch.

diff --git 
a/dwm.suckless.org/patches/betterswallow/dwm-betterswallow-20241215-89eeca1.diff
 
b/dwm.suckless.org/patches/betterswallow/dwm-betterswallow-20241215-89eeca1.diff
new file mode 100644
index 00000000..f80532c1
--- /dev/null
+++ 
b/dwm.suckless.org/patches/betterswallow/dwm-betterswallow-20241215-89eeca1.diff
@@ -0,0 +1,277 @@
+diff -up -x dwm -x '*.o' -x compile_commands.json dwm-6.3-orig/config.mk 
dwm-6.3/config.mk
+--- dwm-6.3-orig/config.mk     2024-06-25 01:55:26.769203813 +0200
++++ dwm-6.3/config.mk  2024-12-15 01:12:39.132847648 +0100
+@@ -24,6 +24,8 @@ FREETYPEINC = /usr/include/freetype2
+ INCS = -I${X11INC} -I${FREETYPEINC}
+ LIBS = -L${X11LIB} -lX11 ${XINERAMALIBS} ${FREETYPELIBS}
+ 
++LIBS += -lXRes
++
+ # flags
+ CPPFLAGS = -D_DEFAULT_SOURCE -D_BSD_SOURCE -D_POSIX_C_SOURCE=200809L 
-DVERSION=\"${VERSION}\" ${XINERAMAFLAGS}
+ #CFLAGS   = -g -std=c99 -pedantic -Wall -O0 ${INCS} ${CPPFLAGS}
+diff -up -x dwm -x '*.o' -x compile_commands.json dwm-6.3-orig/dwm.c 
dwm-6.3/dwm.c
+--- dwm-6.3-orig/dwm.c 2024-06-25 01:55:26.771203825 +0200
++++ dwm-6.3/dwm.c      2024-12-15 01:44:35.963138226 +0100
+@@ -40,6 +40,7 @@
+ #include <X11/extensions/Xinerama.h>
+ #endif /* XINERAMA */
+ #include <X11/Xft/Xft.h>
++#include <X11/extensions/XRes.h>
+ 
+ #include "drw.h"
+ #include "util.h"
+@@ -49,7 +50,7 @@
+ #define CLEANMASK(mask)         (mask & ~(numlockmask|LockMask) & 
(ShiftMask|ControlMask|Mod1Mask|Mod2Mask|Mod3Mask|Mod4Mask|Mod5Mask))
+ #define INTERSECT(x,y,w,h,m)    (MAX(0, MIN((x)+(w),(m)->wx+(m)->ww) - 
MAX((x),(m)->wx)) \
+                                * MAX(0, MIN((y)+(h),(m)->wy+(m)->wh) - 
MAX((y),(m)->wy)))
+-#define ISVISIBLE(C)            ((C->tags & C->mon->tagset[C->mon->seltags]))
++#define ISVISIBLE(C)            (C->swallowed == NULL && (C->tags & 
C->mon->tagset[C->mon->seltags]))
+ #define LENGTH(X)               (sizeof X / sizeof X[0])
+ #define MOUSEMASK               (BUTTONMASK|PointerMotionMask)
+ #define WIDTH(X)                ((X)->w + 2 * (X)->bw)
+@@ -93,6 +94,11 @@ struct Client {
+       int bw, oldbw;
+       unsigned int tags;
+       int isfixed, isfloating, isurgent, neverfocus, oldstate, isfullscreen;
++
++      Client *swallower;
++      Client *swallowed;
++      Client *next_swallowed;
++
+       Client *next;
+       Client *snext;
+       Monitor *mon;
+@@ -141,6 +147,12 @@ typedef struct {
+       int monitor;
+ } Rule;
+ 
++typedef struct SwallowDef {
++      pid_t pid;
++      Client *swallower;
++      struct SwallowDef *next;
++} SwallowDef;
++
+ /* function declarations */
+ static void applyrules(Client *c);
+ static int applysizehints(Client *c, int *x, int *y, int *w, int *h, int 
interact);
+@@ -260,6 +272,8 @@ static void (*handler[LASTEvent]) (XEven
+       [PropertyNotify] = propertynotify,
+       [UnmapNotify] = unmapnotify
+ };
++static Atom swallow_atom;
++static SwallowDef *swallowlist;
+ static Atom wmatom[WMLast], netatom[NetLast];
+ static int running = 1;
+ static Cur *cursor[CurLast];
+@@ -400,6 +414,69 @@ arrangemon(Monitor *m)
+               m->lt[m->sellt]->arrange(m);
+ }
+ 
++pid_t
++wintopid(Window window) {
++  XResClientIdSpec spec;
++  spec.client = window;
++  spec.mask = XRES_CLIENT_ID_XID;
++
++  long count;
++  XResClientIdValue *output;
++  XResQueryClientIds(dpy, 1, &spec, &count, &output);
++
++  pid_t pid = -1;
++
++  for (int i = 0; i < count; ++i)
++    if (output[i].spec.mask == XRES_CLIENT_ID_PID_MASK) {
++      pid = *(pid_t *)output[i].value;
++      break;
++    }
++
++  XResClientIdsDestroy(count, output);
++
++  return pid;
++}
++
++void
++copyclientpos(Client *dst, Client *src) {
++      dst->bw = src->bw;
++      resizeclient(dst, src->x, src->y, src->w, src->h);
++      dst->oldx = src->oldx;
++      dst->oldy = src->oldy;
++      dst->oldw = src->oldw;
++      dst->oldh = src->oldh;
++      dst->oldbw = src->oldbw;
++      dst->oldstate = src->oldstate;
++      dst->isfullscreen = src->isfullscreen;
++      dst->isfloating = src->isfloating;
++      dst->tags = src->tags;
++      dst->mon = src->mon;
++}
++
++void
++checkswallowed(Client *c) {
++      pid_t pid = wintopid(c->win);
++
++      if(pid < 0) return;
++      for(SwallowDef *sd = swallowlist; sd != NULL; sd = sd->next) {
++              if(pid == sd->pid) {
++                      c->swallower = sd->swallower;
++                      copyclientpos(c, sd->swallower);
++
++                      c->next_swallowed = c->swallower->swallowed;
++                      c->swallower->swallowed = c;
++
++                      c->next = c->swallower->next;
++                      c->swallower->next = c;
++
++                      c->snext = c->swallower->snext;
++                      c->swallower->snext = c;
++
++                      return;
++              }
++      }
++}
++
+ void
+ attach(Client *c)
+ {
+@@ -526,7 +603,15 @@ clientmessage(XEvent *e)
+       } else if (cme->message_type == netatom[NetActiveWindow]) {
+               if (c != selmon->sel && !c->isurgent)
+                       seturgent(c, 1);
++      } else if(cme->message_type == swallow_atom) {
++              SwallowDef *node = ecalloc(1, sizeof(SwallowDef));
++              node->pid = cme->data.l[0];
++              node->swallower = c;
++              node->next = swallowlist;
++              swallowlist = node;
++              return;
+       }
++
+ }
+ 
+ void
+@@ -1052,6 +1137,7 @@ manage(Window w, XWindowAttributes *wa)
+       c->y = MAX(c->y, ((c->mon->by == c->mon->my) && (c->x + (c->w / 2) >= 
c->mon->wx)
+               && (c->x + (c->w / 2) < c->mon->wx + c->mon->ww)) ? bh : 
c->mon->my);
+       c->bw = borderpx;
++      checkswallowed(c);
+ 
+       wc.border_width = c->bw;
+       XConfigureWindow(dpy, w, CWBorderWidth, &wc);
+@@ -1066,8 +1152,10 @@ manage(Window w, XWindowAttributes *wa)
+               c->isfloating = c->oldstate = trans != None || c->isfixed;
+       if (c->isfloating)
+               XRaiseWindow(dpy, c->win);
+-      attach(c);
+-      attachstack(c);
++      if(!c->swallower) {
++              attach(c);
++              attachstack(c);
++      }
+       XChangeProperty(dpy, root, netatom[NetClientList], XA_WINDOW, 32, 
PropModeAppend,
+               (unsigned char *) &(c->win), 1);
+       XMoveResizeWindow(dpy, c->win, c->x + 2 * sw, c->y, c->w, c->h); /* 
some windows require this */
+@@ -1164,6 +1252,10 @@ movemouse(const Arg *arg)
+               case Expose:
+               case MapRequest:
+                       handler[ev.type](&ev);
++
++                      // A MapRequest could've caused the current window to 
swallow another one.
++                      if(c->swallowed)
++                              c = c->swallowed;
+                       break;
+               case MotionNotify:
+                       if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+@@ -1318,6 +1410,9 @@ resizemouse(const Arg *arg)
+               case Expose:
+               case MapRequest:
+                       handler[ev.type](&ev);
++
++                      if(c->swallowed)
++                              c = c->swallowed;
+                       break;
+               case MotionNotify:
+                       if ((ev.xmotion.time - lasttime) <= (1000 / 60))
+@@ -1566,6 +1661,7 @@ setup(void)
+       netatom[NetWMWindowType] = XInternAtom(dpy, "_NET_WM_WINDOW_TYPE", 
False);
+       netatom[NetWMWindowTypeDialog] = XInternAtom(dpy, 
"_NET_WM_WINDOW_TYPE_DIALOG", False);
+       netatom[NetClientList] = XInternAtom(dpy, "_NET_CLIENT_LIST", False);
++      swallow_atom = XInternAtom(dpy, "_BETTER_SWALLOW", False);
+       /* init cursors */
+       cursor[CurNormal] = drw_cur_create(drw, XC_left_ptr);
+       cursor[CurResize] = drw_cur_create(drw, XC_sizing);
+@@ -1583,6 +1679,8 @@ setup(void)
+               PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+       XChangeProperty(dpy, wmcheckwin, netatom[NetWMName], utf8string, 8,
+               PropModeReplace, (unsigned char *) "dwm", 3);
++      XChangeProperty(dpy, root, swallow_atom, utf8string, 8,
++              PropModeReplace, (unsigned char *) "supported", 9);
+       XChangeProperty(dpy, root, netatom[NetWMCheck], XA_WINDOW, 32,
+               PropModeReplace, (unsigned char *) &wmcheckwin, 1);
+       /* EWMH support per view */
+@@ -1766,11 +1864,52 @@ unfocus(Client *c, int setfocus)
+ }
+ 
+ void
++deleteswallower(Client *c) {
++      SwallowDef **prevnext = &swallowlist;
++      for(SwallowDef *sd = swallowlist; sd != NULL;) {
++              if(sd->swallower == c) {
++                      SwallowDef *next = sd->next;
++                      *prevnext = next;
++                      free(sd);
++                      sd = next;
++              } else {
++                      prevnext = &sd->next;
++                      sd = sd->next;
++              }
++      }
++
++      Client *sw = c->swallowed;
++      while(sw) {
++              sw->swallower = NULL;
++              Client *next = sw->next_swallowed;
++              sw->next_swallowed = NULL;
++              sw = next;
++      }
++}
++
++void
+ unmanage(Client *c, int destroyed)
+ {
+       Monitor *m = c->mon;
+       XWindowChanges wc;
+ 
++      if(c->swallower) {
++              c->swallower->swallowed = c->next_swallowed;
++              c->next_swallowed = NULL;
++
++              if(c->swallower->swallowed == NULL) {
++                      detach(c->swallower);
++                      detachstack(c->swallower);
++
++                      c->swallower->next = c->next;
++                      c->next = c->swallower;
++                      c->swallower->snext = c->snext;
++                      c->snext = c->swallower;
++
++                      copyclientpos(c->swallower, c);
++              }
++      }
++
+       detach(c);
+       detachstack(c);
+       if (!destroyed) {
+@@ -1783,9 +1922,10 @@ unmanage(Client *c, int destroyed)
+               XSync(dpy, False);
+               XSetErrorHandler(xerror);
+               XUngrabServer(dpy);
+-      }
++      } else deleteswallower(c);
++      if(c->swallower) focus(c->swallower);
++      else focus(NULL);
+       free(c);
+-      focus(NULL);
+       updateclientlist();
+       arrange(m);
+ }
diff --git a/dwm.suckless.org/patches/betterswallow/index.md 
b/dwm.suckless.org/patches/betterswallow/index.md
new file mode 100644
index 00000000..2cd52e28
--- /dev/null
+++ b/dwm.suckless.org/patches/betterswallow/index.md
@@ -0,0 +1,56 @@
+# betterswallow
+
+## Description
+
+This patch adds "window swallowing" to dwm, as a few existing patches already 
do,
+but with another take on dynamic swallowing. In contrast to the existing
+[dynamicswallow](https://dwm.suckless.org/patches/dynamicswallow/) patch,
+`betterswallow` uses PID-based swallowing and a non-standard X11 ClientMessage
+for communication. As with `dynamicswallow`, an external tool `better-swallow`
+is required to use it. `better-swallow` is a separate tool that does not
+*require* this patch but is only enhanced by its presence, without the patch
+it falls back to the devour mechanism of unmapping the parent window itself.
+
+Development of this patch should only happen on the
+[GitHub repository](https://github.com/afishhh/better-swallow) of
+`better-swallow`, if you find any bugs feel free to open an issue.
+
+Currently only a version for dwm-6.3 exists, if you wish to update the patch to
+a newer version of DWM you can open a pull request to the `better-swallow`
+repository.
+
+## Download
+
+- [dwm-betterswallow-20241215-6.3.diff](dwm-betterswallow-20241215-6.3.diff)
+- [better-swallow](https://github.com/afishhh/better-swallow)
+
+## Patching
+
+The patch requires the `Xres` library, make sure you have it in your build 
environment.
+
+Unlike `dynamicswallow` you don't have to worry about any IPC patches, because
+this patch uses the simpler method of a ClientMessage for registering 
swallowers.
+
+If you have any patches that store geometry parameters in the `Client` struct,
+make sure they're copied in the `copyclientpos` function.
+
+## Usage
+
+Run any graphical program you want to be swallowed as
+`better-swallow <CMD>`, this will cause any windows it spawns to replace the
+parent window.
+
+Since `better-swallow` is a pretty long name I recommend creating an alias 
such as
+`bs`.
+
+## Limitations
+
+- Due to the reliance on the `Xres` extension and PIDs, this will fail if the 
X server
+is not running on the same machine as `better-swallow` and possibly add 
nonsensical
+entries to the "swallow queue".
+- If a swallowed process opens the window deeper in the process tree it will 
not get
+swallowed. This may be fixed in the future by traversing the whole process 
chain
+instead of just one step up. Open an issue if you actually encounter this.
+
+## Author
+- Hubert GÅ‚uchowski (<[email protected]>)


Reply via email to