swallow: rewrite patch, update README.md

This commit is contained in:
Nikita Ivanov 2025-03-03 21:49:52 +01:00
parent 4d0bfa66b4
commit 2e5748edfe
No known key found for this signature in database
GPG Key ID: 6E656AC5B97B5133
3 changed files with 284 additions and 152 deletions

View File

@ -1,29 +1,52 @@
### Description
Terminals swallow windows that they are the parent of.
foot is the terminal by default, you can change it in client rules in config.h.
This patch adds "window swallowing" to dwl.
2023-08-16 and up are made to also work with x windows: https://codeberg.org/dwl/dwl/issues/331
If a user runs a graphical program from the terminal (e.g., `mpv`), the terminal
will be hidden and only a window of the newly spawned graphical program will
be visible. The terminal stays hidden until the graphical program is closed.
This patch helps users spawning a lot of graphical programs from their command
line by avoiding cluttering the screen with many unusable terminals.
for freebsd users: apply swallow-freebsd.patch **on top of** swallow.patch
`foot` is the terminal by default, you can change it in client rules in config.h.
In `2025-03-03 v0.7` version, the patch had been rewritten from scratch to make
it more robust and add a few more features:
- "dynamically swallow" windows by pressing `Alt+a` (a focused window will
swallow/unswallow the previously focused one)
- toggle automatic swallowing by `Alt+Shift+a`
- if a window has swallowed another, it get thicker borders
### Download
- [git branch](https://codeberg.org/notchoc/dwl/src/branch/swallow)
- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow.patch)
#### swallow.patch
- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/swallow)
- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow.patch) (added "dynamic swallowing support")
- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow.patch)
- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f64d701bab2f9f52d3637edd091684f920407d87/patches/swallow/swallow.patch)
- [2024-05-02](https://codeberg.org/dwl/dwl-patches/raw/commit/9c5d5d85f3ac780e7a14d5d0535e3349ce8b8f53/patches/swallow/swallow.patch)
- [2024-04-03](https://codeberg.org/dwl/dwl-patches/raw/commit/3c9a8e3232a8531871924484cef1ef0938730e15/swallow/swallow.patch)
- [2024-01-01](https://codeberg.org/dwl/dwl-patches/raw/commit/8a352a1b27a64821ba9fbfda52fe82463ac84c66/swallow/swallow.patch)
- [2023-10-26](https://github.com/djpohly/dwl/compare/main...youbitchoc:swallow.patch)
- [2023-08-16](https://github.com/djpohly/dwl/compare/main...mewkl:swallowx.patch)
- [2023-08-16](https://github.com/djpohly/dwl/compare/main...mewkl:swallowx.patch) (added XWayland support)
- [2023-07-15](https://github.com/djpohly/dwl/compare/main...NikitaIvanovV:swallow.patch)
- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-swallow.patch)
- [2021-12-03](https://github.com/djpohly/dwl/compare/main...dm1tz:swallow.patch)
#### swallow-freebsd.patch
Apply this patch on top of the swallow.patch if you use FreeBSD.
- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow-freebsd.patch)
- [2024-07-13](https://codeberg.org/dwl/dwl-patches/raw/commit/f1ed83eaeba46108f4ee8164094cb431d64a3e68/patches/swallow/swallow-freebsd.patch)
### Authors
- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV))
- [Dmitry Zakharchenko](https://github.com/dm1tz)
- [Palanix](https://codeberg.org/Palanix)
- [Nikita Ivanov](https://github.com/NikitaIvanovV)
- [Connor Worrell](https://github.com/ConnorWorrell)
- [Mewkl](https://github.com/mewkl)
- [Choc](https://codeberg.org/notchoc)

View File

@ -1,17 +1,17 @@
From 49dc947ba4c33324b969ef7179768c806910fffb Mon Sep 17 00:00:00 2001
From: choc <notchoc@proton.me>
Date: Sat, 22 Jun 2024 10:52:33 +0800
Subject: [PATCH] swallow: add freebsd support
From 002e11e197cd254f06b65681ffd5bcf617d830b9 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Mon, 3 Mar 2025 19:49:07 +0100
Subject: [PATCH] swallow: add FreeBSD support
---
dwl.c | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
diff --git a/dwl.c b/dwl.c
index 3a3167b..ee9e965 100644
index bbbbe6f..dc55319 100644
--- a/dwl.c
+++ b/dwl.c
@@ -65,6 +65,14 @@
@@ -67,6 +67,14 @@
#include <xcb/xcb_icccm.h>
#endif
@ -26,17 +26,17 @@ index 3a3167b..ee9e965 100644
#include "util.h"
/* macros */
@@ -1486,6 +1494,7 @@ handlesig(int signo)
@@ -2032,6 +2040,7 @@ outputmgrtest(struct wl_listener *listener, void *data)
pid_t
getparentprocess(pid_t p)
parentpid(pid_t pid)
{
+#ifdef __linux__
unsigned int v = 0;
FILE *f;
@@ -1499,6 +1508,14 @@ getparentprocess(pid_t p)
char buf[256];
@@ -2041,6 +2050,14 @@ parentpid(pid_t pid)
fscanf(f, "%*u %*s %*c %u", &v);
fclose(f);
return (pid_t)v;
+#elif defined(__FreeBSD__)
+ struct kinfo_proc kip;
@ -48,8 +48,7 @@ index 3a3167b..ee9e965 100644
+#endif
}
int
void
--
2.43.0
2.48.1

View File

@ -1,13 +1,13 @@
From 4b80c425c9f414bc079a0e61f5a3ef42eea85476 Mon Sep 17 00:00:00 2001
From: choc <notchoc@proton.me>
Date: Fri, 15 Sep 2023 10:36:21 +0800
Subject: [PATCH] implement swallow
From 7255d7e2e1b87c0583a202ea20c83fa75466c0fc Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Wed, 5 Feb 2025 02:34:39 +0100
Subject: [PATCH] Swallow: hide the terminal when it spawns a client
---
client.h | 12 ++++++
config.def.h | 7 ++--
dwl.c | 112 ++++++++++++++++++++++++++++++++++++++++++++++++---
3 files changed, 123 insertions(+), 8 deletions(-)
client.h | 12 ++++
config.def.h | 10 +++-
dwl.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 167 insertions(+), 7 deletions(-)
diff --git a/client.h b/client.h
index 42f225f..bc9cad2 100644
@ -33,10 +33,18 @@ index 42f225f..bc9cad2 100644
client_get_clip(Client *c, struct wlr_box *clip)
{
diff --git a/config.def.h b/config.def.h
index 22d2171..7e5fef1 100644
index 22d2171..fb5f8fb 100644
--- a/config.def.h
+++ b/config.def.h
@@ -22,10 +22,11 @@ static int log_level = WLR_ERROR;
@@ -13,6 +13,7 @@ static const float focuscolor[] = COLOR(0x005577ff);
static const float urgentcolor[] = COLOR(0xff0000ff);
/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */
+static int enableautoswallow = 1; /* enables autoswallowing newly spawned clients */
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -22,10 +23,11 @@ static int log_level = WLR_ERROR;
/* NOTE: ALWAYS keep a rule declared even if you don't use rules (e.g leave at least one example) */
static const Rule rules[] = {
@ -45,17 +53,41 @@ index 22d2171..7e5fef1 100644
/* examples: */
- { "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */
- { "firefox_EXAMPLE", NULL, 1 << 8, 0, -1 }, /* Start on ONLY tag "9" */
+ { "foot", NULL, 0, 0, 1, 1, -1 },
+ { "Gimp_EXAMPLE", NULL, 0, 1, 0, 0, -1 }, /* Start on currently visible tags floating, not tiled */
+ { "firefox_EXAMPLE", NULL, 1 << 8, 0, 0, 0, -1 }, /* Start on ONLY tag "9" */
+ { "foot", NULL, 0, 0, 1, 1, -1 }, /* make foot swallow clients that are not foot */
};
/* layout(s) */
@@ -142,6 +144,8 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_space, setlayout, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
+ { MODKEY, XKB_KEY_a, toggleswallow, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, toggleautoswallow,{0} },
{ MODKEY, XKB_KEY_0, view, {.ui = ~0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} },
{ MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} },
diff --git a/dwl.c b/dwl.c
index dc0437e..c6a5e9d 100644
index def2562..bbbbe6f 100644
--- a/dwl.c
+++ b/dwl.c
@@ -103,7 +103,8 @@ typedef struct {
@@ -73,12 +73,13 @@
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS)
-#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags]))
+#define VISIBLEON(C, M) ((M) && (C)->mon == (M) && ((C)->tags & (M)->tagset[(M)->seltags]) && !(C)->swallowedby)
#define LENGTH(X) (sizeof X / sizeof X[0])
#define END(A) ((A) + LENGTH(A))
#define TAGMASK ((1u << TAGCOUNT) - 1)
#define LISTEN(E, L, H) wl_signal_add((E), ((L)->notify = (H), (L)))
#define LISTEN_STATIC(E, H) do { static struct wl_listener _l = {.notify = (H)}; wl_signal_add((E), &_l); } while (0)
+#define BORDERPX(C) (borderpx + ((C)->swallowing ? (C)->swallowing->bw : 0))
/* enums */
enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */
@@ -104,7 +105,8 @@ typedef struct {
} Button;
typedef struct Monitor Monitor;
@ -65,21 +97,21 @@ index dc0437e..c6a5e9d 100644
/* Must keep these three elements in this order */
unsigned int type; /* XDGShell or X11* */
struct wlr_box geom; /* layout-relative, includes border */
@@ -138,9 +139,11 @@ typedef struct {
#endif
@@ -140,8 +142,12 @@ typedef struct {
unsigned int bw;
uint32_t tags;
- int isfloating, isurgent, isfullscreen;
+ int isfloating, isurgent, isfullscreen, isterm, noswallow;
int isfloating, isurgent, isfullscreen;
+ int isterm, noswallow;
uint32_t resize; /* configure serial of a pending resize */
-} Client;
+ pid_t pid;
+ Client *swallowing, *swallowedby;
+ Client *swallowing; /* client being hidden */
+ Client *swallowedby;
+};
typedef struct {
uint32_t mod;
@@ -229,6 +232,8 @@ typedef struct {
@@ -230,6 +236,8 @@ typedef struct {
const char *title;
uint32_t tags;
int isfloating;
@ -88,18 +120,31 @@ index dc0437e..c6a5e9d 100644
int monitor;
} Rule;
@@ -351,6 +356,10 @@ static Monitor *xytomon(double x, double y);
static void xytonode(double x, double y, struct wlr_surface **psurface,
Client **pc, LayerSurface **pl, double *nx, double *ny);
static void zoom(const Arg *arg);
+static pid_t getparentprocess(pid_t p);
+static int isdescprocess(pid_t p, pid_t c);
+static Client *termforwin(Client *w);
+static void swallow(Client *c, Client *w);
/* variables */
static const char broken[] = "broken";
@@ -461,10 +470,14 @@ applyrules(Client *c)
@@ -311,6 +319,7 @@ static void moveresize(const Arg *arg);
static void outputmgrapply(struct wl_listener *listener, void *data);
static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int test);
static void outputmgrtest(struct wl_listener *listener, void *data);
+static pid_t parentpid(pid_t pid);
static void pointerfocus(Client *c, struct wlr_surface *surface,
double sx, double sy, uint32_t time);
static void printstatus(void);
@@ -335,11 +344,15 @@ static void setsel(struct wl_listener *listener, void *data);
static void setup(void);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
+static void swallow(Client *c, Client *toswallow);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
+static Client *termforwin(Client *c);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
+static void toggleswallow(const Arg *arg);
+static void toggleautoswallow(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -466,11 +479,15 @@ applyrules(Client *c)
if (!(title = client_get_title(c)))
title = broken;
@ -109,132 +154,197 @@ index dc0437e..c6a5e9d 100644
if ((!r->title || strstr(title, r->title))
&& (!r->id || strstr(appid, r->id))) {
c->isfloating = r->isfloating;
newtags |= r->tags;
+ c->isterm = r->isterm;
+ c->noswallow = r->noswallow;
newtags |= r->tags;
i = 0;
wl_list_for_each(m, &mons, link) {
@@ -473,6 +486,21 @@ applyrules(Client *c)
if (r->monitor == i++)
@@ -478,6 +495,12 @@ applyrules(Client *c)
}
}
}
+ if (!c->noswallow && !client_is_float_type(c)
+ && !c->surface.xdg->initial_commit) {
+ if (enableautoswallow && !c->noswallow && !c->isfloating &&
+ !c->surface.xdg->initial_commit) {
+ Client *p = termforwin(c);
+ if (p) {
+ c->swallowedby = p;
+ p->swallowing = c;
+ wl_list_remove(&c->link);
+ wl_list_remove(&c->flink);
+ if (p)
+ swallow(c, p);
+ wl_list_remove(&p->link);
+ wl_list_remove(&p->flink);
+ mon = p->mon;
+ newtags = p->tags;
+ }
+ }
setmon(c, mon, newtags);
}
@@ -1467,6 +1495,63 @@ handlesig(int signo)
}
@@ -2006,6 +2029,20 @@ outputmgrtest(struct wl_listener *listener, void *data)
outputmgrapplyortest(config, 1);
}
+pid_t
+getparentprocess(pid_t p)
+parentpid(pid_t pid)
+{
+ unsigned int v = 0;
+
+ FILE *f;
+ char buf[256];
+ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)p);
+
+ snprintf(buf, sizeof(buf) - 1, "/proc/%u/stat", (unsigned)pid);
+ if (!(f = fopen(buf, "r")))
+ return 0;
+
+ fscanf(f, "%*u %*s %*c %u", &v);
+ fclose(f);
+
+ return (pid_t)v;
+}
+
+int
+isdescprocess(pid_t p, pid_t c)
+{
+ while (p != c && c != 0)
+ c = getparentprocess(c);
+
+ return (int)c;
+}
+
+Client *
+termforwin(Client *w)
+{
+ Client *c;
+
+ if (!w->pid || w->isterm || w->noswallow)
+ return NULL;
+
+ wl_list_for_each(c, &fstack, flink)
+ if (c->isterm && !c->swallowing && c->pid && isdescprocess(c->pid, w->pid))
+ return c;
+
+ return NULL;
+}
void
pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time)
@@ -2326,7 +2363,7 @@ setfullscreen(Client *c, int fullscreen)
c->isfullscreen = fullscreen;
if (!c->mon || !client_surface(c)->mapped)
return;
- c->bw = fullscreen ? 0 : borderpx;
+ c->bw = fullscreen ? 0 : BORDERPX(c);
client_set_fullscreen(c, fullscreen);
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen
? LyrFS : c->isfloating ? LyrFloat : LyrTile]);
@@ -2404,6 +2441,9 @@ setmon(Client *c, Monitor *m, uint32_t newtags)
setfloating(c, c->isfloating);
}
focusclient(focustop(selmon), 1);
+
+ if (c->swallowing)
+ setmon(c->swallowing, m, newtags);
}
void
@@ -2669,6 +2709,44 @@ startdrag(struct wl_listener *listener, void *data)
LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon);
}
+void
+swallow(Client *c, Client *w)
+swallow(Client *c, Client *toswallow)
+{
+ c->bw = w->bw;
+ c->isfloating = w->isfloating;
+ c->isurgent = w->isurgent;
+ c->isfullscreen = w->isfullscreen;
+ c->tags = w->tags;
+ c->geom = w->geom;
+ wl_list_insert(&w->link, &c->link);
+ wl_list_insert(&w->flink, &c->flink);
+ wlr_scene_node_set_enabled(&w->scene->node, 0);
+ wlr_scene_node_set_enabled(&c->scene->node, 1);
+ /* Do not allow a client to swallow itself */
+ if (c == toswallow)
+ return;
+
+ /* Swallow */
+ if (toswallow && !c->swallowing) {
+ c->swallowing = toswallow;
+ toswallow->swallowedby = c;
+ toswallow->mon = c->mon;
+ toswallow->mon = c->mon;
+ wl_list_remove(&c->link);
+ wl_list_insert(&c->swallowing->link, &c->link);
+ wl_list_remove(&c->flink);
+ wl_list_insert(&c->swallowing->flink, &c->flink);
+ c->bw = BORDERPX(c);
+ c->tags = toswallow->tags;
+ c->isfloating = toswallow->isfloating;
+ c->geom = toswallow->geom;
+ setfullscreen(toswallow, 0);
+ }
+
+ /* Unswallow */
+ else if (c->swallowing) {
+ wl_list_remove(&c->swallowing->link);
+ wl_list_insert(&c->link, &c->swallowing->link);
+ wl_list_remove(&c->swallowing->flink);
+ wl_list_insert(&c->flink, &c->swallowing->flink);
+ c->swallowing->tags = c->tags;
+ c->swallowing->swallowedby = NULL;
+ c->swallowing = NULL;
+ c->bw = BORDERPX(c);
+ setfullscreen(c, 0);
+ }
+}
+
void
incnmaster(const Arg *arg)
tag(const Arg *arg)
{
@@ -2746,15 +2831,32 @@ unmapnotify(struct wl_listener *listener, void *data)
@@ -2690,6 +2768,40 @@ tagmon(const Arg *arg)
setmon(sel, dirtomon(arg->i), 0);
}
+Client *
+termforwin(Client *c)
+{
+ Client *p;
+ pid_t pid;
+ pid_t pids[32];
+ size_t i, pids_len;
+
+ if (!c->pid || c->isterm)
+ return NULL;
+
+ /* Get all parent pids */
+ pids_len = 0;
+ pid = c->pid;
+ while (pids_len < LENGTH(pids)) {
+ pid = parentpid(pid);
+ if (!pid)
+ break;
+ pids[pids_len++] = pid;
+ }
+
+ /* Find closest parent */
+ for (i = 0; i < pids_len; i++) {
+ wl_list_for_each(p, &clients, link) {
+ if (!p->pid || !p->isterm || p->swallowedby)
+ continue;
+ if (pids[i] == p->pid)
+ return p;
+ }
+ }
+
+ return NULL;
+}
+
void
tile(Monitor *m)
{
@@ -2741,6 +2853,32 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
+void
+toggleswallow(const Arg *arg)
+{
+ Client *c, *sel = focustop(selmon);
+ if (!sel)
+ return;
+
+ if (sel->swallowing) {
+ swallow(sel, NULL);
+ } else {
+ wl_list_for_each(c, &sel->flink, flink) {
+ if (&c->flink == &fstack)
+ continue; /* wrap past the sentinel node */
+ if (VISIBLEON(c, selmon))
+ break; /* found it */
+ }
+ swallow(sel, c);
+ }
+}
+
+void
+toggleautoswallow(const Arg *arg)
+{
+ enableautoswallow = !enableautoswallow;
+}
+
void
toggletag(const Arg *arg)
{
@@ -2801,6 +2939,12 @@ unmapnotify(struct wl_listener *listener, void *data)
grabc = NULL;
}
+ if (c->swallowedby)
+ swallow(c->swallowedby, c);
+ if (c->swallowing) {
+ swallow(c, NULL);
+ } else if (c->swallowedby) {
+ swallow(c->swallowedby, NULL);
+ }
+
if (client_is_unmanaged(c)) {
if (c == exclusive_focus) {
exclusive_focus = NULL;
focusclient(focustop(selmon), 1);
}
} else {
- wl_list_remove(&c->link);
+ if (!c->swallowing)
+ wl_list_remove(&c->link);
setmon(c, NULL, 0);
- wl_list_remove(&c->flink);
+ if (!c->swallowing)
+ wl_list_remove(&c->flink);
+ }
+
+ if (c->swallowedby) {
+ c->swallowedby->prev = c->geom;
+ setfullscreen(c->swallowedby, c->isfullscreen);
+ c->swallowedby->swallowing = NULL;
+ c->swallowedby = NULL;
+ }
+
+ if (c->swallowing) {
+ c->swallowing->swallowedby = NULL;
+ c->swallowing = NULL;
}
wlr_scene_node_destroy(&c->scene->node);
--
2.43.0
2.48.1