Compare commits

...

10 Commits

Author SHA1 Message Date
Nikita Ivanov
8ac41e9d73
menurule: update to 0.8 2026-03-16 21:49:06 +01:00
Nikita Ivanov
50651a80d2
menu: update to 0.8 2026-03-16 21:44:32 +01:00
Nikita Ivanov
79de70e29e
swallow: update to 0.8 2026-03-16 21:25:48 +01:00
Nikita Ivanov
6abdb9f50c
setrule: update to 0.8 2026-03-16 21:17:03 +01:00
Nikita Ivanov
05895bbe1b
en-keycodes: update to 0.8 2026-03-16 21:06:48 +01:00
Nikita Ivanov
36439e54ba
kblayout: update to 0.8 2026-03-16 20:58:53 +01:00
Nikita Ivanov
870a322831
movestack: update to 0.8 2026-03-16 20:52:50 +01:00
Nikita Ivanov
5ccc71e0b5
centeredmaster: update to 0.8 2026-03-16 20:42:50 +01:00
Nikita Ivanov
52a7559a87
snail: fix formatting 2026-03-16 20:38:06 +01:00
Nikita Ivanov
ca06146655
Update snail patch to 0.8 2026-03-16 20:27:22 +01:00
27 changed files with 1642 additions and 15 deletions

View File

@ -24,7 +24,8 @@ With one and two clients in master respectively this results in:
### Download
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/centeredmaster/centeredmaster.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/centeredmaster/centeredmaster-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/centeredmaster/centeredmaster-0.7.patch)
- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/b104a580a80ebaf9f7e8917fe574e3e97ddd019a/centeredmaster/centeredmaster.patch)
- [0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/centeredmaster/centeredmaster.patch)

View File

@ -0,0 +1,114 @@
From 1fb8c15bb02c50279a5ccbd410dc1293abb747f3 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Sat, 8 Feb 2025 16:10:25 +0100
Subject: [PATCH] Add centeredmaster layout
---
config.def.h | 2 ++
dwl.c | 63 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 65 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..70954cc 100644
--- a/config.def.h
+++ b/config.def.h
@@ -33,6 +33,7 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
+ { "|M|", centeredmaster },
};
/* monitors */
@@ -135,6 +136,7 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XKB_KEY_c, setlayout, {.v = &layouts[3]} },
{ MODKEY, XKB_KEY_space, setlayout, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
diff --git a/dwl.c b/dwl.c
index 44f3ad9..bf859b0 100644
--- a/dwl.c
+++ b/dwl.c
@@ -248,6 +248,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
static void arrangelayers(Monitor *m);
static void axisnotify(struct wl_listener *listener, void *data);
static void buttonpress(struct wl_listener *listener, void *data);
+static void centeredmaster(Monitor *m);
static void chvt(const Arg *arg);
static void checkidleinhibitor(struct wlr_surface *exclude);
static void cleanup(void);
@@ -672,6 +673,68 @@ buttonpress(struct wl_listener *listener, void *data)
event->time_msec, event->button, event->state);
}
+void
+centeredmaster(Monitor *m)
+{
+ int i, n, h, mw, mx, my, oty, ety, tw;
+ Client *c;
+
+ n = 0;
+ wl_list_for_each(c, &clients, link)
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ n++;
+ if (n == 0)
+ return;
+
+ /* initialize areas */
+ mw = m->w.width;
+ mx = 0;
+ my = 0;
+ tw = mw;
+
+ if (n > m->nmaster) {
+ /* go mfact box in the center if more than nmaster clients */
+ mw = m->nmaster ? (int)roundf(m->w.width * m->mfact) : 0;
+ tw = m->w.width - mw;
+
+ if (n - m->nmaster > 1) {
+ /* only one client */
+ mx = (m->w.width - mw) / 2;
+ tw = (m->w.width - mw) / 2;
+ }
+ }
+
+ i = 0;
+ oty = 0;
+ ety = 0;
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ if (i < m->nmaster) {
+ /* nmaster clients are stacked vertically, in the center
+ * of the screen */
+ h = (m->w.height - my) / (MIN(n, m->nmaster) - i);
+ resize(c, (struct wlr_box){.x = m->w.x + mx, .y = m->w.y + my, .width = mw,
+ .height = h}, 0);
+ my += c->geom.height;
+ } else {
+ /* stack clients are stacked vertically */
+ if ((i - m->nmaster) % 2) {
+ h = (m->w.height - ety) / ( (1 + n - i) / 2);
+ resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + ety, .width = tw,
+ .height = h}, 0);
+ ety += c->geom.height;
+ } else {
+ h = (m->w.height - oty) / ((1 + n - i) / 2);
+ resize(c, (struct wlr_box){.x = m->w.x + mx + mw, .y = m->w.y + oty, .width = tw,
+ .height = h}, 0);
+ oty += c->geom.height;
+ }
+ }
+ i++;
+ }
+}
+
void
chvt(const Arg *arg)
{
--
2.53.0

View File

@ -2,8 +2,8 @@
Always use the English keymap to get keycodes, so key bindings work even when using a non-English keyboard layout.
### Download
- [git branch](https://codeberg.org/ForzCross/dwl/src/branch/en-keycodes.patch)
- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/en-keycodes/en-keycodes.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/en-keycodes/en-keycodes-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/en-keycodes/en-keycodes-0.7.patch)
### Authors
- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV))

View File

@ -0,0 +1,75 @@
From cde0921304c11c2e1341539a5ada4f7b9715b178 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Sun, 30 Nov 2025 23:22:42 +0100
Subject: [PATCH] Always use the English keymap to get keycodes
---
dwl.c | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/dwl.c b/dwl.c
index 44f3ad9..97ba897 100644
--- a/dwl.c
+++ b/dwl.c
@@ -436,6 +436,11 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
static struct wl_listener new_session_lock = {.notify = locksession};
+static struct xkb_rule_names en_rules;
+static struct xkb_context *en_context;
+static struct xkb_keymap *en_keymap;
+static struct xkb_state *en_state;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -718,6 +723,9 @@ cleanup(void)
wlr_backend_destroy(backend);
wl_display_destroy(dpy);
+ xkb_state_unref(en_state);
+ xkb_keymap_unref(en_keymap);
+ xkb_context_unref(en_context);
/* Destroy after the wayland display (when the monitors are already destroyed)
to avoid destroying them with an invalid scene output. */
wlr_scene_node_destroy(&scene->tree.node);
@@ -1632,16 +1640,19 @@ keypress(struct wl_listener *listener, void *data)
/* This event is raised when a key is pressed or released. */
KeyboardGroup *group = wl_container_of(listener, group, key);
struct wlr_keyboard_key_event *event = data;
+ int nsyms, handled;
/* Translate libinput keycode -> xkbcommon */
uint32_t keycode = event->keycode + 8;
/* Get a list of keysyms based on the keymap for this keyboard */
const xkb_keysym_t *syms;
- int nsyms = xkb_state_key_get_syms(
- group->wlr_group->keyboard.xkb_state, keycode, &syms);
-
- int handled = 0;
uint32_t mods = wlr_keyboard_get_modifiers(&group->wlr_group->keyboard);
+ xkb_state_update_key(en_state, keycode,
+ (event->state == WL_KEYBOARD_KEY_STATE_PRESSED)
+ ? XKB_KEY_DOWN : XKB_KEY_UP);
+ nsyms = xkb_state_key_get_syms(en_state, keycode, &syms);
+
+ handled = 0;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -2624,6 +2635,12 @@ setup(void)
* pointer, touch, and drawing tablet device. We also rig up a listener to
* let us know when new input devices are available on the backend.
*/
+ en_context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ en_rules = xkb_rules;
+ en_rules.layout = "us";
+ en_keymap = xkb_keymap_new_from_names(en_context, &en_rules,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+ en_state = xkb_state_new(en_keymap);
wl_signal_add(&backend->events.new_input, &new_input_device);
virtual_keyboard_mgr = wlr_virtual_keyboard_manager_v1_create(dpy);
wl_signal_add(&virtual_keyboard_mgr->events.new_virtual_keyboard,
--
2.53.0

View File

@ -23,8 +23,8 @@ any of these features, just disable it in `config.h`.
### Download
- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/kblayout)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/kblayout/kblayout.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/kblayout/kblayout-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/kblayout/kblayout-0.7.patch)
### Authors

View File

@ -0,0 +1,188 @@
From c80ab5d3dacc5cf22382f88287eeba5d298be483 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Tue, 4 Feb 2025 23:42:10 +0100
Subject: [PATCH] Add per client keyboard layout and status bar info
---
config.def.h | 3 +++
dwl.c | 71 +++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 73 insertions(+), 1 deletion(-)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..09b6e57 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,6 +13,9 @@ 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.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
+/* keyboard layout change notification for status bar */
+static const char kblayout_file[] = "/tmp/dwl-keymap";
+static const char *kblayout_cmd[] = {"pkill", "-RTMIN+3", "someblocks", NULL};
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
diff --git a/dwl.c b/dwl.c
index 44f3ad9..279571b 100644
--- a/dwl.c
+++ b/dwl.c
@@ -14,6 +14,7 @@
#include <wayland-server-core.h>
#include <wlr/backend.h>
#include <wlr/backend/libinput.h>
+#include <wlr/interfaces/wlr_keyboard.h>
#include <wlr/render/allocator.h>
#include <wlr/render/wlr_renderer.h>
#include <wlr/types/wlr_alpha_modifier_v1.h>
@@ -139,6 +140,7 @@ typedef struct {
uint32_t tags;
int isfloating, isurgent, isfullscreen;
uint32_t resize; /* configure serial of a pending resize */
+ unsigned int kblayout_idx;
} Client;
typedef struct {
@@ -291,6 +293,7 @@ static void gpureset(struct wl_listener *listener, void *data);
static void handlesig(int signo);
static void incnmaster(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
+static void kblayout(KeyboardGroup *kb);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
static void keypress(struct wl_listener *listener, void *data);
static void keypressmod(struct wl_listener *listener, void *data);
@@ -436,6 +439,8 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
static struct wl_listener new_session_lock = {.notify = locksession};
+static unsigned int kblayout_idx = -1;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -945,6 +950,8 @@ createkeyboard(struct wlr_keyboard *keyboard)
/* Add the new keyboard to the group */
wlr_keyboard_group_add_keyboard(kb_group->wlr_group, keyboard);
+
+ kblayout(kb_group);
}
KeyboardGroup *
@@ -1122,11 +1129,13 @@ createnotify(struct wl_listener *listener, void *data)
/* This event is raised when a client creates a new toplevel (application window). */
struct wlr_xdg_toplevel *toplevel = data;
Client *c = NULL;
+ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat);
/* Allocate a Client for this surface */
c = toplevel->base->data = ecalloc(1, sizeof(*c));
c->surface.xdg = toplevel->base;
c->bw = borderpx;
+ c->kblayout_idx = kb ? kb->modifiers.group : 0;
LISTEN(&toplevel->base->surface->events.commit, &c->commit, commitnotify);
LISTEN(&toplevel->base->surface->events.map, &c->map, mapnotify);
@@ -1402,10 +1411,24 @@ dirtomon(enum wlr_direction dir)
void
focusclient(Client *c, int lift)
{
+ /* Copied from wlroots/types/wlr_keyboard_group.c */
+ struct keyboard_group_device {
+ struct wlr_keyboard *keyboard;
+ struct wl_listener key;
+ struct wl_listener modifiers;
+ struct wl_listener keymap;
+ struct wl_listener repeat_info;
+ struct wl_listener destroy;
+ struct wl_list link; // wlr_keyboard_group.devices
+ };
+
struct wlr_surface *old = seat->keyboard_state.focused_surface;
int unused_lx, unused_ly, old_client_type;
Client *old_c = NULL;
LayerSurface *old_l = NULL;
+ struct keyboard_group_device *device;
+ struct wlr_keyboard *kb = wlr_seat_get_keyboard(seat);
+ struct wlr_keyboard_group *group = kb ? wlr_keyboard_group_from_wlr_keyboard(kb) : NULL;
if (locked)
return;
@@ -1457,6 +1480,20 @@ focusclient(Client *c, int lift)
}
printstatus();
+ /* Update keyboard layout */
+ if (group) {
+ // Update the first real device, because kb or group->kb is not a real
+ // keyboard and its effective layout gets overwritten
+ device = wl_container_of(group->devices.next, device, link);
+ if (device->keyboard)
+ wlr_keyboard_notify_modifiers(device->keyboard,
+ device->keyboard->modifiers.depressed,
+ device->keyboard->modifiers.latched,
+ device->keyboard->modifiers.locked,
+ c ? c->kblayout_idx : 0
+ );
+ }
+
if (!c) {
/* With no client, all we have left is to clear focus */
wlr_seat_keyboard_notify_clear_focus(seat);
@@ -1467,7 +1504,7 @@ focusclient(Client *c, int lift)
motionnotify(0, NULL, 0, 0, 0, 0);
/* Have a client, so focus its top-level wlr_surface */
- client_notify_enter(client_surface(c), wlr_seat_get_keyboard(seat));
+ client_notify_enter(client_surface(c), kb);
/* Activate the new client */
client_activate_surface(client_surface(c), 1);
@@ -1605,6 +1642,36 @@ inputdevice(struct wl_listener *listener, void *data)
wlr_seat_set_capabilities(seat, caps);
}
+void
+kblayout(KeyboardGroup *kb)
+{
+ FILE *f;
+ Client *c;
+ unsigned int idx = kb->wlr_group->keyboard.modifiers.group;
+
+ // If layout did not change, do nothing
+ if (kblayout_idx == idx)
+ return;
+ kblayout_idx = idx;
+
+ // Update client layout
+ if ((c = focustop(selmon)))
+ c->kblayout_idx = kblayout_idx;
+
+ // Save current layout to kblayout_file
+ if (*kblayout_file && (f = fopen(kblayout_file, "w"))) {
+ fputs(xkb_keymap_layout_get_name(kb->wlr_group->keyboard.keymap,
+ idx), f);
+ fclose(f);
+ }
+
+ // Run kblayout_cmd
+ if (kblayout_cmd[0] && fork() == 0) {
+ execvp(kblayout_cmd[0], (char *const *)kblayout_cmd);
+ die("dwl: execvp %s failed:", kblayout_cmd[0]);
+ }
+}
+
int
keybinding(uint32_t mods, xkb_keysym_t sym)
{
@@ -1683,6 +1750,8 @@ keypressmod(struct wl_listener *listener, void *data)
/* Send modifiers to the client. */
wlr_seat_keyboard_notify_modifiers(seat,
&group->wlr_group->keyboard.modifiers);
+
+ kblayout(group);
}
int
--
2.53.0

View File

@ -11,8 +11,9 @@ Edit `menus` array in `config.h` to add/change menus and use a different dmenu
program (`wmenu` is the default).
### Download
- [2025-03-21 v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/menu/menu.patch)
- [2024-07-13 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/menu/menu.patch)
- [v0.8](/dwl-patches/raw/branch/main/patches/menu/menu-0.8.patch)
- [2025-03-21 v0.7](/dwl/dwl-patches/raw/branch/main/patches/menu/menu-0.7.patch)
- [2024-07-13 v0.7](/dwl/dwl-patches/raw/commit/65ea99519bbf7a52f48932aea7385f81f8b30867/patches/menu/menu.patch)
### Authors
- [Nikita Ivanov](https://codeberg.org/nikitaivanov) ([GitHub](https://github.com/NikitaIvanovV))

227
patches/menu/menu-0.8.patch Normal file
View File

@ -0,0 +1,227 @@
From 4347c2fd720166c526384827ef4f586a84d22040 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Fri, 21 Mar 2025 21:48:42 +0100
Subject: [PATCH] Add menu command
---
config.def.h | 8 +++
dwl.c | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 164 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..1de52fa 100644
--- a/config.def.h
+++ b/config.def.h
@@ -20,6 +20,12 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca
/* logging */
static int log_level = WLR_ERROR;
+static const Menu menus[] = {
+ /* command feed function action function */
+ { "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction },
+ { "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction },
+};
+
static const Rule rules[] = {
/* app_id title tags mask isfloating monitor */
{ "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */
@@ -136,6 +142,8 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
{ MODKEY, XKB_KEY_space, setlayout, {0} },
+ { MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
{ MODKEY, XKB_KEY_0, view, {.ui = ~0} },
diff --git a/dwl.c b/dwl.c
index 44f3ad9..7e0e7b6 100644
--- a/dwl.c
+++ b/dwl.c
@@ -239,6 +239,12 @@ typedef struct {
struct wl_listener destroy;
} SessionLock;
+typedef struct {
+ const char *cmd; /* command to run a menu */
+ void (*feed)(FILE *f); /* feed input to menu */
+ void (*action)(char *line); /* do action based on menu output */
+} Menu;
+
/* function declarations */
static void applybounds(Client *c, struct wlr_box *bbox);
static void applyrules(Client *c);
@@ -299,6 +305,12 @@ static void killclient(const Arg *arg);
static void locksession(struct wl_listener *listener, void *data);
static void mapnotify(struct wl_listener *listener, void *data);
static void maximizenotify(struct wl_listener *listener, void *data);
+static void menu(const Arg *arg);
+static int menuread(int fd, uint32_t mask, void *data);
+static void menuwinfeed(FILE *f);
+static void menuwinaction(char *line);
+static void menulayoutfeed(FILE *f);
+static void menulayoutaction(char *line);
static void monocle(Monitor *m);
static void motionabsolute(struct wl_listener *listener, void *data);
static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx,
@@ -436,6 +448,11 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
static struct wl_listener new_session_lock = {.notify = locksession};
+static const Menu *menu_current;
+static int menu_fd;
+static pid_t menu_pid;
+static struct wl_event_source *menu_source;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -1820,6 +1837,145 @@ maximizenotify(struct wl_listener *listener, void *data)
wlr_xdg_surface_schedule_configure(c->surface.xdg);
}
+void
+menu(const Arg *arg)
+{
+ FILE *f;
+ int fd_right[2], fd_left[2];
+
+ if (menu_current != NULL) {
+ wl_event_source_remove(menu_source);
+ close(menu_fd);
+ kill(menu_pid, SIGTERM);
+ menu_current = NULL;
+ if (!arg->v)
+ return;
+ }
+
+ if (pipe(fd_right) == -1 || pipe(fd_left) == -1)
+ return;
+ if ((menu_pid = fork()) == -1)
+ return;
+ if (menu_pid == 0) {
+ close(fd_right[1]);
+ close(fd_left[0]);
+ dup2(fd_right[0], STDIN_FILENO);
+ close(fd_right[0]);
+ dup2(fd_left[1], STDOUT_FILENO);
+ close(fd_left[1]);
+ execl("/bin/sh", "/bin/sh", "-c", ((Menu *)(arg->v))->cmd, NULL);
+ die("dwl: execl %s failed:", "/bin/sh");
+ }
+
+ close(fd_right[0]);
+ close(fd_left[1]);
+ menu_fd = fd_left[0];
+ if (fd_set_nonblock(menu_fd) == -1)
+ return;
+ if (!(f = fdopen(fd_right[1], "w")))
+ return;
+ menu_current = arg->v;
+ menu_current->feed(f);
+ fclose(f);
+ menu_source = wl_event_loop_add_fd(event_loop,
+ menu_fd, WL_EVENT_READABLE, menuread, NULL);
+}
+
+int
+menuread(int fd, uint32_t mask, void *data)
+{
+ char *s;
+ int n;
+ static char line[512];
+ static int i = 0;
+
+ if (mask & (WL_EVENT_HANGUP | WL_EVENT_ERROR)) {
+ i = 0;
+ menu(&(const Arg){ .v = NULL });
+ return 0;
+ }
+ if ((n = read(menu_fd, line + i, LENGTH(line) - 1 - i)) == -1) {
+ if (errno != EAGAIN) {
+ i = 0;
+ menu(&(const Arg){ .v = NULL });
+ }
+ return 0;
+ }
+ line[i + n] = '\0';
+ if (!(s = strchr(line + i, '\n'))) {
+ i += n;
+ return 0;
+ }
+ i = 0;
+ *s = '\0';
+ menu_current->action(line);
+ return 0;
+}
+
+void
+menuwinfeed(FILE *f)
+{
+ Client *c;
+ const char *title, *appid;
+
+ wl_list_for_each(c, &fstack, flink) {
+ if (!(title = client_get_title(c)))
+ continue;
+ fprintf(f, "%s", title);
+ if ((appid = client_get_appid(c)))
+ fprintf(f, " | %s", appid);
+ fputc('\n', f);
+ }
+}
+
+void
+menuwinaction(char *line)
+{
+ Client *c;
+ const char *appid, *title;
+ static char buf[512];
+
+ wl_list_for_each(c, &fstack, flink) {
+ if (!(title = client_get_title(c)))
+ continue;
+ appid = client_get_appid(c);
+ snprintf(buf, LENGTH(buf) - 1, "%s%s%s",
+ title, appid ? " | " : "", appid ? appid : "");
+ if (strcmp(line, buf) == 0)
+ goto found;
+ }
+ return;
+
+found:
+ if (!c->mon)
+ return;
+ wl_list_remove(&c->flink);
+ wl_list_insert(&fstack, &c->flink);
+ selmon = c->mon;
+ view(&(const Arg){ .ui = c->tags });
+}
+
+void
+menulayoutfeed(FILE *f)
+{
+ const Layout *l;
+ for (l = layouts; l < END(layouts); l++)
+ fprintf(f, "%s\n", l->symbol);
+}
+
+void
+menulayoutaction(char *line)
+{
+ const Layout *l;
+ for (l = layouts; l < END(layouts); l++)
+ if (strcmp(line, l->symbol) == 0)
+ goto found;
+ return;
+
+found:
+ setlayout(&(const Arg){ .v = l });
+}
+
void
monocle(Monitor *m)
{
--
2.53.0

View File

@ -63,7 +63,8 @@ index 34397fc..f1b31ea 100644
### Download
- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/menurule/menurule.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/menurule/menurule-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/menurule/menurule-0.7.patch)
### Authors

View File

@ -0,0 +1,169 @@
From ffbc9d95316ec42e3f8e08816bfcf7b91a6801b6 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Wed, 19 Mar 2025 02:28:46 +0100
Subject: [PATCH] Add menurule to tweak rules at runtime
---
config.def.h | 2 +
dwl.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 120 insertions(+)
diff --git a/config.def.h b/config.def.h
index 77a72a5..e7601a4 100644
--- a/config.def.h
+++ b/config.def.h
@@ -24,6 +24,7 @@ static const Menu menus[] = {
/* command feed function action function */
{ "wmenu -i -l 10 -p Windows", menuwinfeed, menuwinaction },
{ "wmenu -i -p Layouts", menulayoutfeed, menulayoutaction },
+ { "wmenu -i -l 10 -p Rules", menurulefeed, menuruleaction },
};
/* Max amount of dynamically added rules */
@@ -147,6 +148,7 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_space, setlayout, {0} },
{ MODKEY, XKB_KEY_o, menu, {.v = &menus[0]} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, menu, {.v = &menus[1]} },
+ { MODKEY, XKB_KEY_r, menu, {.v = &menus[2]} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{0} },
diff --git a/dwl.c b/dwl.c
index 3d65e16..49daddb 100644
--- a/dwl.c
+++ b/dwl.c
@@ -312,6 +312,8 @@ static void menuwinfeed(FILE *f);
static void menuwinaction(char *line);
static void menulayoutfeed(FILE *f);
static void menulayoutaction(char *line);
+static void menurulefeed(FILE *f);
+static void menuruleaction(char *line);
static void monocle(Monitor *m);
static void motionabsolute(struct wl_listener *listener, void *data);
static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx,
@@ -2026,6 +2028,122 @@ found:
setlayout(&(const Arg){ .v = l });
}
+void
+menurulefeed(FILE *f)
+{
+ Rule t, *p, *r;
+ const char *appid, *title;
+ static char buf[515];
+ Client *c = focustop(selmon);
+ int n, wid = 0, match;
+
+ t = (Rule){ 0 };
+ t.monitor = -1;
+ if (c) {
+ t.id = appid = client_get_appid(c);
+ t.title = title = client_get_title(c);
+ if (strcmp(t.id, "broken") == 0)
+ t.id = NULL;
+ if (strcmp(t.title, "broken") == 0)
+ t.title = NULL;
+ }
+
+ for (p = drules; p <= drules + druleslen; p++) {
+ r = (p == drules + druleslen) ? &t : p;
+ n = 0;
+ n += strlen(r->id ? r->id : "NULL");
+ n += strlen(r->title ? r->title : "NULL");
+ n += 3;
+ wid = MAX(wid, n);
+ }
+ wid = MIN(wid, 40);
+
+ for (p = drules; p <= drules + druleslen; p++) {
+ match = 0;
+ /* Check if rule applies to the focused client */
+ if (c && p < drules + druleslen) {
+ match = (!p->title || strstr(title, p->title))
+ && (!p->id || strstr(appid, p->id));
+ if (match && p->id)
+ t.id = NULL;
+ if (match && p->title)
+ t.title = NULL;
+ }
+ r = (p == drules + druleslen) ? &t : p;
+ if (r == &t && t.id)
+ t.title = NULL;
+ /* Do not suggest to add a new empty rule */
+ if (r == &t && !(t.id || t.title))
+ continue;
+ if (r->id && r->title &&
+ strcmp(r->id, "removedrule") == 0 && strcmp(r->title, "removedrule") == 0)
+ continue;
+ snprintf(buf, LENGTH(buf) - 1, "[%s|%s]",
+ r->id ? r->id : "NULL", r->title ? r->title : "NULL");
+ fprintf(f, "%-*s "
+ " tags:%-4"PRIi32
+ " isfloating:%-2d"
+ " monitor:%-2d"
+ "%s\n", wid, buf,
+ r->tags,
+ r->isfloating,
+ r->monitor,
+ (r == &t) ? " (NEW)" : match ? " <" : "");
+ }
+}
+
+void
+menuruleaction(char *line)
+{
+ Rule r, *f;
+ static char appid[256], title[256];
+ int del = 0, end;
+
+ if (line[0] == '-') {
+ del = 1;
+ line++;
+ }
+ end = 0;
+ sscanf(line, "[%255[^|]|%255[^]]]"
+ " tags:%"SCNu32
+ " isfloating:%d"
+ " monitor:%d"
+ "%n", appid, title,
+ &r.tags,
+ &r.isfloating,
+ &r.monitor,
+ &end);
+
+ /* Full line was not parsed, exit */
+ if (!end)
+ return;
+
+ r.id = (strcmp(appid, "NULL") != 0) ? appid : NULL;
+ r.title = (strcmp(title, "NULL") != 0) ? title : NULL;
+
+ /* Find which rule we are trying to edit */
+ for (f = drules; f < drules + druleslen; f++)
+ if (((!r.title && !f->title) || (r.title && f->title && !strcmp(r.title, f->title)))
+ && (((!r.id && !f->id) || (r.id && f->id && !strcmp(r.id, f->id)))))
+ goto found;
+
+ if (druleslen >= LENGTH(rules) + RULES_MAX)
+ return; /* No free slots left */
+
+ f = drules + druleslen++;
+ f->id = r.id ? strdup(r.id) : NULL;
+ f->title = r.title ? strdup(r.title) : NULL;
+
+found:
+ if (del) {
+ f->id = f->title = "removedrule";
+ return;
+ }
+ r.id = f->id;
+ r.title = f->title;
+ *f = r;
+}
+
void
monocle(Monitor *m)
{
--
2.53.0

View File

@ -2,8 +2,8 @@
Allows you to move a window up and down the stack.
### Download
- [git branch](https://codeberg.org/nikitaivanov/dwl/src/branch/movestack)
- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/movestack/movestack.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/movestack/movestack-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/movestack/movestack-0.7.patch)
### Authors
- [wochap](https://codeberg.org/wochap)

View File

@ -0,0 +1,87 @@
From 7e8de6e79d24114272b22b55c906d7b55c36173a Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Mon, 16 Mar 2026 20:50:44 +0100
Subject: [PATCH] Add movestack
---
config.def.h | 2 ++
dwl.c | 43 +++++++++++++++++++++++++++++++++++++++++++
2 files changed, 45 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..f490e24 100644
--- a/config.def.h
+++ b/config.def.h
@@ -125,6 +125,8 @@ static const Key keys[] = {
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} },
{ MODKEY, XKB_KEY_j, focusstack, {.i = +1} },
{ MODKEY, XKB_KEY_k, focusstack, {.i = -1} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_j, movestack, {.i = +1} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_k, movestack, {.i = -1} },
{ MODKEY, XKB_KEY_i, incnmaster, {.i = +1} },
{ MODKEY, XKB_KEY_d, incnmaster, {.i = -1} },
{ MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
diff --git a/dwl.c b/dwl.c
index 44f3ad9..726fbdd 100644
--- a/dwl.c
+++ b/dwl.c
@@ -300,6 +300,7 @@ static void locksession(struct wl_listener *listener, void *data);
static void mapnotify(struct wl_listener *listener, void *data);
static void maximizenotify(struct wl_listener *listener, void *data);
static void monocle(Monitor *m);
+static void movestack(const Arg *arg);
static void motionabsolute(struct wl_listener *listener, void *data);
static void motionnotify(uint32_t time, struct wlr_input_device *device, double sx,
double sy, double sx_unaccel, double sy_unaccel);
@@ -1838,6 +1839,48 @@ monocle(Monitor *m)
wlr_scene_node_raise_to_top(&c->scene->node);
}
+void
+movestack(const Arg *arg)
+{
+ Client *c, *sel = focustop(selmon);
+
+ if (!sel) {
+ return;
+ }
+
+ if (wl_list_length(&clients) <= 1) {
+ return;
+ }
+
+ if (arg->i > 0) {
+ wl_list_for_each(c, &sel->link, link) {
+ if (&c->link == &clients) {
+ c = wl_container_of(&clients, c, link);
+ break; /* wrap past the sentinel node */
+ }
+ if (VISIBLEON(c, selmon) || &c->link == &clients) {
+ break; /* found it */
+ }
+ }
+ } else {
+ wl_list_for_each_reverse(c, &sel->link, link) {
+ if (&c->link == &clients) {
+ c = wl_container_of(&clients, c, link);
+ break; /* wrap past the sentinel node */
+ }
+ if (VISIBLEON(c, selmon) || &c->link == &clients) {
+ break; /* found it */
+ }
+ }
+ /* backup one client */
+ c = wl_container_of(c->link.prev, c, link);
+ }
+
+ wl_list_remove(&sel->link);
+ wl_list_insert(&c->link, &sel->link);
+ arrange(selmon);
+}
+
void
motionabsolute(struct wl_listener *listener, void *data)
{
--
2.53.0

View File

@ -38,7 +38,8 @@ with dmenu.
### Download
- [v0.7](/dwl/dwl-patches/raw/branch/main/patches/setrule/setrule.patch)
- [0.8](/dwl/dwl-patches/raw/branch/main/patches/setrule/setrule-0.8.patch)
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/setrule/setrule-0.7.patch)
- [2025-02-14 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/268bee3cee239e5bd52cceed88a52bfc21143cc3/patches/setrule/setrule.patch)
### Authors

View File

@ -0,0 +1,153 @@
From b46440c982a5149570bb58369f84ef5082279123 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Sun, 9 Feb 2025 23:12:09 +0100
Subject: [PATCH] setrule: add/change rules at runtime
---
config.def.h | 4 ++++
dwl.c | 65 +++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 68 insertions(+), 1 deletion(-)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..48acf2d 100644
--- a/config.def.h
+++ b/config.def.h
@@ -20,6 +20,9 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca
/* logging */
static int log_level = WLR_ERROR;
+/* Max amount of dynamically added rules */
+#define RULES_MAX 100
+
static const Rule rules[] = {
/* app_id title tags mask isfloating monitor */
{ "Gimp_EXAMPLE", NULL, 0, 1, -1 }, /* Start on currently visible tags floating, not tiled */
@@ -138,6 +141,7 @@ 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|WLR_MODIFIER_SHIFT, XKB_KEY_R, setruleisfloating,{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 44f3ad9..3c765d1 100644
--- a/dwl.c
+++ b/dwl.c
@@ -287,6 +287,7 @@ static void focusmon(const Arg *arg);
static void focusstack(const Arg *arg);
static Client *focustop(Monitor *m);
static void fullscreennotify(struct wl_listener *listener, void *data);
+static Rule *getrule(Client *c);
static void gpureset(struct wl_listener *listener, void *data);
static void handlesig(int signo);
static void incnmaster(const Arg *arg);
@@ -327,6 +328,7 @@ static void setlayout(const Arg *arg);
static void setmfact(const Arg *arg);
static void setmon(Client *c, Monitor *m, uint32_t newtags);
static void setpsel(struct wl_listener *listener, void *data);
+static void setruleisfloating(const Arg *arg);
static void setsel(struct wl_listener *listener, void *data);
static void setup(void);
static void spawn(const Arg *arg);
@@ -436,6 +438,9 @@ static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
static struct wl_listener new_session_lock = {.notify = locksession};
+static Rule *drules;
+static size_t druleslen;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -486,7 +491,7 @@ applyrules(Client *c)
appid = client_get_appid(c);
title = client_get_title(c);
- for (r = rules; r < END(rules); r++) {
+ for (r = drules; r < drules + druleslen; r++) {
if ((!r->title || strstr(title, r->title))
&& (!r->id || strstr(appid, r->id))) {
c->isfloating = r->isfloating;
@@ -1532,6 +1537,51 @@ fullscreennotify(struct wl_listener *listener, void *data)
setfullscreen(c, client_wants_fullscreen(c));
}
+Rule *
+getrule(Client *c)
+{
+ Rule *r;
+ const Rule *e;
+ const char *appid, *title;
+
+ if (!c)
+ return NULL;
+
+ appid = client_get_appid(c);
+ title = client_get_title(c);
+
+ for (r = drules + druleslen - 1; r >= drules; r--)
+ if ((!r->title || strstr(title, r->title))
+ && (!r->id || strstr(appid, r->id)))
+ goto found;
+
+ if (druleslen >= LENGTH(rules) + RULES_MAX)
+ return NULL; /* No free slots left */
+
+ r = drules + druleslen++;
+
+ /* Use [NULL,NULL] as the default rule if exists */
+ for (e = rules; e < END(rules); e++)
+ if (!e->title && !e->id) {
+ *r = *e;
+ break;
+ }
+
+ /* No default rule found, set reasoble defaults */
+ if (e >= END(rules)) {
+ r->monitor = -1;
+ }
+
+ /* Only set title if appid is unset */
+ if (strcmp(appid, "broken") == 0)
+ r->title = strdup(title);
+ else
+ r->id = strdup(appid);
+
+found:
+ return r;
+}
+
void
gpureset(struct wl_listener *listener, void *data)
{
@@ -2431,6 +2481,15 @@ setpsel(struct wl_listener *listener, void *data)
wlr_seat_set_primary_selection(seat, event->source, event->serial);
}
+void
+setruleisfloating(const Arg *arg)
+{
+ Rule *r = getrule(focustop(selmon));
+ if (!r)
+ return;
+ r->isfloating = !r->isfloating;
+}
+
void
setsel(struct wl_listener *listener, void *data)
{
@@ -2664,6 +2723,10 @@ setup(void)
fprintf(stderr, "failed to setup XWayland X server, continuing without it\n");
}
#endif
+
+ drules = ecalloc(LENGTH(rules) + RULES_MAX, sizeof(Rule));
+ memcpy(drules, rules, sizeof(rules));
+ druleslen = LENGTH(rules);
}
void
--
2.53.0

View File

@ -1,9 +1,106 @@
### Description
Adds a spiral-inspired layout for wide screens.
This layout is a scalable alternative to the "tile" and "spiral" layouts, optimized for wide monitors. Both the master area and the stack are "spirals", but windows in the master area are split horizontally as long as the master area has enough horizontal space, and the first window in the stack is split vertically unless the stack is wide.
With one window in the master area and `mfact = 0.5`, it behaves like the spiral layout:
```
┌───────────────┬────────────────┐
│ │ │
│ │ │
│ │ │
│ │ │
│ ├───┬───┬────────┤
│ │ │ │ │
│ ├───┴───┤ │
│ │ │ │
│ │ │ │
└───────────────┴───────┴────────┘
```
With 2 windows in the master area and 2 in the stack:
```
┌───────────────┬────────────────┐
│ │ │
│ │ │
│ │ │
│ │ │
├───────────────┼────────────────┤
│ │ │
│ │ │
│ │ │
│ │ │
└───────────────┴────────────────┘
```
With 3 windows in the master area and 2 in the stack:
```
┌───────────────┬────────────────┐
│ │ │
│ │ │
│ │ │
│ │ │
├───────┬───────┼────────────────┤
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
└───────┴───────┴────────────────┘
```
With many windows in both areas:
```
┌───────────────┬────────────────┐
│ │ │
│ │ │
│ │ │
│ │ │
├───┬───┬───────┼───┬───┬────────┤
│ │ │ │ │ │ │
├───┴───┤ ├───┴───┤ │
│ │ │ │ │
│ │ │ │ │
└───────┴───────┴───────┴────────┘
```
With 2 windows in the master area, many windows in the stack and high mfact:
```
┌──────────┬──────────┬──────────┐
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
│ master ├──┬stack──┤
│ │ │ │ │ │
│ │ ├──┴──┤ │
│ │ │ │ │
│ │ │ │ │
└──────────┴──────────┴─────┴────┘
```
With 2 windows in the master area, many windows in the stack and low mfact:
```
┌──────────┬──────────┬──────────┐
│ │ │ │
│ │ │ │
│ │ │ │
│ │ │ │
├──master──┤ stack┬──┬────┤
│ │ │ │ │ │
│ │ ├──┴──┤ │
│ │ │ │ │
│ │ │ │ │
└──────────┴──────────┴─────┴────┘
```
### Download
- [2024-02-11](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail/snail.patch)
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail/snail-0.8.patch)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/snail/snail-0.7.patch)
### Authors
- [Dima Krasner](https://codeberg.org/dimkr) (<dima@dimakrasner.com>)
- [Nikita Ivanov](https://github.com/NikitaIvanovV) (fix for flickering)
- [Nikita Ivanov](https://github.com/NikitaIvanovV)

View File

@ -0,0 +1,162 @@
From 9c97743480e901029fce2a8ccdde4f8b6a34040a Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Tue, 25 Mar 2025 02:49:43 +0100
Subject: [PATCH] Add snail layout
---
config.def.h | 4 +-
dwl.c | 102 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 105 insertions(+), 1 deletion(-)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..5080a2c 100644
--- a/config.def.h
+++ b/config.def.h
@@ -33,6 +33,7 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
+ { "@|@", snail },
};
/* monitors */
@@ -43,7 +44,7 @@ static const MonitorRule monrules[] = {
/* name mfact nmaster scale layout rotate/reflect x y
* example of a HiDPI laptop monitor:
{ "eDP-1", 0.5f, 1, 2, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 }, */
- { NULL, 0.55f, 1, 1, &layouts[0], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 },
+ { NULL, 0.64f, 1, 1, &layouts[3], WL_OUTPUT_TRANSFORM_NORMAL, -1, -1 },
/* default monitor rule: can be changed but cannot be eliminated; at least one monitor rule must exist */
};
@@ -135,6 +136,7 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
+ { MODKEY, XKB_KEY_s, setlayout, {.v = &layouts[3]} },
{ MODKEY, XKB_KEY_space, setlayout, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
diff --git a/dwl.c b/dwl.c
index 44f3ad9..1250fb7 100644
--- a/dwl.c
+++ b/dwl.c
@@ -329,6 +329,7 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
static void setpsel(struct wl_listener *listener, void *data);
static void setsel(struct wl_listener *listener, void *data);
static void setup(void);
+static void snail(Monitor *m);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -2666,6 +2667,107 @@ setup(void)
#endif
}
+void
+snail(Monitor *m)
+{
+ int i = 0, n = 0;
+ unsigned int mw = m->w.width;
+ Client *c, *prev;
+ enum wlr_direction dir = WLR_DIRECTION_RIGHT;
+
+ wl_list_for_each(c, &clients, link)
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ n++;
+ if (n == 0)
+ return;
+
+ if (n > m->nmaster)
+ mw = m->nmaster ? (int)round(m->w.width * m->mfact) : 0;
+
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+
+ /*
+ * If the master area exists and this is the first window, fill the
+ * master area with this window
+ */
+ if (mw > 0 && i == 0) {
+ c->geom = (struct wlr_box){.x = m->w.x, .y = m->w.y,
+ .width = mw, .height = m->w.height};
+ /*
+ * If the first window in the master area is wide, split it
+ * horizontally and put next one on its right; otherwise, split it
+ * vertically and put the next one below it
+ */
+ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN;
+ /*
+ * If the master area is full or doesn't exist, fill the stack with the
+ * m->nmaster-th window
+ */
+ } else if (i == m->nmaster) {
+ c->geom = (struct wlr_box){.x = m->w.x + mw, .y = m->w.y,
+ .width = m->w.width - mw, .height = m->w.height};
+ /*
+ * If the first window in the stack is wide, split it horizontally
+ * and put next one on its right; otherwise, split it vertically and
+ * put the next one below it
+ */
+ dir = c->geom.width > m->w.height ? WLR_DIRECTION_RIGHT : WLR_DIRECTION_DOWN;
+ /*
+ * Split the previous horizontally and put the current window on the right
+ */
+ } else if (dir == WLR_DIRECTION_RIGHT) {
+ c->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y,
+ .width = prev->geom.width / 2, .height = prev->geom.height};
+ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y,
+ .width = prev->geom.width / 2, .height = prev->geom.height};
+ /*
+ * If it's a stack window or the first narrow window in the master
+ * area, put the next one below it
+ */
+ if (i >= m->nmaster || c->geom.width < m->w.height)
+ dir = WLR_DIRECTION_DOWN;
+ /*
+ * Split the previous vertically and put the current window below it
+ */
+ } else if (dir == WLR_DIRECTION_DOWN) {
+ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2,
+ .width = prev->geom.width, .height = prev->geom.height / 2};
+ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y,
+ .width = prev->geom.width, .height = prev->geom.height / 2};
+ dir = WLR_DIRECTION_LEFT;
+ /*
+ * Split the previous horizontally and put the current window on the left
+ */
+ } else if (dir == WLR_DIRECTION_LEFT) {
+ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y,
+ .width = prev->geom.width / 2, .height = prev->geom.height};
+ prev->geom = (struct wlr_box){.x = prev->geom.x + prev->geom.width / 2, .y = prev->geom.y,
+ .width = prev->geom.width / 2, .height = prev->geom.height};
+ dir = WLR_DIRECTION_UP;
+ /*
+ * Split the previous vertically and put the current window above it
+ */
+ } else {
+ c->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y,
+ .width = prev->geom.width, .height = prev->geom.height / 2};
+ prev->geom = (struct wlr_box){.x = prev->geom.x, .y = prev->geom.y + prev->geom.height / 2,
+ .width = prev->geom.width, .height = prev->geom.height / 2};
+ dir = WLR_DIRECTION_RIGHT;
+ }
+ i++;
+ prev = c;
+ }
+
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+
+ resize(c, c->geom, 0);
+ }
+}
+
void
spawn(const Arg *arg)
{
--
2.53.0

View File

@ -22,7 +22,8 @@ scratch to make it more robust and add a few more features:
#### swallow.patch
- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow.patch)
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow-0.8.patch)
- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swallow/swallow-0.7.patch)
- [2025-03-03 v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/2e5748edfe1129f95c7bb1bf9dd590a897f55f57/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)

View File

@ -0,0 +1,350 @@
From 66efeab90dad4b6702a17b771cb08aeef159506a 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 | 11 +++-
dwl.c | 152 +++++++++++++++++++++++++++++++++++++++++++++++++--
3 files changed, 168 insertions(+), 7 deletions(-)
diff --git a/client.h b/client.h
index d9f90bb..83e1c57 100644
--- a/client.h
+++ b/client.h
@@ -131,6 +131,18 @@ client_get_appid(Client *c)
return c->surface.xdg->toplevel->app_id ? c->surface.xdg->toplevel->app_id : "broken";
}
+static inline int
+client_get_pid(Client *c)
+{
+ pid_t pid;
+#ifdef XWAYLAND
+ if (client_is_x11(c))
+ return c->surface.xwayland->pid;
+#endif
+ wl_client_get_credentials(c->surface.xdg->client->client, &pid, NULL, NULL);
+ return pid;
+}
+
static inline void
client_get_clip(Client *c, struct wlr_box *clip)
{
diff --git a/config.def.h b/config.def.h
index 8a6eda0..1a82020 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,6 +13,8 @@ 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.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
+static int enableautoswallow = 1; /* enables autoswallowing newly spawned clients */
+static float swallowborder = 1.0f; /* add this multiplied by borderpx to border when a client is swallowed */
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -21,9 +23,10 @@ static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You ca
static int log_level = WLR_ERROR;
static const Rule rules[] = {
- /* app_id title tags mask isfloating monitor */
- { "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" */
+ /* app_id title tags mask isfloating isterm noswallow monitor */
+ { "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" */
/* default/example rule: can be changed but cannot be eliminated; at least one rule must exist */
};
@@ -138,6 +141,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 44f3ad9..066b705 100644
--- a/dwl.c
+++ b/dwl.c
@@ -74,12 +74,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 { struct wl_listener *_l = ecalloc(1, sizeof(*_l)); _l->notify = (H); wl_signal_add((E), _l); } while (0)
+#define BORDERPX(C) (borderpx + ((C)->swallowing ? (int)ceilf(swallowborder * (C)->swallowing->bw) : 0))
/* enums */
enum { CurNormal, CurPressed, CurMove, CurResize }; /* cursor */
@@ -101,7 +102,8 @@ typedef struct {
} Button;
typedef struct Monitor Monitor;
-typedef struct {
+typedef struct Client Client;
+struct Client {
/* Must keep this field first */
unsigned int type; /* XDGShell or X11* */
@@ -138,8 +140,12 @@ typedef struct {
unsigned int bw;
uint32_t tags;
int isfloating, isurgent, isfullscreen;
+ int isterm, noswallow;
uint32_t resize; /* configure serial of a pending resize */
-} Client;
+ pid_t pid;
+ Client *swallowing; /* client being hidden */
+ Client *swallowedby;
+};
typedef struct {
uint32_t mod;
@@ -227,6 +233,8 @@ typedef struct {
const char *title;
uint32_t tags;
int isfloating;
+ int isterm;
+ int noswallow;
int monitor;
} Rule;
@@ -308,6 +316,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);
@@ -331,11 +340,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);
@@ -486,11 +499,15 @@ applyrules(Client *c)
appid = client_get_appid(c);
title = client_get_title(c);
+ c->pid = client_get_pid(c);
+
for (r = rules; r < END(rules); r++) {
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;
i = 0;
wl_list_for_each(m, &mons, link) {
if (r->monitor == i++)
@@ -500,6 +517,12 @@ applyrules(Client *c)
}
c->isfloating |= client_is_float_type(c);
+ if (enableautoswallow && !c->noswallow && !c->isfloating &&
+ !c->surface.xdg->initial_commit) {
+ Client *p = termforwin(c);
+ if (p)
+ swallow(c, p);
+ }
setmon(c, mon, newtags);
}
@@ -2057,6 +2080,20 @@ outputmgrtest(struct wl_listener *listener, void *data)
outputmgrapplyortest(config, 1);
}
+pid_t
+parentpid(pid_t pid)
+{
+ unsigned int v = 0;
+ FILE *f;
+ char buf[256];
+ 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;
+}
+
void
pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
uint32_t time)
@@ -2351,7 +2388,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]);
@@ -2418,6 +2455,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
@@ -2688,6 +2728,44 @@ startdrag(struct wl_listener *listener, void *data)
LISTEN_STATIC(&drag->icon->events.destroy, destroydragicon);
}
+void
+swallow(Client *c, Client *toswallow)
+{
+ /* 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
tag(const Arg *arg)
{
@@ -2709,6 +2787,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)
{
@@ -2760,6 +2872,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)
{
@@ -2820,6 +2958,12 @@ unmapnotify(struct wl_listener *listener, void *data)
grabc = NULL;
}
+ 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;
--
2.53.0