Merge branch 'main' into bar-notitle

This commit is contained in:
pi66
2026-03-27 19:09:59 +01:00
98 changed files with 5936 additions and 1914 deletions
+2 -2
View File
@@ -47,9 +47,9 @@ If you target the unstable `main` branch, specify that in the `Download` link on
You may choose to include screenshots (hosted in your patch's subdirectory) in your `README.md`. The process is described [here](https://docs.codeberg.org/markdown/using-images/).
8. Use the Codeberg web interface to send a pull request to [dwl-patches] (NOT to [dwl])
9. WHEN YOUR PULL REQUEST IS APPROVED, your Codeberg account will also be granted commit access to [dwl-patches]. Once you have write access, you can make direct modifications/upates to your patches and you are free to create new patches rather than creating pull requests.
9. WHEN YOUR PULL REQUEST IS APPROVED, your Codeberg account will also be granted commit access to [dwl-patches]. Once you have write access, you can make direct modifications/updates to your patches and you are free to create new patches rather than creating pull requests.
Individuals who have made known that they no longer intend to maintain their patches will have commit access to the [dwl-pathces] repository removed.
Individuals who have made known that they no longer intend to maintain their patches will have commit access to the [dwl-patches] repository removed.
A returning user who formerly had commit access is welcome to open an issue on [dwl-patches] requesting commit access be reinstated. When doing so, please link to the original issue opened that granted commit access.
+31
View File
@@ -0,0 +1,31 @@
![dwl bar-modes patch example](./example.png)
### Description
Add a mode indicator to bar that tells which mode you are in, just like
river-classic's [dam](https://codeberg.org/sewn/dam) bar.
The string from `modes_labels` defined in `config.h` is used, while normal mode
is ignored.
Another usage is to serve as a hint for each modes keybindings:
```c
enum {
BROWSER,
};
const char *modes_labels[] = {
"[f]irefox [b]rave [c]hromium [q]utebrowser",
};
```
### Dependencies
this patch depends on:
- [bar](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar/) patch
- [modes](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/modes/) patch
### Download
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/bar-modes/bar-modes.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad/)
+144
View File
@@ -0,0 +1,144 @@
From 04b37902e0098fb69250fd25b5afbc610a284529 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Mon, 2 Mar 2026 21:54:03 +0800
Subject: [PATCH] Patch: bar-modes for 0.8
Add modes_labels indicator to bar, which behaves like river-classic's
dam bar. This patch has to be applied after the bar and modes patch.
---
dwl.c | 87 ++++++++++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 72 insertions(+), 15 deletions(-)
diff --git a/dwl.c b/dwl.c
index ae290ad..7229034 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1585,19 +1585,29 @@ drawbar(Monitor *m)
uint32_t i, occ = 0, urg = 0;
Client *c;
Buffer *buf;
+ char mode_text[256] = "";
+ int mode_width = 0;
+ int title_width;
+ int remaining;
+ int status_shown = 0;
if (!m->scene_buffer->node.enabled)
return;
if (!(buf = bufmon(m)))
return;
- /* draw status first so it can be overdrawn by tags later */
- if (m == selmon) { /* status is only drawn on selected monitor */
- drwl_setscheme(m->drw, colors[SchemeNorm]);
- tw = TEXTW(m, stext) - m->lrpad + 2; /* 2px right padding */
- drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0);
+ /* get current mode text if in a mode (not normal) */
+ if (active_mode_index >= 0 && active_mode_index < LENGTH(modes_labels) &&
+ modes_labels[active_mode_index]) {
+ strncpy(mode_text, modes_labels[active_mode_index], sizeof(mode_text) - 1);
+ mode_text[sizeof(mode_text) - 1] = '\0';
+ mode_width = TEXTW(m, mode_text);
}
+ /* calculate status text width */
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ tw = TEXTW(m, stext) - m->lrpad + 2;
+
wl_list_for_each(c, &clients, link) {
if (c->mon != m)
continue;
@@ -1607,6 +1617,8 @@ drawbar(Monitor *m)
}
x = 0;
c = focustop(m);
+
+ /* draw tags (always shown) */
for (i = 0; i < LENGTH(tags); i++) {
w = TEXTW(m, tags[i]);
drwl_setscheme(m->drw, colors[m->tagset[m->seltags] & 1 << i ? SchemeSel : SchemeNorm]);
@@ -1617,19 +1629,63 @@ drawbar(Monitor *m)
urg & 1 << i);
x += w;
}
+
+ /* draw mode indicator after tags if in a mode */
+ if (mode_text[0]) {
+ drwl_setscheme(m->drw, colors[SchemeSel]);
+ drwl_text(m->drw, x, 0, mode_width, m->b.height, m->lrpad / 2, mode_text, 0);
+ x += mode_width;
+ }
+
+ /* draw layout symbol after mode */
w = TEXTW(m, m->ltsymbol);
drwl_setscheme(m->drw, colors[SchemeNorm]);
- x = drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0);
-
- if ((w = m->b.width - tw - x) > m->b.height) {
- if (c) {
- drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]);
- drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, client_get_title(c), 0);
- if (c && c->isfloating)
- drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0);
- } else {
+ drwl_text(m->drw, x, 0, w, m->b.height, m->lrpad / 2, m->ltsymbol, 0);
+ x += w;
+
+ remaining = m->b.width - x;
+
+ if (mode_text[0] && remaining >= tw) {
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0);
+ remaining -= tw;
+ status_shown = 1;
+ }
+
+ title_width = remaining;
+
+ if (c && title_width > m->b.height) {
+ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]);
+ drwl_text(m->drw, x, 0, title_width, m->b.height, m->lrpad / 2,
+ client_get_title(c), 0);
+ if (c && c->isfloating)
+ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0);
+ } else if (title_width > 0) {
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ drwl_rect(m->drw, x, 0, title_width, m->b.height, 1, 1);
+ }
+
+ if (!mode_text[0]) {
+ /* not in a mode - normal behavior with status */
+ if (remaining >= tw) {
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ drwl_text(m->drw, m->b.width - tw, 0, tw, m->b.height, 0, stext, 0);
+
+ int title_space = remaining - tw;
+ if (title_space > m->b.height && c) {
+ drwl_setscheme(m->drw, colors[m == selmon ? SchemeSel : SchemeNorm]);
+ drwl_text(m->drw, x, 0, title_space, m->b.height, m->lrpad / 2,
+ client_get_title(c), 0);
+ if (c && c->isfloating)
+ drwl_rect(m->drw, x + boxs, boxs, boxw, boxw, 0, 0);
+ } else if (title_space > 0) {
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ drwl_rect(m->drw, x, 0, title_space, m->b.height, 1, 1);
+ }
+ } else if (remaining > 0) {
drwl_setscheme(m->drw, colors[SchemeNorm]);
- drwl_rect(m->drw, x, 0, w, m->b.height, 1, 1);
+ drwl_text(m->drw, m->b.width - remaining, 0, remaining, m->b.height, 0,
+ stext, 0);
}
}
@@ -3437,6 +3493,7 @@ entermode(const Arg *arg)
{
active_mode_index = arg->i;
printstatus();
+ drawbars();
}
#ifdef XWAYLAND
--
2.53.0
Binary file not shown.

After

Width:  |  Height:  |  Size: 6.4 KiB

+1 -1
View File
@@ -301,7 +301,7 @@ index 0000000..125312c
+ pipefd = ecalloc(2, sizeof(int));
+
+ /*
+ * Libdbus forbids calling dbus_connection_dipatch from the
+ * Libdbus forbids calling dbus_connection_dispatch from the
+ * DBusDispatchStatusFunction directly. Notify the event loop of
+ * updates via a self-pipe.
+ */
+1 -1
View File
@@ -3,7 +3,7 @@ From: wochap <gean.marroquin@gmail.com>
Date: Tue, 4 Jun 2024 16:02:25 -0500
Subject: [PATCH] implement borders patch
tihs patch adds 2 extra borders relative to the client, they don't
this patch adds 2 extra borders relative to the client, they don't
change the size of the client
---
client.h | 16 +++++++++++++---
+2
View File
@@ -15,12 +15,14 @@ bstack (TTT) bstackhoriz (===)
### Download
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/bottomstack/bottomstack.patch)
- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.6-b/bottomstack)
- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/20de07dc8759200c8a4c9651475acb331d245890/patches/bottomstack/bottomstack.patch)
- [2024-04-11](https://codeberg.org/dwl/dwl-patches/raw/commit/0f4e40fee49d1b8b430778e241b29496ae3b3b70/bottomstack/bottomstack.patch)
- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/5368aa392c7ebf8d7d24c232b80cfae1be457d41/bottomstack/bottomstack.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad)
- [wochap](https://codeberg.org/wochap)
- [DanielMowitz](https://github.com/DanielMowitz)
- [Abanoub8](https://github.com/Abanoub8)
+21 -20
View File
@@ -1,7 +1,7 @@
From b352fb08f40b1ee2d8c4748be4922df711e3aaa9 Mon Sep 17 00:00:00 2001
From: wochap <gean.marroquin@gmail.com>
Date: Fri, 5 Jul 2024 10:44:29 -0500
Subject: [PATCH] implement bottomstack
From 86e379b187bcc9f8f5896a87309474fcf3736de9 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 21:29:53 +0800
Subject: [PATCH] Patch: bottomstack-0.8.patch
---
config.def.h | 4 +++
@@ -9,10 +9,10 @@ Subject: [PATCH] implement bottomstack
2 files changed, 88 insertions(+)
diff --git a/config.def.h b/config.def.h
index 22d2171..5aac3e9 100644
index 8a6eda0..ae87518 100644
--- a/config.def.h
+++ b/config.def.h
@@ -34,6 +34,8 @@ static const Layout layouts[] = {
@@ -33,6 +33,8 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -21,20 +21,20 @@ index 22d2171..5aac3e9 100644
};
/* monitors */
@@ -139,6 +141,8 @@ 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_u, setlayout, {.v = &layouts[3]} },
+ { MODKEY, XKB_KEY_o, setlayout, {.v = &layouts[4]} },
{ MODKEY, XKB_KEY_space, setlayout, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
@@ -135,6 +137,8 @@ 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_u, setlayout, {.v = &layouts[3]} },
+ { MODKEY, XKB_KEY_o, setlayout, {.v = &layouts[4]} },
{ 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 dc0437e..5648d5f 100644
index 44f3ad9..68c25a5 100644
--- a/dwl.c
+++ b/dwl.c
@@ -57,6 +57,7 @@
@@ -59,6 +59,7 @@
#include <wlr/types/wlr_xdg_decoration_v1.h>
#include <wlr/types/wlr_xdg_output_v1.h>
#include <wlr/types/wlr_xdg_shell.h>
@@ -50,8 +50,8 @@ index dc0437e..5648d5f 100644
+static void bstackhoriz(Monitor *m);
/* variables */
static const char broken[] = "broken";
@@ -3160,3 +3163,84 @@ main(int argc, char *argv[])
static pid_t child_pid = -1;
@@ -3213,3 +3216,84 @@ main(int argc, char *argv[])
usage:
die("Usage: %s [-v] [-d] [-s startup command]", argv[0]);
}
@@ -137,4 +137,5 @@ index dc0437e..5648d5f 100644
+ }
+}
--
2.45.1
2.53.0
+5 -3
View File
@@ -61,7 +61,7 @@ When a new client appears:
btrtile adds couple variables to config.h to fine tune the mouse resizing of tiled clients.
1. **resize_factor**
- A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensivity.
- A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensitivity.
Defaults to 0.0002f.
2. **resize_interval_ms**
@@ -96,8 +96,10 @@ If mouse resizing feels sluggish, you can try compiling dwl with more aggressive
### Download
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch)
- [0.7 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gaps.patch)
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8.patch)
- [0.8 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8-gaps.patch)
- [wlroots-next](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch)
- [wlroots-next WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch)
### Authors
- [julmajustus](https://codeberg.org/julmajustus)
@@ -1,30 +1,32 @@
From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001
From f5d7fe287bd4405879a92d649243d3f646652617 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Thu, 13 Feb 2025 23:25:20 +0200
Subject: [PATCH] btrtile-gaps with multi-tag support
Date: Thu, 19 Mar 2026 00:08:10 +0200
Subject: [PATCH] Refactor btrtile-gaps to v0.8
---
btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 152 +++++++++++---
3 files changed, 717 insertions(+), 29 deletions(-)
dwl.c | 148 ++++++++++---
3 files changed, 717 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..650cab5
index 0000000..afe3c8f
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,582 @@
@@ -0,0 +1,584 @@
+/* ************************************************************************** */
+/* */
+/* ::: :::::::: */
+/* btrtile.c :+: :+: :+: */
+/* +:+ +:+ +:+ */
+/* By: jmakkone <jmakkone@student.hive.fi> +#+ +:+ +#+ */
+/* +#+#+#+#+#+ +#+ */
+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */
+/* Updated: 2025/02/13 23:25:03 by jmakkone ### ########.fr */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* btrtile.c @!! @!@ @! !@! */
+/* !!! !@!!! !!! */
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/19 00:07:19 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -74,7 +76,7 @@ index 0000000..650cab5
+ if (!node)
+ return;
+
+ if (is_root && e) {
+ if (is_root && e) {
+ area.x += gappx;
+ area.y += gappx;
+ area.width -= 2 * gappx;
@@ -127,7 +129,7 @@ index 0000000..650cab5
+ right_area.width = area.width - mid;
+ right_area.height = area.height;
+
+ if (e) {
+ if (e) {
+ left_area.width -= gappx / 2;
+ right_area.x += gappx / 2;
+ right_area.width -= gappx / 2;
@@ -145,7 +147,7 @@ index 0000000..650cab5
+ right_area.width = area.width;
+ right_area.height= area.height - mid;
+
+ if (e) {
+ if (e) {
+ left_area.height -= gappx / 2;
+ right_area.y += gappx / 2;
+ right_area.height -= gappx / 2;
@@ -599,13 +601,13 @@ index 0000000..650cab5
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 22d2171..92f3ad6 100644
index 8a6eda0..bc04e3f 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,7 +13,10 @@ 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 const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
@@ -613,7 +615,7 @@ index 22d2171..92f3ad6 100644
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -31,6 +34,7 @@ static const Rule rules[] = {
@@ -30,6 +33,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
@@ -621,23 +623,23 @@ index 22d2171..92f3ad6 100644
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -148,6 +152,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
@@ -144,6 +148,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
diff --git a/dwl.c b/dwl.c
index a2711f6..e49a061 100644
index 44f3ad9..f74d9a1 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -648,15 +650,15 @@ index a2711f6..e49a061 100644
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -103,6 +104,7 @@ typedef struct {
@@ -100,6 +101,7 @@ typedef struct {
const Arg arg;
} Button;
+typedef struct LayoutNode LayoutNode;
typedef struct Monitor Monitor;
typedef struct {
/* Must keep these three elements in this order */
@@ -139,8 +141,9 @@ typedef struct {
/* Must keep this field first */
@@ -137,8 +139,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@@ -667,7 +669,7 @@ index a2711f6..e49a061 100644
} Client;
typedef struct {
@@ -208,6 +211,7 @@ struct Monitor {
@@ -205,6 +208,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
@@ -675,7 +677,7 @@ index a2711f6..e49a061 100644
};
typedef struct {
@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
static void axisnotify(struct wl_listener *listener, void *data);
@@ -683,7 +685,7 @@ index a2711f6..e49a061 100644
static void buttonpress(struct wl_listener *listener, void *data);
static void chvt(const Arg *arg);
static void checkidleinhibitor(struct wlr_surface *exclude);
@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
@@ -329,6 +334,9 @@ 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);
@@ -693,7 +695,7 @@ index a2711f6..e49a061 100644
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
@@ -454,6 +462,7 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
@@ -701,7 +703,7 @@ index a2711f6..e49a061 100644
/* function implementations */
void
@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
@@ -710,7 +712,7 @@ index a2711f6..e49a061 100644
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data)
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (b = buttons; b < END(buttons); b++) {
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
@@ -719,9 +721,9 @@ index a2711f6..e49a061 100644
b->func(&b->arg);
return;
}
@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data)
/* If you released any buttons, we exit interactive move/resize mode. */
/* TODO should reset to the pointer focus's current setcursor */
/* TODO: should reset to the pointer focus's current setcursor */
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
+ c = grabc;
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
@@ -747,18 +749,7 @@ index a2711f6..e49a061 100644
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
cursor_mode = CurNormal;
/* Drop the window off on its new monitor */
selmon = xytomon(cursor->x, cursor->y);
setmon(grabc, selmon, 0);
+ grabc = NULL;
return;
- } else {
- cursor_mode = CurNormal;
}
+ cursor_mode = CurNormal;
break;
}
/* If the event wasn't handled by the compositor, notify the client with
@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data)
@@ -746,6 +776,7 @@ cleanupmon(struct wl_listener *listener, void *data)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
@@ -766,7 +757,7 @@ index a2711f6..e49a061 100644
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
@@ -774,7 +765,7 @@ index a2711f6..e49a061 100644
/* The xdg-protocol specifies:
*
@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data)
wl_list_remove(&c->destroy.link);
wl_list_remove(&c->set_title.link);
wl_list_remove(&c->fullscreen.link);
@@ -785,7 +776,7 @@ index a2711f6..e49a061 100644
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1809,7 +1845,8 @@ void
@@ -1862,7 +1898,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -795,7 +786,7 @@ index a2711f6..e49a061 100644
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct wlr_surface *surface = NULL;
@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
@@ -1916,18 +1953,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
/* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
@@ -859,7 +850,7 @@ index a2711f6..e49a061 100644
/* If there's no client surface under the cursor, set the cursor image to a
* default. This is what makes the cursor image appear when you move it
* off of a client or over its border. */
@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -869,7 +860,7 @@ index a2711f6..e49a061 100644
- case CurMove:
- grabcx = (int)round(cursor->x) - grabc->geom.x;
- grabcy = (int)round(cursor->y) - grabc->geom.y;
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
@@ -918,5 +909,5 @@ index a2711f6..e49a061 100644
}
--
2.45.3
2.52.0
+894
View File
@@ -0,0 +1,894 @@
From 1b75163aac16942a715c6fc0aaf5d508e778dd6a Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Wed, 18 Mar 2026 20:23:00 +0200
Subject: [PATCH] Refactor btrtile for v0.8
---
btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 +++++++++++---
3 files changed, 698 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..d706c3b
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,565 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* btrtile.c @!! @!@ @! !@! */
+/* !!! !@!!! !!! */
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
+typedef struct LayoutNode {
+ unsigned int is_client_node;
+ unsigned int is_split_vertically;
+ float split_ratio;
+ struct LayoutNode *left;
+ struct LayoutNode *right;
+ struct LayoutNode *split_node;
+ Client *client;
+} LayoutNode;
+
+static void apply_layout(Monitor *m, LayoutNode *node,
+ struct wlr_box area, unsigned int is_root);
+static void btrtile(Monitor *m);
+static LayoutNode *create_client_node(Client *c);
+static LayoutNode *create_split_node(unsigned int is_split_vertically,
+ LayoutNode *left, LayoutNode *right);
+static void destroy_node(LayoutNode *node);
+static void destroy_tree(Monitor *m);
+static LayoutNode *find_client_node(LayoutNode *node, Client *c);
+static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert);
+static void init_tree(Monitor *m);
+static void insert_client(Monitor *m, Client *focused_client, Client *new_client);
+static LayoutNode *remove_client_node(LayoutNode *node, Client *c);
+static void remove_client(Monitor *m, Client *c);
+static void setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
+static unsigned int visible_count(LayoutNode *node, Monitor *m);
+static Client *xytoclient(double x, double y);
+
+static int resizing_from_mouse = 0;
+static double resize_last_update_x, resize_last_update_y;
+static uint32_t last_resize_time = 0;
+
+void
+apply_layout(Monitor *m, LayoutNode *node,
+ struct wlr_box area, unsigned int is_root)
+{
+ Client *c;
+ float ratio;
+ unsigned int left_count, right_count, mid;
+ struct wlr_box left_area, right_area;
+
+ if (!node)
+ return;
+
+ /* If this node is a client node, check if it is visible. */
+ if (node->is_client_node) {
+ c = node->client;
+ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ return;
+ resize(c, area, 0);
+ c->old_geom = area;
+ return;
+ }
+
+ /* For a split node, we see how many visible children are on each side: */
+ left_count = visible_count(node->left, m);
+ right_count = visible_count(node->right, m);
+
+ if (left_count == 0 && right_count == 0) {
+ return;
+ } else if (left_count > 0 && right_count == 0) {
+ apply_layout(m, node->left, area, 0);
+ return;
+ } else if (left_count == 0 && right_count > 0) {
+ apply_layout(m, node->right, area, 0);
+ return;
+ }
+
+ /* If we’re here, we have visible clients in both subtrees. */
+ ratio = node->split_ratio;
+ if (ratio < 0.05f)
+ ratio = 0.05f;
+ if (ratio > 0.95f)
+ ratio = 0.95f;
+
+ memset(&left_area, 0, sizeof(left_area));
+ memset(&right_area, 0, sizeof(right_area));
+
+ if (node->is_split_vertically) {
+ mid = (unsigned int)(area.width * ratio);
+ left_area.x = area.x;
+ left_area.y = area.y;
+ left_area.width = mid;
+ left_area.height = area.height;
+
+ right_area.x = area.x + mid;
+ right_area.y = area.y;
+ right_area.width = area.width - mid;
+ right_area.height = area.height;
+ } else {
+ /* horizontal split */
+ mid = (unsigned int)(area.height * ratio);
+ left_area.x = area.x;
+ left_area.y = area.y;
+ left_area.width = area.width;
+ left_area.height = mid;
+
+ right_area.x = area.x;
+ right_area.y = area.y + mid;
+ right_area.width = area.width;
+ right_area.height= area.height - mid;
+ }
+
+ apply_layout(m, node->left, left_area, 0);
+ apply_layout(m, node->right, right_area, 0);
+}
+
+void
+btrtile(Monitor *m)
+{
+ Client *c, *focused = NULL;
+ int n = 0;
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+
+ /* Remove non tiled clients from tree. */
+ wl_list_for_each(c, &clients, link) {
+ if (c->mon == m && !c->isfloating && !c->isfullscreen) {
+ } else {
+ remove_client(m, c);
+ }
+ }
+
+ /* If no client is found under cursor, fallback to focustop(m) */
+ if (!(focused = xytoclient(cursor->x, cursor->y)))
+ focused = focustop(m);
+
+ /* Insert visible clients that are not part of the tree. */
+ wl_list_for_each(c, &clients, link) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) {
+ found = find_client_node(m->root, c);
+ if (!found) {
+ insert_client(m, focused, c);
+ }
+ n++;
+ }
+ }
+
+ if (n == 0)
+ return;
+
+ full_area = m->w;
+ apply_layout(m, m->root, full_area, 1);
+}
+
+LayoutNode *
+create_client_node(Client *c)
+{
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+ if (!node)
+ return NULL;
+ node->is_client_node = 1;
+ node->split_ratio = 0.5f;
+ node->client = c;
+ return node;
+}
+
+LayoutNode *
+create_split_node(unsigned int is_split_vertically,
+ LayoutNode *left, LayoutNode *right)
+{
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+ if (!node)
+ return NULL;
+ node->is_client_node = 0;
+ node->split_ratio = 0.5f;
+ node->is_split_vertically = is_split_vertically;
+ node->left = left;
+ node->right = right;
+ if (left)
+ left->split_node = node;
+ if (right)
+ right->split_node = node;
+ return node;
+}
+
+void
+destroy_node(LayoutNode *node)
+{
+ if (!node)
+ return;
+ if (!node->is_client_node) {
+ destroy_node(node->left);
+ destroy_node(node->right);
+ }
+ free(node);
+}
+
+void
+destroy_tree(Monitor *m)
+{
+ if (!m || !m->root)
+ return;
+ destroy_node(m->root);
+ m->root = NULL;
+}
+
+LayoutNode *
+find_client_node(LayoutNode *node, Client *c)
+{
+ LayoutNode *res;
+
+ if (!node || !c)
+ return NULL;
+ if (node->is_client_node) {
+ return (node->client == c) ? node : NULL;
+ }
+ res = find_client_node(node->left, c);
+ return res ? res : find_client_node(node->right, c);
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ n = n->split_node;
+
+ while (n) {
+ if (!n->is_client_node && n->is_split_vertically == need_vertical &&
+ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0)
+ return n;
+ n = n->split_node;
+ }
+ return NULL;
+}
+
+void
+init_tree(Monitor *m)
+{
+ if (!m)
+ return;
+ m->root = calloc(1, sizeof(LayoutNode));
+ if (!m->root)
+ m->root = NULL;
+}
+
+void
+insert_client(Monitor *m, Client *focused_client, Client *new_client)
+{
+ Client *old_client;
+ LayoutNode **root = &m->root, *old_root,
+ *focused_node, *new_client_node, *old_client_node;
+ unsigned int wider, mid_x, mid_y;
+
+ /* If no root , new client becomes the root. */
+ if (!*root) {
+ *root = create_client_node(new_client);
+ return;
+ }
+
+ /* Find the focused_client node,
+ * if not found split the root. */
+ focused_node = focused_client ?
+ find_client_node(*root, focused_client) : NULL;
+ if (!focused_node) {
+ old_root = *root;
+ new_client_node = create_client_node(new_client);
+ *root = create_split_node(1, old_root, new_client_node);
+ return;
+ }
+
+ /* Turn focused node from a client node into a split node,
+ * and attach old_client + new_client. */
+ old_client = focused_node->client;
+ old_client_node = create_client_node(old_client);
+ new_client_node = create_client_node(new_client);
+
+ /* Decide split direction. */
+ wider = (focused_client->geom.width >= focused_client->geom.height);
+ focused_node->is_client_node = 0;
+ focused_node->client = NULL;
+ focused_node->is_split_vertically = (wider ? 1 : 0);
+
+ /* Pick new_client side depending on the cursor position. */
+ mid_x = focused_client->geom.x + focused_client->geom.width / 2;
+ mid_y = focused_client->geom.y + focused_client->geom.height / 2;
+
+ if (wider) {
+ /* vertical split => left vs right */
+ if (cursor->x <= mid_x) {
+ focused_node->left = new_client_node;
+ focused_node->right = old_client_node;
+ } else {
+ focused_node->left = old_client_node;
+ focused_node->right = new_client_node;
+ }
+ } else {
+ /* horizontal split => top vs bottom */
+ if (cursor->y <= mid_y) {
+ focused_node->left = new_client_node;
+ focused_node->right = old_client_node;
+ } else {
+ focused_node->left = old_client_node;
+ focused_node->right = new_client_node;
+ }
+ }
+ old_client_node->split_node = focused_node;
+ new_client_node->split_node = focused_node;
+ focused_node->split_ratio = 0.5f;
+}
+
+LayoutNode *
+remove_client_node(LayoutNode *node, Client *c)
+{
+ LayoutNode *tmp;
+ if (!node)
+ return NULL;
+ if (node->is_client_node) {
+ /* If this client_node is the client we're removing,
+ * return NULL to remove it */
+ if (node->client == c) {
+ free(node);
+ return NULL;
+ }
+ return node;
+ }
+
+ node->left = remove_client_node(node->left, c);
+ node->right = remove_client_node(node->right, c);
+
+ /* If one of the client node is NULL after removal and the other is not,
+ * we "lift" the other client node up to replace this split node. */
+ if (!node->left && node->right) {
+ tmp = node->right;
+
+ /* Save pointer to split node */
+ if (tmp)
+ tmp->split_node = node->split_node;
+
+ free(node);
+ return tmp;
+ }
+
+ if (!node->right && node->left) {
+ tmp = node->left;
+
+ /* Save pointer to split node */
+ if (tmp)
+ tmp->split_node = node->split_node;
+
+ free(node);
+ return tmp;
+ }
+
+ /* If both children exist or both are NULL (empty tree),
+ * return node as is. */
+ return node;
+}
+
+void
+remove_client(Monitor *m, Client *c)
+{
+ if (!m->root || !c)
+ return;
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ if (!split_node)
+ return;
+
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
+ if (new_ratio < 0.05f)
+ new_ratio = 0.05f;
+ if (new_ratio > 0.95f)
+ new_ratio = 0.95f;
+ split_node->split_ratio = new_ratio;
+
+ /* Skip the arrange if done resizing by mouse,
+ * we call arrange from motionotify */
+ if (!resizing_from_mouse) {
+ arrange(selmon);
+ }
+}
+
+void
+setratio_v(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 0);
+ if (!split_node)
+ return;
+
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
+ if (new_ratio < 0.05f)
+ new_ratio = 0.05f;
+ if (new_ratio > 0.95f)
+ new_ratio = 0.95f;
+ split_node->split_ratio = new_ratio;
+
+ /* Skip the arrange if done resizing by mouse,
+ * we call arrange from motionotify */
+ if (!resizing_from_mouse) {
+ arrange(selmon);
+ }
+}
+
+void swapclients(const Arg *arg) {
+ Client *c, *tmp, *target = NULL, *sel = focustop(selmon);
+ LayoutNode *sel_node, *target_node;
+ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y,
+ cand_center_x, cand_center_y;
+
+ if (!sel || sel->isfullscreen ||
+ !selmon->root || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+
+ /* Get the center coordinates of the selected client */
+ sel_center_x = sel->geom.x + sel->geom.width / 2;
+ sel_center_y = sel->geom.y + sel->geom.height / 2;
+
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel)
+ continue;
+
+ /* Get the center of candidate client */
+ cand_center_x = c->geom.x + c->geom.width / 2;
+ cand_center_y = c->geom.y + c->geom.height / 2;
+
+ /* Check that the candidate lies in the requested direction. */
+ switch (arg->ui) {
+ case 0:
+ if (cand_center_x >= sel_center_x)
+ continue;
+ break;
+ case 1:
+ if (cand_center_x <= sel_center_x)
+ continue;
+ break;
+ case 2:
+ if (cand_center_y >= sel_center_y)
+ continue;
+ break;
+ case 3:
+ if (cand_center_y <= sel_center_y)
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ /* Get distance between the centers */
+ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y);
+ if (dist < closest_dist) {
+ closest_dist = dist;
+ target = c;
+ }
+ }
+
+ /* If target is found, swap the two clients’ positions in the layout tree */
+ if (target) {
+ sel_node = find_client_node(selmon->root, sel);
+ target_node = find_client_node(selmon->root, target);
+ if (sel_node && target_node) {
+ tmp = sel_node->client;
+ sel_node->client = target_node->client;
+ target_node->client = tmp;
+ arrange(selmon);
+ }
+ }
+}
+
+unsigned int
+visible_count(LayoutNode *node, Monitor *m)
+{
+ Client *c;
+
+ if (!node)
+ return 0;
+ /* Check if this client is visible. */
+ if (node->is_client_node) {
+ c = node->client;
+ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ return 1;
+ return 0;
+ }
+ /* Else it’s a split node. */
+ return visible_count(node->left, m) + visible_count(node->right, m);
+}
+
+Client *
+xytoclient(double x, double y) {
+ Client *c, *closest = NULL;
+ double dist, mindist = INT_MAX, dx, dy;
+
+ wl_list_for_each_reverse(c, &clients, link) {
+ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen &&
+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) &&
+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){
+ return c;
+ }
+ }
+
+ /* If no client was found at cursor position fallback to closest. */
+ wl_list_for_each_reverse(c, &clients, link) {
+ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
+ dx = c->geom.x - x;
+ else if (x > (c->geom.x + c->geom.width))
+ dx = x - (c->geom.x + c->geom.width);
+
+ if (y < c->geom.y)
+ dy = c->geom.y - y;
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
+ }
+ }
+ }
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 8a6eda0..bc04e3f 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,7 +13,10 @@ 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 const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
+enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -30,6 +33,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
+ { "|w|", btrtile },
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -144,6 +148,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
diff --git a/dwl.c b/dwl.c
index 44f3ad9..f74d9a1 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
/*
* See LICENSE file for copyright and license details.
*/
+#include <limits.h>
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -100,6 +101,7 @@ typedef struct {
const Arg arg;
} Button;
+typedef struct LayoutNode LayoutNode;
typedef struct Monitor Monitor;
typedef struct {
/* Must keep this field first */
@@ -137,8 +139,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
- int isfloating, isurgent, isfullscreen;
+ int isfloating, isurgent, isfullscreen, was_tiled;
uint32_t resize; /* configure serial of a pending resize */
+ struct wlr_box old_geom;
} Client;
typedef struct {
@@ -205,6 +208,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
+ LayoutNode *root;
};
typedef struct {
@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
static void axisnotify(struct wl_listener *listener, void *data);
+static void btrtile(Monitor *m);
static void buttonpress(struct wl_listener *listener, void *data);
static void chvt(const Arg *arg);
static void checkidleinhibitor(struct wlr_surface *exclude);
@@ -329,6 +334,9 @@ 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 setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -454,6 +462,7 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
+#include "btrtile.c"
/* function implementations */
void
@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
- Client *c;
+ Client *c, *target = NULL;
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data)
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (b = buttons; b < END(buttons); b++) {
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
- event->button == b->button && b->func) {
+ event->button == b->button && b->func) {
b->func(&b->arg);
return;
}
@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data)
/* If you released any buttons, we exit interactive move/resize mode. */
/* TODO: should reset to the pointer focus's current setcursor */
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
+ c = grabc;
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
+ if (cursor_mode == CurMove && c->isfloating) {
+ target = xytoclient(cursor->x, cursor->y);
+
+ if (target && !target->isfloating && !target->isfullscreen)
+ insert_client(selmon, target, c);
+ else
+ selmon->root = create_client_node(c);
+
+ setfloating(c, 0);
+ arrange(selmon);
+
+ } else if (cursor_mode == CurResize && !c->isfloating) {
+ resizing_from_mouse = 0;
+ }
+ } else {
+ if (cursor_mode == CurResize && resizing_from_mouse)
+ resizing_from_mouse = 0;
+ }
+ /* Default behaviour */
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
cursor_mode = CurNormal;
/* Drop the window off on its new monitor */
@@ -746,6 +776,7 @@ cleanupmon(struct wl_listener *listener, void *data)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
+ destroy_tree(m);
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
+ init_tree(m);
/* The xdg-protocol specifies:
*
@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data)
wl_list_remove(&c->destroy.link);
wl_list_remove(&c->set_title.link);
wl_list_remove(&c->fullscreen.link);
+ /* We check if the destroyed client was part of any tiled_list, to catch
+ * client removals even if they would not be currently managed by btrtile */
+ if (selmon && selmon->root)
+ remove_client(selmon, c);
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1862,7 +1898,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
- double sx = 0, sy = 0, sx_confined, sy_confined;
+ int tiled = 0;
+ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total;
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct wlr_surface *surface = NULL;
@@ -1916,18 +1953,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
/* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
- /* If we are currently grabbing the mouse, handle and return */
+ /* Skip if internal call or already resizing */
+ if (time == 0 && resizing_from_mouse)
+ goto focus;
+
+ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
if (cursor_mode == CurMove) {
/* Move the grabbed client to the new position. */
- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
- .width = grabc->geom.width, .height = grabc->geom.height}, 1);
- return;
+ if (grabc && grabc->isfloating) {
+ resize(grabc, (struct wlr_box){
+ .x = (int)round(cursor->x) - grabcx,
+ .y = (int)round(cursor->y) - grabcy,
+ .width = grabc->geom.width,
+ .height = grabc->geom.height
+ }, 1);
+ return;
+ }
} else if (cursor_mode == CurResize) {
- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
- return;
+ if (tiled && resizing_from_mouse) {
+ dx_total = cursor->x - resize_last_update_x;
+ dy_total = cursor->y - resize_last_update_y;
+
+ if (time - last_resize_time >= resize_interval_ms) {
+ Arg a = {0};
+ if (fabs(dx_total) > fabs(dy_total)) {
+ a.f = (float)(dx_total * resize_factor);
+ setratio_h(&a);
+ } else {
+ a.f = (float)(dy_total * resize_factor);
+ setratio_v(&a);
+ }
+ arrange(selmon);
+
+ last_resize_time = time;
+ resize_last_update_x = cursor->x;
+ resize_last_update_y = cursor->y;
+ }
+
+ } else if (grabc && grabc->isfloating) {
+ /* Floating resize as original */
+ resize(grabc, (struct wlr_box){
+ .x = grabc->geom.x,
+ .y = grabc->geom.y,
+ .width = (int)round(cursor->x) - grabc->geom.x,
+ .height = (int)round(cursor->y) - grabc->geom.y
+ }, 1);
+ return;
+ }
}
+focus:
/* If there's no client surface under the cursor, set the cursor image to a
* default. This is what makes the cursor image appear when you move it
* off of a client or over its border. */
@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
- /* Float the window and tell motionnotify to grab it */
- setfloating(grabc, 1);
- switch (cursor_mode = arg->ui) {
- case CurMove:
- grabcx = (int)round(cursor->x) - grabc->geom.x;
- grabcy = (int)round(cursor->y) - grabc->geom.y;
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
- * returns the cursor to where it started */
- wlr_cursor_warp_closest(cursor, NULL,
- grabc->geom.x + grabc->geom.width,
- grabc->geom.y + grabc->geom.height);
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
- break;
+ cursor_mode = arg->ui;
+ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
+
+ if (grabc->was_tiled) {
+ switch (cursor_mode) {
+ case CurMove:
+ setfloating(grabc, 1);
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+ break;
+ case CurResize:
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+ resize_last_update_x = cursor->x;
+ resize_last_update_y = cursor->y;
+ resizing_from_mouse = 1;
+ break;
+ }
+ } else {
+ /* Default floating logic */
+ /* Float the window and tell motionnotify to grab it */
+ setfloating(grabc, 1);
+ switch (cursor_mode) {
+ case CurMove:
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+ break;
+ case CurResize:
+ wlr_cursor_warp_closest(cursor, NULL,
+ grabc->geom.x + grabc->geom.width,
+ grabc->geom.y + grabc->geom.height);
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+ break;
+ }
}
}
--
2.52.0
@@ -0,0 +1,913 @@
From a9329abe48083a4c5b64eee726ceefdb4bf0bb9f Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Thu, 19 Mar 2026 22:59:49 +0200
Subject: [PATCH] Refactor btrtile-gaps to wlroots-next
---
btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 ++++++++++---
3 files changed, 717 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..8703617
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,584 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* btrtile.c @!! @!@ @! !@! */
+/* !!! !@!!! !!! */
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/19 22:59:12 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
+typedef struct LayoutNode {
+ unsigned int is_client_node;
+ unsigned int is_split_vertically;
+ float split_ratio;
+ struct LayoutNode *left;
+ struct LayoutNode *right;
+ struct LayoutNode *split_node;
+ Client *client;
+} LayoutNode;
+
+static void apply_layout(Monitor *m, LayoutNode *node,
+ struct wlr_box area, unsigned int is_root);
+static void btrtile(Monitor *m);
+static LayoutNode *create_client_node(Client *c);
+static LayoutNode *create_split_node(unsigned int is_split_vertically,
+ LayoutNode *left, LayoutNode *right);
+static void destroy_node(LayoutNode *node);
+static void destroy_tree(Monitor *m);
+static LayoutNode *find_client_node(LayoutNode *node, Client *c);
+static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert);
+static void init_tree(Monitor *m);
+static void insert_client(Monitor *m, Client *focused_client, Client *new_client);
+static LayoutNode *remove_client_node(LayoutNode *node, Client *c);
+static void remove_client(Monitor *m, Client *c);
+static void setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
+static unsigned int visible_count(LayoutNode *node, Monitor *m);
+static Client *xytoclient(double x, double y);
+
+static int resizing_from_mouse = 0;
+static double resize_last_update_x, resize_last_update_y;
+static uint32_t last_resize_time = 0;
+
+void
+apply_layout(Monitor *m, LayoutNode *node,
+ struct wlr_box area, unsigned int is_root)
+{
+ Client *c;
+ float ratio;
+ unsigned int left_count, right_count, mid, e = m->gaps;
+ struct wlr_box left_area, right_area;
+
+ if (!node)
+ return;
+
+ if (is_root && e) {
+ area.x += gappx;
+ area.y += gappx;
+ area.width -= 2 * gappx;
+ area.height -= 2 * gappx;
+ }
+
+ /* If this node is a client node, check if it is visible. */
+ if (node->is_client_node) {
+ c = node->client;
+ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ return;
+ resize(c, area, 0);
+ c->old_geom = area;
+ return;
+ }
+
+ /* For a split node, we see how many visible children are on each side: */
+ left_count = visible_count(node->left, m);
+ right_count = visible_count(node->right, m);
+
+ if (left_count == 0 && right_count == 0) {
+ return;
+ } else if (left_count > 0 && right_count == 0) {
+ apply_layout(m, node->left, area, 0);
+ return;
+ } else if (left_count == 0 && right_count > 0) {
+ apply_layout(m, node->right, area, 0);
+ return;
+ }
+
+ /* If we’re here, we have visible clients in both subtrees. */
+ ratio = node->split_ratio;
+ if (ratio < 0.05f)
+ ratio = 0.05f;
+ if (ratio > 0.95f)
+ ratio = 0.95f;
+
+ memset(&left_area, 0, sizeof(left_area));
+ memset(&right_area, 0, sizeof(right_area));
+
+ if (node->is_split_vertically) {
+ mid = (unsigned int)(area.width * ratio);
+ left_area.x = area.x;
+ left_area.y = area.y;
+ left_area.width = mid;
+ left_area.height = area.height;
+
+ right_area.x = area.x + mid;
+ right_area.y = area.y;
+ right_area.width = area.width - mid;
+ right_area.height = area.height;
+
+ if (e) {
+ left_area.width -= gappx / 2;
+ right_area.x += gappx / 2;
+ right_area.width -= gappx / 2;
+ }
+ } else {
+ /* horizontal split */
+ mid = (unsigned int)(area.height * ratio);
+ left_area.x = area.x;
+ left_area.y = area.y;
+ left_area.width = area.width;
+ left_area.height = mid;
+
+ right_area.x = area.x;
+ right_area.y = area.y + mid;
+ right_area.width = area.width;
+ right_area.height= area.height - mid;
+
+ if (e) {
+ left_area.height -= gappx / 2;
+ right_area.y += gappx / 2;
+ right_area.height -= gappx / 2;
+ }
+ }
+
+ apply_layout(m, node->left, left_area, 0);
+ apply_layout(m, node->right, right_area, 0);
+}
+
+void
+btrtile(Monitor *m)
+{
+ Client *c, *focused = NULL;
+ int n = 0;
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+
+ /* Remove non tiled clients from tree. */
+ wl_list_for_each(c, &clients, link) {
+ if (c->mon == m && !c->isfloating && !c->isfullscreen) {
+ } else {
+ remove_client(m, c);
+ }
+ }
+
+ /* If no client is found under cursor, fallback to focustop(m) */
+ if (!(focused = xytoclient(cursor->x, cursor->y)))
+ focused = focustop(m);
+
+ /* Insert visible clients that are not part of the tree. */
+ wl_list_for_each(c, &clients, link) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) {
+ found = find_client_node(m->root, c);
+ if (!found) {
+ insert_client(m, focused, c);
+ }
+ n++;
+ }
+ }
+
+ if (n == 0)
+ return;
+
+ full_area = m->w;
+ apply_layout(m, m->root, full_area, 1);
+}
+
+LayoutNode *
+create_client_node(Client *c)
+{
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+ if (!node)
+ return NULL;
+ node->is_client_node = 1;
+ node->split_ratio = 0.5f;
+ node->client = c;
+ return node;
+}
+
+LayoutNode *
+create_split_node(unsigned int is_split_vertically,
+ LayoutNode *left, LayoutNode *right)
+{
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
+
+ if (!node)
+ return NULL;
+ node->is_client_node = 0;
+ node->split_ratio = 0.5f;
+ node->is_split_vertically = is_split_vertically;
+ node->left = left;
+ node->right = right;
+ if (left)
+ left->split_node = node;
+ if (right)
+ right->split_node = node;
+ return node;
+}
+
+void
+destroy_node(LayoutNode *node)
+{
+ if (!node)
+ return;
+ if (!node->is_client_node) {
+ destroy_node(node->left);
+ destroy_node(node->right);
+ }
+ free(node);
+}
+
+void
+destroy_tree(Monitor *m)
+{
+ if (!m || !m->root)
+ return;
+ destroy_node(m->root);
+ m->root = NULL;
+}
+
+LayoutNode *
+find_client_node(LayoutNode *node, Client *c)
+{
+ LayoutNode *res;
+
+ if (!node || !c)
+ return NULL;
+ if (node->is_client_node) {
+ return (node->client == c) ? node : NULL;
+ }
+ res = find_client_node(node->left, c);
+ return res ? res : find_client_node(node->right, c);
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ n = n->split_node;
+
+ while (n) {
+ if (!n->is_client_node && n->is_split_vertically == need_vertical &&
+ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0)
+ return n;
+ n = n->split_node;
+ }
+ return NULL;
+}
+
+void
+init_tree(Monitor *m)
+{
+ if (!m)
+ return;
+ m->root = calloc(1, sizeof(LayoutNode));
+ if (!m->root)
+ m->root = NULL;
+}
+
+void
+insert_client(Monitor *m, Client *focused_client, Client *new_client)
+{
+ Client *old_client;
+ LayoutNode **root = &m->root, *old_root,
+ *focused_node, *new_client_node, *old_client_node;
+ unsigned int wider, mid_x, mid_y;
+
+ /* If no root , new client becomes the root. */
+ if (!*root) {
+ *root = create_client_node(new_client);
+ return;
+ }
+
+ /* Find the focused_client node,
+ * if not found split the root. */
+ focused_node = focused_client ?
+ find_client_node(*root, focused_client) : NULL;
+ if (!focused_node) {
+ old_root = *root;
+ new_client_node = create_client_node(new_client);
+ *root = create_split_node(1, old_root, new_client_node);
+ return;
+ }
+
+ /* Turn focused node from a client node into a split node,
+ * and attach old_client + new_client. */
+ old_client = focused_node->client;
+ old_client_node = create_client_node(old_client);
+ new_client_node = create_client_node(new_client);
+
+ /* Decide split direction. */
+ wider = (focused_client->geom.width >= focused_client->geom.height);
+ focused_node->is_client_node = 0;
+ focused_node->client = NULL;
+ focused_node->is_split_vertically = (wider ? 1 : 0);
+
+ /* Pick new_client side depending on the cursor position. */
+ mid_x = focused_client->geom.x + focused_client->geom.width / 2;
+ mid_y = focused_client->geom.y + focused_client->geom.height / 2;
+
+ if (wider) {
+ /* vertical split => left vs right */
+ if (cursor->x <= mid_x) {
+ focused_node->left = new_client_node;
+ focused_node->right = old_client_node;
+ } else {
+ focused_node->left = old_client_node;
+ focused_node->right = new_client_node;
+ }
+ } else {
+ /* horizontal split => top vs bottom */
+ if (cursor->y <= mid_y) {
+ focused_node->left = new_client_node;
+ focused_node->right = old_client_node;
+ } else {
+ focused_node->left = old_client_node;
+ focused_node->right = new_client_node;
+ }
+ }
+ old_client_node->split_node = focused_node;
+ new_client_node->split_node = focused_node;
+ focused_node->split_ratio = 0.5f;
+}
+
+LayoutNode *
+remove_client_node(LayoutNode *node, Client *c)
+{
+ LayoutNode *tmp;
+ if (!node)
+ return NULL;
+ if (node->is_client_node) {
+ /* If this client_node is the client we're removing,
+ * return NULL to remove it */
+ if (node->client == c) {
+ free(node);
+ return NULL;
+ }
+ return node;
+ }
+
+ node->left = remove_client_node(node->left, c);
+ node->right = remove_client_node(node->right, c);
+
+ /* If one of the client node is NULL after removal and the other is not,
+ * we "lift" the other client node up to replace this split node. */
+ if (!node->left && node->right) {
+ tmp = node->right;
+
+ /* Save pointer to split node */
+ if (tmp)
+ tmp->split_node = node->split_node;
+
+ free(node);
+ return tmp;
+ }
+
+ if (!node->right && node->left) {
+ tmp = node->left;
+
+ /* Save pointer to split node */
+ if (tmp)
+ tmp->split_node = node->split_node;
+
+ free(node);
+ return tmp;
+ }
+
+ /* If both children exist or both are NULL (empty tree),
+ * return node as is. */
+ return node;
+}
+
+void
+remove_client(Monitor *m, Client *c)
+{
+ if (!m->root || !c)
+ return;
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ if (!split_node)
+ return;
+
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
+ if (new_ratio < 0.05f)
+ new_ratio = 0.05f;
+ if (new_ratio > 0.95f)
+ new_ratio = 0.95f;
+ split_node->split_ratio = new_ratio;
+
+ /* Skip the arrange if done resizing by mouse,
+ * we call arrange from motionotify */
+ if (!resizing_from_mouse) {
+ arrange(selmon);
+ }
+}
+
+void
+setratio_v(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 0);
+ if (!split_node)
+ return;
+
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
+ if (new_ratio < 0.05f)
+ new_ratio = 0.05f;
+ if (new_ratio > 0.95f)
+ new_ratio = 0.95f;
+ split_node->split_ratio = new_ratio;
+
+ /* Skip the arrange if done resizing by mouse,
+ * we call arrange from motionotify */
+ if (!resizing_from_mouse) {
+ arrange(selmon);
+ }
+}
+
+void swapclients(const Arg *arg) {
+ Client *c, *tmp, *target = NULL, *sel = focustop(selmon);
+ LayoutNode *sel_node, *target_node;
+ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y,
+ cand_center_x, cand_center_y;
+
+ if (!sel || sel->isfullscreen ||
+ !selmon->root || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+
+ /* Get the center coordinates of the selected client */
+ sel_center_x = sel->geom.x + sel->geom.width / 2;
+ sel_center_y = sel->geom.y + sel->geom.height / 2;
+
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel)
+ continue;
+
+ /* Get the center of candidate client */
+ cand_center_x = c->geom.x + c->geom.width / 2;
+ cand_center_y = c->geom.y + c->geom.height / 2;
+
+ /* Check that the candidate lies in the requested direction. */
+ switch (arg->ui) {
+ case 0:
+ if (cand_center_x >= sel_center_x)
+ continue;
+ break;
+ case 1:
+ if (cand_center_x <= sel_center_x)
+ continue;
+ break;
+ case 2:
+ if (cand_center_y >= sel_center_y)
+ continue;
+ break;
+ case 3:
+ if (cand_center_y <= sel_center_y)
+ continue;
+ break;
+ default:
+ continue;
+ }
+
+ /* Get distance between the centers */
+ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y);
+ if (dist < closest_dist) {
+ closest_dist = dist;
+ target = c;
+ }
+ }
+
+ /* If target is found, swap the two clients’ positions in the layout tree */
+ if (target) {
+ sel_node = find_client_node(selmon->root, sel);
+ target_node = find_client_node(selmon->root, target);
+ if (sel_node && target_node) {
+ tmp = sel_node->client;
+ sel_node->client = target_node->client;
+ target_node->client = tmp;
+ arrange(selmon);
+ }
+ }
+}
+
+unsigned int
+visible_count(LayoutNode *node, Monitor *m)
+{
+ Client *c;
+
+ if (!node)
+ return 0;
+ /* Check if this client is visible. */
+ if (node->is_client_node) {
+ c = node->client;
+ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ return 1;
+ return 0;
+ }
+ /* Else it’s a split node. */
+ return visible_count(node->left, m) + visible_count(node->right, m);
+}
+
+Client *
+xytoclient(double x, double y) {
+ Client *c, *closest = NULL;
+ double dist, mindist = INT_MAX, dx, dy;
+
+ wl_list_for_each_reverse(c, &clients, link) {
+ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen &&
+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) &&
+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){
+ return c;
+ }
+ }
+
+ /* If no client was found at cursor position fallback to closest. */
+ wl_list_for_each_reverse(c, &clients, link) {
+ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
+ dx = c->geom.x - x;
+ else if (x > (c->geom.x + c->geom.width))
+ dx = x - (c->geom.x + c->geom.width);
+
+ if (y < c->geom.y)
+ dy = c->geom.y - y;
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
+ }
+ }
+ }
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 8a6eda0..bc04e3f 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,7 +13,10 @@ 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 const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
+enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -30,6 +33,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
+ { "|w|", btrtile },
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -144,6 +148,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
diff --git a/dwl.c b/dwl.c
index 8101ffa..e4e7074 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
/*
* See LICENSE file for copyright and license details.
*/
+#include <limits.h>
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -104,6 +105,7 @@ typedef struct {
const Arg arg;
} Button;
+typedef struct LayoutNode LayoutNode;
typedef struct Monitor Monitor;
typedef struct {
/* Must keep this field first */
@@ -141,8 +143,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
- int isfloating, isurgent, isfullscreen;
+ int isfloating, isurgent, isfullscreen, was_tiled;
uint32_t resize; /* configure serial of a pending resize */
+ struct wlr_box old_geom;
} Client;
typedef struct {
@@ -209,6 +212,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
+ LayoutNode *root;
};
typedef struct {
@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
static void axisnotify(struct wl_listener *listener, void *data);
+static void btrtile(Monitor *m);
static void buttonpress(struct wl_listener *listener, void *data);
static void chvt(const Arg *arg);
static void checkidleinhibitor(struct wlr_surface *exclude);
@@ -333,6 +338,9 @@ 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 setratio_h(const Arg *arg);
+static void setratio_v(const Arg *arg);
+static void swapclients(const Arg *arg);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -458,6 +466,7 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
+#include "btrtile.c"
/* function implementations */
void
@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
- Client *c;
+ Client *c, *target = NULL;
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data)
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (b = buttons; b < END(buttons); b++) {
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
- event->button == b->button && b->func) {
+ event->button == b->button && b->func) {
b->func(&b->arg);
return;
}
@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data)
/* If you released any buttons, we exit interactive move/resize mode. */
/* TODO: should reset to the pointer focus's current setcursor */
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
+ c = grabc;
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
+ if (cursor_mode == CurMove && c->isfloating) {
+ target = xytoclient(cursor->x, cursor->y);
+
+ if (target && !target->isfloating && !target->isfullscreen)
+ insert_client(selmon, target, c);
+ else
+ selmon->root = create_client_node(c);
+
+ setfloating(c, 0);
+ arrange(selmon);
+
+ } else if (cursor_mode == CurResize && !c->isfloating) {
+ resizing_from_mouse = 0;
+ }
+ } else {
+ if (cursor_mode == CurResize && resizing_from_mouse)
+ resizing_from_mouse = 0;
+ }
+ /* Default behaviour */
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
cursor_mode = CurNormal;
/* Drop the window off on its new monitor */
@@ -750,6 +780,7 @@ cleanupmon(struct wl_listener *listener, void *data)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
+ destroy_tree(m);
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
+ init_tree(m);
/* The xdg-protocol specifies:
*
@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data)
wl_list_remove(&c->destroy.link);
wl_list_remove(&c->set_title.link);
wl_list_remove(&c->fullscreen.link);
+ /* We check if the destroyed client was part of any tiled_list, to catch
+ * client removals even if they would not be currently managed by btrtile */
+ if (selmon && selmon->root)
+ remove_client(selmon, c);
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1866,7 +1902,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
- double sx = 0, sy = 0, sx_confined, sy_confined;
+ int tiled = 0;
+ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total;
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct wlr_surface *surface = NULL;
@@ -1920,18 +1957,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
/* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
- /* If we are currently grabbing the mouse, handle and return */
+ /* Skip if internal call or already resizing */
+ if (time == 0 && resizing_from_mouse)
+ goto focus;
+
+ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
if (cursor_mode == CurMove) {
/* Move the grabbed client to the new position. */
- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
- .width = grabc->geom.width, .height = grabc->geom.height}, 1);
- return;
+ if (grabc && grabc->isfloating) {
+ resize(grabc, (struct wlr_box){
+ .x = (int)round(cursor->x) - grabcx,
+ .y = (int)round(cursor->y) - grabcy,
+ .width = grabc->geom.width,
+ .height = grabc->geom.height
+ }, 1);
+ return;
+ }
} else if (cursor_mode == CurResize) {
- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
- return;
+ if (tiled && resizing_from_mouse) {
+ dx_total = cursor->x - resize_last_update_x;
+ dy_total = cursor->y - resize_last_update_y;
+
+ if (time - last_resize_time >= resize_interval_ms) {
+ Arg a = {0};
+ if (fabs(dx_total) > fabs(dy_total)) {
+ a.f = (float)(dx_total * resize_factor);
+ setratio_h(&a);
+ } else {
+ a.f = (float)(dy_total * resize_factor);
+ setratio_v(&a);
+ }
+ arrange(selmon);
+
+ last_resize_time = time;
+ resize_last_update_x = cursor->x;
+ resize_last_update_y = cursor->y;
+ }
+
+ } else if (grabc && grabc->isfloating) {
+ /* Floating resize as original */
+ resize(grabc, (struct wlr_box){
+ .x = grabc->geom.x,
+ .y = grabc->geom.y,
+ .width = (int)round(cursor->x) - grabc->geom.x,
+ .height = (int)round(cursor->y) - grabc->geom.y
+ }, 1);
+ return;
+ }
}
+focus:
/* If there's no client surface under the cursor, set the cursor image to a
* default. This is what makes the cursor image appear when you move it
* off of a client or over its border. */
@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
- /* Float the window and tell motionnotify to grab it */
- setfloating(grabc, 1);
- switch (cursor_mode = arg->ui) {
- case CurMove:
- grabcx = (int)round(cursor->x) - grabc->geom.x;
- grabcy = (int)round(cursor->y) - grabc->geom.y;
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
- * returns the cursor to where it started */
- wlr_cursor_warp_closest(cursor, NULL,
- grabc->geom.x + grabc->geom.width,
- grabc->geom.y + grabc->geom.height);
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
- break;
+ cursor_mode = arg->ui;
+ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
+
+ if (grabc->was_tiled) {
+ switch (cursor_mode) {
+ case CurMove:
+ setfloating(grabc, 1);
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+ break;
+ case CurResize:
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+ resize_last_update_x = cursor->x;
+ resize_last_update_y = cursor->y;
+ resizing_from_mouse = 1;
+ break;
+ }
+ } else {
+ /* Default floating logic */
+ /* Float the window and tell motionnotify to grab it */
+ setfloating(grabc, 1);
+ switch (cursor_mode) {
+ case CurMove:
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
+ break;
+ case CurResize:
+ wlr_cursor_warp_closest(cursor, NULL,
+ grabc->geom.x + grabc->geom.width,
+ grabc->geom.y + grabc->geom.height);
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
+ break;
+ }
}
}
--
2.52.0
@@ -1,30 +1,32 @@
From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001
From 912701a41b19c013a9adcb176a7b6503aa1cf9c1 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Thu, 13 Feb 2025 23:23:40 +0200
Subject: [PATCH] btrtile with multi-tag support
Date: Thu, 19 Mar 2026 22:56:45 +0200
Subject: [PATCH] Refactor btrtile to wlroots-next
---
btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 152 +++++++++++---
3 files changed, 698 insertions(+), 29 deletions(-)
dwl.c | 148 +++++++++++---
3 files changed, 698 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..03f4680
index 0000000..d706c3b
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,563 @@
@@ -0,0 +1,565 @@
+/* ************************************************************************** */
+/* */
+/* ::: :::::::: */
+/* btrtile.c :+: :+: :+: */
+/* +:+ +:+ +:+ */
+/* By: jmakkone <jmakkone@student.hive.fi> +#+ +:+ +#+ */
+/* +#+#+#+#+#+ +#+ */
+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */
+/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* btrtile.c @!! @!@ @! !@! */
+/* !!! !@!!! !!! */
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -580,13 +582,13 @@ index 0000000..03f4680
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 22d2171..92f3ad6 100644
index 8a6eda0..bc04e3f 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,7 +13,10 @@ 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 const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
@@ -594,7 +596,7 @@ index 22d2171..92f3ad6 100644
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -31,6 +34,7 @@ static const Rule rules[] = {
@@ -30,6 +33,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
@@ -602,23 +604,23 @@ index 22d2171..92f3ad6 100644
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -148,6 +152,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
@@ -144,6 +148,14 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
diff --git a/dwl.c b/dwl.c
index a2711f6..e49a061 100644
index 8101ffa..e4e7074 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -629,15 +631,15 @@ index a2711f6..e49a061 100644
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -103,6 +104,7 @@ typedef struct {
@@ -104,6 +105,7 @@ typedef struct {
const Arg arg;
} Button;
+typedef struct LayoutNode LayoutNode;
typedef struct Monitor Monitor;
typedef struct {
/* Must keep these three elements in this order */
@@ -139,8 +141,9 @@ typedef struct {
/* Must keep this field first */
@@ -141,8 +143,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@@ -648,7 +650,7 @@ index a2711f6..e49a061 100644
} Client;
typedef struct {
@@ -208,6 +211,7 @@ struct Monitor {
@@ -209,6 +212,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
@@ -656,7 +658,7 @@ index a2711f6..e49a061 100644
};
typedef struct {
@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
static void axisnotify(struct wl_listener *listener, void *data);
@@ -674,7 +676,7 @@ index a2711f6..e49a061 100644
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
@@ -458,6 +466,7 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
@@ -682,7 +684,7 @@ index a2711f6..e49a061 100644
/* function implementations */
void
@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
@@ -691,7 +693,7 @@ index a2711f6..e49a061 100644
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data)
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
for (b = buttons; b < END(buttons); b++) {
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
@@ -700,9 +702,9 @@ index a2711f6..e49a061 100644
b->func(&b->arg);
return;
}
@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data)
/* If you released any buttons, we exit interactive move/resize mode. */
/* TODO should reset to the pointer focus's current setcursor */
/* TODO: should reset to the pointer focus's current setcursor */
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
+ c = grabc;
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
@@ -728,18 +730,7 @@ index a2711f6..e49a061 100644
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
cursor_mode = CurNormal;
/* Drop the window off on its new monitor */
selmon = xytomon(cursor->x, cursor->y);
setmon(grabc, selmon, 0);
+ grabc = NULL;
return;
- } else {
- cursor_mode = CurNormal;
}
+ cursor_mode = CurNormal;
break;
}
/* If the event wasn't handled by the compositor, notify the client with
@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data)
@@ -750,6 +780,7 @@ cleanupmon(struct wl_listener *listener, void *data)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
@@ -747,7 +738,7 @@ index a2711f6..e49a061 100644
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
@@ -755,7 +746,7 @@ index a2711f6..e49a061 100644
/* The xdg-protocol specifies:
*
@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data)
wl_list_remove(&c->destroy.link);
wl_list_remove(&c->set_title.link);
wl_list_remove(&c->fullscreen.link);
@@ -766,7 +757,7 @@ index a2711f6..e49a061 100644
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1809,7 +1845,8 @@ void
@@ -1866,7 +1902,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -776,7 +767,7 @@ index a2711f6..e49a061 100644
Client *c = NULL, *w = NULL;
LayerSurface *l = NULL;
struct wlr_surface *surface = NULL;
@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
@@ -1920,18 +1957,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
/* Update drag icon's position */
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
@@ -840,7 +831,7 @@ index a2711f6..e49a061 100644
/* If there's no client surface under the cursor, set the cursor image to a
* default. This is what makes the cursor image appear when you move it
* off of a client or over its border. */
@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -850,7 +841,7 @@ index a2711f6..e49a061 100644
- case CurMove:
- grabcx = (int)round(cursor->x) - grabc->geom.x;
- grabcy = (int)round(cursor->y) - grabc->geom.y;
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
@@ -899,5 +890,5 @@ index a2711f6..e49a061 100644
}
--
2.45.3
2.52.0
+2 -1
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)
@@ -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
+3 -2
View File
@@ -2,8 +2,9 @@
This patch allows a window to adjust its layout as if it was fullscreen, but it won't change its size and position, and it will stays under the control of dwl. For example a video on a browser can occupy the whole space reserved to the window, but we can still resize it and move it and see the status bar.
### Download
- [git branch](https://codeberg.org/Kana/dwl/src/branch/controlled_fullscreen)
- [main 2025-11-13](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/controlled_fullscreen/controlled_fullscreen.patch)
- [dwl 0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/controlled_fullscreen/controlled_fullscreen.patch)
- [2025-11-13](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/controlled_fullscreen/controlled_fullscreen_2025_11_13.patch)
- [2025-10-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/controlled_fullscreen/controlled_fullscreen_2025_10_08.patch)
### Authors
@@ -1,17 +1,17 @@
From e0cecc228d436425c0d921a1eec5e0370d24613d Mon Sep 17 00:00:00 2001
From 234dc2774b23d21c647c6967b1dd38e61165fabf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Thu, 13 Nov 2025 09:09:22 -0300
Subject: [PATCH] controlled fullscreen fix
Date: Sat, 14 Mar 2026 17:20:21 -0300
Subject: [PATCH] update to dwl 0.8
---
dwl.c | 32 ++++++++++++--------------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/dwl.c b/dwl.c
index 12f441e..4f124eb 100644
index 101a45f..0ec8db6 100644
--- a/dwl.c
+++ b/dwl.c
@@ -518,9 +518,6 @@ arrange(Monitor *m)
@@ -519,9 +519,6 @@ arrange(Monitor *m)
}
}
@@ -21,7 +21,7 @@ index 12f441e..4f124eb 100644
strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol));
/* We move all clients (except fullscreen and unmanaged) to LyrTile while
@@ -878,7 +875,7 @@ commitnotify(struct wl_listener *listener, void *data)
@@ -879,7 +876,7 @@ commitnotify(struct wl_listener *listener, void *data)
return;
}
@@ -30,7 +30,7 @@ index 12f441e..4f124eb 100644
/* mark a pending resize as completed */
if (c->resize && c->resize <= c->surface.xdg->current.configure_serial)
@@ -1490,7 +1487,7 @@ focusstack(const Arg *arg)
@@ -1491,7 +1488,7 @@ focusstack(const Arg *arg)
{
/* Focus the next or previous client (in tiling order) on selmon */
Client *c, *sel = focustop(selmon);
@@ -39,7 +39,7 @@ index 12f441e..4f124eb 100644
return;
if (arg->i > 0) {
wl_list_for_each(c, &sel->link, link) {
@@ -1826,7 +1823,7 @@ monocle(Monitor *m)
@@ -1828,7 +1825,7 @@ monocle(Monitor *m)
int n = 0;
wl_list_for_each(c, &clients, link) {
@@ -48,7 +48,7 @@ index 12f441e..4f124eb 100644
continue;
resize(c, m->w, 0);
n++;
@@ -1957,7 +1954,7 @@ moveresize(const Arg *arg)
@@ -1959,7 +1956,7 @@ moveresize(const Arg *arg)
if (cursor_mode != CurNormal && cursor_mode != CurPressed)
return;
xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL);
@@ -57,7 +57,7 @@ index 12f441e..4f124eb 100644
return;
/* Float the window and tell motionnotify to grab it */
@@ -2332,14 +2329,12 @@ setcursorshape(struct wl_listener *listener, void *data)
@@ -2334,14 +2331,12 @@ setcursorshape(struct wl_listener *listener, void *data)
void
setfloating(Client *c, int floating)
{
@@ -74,7 +74,7 @@ index 12f441e..4f124eb 100644
arrange(c->mon);
printstatus();
}
@@ -2352,12 +2347,12 @@ setfullscreen(Client *c, int fullscreen)
@@ -2354,12 +2349,12 @@ setfullscreen(Client *c, int fullscreen)
return;
c->bw = fullscreen ? 0 : borderpx;
client_set_fullscreen(c, fullscreen);
@@ -90,7 +90,7 @@ index 12f441e..4f124eb 100644
} else {
/* restore previous size instead of arrange for floating windows since
* client positions are set by the user and cannot be recalculated */
@@ -2716,7 +2711,7 @@ tile(Monitor *m)
@@ -2719,7 +2714,7 @@ tile(Monitor *m)
Client *c;
wl_list_for_each(c, &clients, link)
@@ -99,7 +99,7 @@ index 12f441e..4f124eb 100644
n++;
if (n == 0)
return;
@@ -2727,7 +2722,7 @@ tile(Monitor *m)
@@ -2730,7 +2725,7 @@ tile(Monitor *m)
mw = m->w.width;
i = my = ty = 0;
wl_list_for_each(c, &clients, link) {
@@ -108,7 +108,7 @@ index 12f441e..4f124eb 100644
continue;
if (i < m->nmaster) {
resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw,
@@ -2747,7 +2742,7 @@ togglefloating(const Arg *arg)
@@ -2750,7 +2745,7 @@ togglefloating(const Arg *arg)
{
Client *sel = focustop(selmon);
/* return if fullscreen */
@@ -117,7 +117,7 @@ index 12f441e..4f124eb 100644
setfloating(sel, !sel->isfloating);
}
@@ -2902,9 +2897,6 @@ updatemons(struct wl_listener *listener, void *data)
@@ -2905,9 +2900,6 @@ updatemons(struct wl_listener *listener, void *data)
arrangelayers(m);
/* Don't move clients to the left output when plugging monitors */
arrange(m);
@@ -0,0 +1,132 @@
From e0cecc228d436425c0d921a1eec5e0370d24613d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Thu, 13 Nov 2025 09:09:22 -0300
Subject: [PATCH] controlled fullscreen fix
---
dwl.c | 32 ++++++++++++--------------------
1 file changed, 12 insertions(+), 20 deletions(-)
diff --git a/dwl.c b/dwl.c
index 12f441e..4f124eb 100644
--- a/dwl.c
+++ b/dwl.c
@@ -518,9 +518,6 @@ arrange(Monitor *m)
}
}
- wlr_scene_node_set_enabled(&m->fullscreen_bg->node,
- (c = focustop(m)) && c->isfullscreen);
-
strncpy(m->ltsymbol, m->lt[m->sellt]->symbol, LENGTH(m->ltsymbol));
/* We move all clients (except fullscreen and unmanaged) to LyrTile while
@@ -878,7 +875,7 @@ commitnotify(struct wl_listener *listener, void *data)
return;
}
- resize(c, c->geom, (c->isfloating && !c->isfullscreen));
+ resize(c, c->geom, (c->isfloating));
/* mark a pending resize as completed */
if (c->resize && c->resize <= c->surface.xdg->current.configure_serial)
@@ -1490,7 +1487,7 @@ focusstack(const Arg *arg)
{
/* Focus the next or previous client (in tiling order) on selmon */
Client *c, *sel = focustop(selmon);
- if (!sel || (sel->isfullscreen && !client_has_children(sel)))
+ if (!sel)
return;
if (arg->i > 0) {
wl_list_for_each(c, &sel->link, link) {
@@ -1826,7 +1823,7 @@ monocle(Monitor *m)
int n = 0;
wl_list_for_each(c, &clients, link) {
- if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ if (!VISIBLEON(c, m) || c->isfloating)
continue;
resize(c, m->w, 0);
n++;
@@ -1957,7 +1954,7 @@ moveresize(const Arg *arg)
if (cursor_mode != CurNormal && cursor_mode != CurPressed)
return;
xytonode(cursor->x, cursor->y, NULL, &grabc, NULL, NULL, NULL);
- if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
+ if (!grabc || client_is_unmanaged(grabc))
return;
/* Float the window and tell motionnotify to grab it */
@@ -2332,14 +2329,12 @@ setcursorshape(struct wl_listener *listener, void *data)
void
setfloating(Client *c, int floating)
{
- Client *p = client_get_parent(c);
c->isfloating = floating;
/* If in floating layout do not change the client's layer */
if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange)
return;
- wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
- (p && p->isfullscreen) ? LyrFS
- : c->isfloating ? LyrFloat : LyrTile]);
+ wlr_scene_node_reparent(&c->scene->node,
+ layers[c->isfloating ? LyrFloat : LyrTile]);
arrange(c->mon);
printstatus();
}
@@ -2352,12 +2347,12 @@ setfullscreen(Client *c, int fullscreen)
return;
c->bw = fullscreen ? 0 : borderpx;
client_set_fullscreen(c, fullscreen);
- wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen
- ? LyrFS : c->isfloating ? LyrFloat : LyrTile]);
+ wlr_scene_node_reparent(&c->scene->node,
+ layers[c->isfloating ? LyrFloat : LyrTile]);
if (fullscreen) {
c->prev = c->geom;
- resize(c, c->mon->m, 0);
+ resize(c, c->mon->w, 0);
} else {
/* restore previous size instead of arrange for floating windows since
* client positions are set by the user and cannot be recalculated */
@@ -2716,7 +2711,7 @@ tile(Monitor *m)
Client *c;
wl_list_for_each(c, &clients, link)
- if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
+ if (VISIBLEON(c, m) && !c->isfloating)
n++;
if (n == 0)
return;
@@ -2727,7 +2722,7 @@ tile(Monitor *m)
mw = m->w.width;
i = my = ty = 0;
wl_list_for_each(c, &clients, link) {
- if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ if (!VISIBLEON(c, m) || c->isfloating)
continue;
if (i < m->nmaster) {
resize(c, (struct wlr_box){.x = m->w.x, .y = m->w.y + my, .width = mw,
@@ -2747,7 +2742,7 @@ togglefloating(const Arg *arg)
{
Client *sel = focustop(selmon);
/* return if fullscreen */
- if (sel && !sel->isfullscreen)
+ if (sel)
setfloating(sel, !sel->isfloating);
}
@@ -2902,9 +2897,6 @@ updatemons(struct wl_listener *listener, void *data)
arrangelayers(m);
/* Don't move clients to the left output when plugging monitors */
arrange(m);
- /* make sure fullscreen clients have the right size */
- if ((c = focustop(m)) && c->isfullscreen)
- resize(c, m->m, 0);
/* Try to re-set the gamma LUT when updating monitors,
* it's only really needed when enabling a disabled output, but meh. */
--
2.51.0
+1 -1
View File
@@ -1,5 +1,5 @@
### Description
Generate a coredump if dwl exited abnormally (to be more usefull you need to
Generate a coredump if dwl exited abnormally (to be more useful you need to
compile dwl and wlroots with debug symbols)
### Download
+2 -1
View File
@@ -2,8 +2,9 @@
Deck is a dwl-layout which is inspired by the dwm Deck layout (which is inspired by TTWM window manager). It applies the monocle-layout to the clients in the stack. The master-client is still visible. The stacked clients are like a deck of cards, hence the name.
### Download
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/decklayout/decklayout.patch)
- [git branch](https://codeberg.org/Kana/dwl/src/branch/decklayout)
- [main 2025-10-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/decklayout/decklayout.patch)
- [main 2025-10-08](https://codeberg.org/dwl/dwl-patches/raw/commit/f8d1cfad116c19c01593f7436468ec0cb7a3297b/patches/decklayout/decklayout.patch)
### Authors
- [André Desgualdo Pereira](https://codeberg.org/Kana)
+17 -17
View File
@@ -1,7 +1,7 @@
From 095439425e64f2567f141d5d941178b148ef0d3a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Sun, 12 Oct 2025 11:44:26 -0300
Subject: [PATCH] decklayout
From c488515313e20f51ab961691002f9d483682ab16 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 21:32:46 +0800
Subject: [PATCH] Patch: decklayout-0.8.patch
---
config.def.h | 2 ++
@@ -9,10 +9,10 @@ Subject: [PATCH] decklayout
2 files changed, 63 insertions(+)
diff --git a/config.def.h b/config.def.h
index 95c2afa..cc846eb 100644
index 8a6eda0..3ec9ceb 100644
--- a/config.def.h
+++ b/config.def.h
@@ -34,6 +34,7 @@ static const Layout layouts[] = {
@@ -33,6 +33,7 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -20,16 +20,16 @@ index 95c2afa..cc846eb 100644
};
/* monitors */
@@ -139,6 +140,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_a, 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} },
@@ -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_a, 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 12f441e..227004f 100644
index 44f3ad9..f13f48b 100644
--- a/dwl.c
+++ b/dwl.c
@@ -278,6 +278,7 @@ static void destroylayersurfacenotify(struct wl_listener *listener, void *data);
@@ -40,7 +40,7 @@ index 12f441e..227004f 100644
static void destroypointerconstraint(struct wl_listener *listener, void *data);
static void destroysessionlock(struct wl_listener *listener, void *data);
static void destroykeyboardgroup(struct wl_listener *listener, void *data);
@@ -1837,6 +1838,66 @@ monocle(Monitor *m)
@@ -1838,6 +1839,66 @@ monocle(Monitor *m)
wlr_scene_node_raise_to_top(&c->scene->node);
}
@@ -108,5 +108,5 @@ index 12f441e..227004f 100644
motionabsolute(struct wl_listener *listener, void *data)
{
--
2.51.0
2.53.0
+2 -1
View File
@@ -8,7 +8,8 @@ There are also two functions that can be bound to a `Key` or `Button`,
2. `toggledimmingclient`: Which toggles dimming for the focused window, as if the client had `neverdim` applied to it. This overwrites an applied `Rule`.
### Download
- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused.patch)
- [2026-03-10](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused.patch)
- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240918.patch)
- [2024-09-03](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240903.patch)
- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240714.patch)
- [2024-05-16](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/dim-unfocused/dim-unfocused-20240516.patch)
@@ -0,0 +1,216 @@
diff --git a/client.h b/client.h
index dabea35..3a31c25 100644
--- a/client.h
+++ b/client.h
@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4])
wlr_scene_rect_set_color(c->border[i], color);
}
+static inline void
+client_set_dimmer_state(Client *c, const int dim)
+{
+ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim);
+}
+
static inline void
client_set_fullscreen(Client *c, int fullscreen)
{
diff --git a/config.def.h b/config.def.h
index 22d2171..4ca21c9 100644
--- a/config.def.h
+++ b/config.def.h
@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */
static const float rootcolor[] = COLOR(0x222222ff);
static const float bordercolor[] = COLOR(0x444444ff);
static const float focuscolor[] = COLOR(0x005577ff);
+static const float unfocuseddim[] = COLOR(0x00000088);
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 */
@@ -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[] = {
- /* app_id title tags mask isfloating monitor */
- /* 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" */
+ /* app_id title tags mask isfloating neverdim monitor */
+ /* examples:
+ { "Gimp_example", NULL, 0, 1, 0, -1 },
+ */
+ { "firefox_example", NULL, 1 << 8, 0, 1, -1 },
};
/* layout(s) */
@@ -140,8 +142,9 @@ 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_apostrophe, toggledimming, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
- { MODKEY, XKB_KEY_e, togglefullscreen, {0} },
+ { MODKEY, XKB_KEY_e, togglefullscreen, {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} },
@@ -172,5 +175,6 @@ static const Key keys[] = {
static const Button buttons[] = {
{ MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} },
{ MODKEY, BTN_MIDDLE, togglefloating, {0} },
+ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} },
{ MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} },
};
diff --git a/dwl.c b/dwl.c
index dc0c861..dcc3ece 100644
--- a/dwl.c
+++ b/dwl.c
@@ -112,6 +112,7 @@ typedef struct {
Monitor *mon;
struct wlr_scene_tree *scene;
struct wlr_scene_rect *border[4]; /* top, bottom, left, right */
+ struct wlr_scene_rect *dimmer;
struct wlr_scene_tree *scene_surface;
struct wl_list link;
struct wl_list flink;
@@ -141,7 +142,7 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
- int isfloating, isurgent, isfullscreen;
+ int isfloating, isurgent, isfullscreen, neverdim;
uint32_t resize; /* configure serial of a pending resize */
} Client;
@@ -231,6 +232,7 @@ typedef struct {
const char *title;
uint32_t tags;
int isfloating;
+ int neverdim;
int monitor;
} Rule;
@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
static void tile(Monitor *m);
+static void toggledimming(const Arg *arg);
+static void toggledimmingclient(const Arg *arg);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout;
static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static int DIMOPT = 1;
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
@@ -466,6 +471,7 @@ applyrules(Client *c)
if ((!r->title || strstr(title, r->title))
&& (!r->id || strstr(appid, r->id))) {
c->isfloating = r->isfloating;
+ c->neverdim = r-> neverdim;
newtags |= r->tags;
i = 0;
wl_list_for_each(m, &mons, link) {
@@ -1365,8 +1371,10 @@ focusclient(Client *c, int lift)
/* Don't change border color if there is an exclusive focus or we are
* handling a drag operation */
- if (!exclusive_focus && !seat->drag)
+ if (!exclusive_focus && !seat->drag) {
client_set_border_color(c, focuscolor);
+ client_set_dimmer_state(c, 0);
+ }
}
/* Deactivate old client if focus is changing */
@@ -1384,7 +1392,7 @@ focusclient(Client *c, int lift)
* and probably other clients */
} else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) {
client_set_border_color(old_c, bordercolor);
-
+ client_set_dimmer_state(old_c, 1);
client_activate_surface(old, 0);
}
}
@@ -1681,8 +1689,7 @@ void
mapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is mapped, or ready to display on-screen. */
- Client *p = NULL;
- Client *w, *c = wl_container_of(listener, c, map);
+ Client *p, *w, *d, *c = wl_container_of(listener, c, map);
Monitor *m;
int i;
@@ -1716,6 +1723,10 @@ mapnotify(struct wl_listener *listener, void *data)
c->border[i]->node.data = c;
}
+ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim);
+ c->dimmer->node.data = c;
+ client_set_dimmer_state(c, 1);
+
/* Initialize client geometry with room for border */
client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT);
c->geom.width += 2 * c->bw;
@@ -1734,6 +1745,10 @@ mapnotify(struct wl_listener *listener, void *data)
setmon(c, p->mon, p->tags);
} else {
applyrules(c);
+ d = focustop(selmon);
+ if (d) {
+ client_set_dimmer_state(d, 0);
+ }
}
printstatus();
@@ -2160,7 +2175,7 @@ resize(Client *c, struct wlr_box geo, int interact)
c->geom = geo;
applybounds(c, bbox);
- /* Update scene-graph, including borders */
+ /* Update scene-graph, including borders and dimmer*/
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y);
wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw);
wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw);
@@ -2170,6 +2185,8 @@ resize(Client *c, struct wlr_box geo, int interact)
wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw);
wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw);
wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw);
+ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw);
+ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw);
/* this is a no-op if size hasn't changed */
c->resize = client_set_size(c, c->geom.width - 2 * c->bw,
@@ -2681,6 +2698,27 @@ tile(Monitor *m)
}
}
+void toggledimming(const Arg *arg)
+{
+ Client *c;
+ DIMOPT ^= 1;
+ wl_list_for_each(c, &clients, link)
+ {
+ client_set_dimmer_state(c, 1);
+ }
+ c = focustop(selmon);
+ if (c)
+ client_set_dimmer_state(c, 0);
+}
+
+void
+toggledimmingclient(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ if (sel)
+ sel -> neverdim ^= 1;
+}
+
void
togglefloating(const Arg *arg)
{
+1 -216
View File
@@ -1,216 +1 @@
diff --git a/client.h b/client.h
index dabea35..3a31c25 100644
--- a/client.h
+++ b/client.h
@@ -319,6 +319,12 @@ client_set_border_color(Client *c, const float color[static 4])
wlr_scene_rect_set_color(c->border[i], color);
}
+static inline void
+client_set_dimmer_state(Client *c, const int dim)
+{
+ wlr_scene_node_set_enabled(&c->dimmer->node, DIMOPT && !c->neverdim && dim);
+}
+
static inline void
client_set_fullscreen(Client *c, int fullscreen)
{
diff --git a/config.def.h b/config.def.h
index 22d2171..4ca21c9 100644
--- a/config.def.h
+++ b/config.def.h
@@ -10,6 +10,7 @@ static const unsigned int borderpx = 1; /* border pixel of windows */
static const float rootcolor[] = COLOR(0x222222ff);
static const float bordercolor[] = COLOR(0x444444ff);
static const float focuscolor[] = COLOR(0x005577ff);
+static const float unfocuseddim[] = COLOR(0x00000088);
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 */
@@ -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[] = {
- /* app_id title tags mask isfloating monitor */
- /* 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" */
+ /* app_id title tags mask isfloating neverdim monitor */
+ /* examples:
+ { "Gimp_example", NULL, 0, 1, 0, -1 },
+ */
+ { "firefox_example", NULL, 1 << 8, 0, 1, -1 },
};
/* layout(s) */
@@ -140,8 +142,9 @@ 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_apostrophe, toggledimming, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
- { MODKEY, XKB_KEY_e, togglefullscreen, {0} },
+ { MODKEY, XKB_KEY_e, togglefullscreen, {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} },
@@ -172,5 +175,6 @@ static const Key keys[] = {
static const Button buttons[] = {
{ MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} },
{ MODKEY, BTN_MIDDLE, togglefloating, {0} },
+ { MODKEY|ShiftMask, BTN_MIDDLE, toggledimmingclient, {0} },
{ MODKEY, BTN_RIGHT, moveresize, {.ui = CurResize} },
};
diff --git a/dwl.c b/dwl.c
index dc0c861..dcc3ece 100644
--- a/dwl.c
+++ b/dwl.c
@@ -112,6 +112,7 @@ typedef struct {
Monitor *mon;
struct wlr_scene_tree *scene;
struct wlr_scene_rect *border[4]; /* top, bottom, left, right */
+ struct wlr_scene_rect *dimmer;
struct wlr_scene_tree *scene_surface;
struct wl_list link;
struct wl_list flink;
@@ -141,7 +142,7 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
- int isfloating, isurgent, isfullscreen;
+ int isfloating, isurgent, isfullscreen, neverdim;
uint32_t resize; /* configure serial of a pending resize */
} Client;
@@ -231,6 +232,7 @@ typedef struct {
const char *title;
uint32_t tags;
int isfloating;
+ int neverdim;
int monitor;
} Rule;
@@ -338,6 +340,8 @@ static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
static void tile(Monitor *m);
+static void toggledimming(const Arg *arg);
+static void toggledimmingclient(const Arg *arg);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
@@ -410,6 +414,7 @@ static struct wlr_output_layout *output_layout;
static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static int DIMOPT = 1;
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
@@ -466,6 +471,7 @@ applyrules(Client *c)
if ((!r->title || strstr(title, r->title))
&& (!r->id || strstr(appid, r->id))) {
c->isfloating = r->isfloating;
+ c->neverdim = r-> neverdim;
newtags |= r->tags;
i = 0;
wl_list_for_each(m, &mons, link) {
@@ -1365,8 +1371,10 @@ focusclient(Client *c, int lift)
/* Don't change border color if there is an exclusive focus or we are
* handling a drag operation */
- if (!exclusive_focus && !seat->drag)
+ if (!exclusive_focus && !seat->drag) {
client_set_border_color(c, focuscolor);
+ client_set_dimmer_state(c, 0);
+ }
}
/* Deactivate old client if focus is changing */
@@ -1384,7 +1392,7 @@ focusclient(Client *c, int lift)
* and probably other clients */
} else if (old_c && !client_is_unmanaged(old_c) && (!c || !client_wants_focus(c))) {
client_set_border_color(old_c, bordercolor);
-
+ client_set_dimmer_state(old_c, 1);
client_activate_surface(old, 0);
}
}
@@ -1681,8 +1689,7 @@ void
mapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is mapped, or ready to display on-screen. */
- Client *p = NULL;
- Client *w, *c = wl_container_of(listener, c, map);
+ Client *p, *w, *d, *c = wl_container_of(listener, c, map);
Monitor *m;
int i;
@@ -1716,6 +1723,10 @@ mapnotify(struct wl_listener *listener, void *data)
c->border[i]->node.data = c;
}
+ c->dimmer = wlr_scene_rect_create(c->scene, 0, 0, unfocuseddim);
+ c->dimmer->node.data = c;
+ client_set_dimmer_state(c, 1);
+
/* Initialize client geometry with room for border */
client_set_tiled(c, WLR_EDGE_TOP | WLR_EDGE_BOTTOM | WLR_EDGE_LEFT | WLR_EDGE_RIGHT);
c->geom.width += 2 * c->bw;
@@ -1734,6 +1745,10 @@ mapnotify(struct wl_listener *listener, void *data)
setmon(c, p->mon, p->tags);
} else {
applyrules(c);
+ d = focustop(selmon);
+ if (d) {
+ client_set_dimmer_state(d, 0);
+ }
}
printstatus();
@@ -2160,7 +2175,7 @@ resize(Client *c, struct wlr_box geo, int interact)
c->geom = geo;
applybounds(c, bbox);
- /* Update scene-graph, including borders */
+ /* Update scene-graph, including borders and dimmer*/
wlr_scene_node_set_position(&c->scene->node, c->geom.x, c->geom.y);
wlr_scene_node_set_position(&c->scene_surface->node, c->bw, c->bw);
wlr_scene_rect_set_size(c->border[0], c->geom.width, c->bw);
@@ -2170,6 +2185,8 @@ resize(Client *c, struct wlr_box geo, int interact)
wlr_scene_node_set_position(&c->border[1]->node, 0, c->geom.height - c->bw);
wlr_scene_node_set_position(&c->border[2]->node, 0, c->bw);
wlr_scene_node_set_position(&c->border[3]->node, c->geom.width - c->bw, c->bw);
+ wlr_scene_rect_set_size(c->dimmer, c->geom.width - 2*c->bw, c-> geom.height - 2*c->bw);
+ wlr_scene_node_set_position(&c->dimmer->node, c->bw, c->bw);
/* this is a no-op if size hasn't changed */
c->resize = client_set_size(c, c->geom.width - 2 * c->bw,
@@ -2681,6 +2698,27 @@ tile(Monitor *m)
}
}
+void toggledimming(const Arg *arg)
+{
+ Client *c;
+ DIMOPT ^= 1;
+ wl_list_for_each(c, &clients, link)
+ {
+ client_set_dimmer_state(c, 1);
+ }
+ c = focustop(selmon);
+ if (c)
+ client_set_dimmer_state(c, 0);
+}
+
+void
+toggledimmingclient(const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ if (sel)
+ sel -> neverdim ^= 1;
+}
+
void
togglefloating(const Arg *arg)
{
09
+2 -2
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))
+75
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
+17
View File
@@ -0,0 +1,17 @@
### extrabar
Add an extra bar on the other side of the primary bar, shows additional status sections.
_Requires_ `bar` patch to be applied first. Heavily inspired by `dwm`'s extrabar patch.
### Status
The status text on the extra bar is set using the stdin as usual, with the text being split by `;`
For example, if `status1;status2;status3` was set,
then `status1` is set as the status of the primary bar,
`status2` is set as the left status of extrabar,
`status3` is set as the right status of extrabar.
### Author
[dhruva_sambrani](/dhruva_sambrani)
+188
View File
@@ -0,0 +1,188 @@
diff --git a/dwl.c b/dwl.c
index 7fe9468..2614a25 100644
--- a/dwl.c
+++ b/dwl.c
@@ -205,6 +205,7 @@ struct Monitor {
struct wlr_output *wlr_output;
struct wlr_scene_output *scene_output;
struct wlr_scene_buffer *scene_buffer; /* bar buffer */
+ struct wlr_scene_buffer *extra_scene_buffer; /* bar buffer */
struct wlr_scene_rect *fullscreen_bg; /* See createmon() for info */
struct wl_listener frame;
struct wl_listener destroy;
@@ -230,6 +231,7 @@ struct Monitor {
int asleep;
Drwl *drw;
Buffer *pool[2];
+ Buffer *extra_pool[2];
int lrpad;
};
@@ -278,7 +280,7 @@ static void bufdestroy(struct wlr_buffer *buffer);
static bool bufdatabegin(struct wlr_buffer *buffer, uint32_t flags,
void **data, uint32_t *format, size_t *stride);
static void bufdataend(struct wlr_buffer *buffer);
-static Buffer *bufmon(Monitor *m);
+static Buffer *bufmon(Monitor *m, Buffer **pool);
static void bufrelease(struct wl_listener *listener, void *data);
static void buttonpress(struct wl_listener *listener, void *data);
static void chvt(const Arg *arg);
@@ -444,6 +446,8 @@ static struct wl_list mons;
static Monitor *selmon;
static char stext[256];
+static char stext2[256];
+static char stext3[256];
static struct wl_event_source *status_event_source;
static const struct wlr_buffer_impl buffer_impl = {
@@ -627,6 +631,11 @@ arrangelayers(Monitor *m)
usable_area.y += topbar ? m->b.real_height : 0;
}
+ if (m->extra_scene_buffer->node.enabled) {
+ usable_area.height -= m->b.real_height;
+ usable_area.y += topbar ? 0 : m->b.real_height;
+ }
+
/* Arrange exclusive surfaces from top->bottom */
for (i = 3; i >= 0; i--)
arrangelayer(m, &m->layers[i], &usable_area, 1);
@@ -706,23 +715,23 @@ bufdataend(struct wlr_buffer *wlr_buffer)
}
Buffer *
-bufmon(Monitor *m)
+bufmon(Monitor *m, Buffer **pool)
{
size_t i;
Buffer *buf = NULL;
for (i = 0; i < LENGTH(m->pool); i++) {
- if (m->pool[i]) {
- if (m->pool[i]->busy)
+ if (pool[i]) {
+ if (pool[i]->busy)
continue;
- buf = m->pool[i];
+ buf = pool[i];
break;
}
buf = ecalloc(1, sizeof(Buffer) + (m->b.width * 4 * m->b.height));
buf->image = drwl_image_create(NULL, m->b.width, m->b.height, buf->data);
wlr_buffer_init(&buf->base, &buffer_impl, m->b.width, m->b.height);
- m->pool[i] = buf;
+ pool[i] = buf;
break;
}
if (!buf)
@@ -1255,6 +1264,8 @@ createmon(struct wl_listener *listener, void *data)
m->scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL);
m->scene_buffer->point_accepts_input = baracceptsinput;
+ m->extra_scene_buffer = wlr_scene_buffer_create(layers[LyrBottom], NULL);
+ m->extra_scene_buffer->point_accepts_input = baracceptsinput;
updatebar(m);
wl_list_insert(&mons, &m->link);
@@ -1580,7 +1591,7 @@ drawbar(Monitor *m)
if (!m->scene_buffer->node.enabled)
return;
- if (!(buf = bufmon(m)))
+ if (!(buf = bufmon(m, m->pool)))
return;
/* draw status first so it can be overdrawn by tags later */
@@ -1633,13 +1644,47 @@ drawbar(Monitor *m)
wlr_buffer_unlock(&buf->base);
}
+void
+extrabar(Monitor *m)
+{
+ int tw = 0;
+ Buffer *buf = NULL;
+
+ /* Skip if the extra bar scene buffer is not initialized/enabled */
+ if (!m->extra_scene_buffer || !m->extra_scene_buffer->node.enabled)
+ return;
+
+ /* Inline bufmon logic for the extra pool to keep changes contained */
+ if (!(buf = bufmon(m, m->extra_pool)))
+ return;
+
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ drwl_rect(m->drw, 0, 0, m->b.width, m->b.height, 1, 1);
+
+ drwl_setscheme(m->drw, colors[SchemeNorm]);
+ tw = TEXTW(m, stext2) - m->lrpad + 2; /* 2px right padding */
+ drwl_text(m->drw, 2, 0, tw, m->b.height, 0, stext2, 0);
+
+ tw = TEXTW(m, stext3) - m->lrpad + 2; /* 2px right padding */
+ drwl_text(m->drw, m->b.width - tw - 4, 0, tw, m->b.height, 0, stext3, 0);
+
+ wlr_scene_buffer_set_dest_size(m->extra_scene_buffer, m->b.real_width, m->b.real_height);
+ wlr_scene_node_set_position(&m->extra_scene_buffer->node, m->m.x,
+ m->m.y + (topbar ? m->m.height - m->b.real_height : 0));
+
+ wlr_scene_buffer_set_buffer(m->extra_scene_buffer, &buf->base);
+ wlr_buffer_unlock(&buf->base);
+}
+
void
drawbars(void)
{
Monitor *m = NULL;
- wl_list_for_each(m, &mons, link)
+ wl_list_for_each(m, &mons, link) {
drawbar(m);
+ extrabar(m);
+ }
}
void
@@ -2896,7 +2941,7 @@ startdrag(struct wl_listener *listener, void *data)
int
statusin(int fd, unsigned int mask, void *data)
{
- char status[256];
+ char status[3*256];
ssize_t n;
if (mask & WL_EVENT_ERROR)
@@ -2911,7 +2956,18 @@ statusin(int fd, unsigned int mask, void *data)
status[n] = '\0';
status[strcspn(status, "\n")] = '\0';
- strncpy(stext, status, sizeof(stext));
+ char *l1 = strchr(status, ';');
+ if (l1) {
+ *l1 = '\0';
+ char *l2 = strchr(++l1, ';');
+ if (l2) {
+ *l2 = '\0';
+ strncpy(stext3, ++l2, sizeof(stext3));
+ }
+ strncpy(stext2, l1, sizeof(stext2));
+ }
+ strncpy(stext, status, sizeof(stext));
+
drawbars();
return 0;
@@ -3206,6 +3262,12 @@ updatebar(Monitor *m)
m->pool[i] = NULL;
}
+ for (i = 0; i < LENGTH(m->extra_pool); i++)
+ if (m->extra_pool[i]) {
+ wlr_buffer_drop(&m->extra_pool[i]->base);
+ m->extra_pool[i] = NULL;
+ }
+
if (m->b.scale == m->wlr_output->scale && m->drw)
return;
+1 -2
View File
@@ -7,8 +7,7 @@ This is the approximately the equivalent of the focusonactive patch of dwm.
If you want a more controlled behavior, for example setting which clients can focus, check [activation-rule patch](https://codeberg.org/sevz/dwl-patches/src/branch/activation-rules).
### Download
- [git branch](https://codeberg.org/Kana/dwl/src/branch/focusonurgent)
- [main 2025-10-08](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/focusonurgent/focusonurgent.patch)
- [main dwl 0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/focusonurgent/focusonurgent.patch)
### Authors
- [André Desgualdo Pereira](https://codeberg.org/Kana)
+5 -5
View File
@@ -1,17 +1,17 @@
From 3613d9a6342fc85279a79ba203f25ff39fc0d8e4 Mon Sep 17 00:00:00 2001
From 7cdba8df596d59a3ad2883521a65962dcd4fe644 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Wed, 29 Oct 2025 09:06:00 -0300
Subject: [PATCH] add focusonurgent patch
Date: Sat, 14 Mar 2026 17:52:25 -0300
Subject: [PATCH] update
---
dwl.c | 6 ++++--
1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/dwl.c b/dwl.c
index 12f441e..5620f66 100644
index 101a45f..6cd2f82 100644
--- a/dwl.c
+++ b/dwl.c
@@ -2097,8 +2097,10 @@ printstatus(void)
@@ -2099,8 +2099,10 @@ printstatus(void)
if (c->mon != m)
continue;
occ |= c->tags;
+2 -1
View File
@@ -4,7 +4,8 @@ Arranges windows in a grid. Except it adjusts the number of windows in the first
On widescreens (w > 2*h), it splits to three columns before splitting rows.
### Download
- [2025-10-31](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid.patch)
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid.patch)
- [2025-10-31](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20251031.patch)
- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20240918.patch)
- [2024-07-14](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20240714.patch)
- [2023-08-01](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/gaplessgrid/gaplessgrid-20230801.patch)
@@ -0,0 +1,102 @@
From 38427b81367e8b4d2aad708a1d463bc793aac65e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Fri, 31 Oct 2025 15:46:48 -0300
Subject: [PATCH] take on gaplessgrid and fix minor indentation and typos
---
config.def.h | 2 ++
dwl.c | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/config.def.h b/config.def.h
index 95c2afa..2054107 100644
--- a/config.def.h
+++ b/config.def.h
@@ -34,6 +34,7 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
+ { "###", gaplessgrid },
};
/* monitors */
@@ -139,6 +140,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_g, 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 12f441e..7ab5323 100644
--- a/dwl.c
+++ b/dwl.c
@@ -288,6 +288,7 @@ static void focusstack(const Arg *arg);
static Client *focustop(Monitor *m);
static void fullscreennotify(struct wl_listener *listener, void *data);
static void gpureset(struct wl_listener *listener, void *data);
+static void gaplessgrid(Monitor *m);
static void handlesig(int signo);
static void incnmaster(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
@@ -1566,6 +1567,56 @@ handlesig(int signo)
quit(NULL);
}
+void
+gaplessgrid(Monitor *m)
+{
+ int n = 0, i = 0, ch, cw, cn, rn, rows, cols;
+ Client *c;
+
+ wl_list_for_each(c, &clients, link)
+ if (VISIBLEON(c, m) && !c->isfloating)
+ n++;
+ if (n == 0)
+ return;
+
+ /* grid dimensions */
+ for (cols = 0; cols <= (n / 2); cols++)
+ if ((cols * cols) >= n)
+ break;
+
+ if (n == 5) /* set layout against the general calculation: not 1:2:2, but 2:3 */
+ cols = 2;
+
+ /* widescreen is better if 3 columns */
+ if (n >= 3 && n <= 6 && (m->w.width / m->w.height) > 1)
+ cols = 3;
+
+ rows = n / cols;
+
+ /* window geometries */
+ cw = cols ? m->w.width / cols : m->w.width;
+ cn = 0; /* current column number */
+ rn = 0; /* current row number */
+ wl_list_for_each(c, &clients, link) {
+ unsigned int cx, cy;
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+
+ if ((i / rows + 1) > (cols - n % cols))
+ rows = n / cols + 1;
+ ch = rows ? m->w.height / rows : m->w.height;
+ cx = m->w.x + cn * cw;
+ cy = m->w.y + rn * ch;
+ resize(c, (struct wlr_box) { cx, cy, cw, ch}, 0);
+ rn++;
+ if (rn >= rows) {
+ rn = 0;
+ cn++;
+ }
+ i++;
+ }
+}
+
void
incnmaster(const Arg *arg)
{
--
2.51.0
+16 -16
View File
@@ -1,7 +1,7 @@
From 38427b81367e8b4d2aad708a1d463bc793aac65e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Fri, 31 Oct 2025 15:46:48 -0300
Subject: [PATCH] take on gaplessgrid and fix minor indentation and typos
From 91d34b1c664fe273a82eb7ff01e92b828feab5a9 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 21:42:04 +0800
Subject: [PATCH] Patch: gaplessgrid.patch
---
config.def.h | 2 ++
@@ -9,10 +9,10 @@ Subject: [PATCH] take on gaplessgrid and fix minor indentation and typos
2 files changed, 53 insertions(+)
diff --git a/config.def.h b/config.def.h
index 95c2afa..2054107 100644
index 8a6eda0..8cb1782 100644
--- a/config.def.h
+++ b/config.def.h
@@ -34,6 +34,7 @@ static const Layout layouts[] = {
@@ -33,6 +33,7 @@ static const Layout layouts[] = {
{ "[]=", tile },
{ "><>", NULL }, /* no layout function means floating behavior */
{ "[M]", monocle },
@@ -20,16 +20,16 @@ index 95c2afa..2054107 100644
};
/* monitors */
@@ -139,6 +140,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_g, 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} },
@@ -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_g, 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 12f441e..7ab5323 100644
index 44f3ad9..83c053f 100644
--- a/dwl.c
+++ b/dwl.c
@@ -288,6 +288,7 @@ static void focusstack(const Arg *arg);
@@ -98,5 +98,5 @@ index 12f441e..7ab5323 100644
incnmaster(const Arg *arg)
{
--
2.51.0
2.53.0
+3 -2
View File
@@ -15,10 +15,11 @@ gaps to be as big or bigger than "inner" gaps anyway.
### Download
- [0.7 2025-02-11](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps-0.7.patch):
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps.patch)
- [0.7 2025-02-11](https://codeberg.org/dwl/dwl-patches/raw/commit/58e371fcb31bde1e55f99498a77afd9b567a30c8/patches/genericgaps/genericgaps-0.7.patch):
added support for `smartgaps` and `monoclegaps` settings and removed the suck
from `arrange()` function
- [0.7](/dwl/dwl-patches/raw/branch/main/patches/genericgaps/genericgaps.patch)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/f8d1cfad116c19c01593f7436468ec0cb7a3297b/patches/genericgaps/genericgaps.patch)
### Authors
+158 -134
View File
@@ -1,23 +1,25 @@
From f2a1b84369266d252fbea57c6f1eb64253617452 Mon Sep 17 00:00:00 2001
From: Nikita Ivanov <nikita.vyach.ivanov@gmail.com>
Date: Thu, 12 Dec 2024 22:44:53 +0100
Subject: [PATCH] Add genericgaps
From ac0b983fef16eb904b77d6b5e98bd61f5cf719f5 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 23:28:08 +0800
Subject: [PATCH] Patch: genericgaps-0.8.patch
---
config.def.h | 22 +++++++
dwl.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 190 insertions(+), 3 deletions(-)
config.def.h | 23 ++++++
dwl.c | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++-
2 files changed, 220 insertions(+), 1 deletion(-)
diff --git a/config.def.h b/config.def.h
index 22d2171..930af12 100644
index 8a6eda0..4adc866 100644
--- a/config.def.h
+++ b/config.def.h
@@ -7,6 +7,12 @@
@@ -6,7 +6,14 @@
/* appearance */
static const int sloppyfocus = 1; /* focus follows mouse */
static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */
static const unsigned int borderpx = 1; /* border pixel of windows */
+static const int enablegaps = 1; /* 1 means gaps are enabled */
+static const int smartgaps = 0; /* 1 means no outer gap when there is only one window */
+static const int monoclegaps = 0; /* 1 means outer gaps in monocle layout */
static const unsigned int borderpx = 1; /* border pixel of windows */
+static const unsigned int gappih = 10; /* horiz inner gap between windows */
+static const unsigned int gappiv = 10; /* vert inner gap between windows */
+static const unsigned int gappoh = 10; /* horiz outer gap between windows and screen edge */
@@ -25,45 +27,38 @@ index 22d2171..930af12 100644
static const float rootcolor[] = COLOR(0x222222ff);
static const float bordercolor[] = COLOR(0x444444ff);
static const float focuscolor[] = COLOR(0x005577ff);
@@ -133,6 +139,22 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_d, incnmaster, {.i = -1} },
{ MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
{ MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} },
+ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } },
+ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } },
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} },
@@ -129,6 +136,22 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_d, incnmaster, {.i = -1} },
{ MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
{ MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_h, incgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_l, incgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_H, incogaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_L, incogaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_h, incigaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_CTRL, XKB_KEY_l, incigaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_0, togglegaps, {0} },
+ { MODKEY|WLR_MODIFIER_LOGO|WLR_MODIFIER_SHIFT, XKB_KEY_parenright,defaultgaps, {0} },
+ { MODKEY, XKB_KEY_y, incihgaps, {.i = +1 } },
+ { MODKEY, XKB_KEY_o, incihgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, incivgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_o, incivgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_y, incohgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_LOGO, XKB_KEY_o, incohgaps, {.i = -1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Y, incovgaps, {.i = +1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_O, incovgaps, {.i = -1 } },
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} },
diff --git a/dwl.c b/dwl.c
index def2562..c14ae43 100644
index 44f3ad9..a3164cb 100644
--- a/dwl.c
+++ b/dwl.c
@@ -107,6 +107,7 @@ typedef struct Monitor Monitor;
typedef struct {
/* Must keep these three elements in this order */
unsigned int type; /* XDGShell or X11* */
+ int interact;
struct wlr_box geom; /* layout-relative, includes border */
Monitor *mon;
struct wlr_scene_tree *scene;
@@ -200,6 +201,10 @@ struct Monitor {
@@ -197,6 +197,11 @@ struct Monitor {
struct wlr_box w; /* window area, layout-relative */
struct wl_list layers[4]; /* LayerSurface.link */
const Layout *lt[2];
+ int enablegaps; /* enable gaps, used by togglegaps */
+ int gappih; /* horizontal gap between windows */
+ int gappiv; /* vertical gap between windows */
+ int gappoh; /* horizontal outer gaps */
@@ -71,7 +66,15 @@ index def2562..c14ae43 100644
unsigned int seltags;
unsigned int sellt;
uint32_t tagset[2];
@@ -273,6 +278,7 @@ static void createpopup(struct wl_listener *listener, void *data);
@@ -243,6 +248,7 @@ typedef struct {
static void applybounds(Client *c, struct wlr_box *bbox);
static void applyrules(Client *c);
static void arrange(Monitor *m);
+void arrangegaps(Monitor *m);
static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
@@ -271,6 +277,7 @@ static void createpopup(struct wl_listener *listener, void *data);
static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
static void cursorframe(struct wl_listener *listener, void *data);
static void cursorwarptohint(void);
@@ -79,7 +82,7 @@ index def2562..c14ae43 100644
static void destroydecoration(struct wl_listener *listener, void *data);
static void destroydragicon(struct wl_listener *listener, void *data);
static void destroyidleinhibitor(struct wl_listener *listener, void *data);
@@ -293,6 +299,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data);
@@ -290,6 +297,13 @@ static void fullscreennotify(struct wl_listener *listener, void *data);
static void gpureset(struct wl_listener *listener, void *data);
static void handlesig(int signo);
static void incnmaster(const Arg *arg);
@@ -93,24 +96,23 @@ index def2562..c14ae43 100644
static void inputdevice(struct wl_listener *listener, void *data);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
static void keypress(struct wl_listener *listener, void *data);
@@ -320,13 +333,15 @@ static void rendermon(struct wl_listener *listener, void *data);
static void requestdecorationmode(struct wl_listener *listener, void *data);
static void requeststartdrag(struct wl_listener *listener, void *data);
static void requestmonstate(struct wl_listener *listener, void *data);
-static void resize(Client *c, struct wlr_box geo, int interact);
+static void resizeapply(Client *c, struct wlr_box geo, int interact);
+static void resizenoapply(Client *c, struct wlr_box geo, int interact);
static void run(char *startup_cmd);
static void setcursor(struct wl_listener *listener, void *data);
@@ -310,6 +324,7 @@ static void outputmgrapplyortest(struct wlr_output_configuration_v1 *config, int
static void outputmgrtest(struct wl_listener *listener, void *data);
static void pointerfocus(Client *c, struct wlr_surface *surface,
double sx, double sy, uint32_t time);
+static void preparegaps(Monitor *m);
static void printstatus(void);
static void powermgrsetmode(struct wl_listener *listener, void *data);
static void quit(const Arg *arg);
@@ -323,6 +338,7 @@ static void setcursor(struct wl_listener *listener, void *data);
static void setcursorshape(struct wl_listener *listener, void *data);
static void setfloating(Client *c, int floating);
static void setfullscreen(Client *c, int fullscreen);
static void setgamma(struct wl_listener *listener, void *data);
+static void setgaps(int oh, int ov, int ih, int iv);
static void setlayout(const Arg *arg);
static void setmfact(const Arg *arg);
static void setmon(Client *c, Monitor *m, uint32_t newtags);
@@ -340,6 +355,7 @@ static void tagmon(const Arg *arg);
@@ -336,6 +352,7 @@ static void tagmon(const Arg *arg);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
@@ -118,83 +120,82 @@ index def2562..c14ae43 100644
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -413,6 +429,9 @@ static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
@@ -435,6 +452,7 @@ static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
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 int resizelock = 0; /* do not actually resize during arrange */
+static int enablegaps = 1; /* enables gaps, used by togglegaps */
+static void (*resize)(Client *c, struct wlr_box geo, int interact) = resizeapply;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -481,9 +500,25 @@ applyrules(Client *c)
setmon(c, mon, newtags);
}
+void
+applygaps(Client *c)
+{
+ struct wlr_box geom = c->geom;
+
+ if (!c->mon)
+ return;
+
+ geom.x += c->mon->gappih + c->mon->gappoh;
+ geom.y += c->mon->gappiv + c->mon->gappov;
+ geom.width -= c->mon->gappih;
+ geom.height -= c->mon->gappiv;
+ resize(c, geom, 0);
+}
+
void
arrange(Monitor *m)
{
+ int save_width, save_height;
Client *c;
if (!m->wlr_output->enabled)
@@ -515,8 +550,26 @@ arrange(Monitor *m)
@@ -537,12 +555,52 @@ arrange(Monitor *m)
: c->scene->node.parent);
}
- if (m->lt[m->sellt]->arrange)
+ if (m->lt[m->sellt]->arrange) {
+ save_width = m->w.width;
+ save_height = m->w.height;
+ if (enablegaps) {
+ m->w.width -= m->gappih + 2 * m->gappoh;
+ m->w.height -= m->gappiv + 2 * m->gappov;
+ }
+ resize = resizenoapply;
+ preparegaps(m);
m->lt[m->sellt]->arrange(m);
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ if (enablegaps)
+ applygaps(c);
+ resizeapply(c, c->geom, c->interact);
+ }
+ m->w.width = save_width;
+ m->w.height = save_height;
+ resize = resizeapply;
+ arrangegaps(m);
+ }
motionnotify(0, NULL, 0, 0, 0, 0);
checkidleinhibitor(NULL);
}
@@ -993,6 +1046,11 @@ createmon(struct wl_listener *listener, void *data)
wlr_output_state_init(&state);
/* Initialize monitor state using configured rules */
+void
+arrangegaps(Monitor *m)
+{
+ Client *c;
+ int n, gaps;
+
+ if (!m->enablegaps)
+ return;
+
+ resizelock = 0;
+
+ n = 0;
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ n++;
+ }
+
+ gaps = !(smartgaps && n == 1) &&
+ (monoclegaps || m->lt[m->sellt]->arrange != monocle);
+ if (gaps) {
+ m->w.width += m->gappih + 2 * m->gappoh;
+ m->w.height += m->gappiv + 2 * m->gappov;
+ }
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ if (gaps) {
+ c->geom.x += c->mon->gappih + c->mon->gappoh;
+ c->geom.y += c->mon->gappiv + c->mon->gappov;
+ c->geom.width -= c->mon->gappih;
+ c->geom.height -= c->mon->gappiv;
+ }
+ resize(c, c->geom, 0);
+ }
+}
+
void
arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive)
{
@@ -1055,6 +1113,12 @@ createmon(struct wl_listener *listener, void *data)
for (i = 0; i < LENGTH(m->layers); i++)
wl_list_init(&m->layers[i]);
+ m->enablegaps = enablegaps;
+ m->gappih = gappih;
+ m->gappiv = gappiv;
+ m->gappoh = gappoh;
+ m->gappov = gappov;
+
wlr_output_state_init(&state);
/* Initialize monitor state using configured rules */
m->tagset[0] = m->tagset[1] = 1;
for (r = monrules; r < END(monrules); r++) {
if (!r->name || strstr(wlr_output->name, r->name)) {
@@ -1173,6 +1231,12 @@ cursorwarptohint(void)
@@ -1237,6 +1301,12 @@ cursorwarptohint(void)
}
}
@@ -207,7 +208,7 @@ index def2562..c14ae43 100644
void
destroydecoration(struct wl_listener *listener, void *data)
{
@@ -1526,6 +1590,83 @@ incnmaster(const Arg *arg)
@@ -1575,6 +1645,83 @@ incnmaster(const Arg *arg)
arrange(selmon);
}
@@ -291,31 +292,52 @@ index def2562..c14ae43 100644
void
inputdevice(struct wl_listener *listener, void *data)
{
@@ -2180,7 +2321,7 @@ requestmonstate(struct wl_listener *listener, void *data)
}
void
-resize(Client *c, struct wlr_box geo, int interact)
+resizeapply(Client *c, struct wlr_box geo, int interact)
{
struct wlr_box *bbox;
struct wlr_box clip;
@@ -2212,6 +2353,13 @@ resize(Client *c, struct wlr_box geo, int interact)
wlr_scene_subsurface_tree_set_clip(&c->scene_surface->node, &clip);
@@ -2085,6 +2232,31 @@ pointerfocus(Client *c, struct wlr_surface *surface, double sx, double sy,
wlr_seat_pointer_notify_motion(seat, time, sx, sy);
}
+void
+resizenoapply(Client *c, struct wlr_box geo, int interact)
+preparegaps(Monitor *m)
+{
+ c->geom = geo;
+ c->interact = interact;
+ Client *c;
+ int n;
+
+ if (!m->enablegaps)
+ return;
+
+ n = 0;
+ wl_list_for_each(c, &clients, link) {
+ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
+ continue;
+ n++;
+ }
+
+ resizelock = 1;
+
+ if ((smartgaps && n == 1) || (!monoclegaps && m->lt[m->sellt]->arrange == monocle))
+ return;
+
+ m->w.width -= m->gappih + 2 * m->gappoh;
+ m->w.height -= m->gappiv + 2 * m->gappov;
+}
+
void
run(char *startup_cmd)
printstatus(void)
{
@@ -2354,6 +2502,16 @@ setgamma(struct wl_listener *listener, void *data)
wlr_output_schedule_frame(m->wlr_output);
@@ -2208,6 +2380,11 @@ resize(Client *c, struct wlr_box geo, int interact)
struct wlr_box *bbox;
struct wlr_box clip;
+ if (resizelock) {
+ c->geom = geo;
+ return;
+ }
+
if (!c->mon || !client_surface(c)->mapped)
return;
@@ -2368,6 +2545,16 @@ setfullscreen(Client *c, int fullscreen)
printstatus();
}
+void
@@ -331,14 +353,16 @@ index def2562..c14ae43 100644
void
setlayout(const Arg *arg)
{
@@ -2741,6 +2899,13 @@ togglefullscreen(const Arg *arg)
@@ -2760,6 +2947,15 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
+void
+togglegaps(const Arg *arg)
+{
+ enablegaps = !enablegaps;
+ if (!selmon)
+ return;
+ selmon->enablegaps = !selmon->enablegaps;
+ arrange(selmon);
+}
+
@@ -346,5 +370,5 @@ index def2562..c14ae43 100644
toggletag(const Arg *arg)
{
--
2.47.1
2.53.0
+15
View File
@@ -0,0 +1,15 @@
### Description
Hide the mouse cursor when you start typing, and restore it again when the
mouse cursor moves or a mouse button is pressed, just like
[xbanish](https://github.com/jcs/xbanish).
### credits
Some code is taken from the
[unclutter](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/unclutter) patch.
### Download
[v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/hide-cursor-when-typing/hide-cursor-when-typing.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad/)
@@ -0,0 +1,170 @@
From c72de058e6635c5bb3d973995ccbdb6e028f354d Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Tue, 3 Mar 2026 16:48:46 +0800
Subject: [PATCH] Patch: hide-cursor-when-typing-0.8.patch
Hide the cursor when start typing, and restore it when start moving
cursor again, just like xbanish.
---
config.def.h | 2 ++
dwl.c | 68 ++++++++++++++++++++++++++++++++++++++++++++++------
2 files changed, 63 insertions(+), 7 deletions(-)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..6205c0a 100644
--- a/config.def.h
+++ b/config.def.h
@@ -102,6 +102,8 @@ LIBINPUT_CONFIG_TAP_MAP_LMR -- 1/2/3 finger tap maps to left/middle/right
*/
static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TAP_MAP_LRM;
+static const int hide_cursor_when_typing = 1;
+
/* If you want to use the windows key for MODKEY, use WLR_MODIFIER_LOGO */
#define MODKEY WLR_MODIFIER_ALT
diff --git a/dwl.c b/dwl.c
index 44f3ad9..9a076b6 100644
--- a/dwl.c
+++ b/dwl.c
@@ -289,6 +289,8 @@ static Client *focustop(Monitor *m);
static void fullscreennotify(struct wl_listener *listener, void *data);
static void gpureset(struct wl_listener *listener, void *data);
static void handlesig(int signo);
+static void handlecursoractivity(void);
+static int hidecursor(void *data);
static void incnmaster(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
@@ -389,6 +391,14 @@ static struct wlr_pointer_constraint_v1 *active_constraint;
static struct wlr_cursor *cursor;
static struct wlr_xcursor_manager *cursor_mgr;
+static struct wl_event_source *hide_source;
+static bool cursor_hidden = false;
+static struct {
+ enum wp_cursor_shape_device_v1_shape shape;
+ struct wlr_surface *surface;
+ int hotspot_x;
+ int hotspot_y;
+} last_cursor;
static struct wlr_scene_rect *root_bg;
static struct wlr_session_lock_manager_v1 *session_lock_mgr;
@@ -610,6 +620,7 @@ axisnotify(struct wl_listener *listener, void *data)
* for example when you move the scroll wheel. */
struct wlr_pointer_axis_event *event = data;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+ handlecursoractivity();
/* TODO: allow usage of scroll wheel for mousebindings, it can be implemented
* by checking the event's orientation and the delta of the event */
/* Notify the client with pointer focus of the axis event. */
@@ -628,6 +639,7 @@ buttonpress(struct wl_listener *listener, void *data)
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+ handlecursoractivity();
switch (event->state) {
case WL_POINTER_BUTTON_STATE_PRESSED:
@@ -1566,6 +1578,30 @@ handlesig(int signo)
quit(NULL);
}
+void
+handlecursoractivity(void)
+{
+ if (!cursor_hidden)
+ return;
+
+ cursor_hidden = false;
+
+ if (last_cursor.shape)
+ wlr_cursor_set_xcursor(cursor, cursor_mgr,
+ wlr_cursor_shape_v1_name(last_cursor.shape));
+ else
+ wlr_cursor_set_surface(cursor, last_cursor.surface,
+ last_cursor.hotspot_x, last_cursor.hotspot_y);
+}
+
+int
+hidecursor(void *data)
+{
+ wlr_cursor_unset_image(cursor);
+ cursor_hidden = true;
+ return 1;
+}
+
void
incnmaster(const Arg *arg)
{
@@ -1645,6 +1681,11 @@ keypress(struct wl_listener *listener, void *data)
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+ /* hide cursor when typing starts */
+ if (hide_cursor_when_typing && !cursor_hidden && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
+ hidecursor(NULL);
+ }
+
/* On _press_ if there is no active screen locker,
* attempt to process a compositor keybinding. */
if (!locked && event->state == WL_KEYBOARD_KEY_STATE_PRESSED) {
@@ -1907,6 +1948,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
wlr_cursor_move(cursor, device, dx, dy);
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+ handlecursoractivity();
/* Update selmon (even while dragging a window) */
if (sloppyfocus)
@@ -1931,7 +1973,7 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
/* If there's no client surface under the cursor, set the cursor image to a
* default. This is what makes the cursor image appear when you move it
* off of a client or over its border. */
- if (!surface && !seat->drag)
+ if (!surface && !seat->drag && !cursor_hidden)
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
pointerfocus(c, surface, sx, sy, time);
@@ -2311,9 +2353,16 @@ setcursor(struct wl_listener *listener, void *data)
* use the provided surface as the cursor image. It will set the
* hardware cursor on the output that it's currently on and continue to
* do so as the cursor moves between outputs. */
- if (event->seat_client == seat->pointer_state.focused_client)
- wlr_cursor_set_surface(cursor, event->surface,
- event->hotspot_x, event->hotspot_y);
+ if (event->seat_client == seat->pointer_state.focused_client) {
+ last_cursor.shape = 0;
+ last_cursor.surface = event->surface;
+ last_cursor.hotspot_x = event->hotspot_x;
+ last_cursor.hotspot_y = event->hotspot_y;
+
+ if (!cursor_hidden)
+ wlr_cursor_set_surface(cursor, event->surface,
+ event->hotspot_x, event->hotspot_y);
+ }
}
void
@@ -2325,9 +2374,14 @@ setcursorshape(struct wl_listener *listener, void *data)
/* This can be sent by any client, so we check to make sure this one
* actually has pointer focus first. If so, we can tell the cursor to
* use the provided cursor shape. */
- if (event->seat_client == seat->pointer_state.focused_client)
- wlr_cursor_set_xcursor(cursor, cursor_mgr,
- wlr_cursor_shape_v1_name(event->shape));
+ if (event->seat_client == seat->pointer_state.focused_client) {
+ last_cursor.shape = event->shape;
+ last_cursor.surface = NULL;
+
+ if (!cursor_hidden)
+ wlr_cursor_set_xcursor(cursor, cursor_mgr,
+ wlr_cursor_shape_v1_name(event->shape));
+ }
}
void
--
2.53.0
+5 -5
View File
@@ -15,7 +15,7 @@ So in short:
#### Limitations
Reloading the compositor will replace all functionality except for `main`, `setup`, `run` and the reload logic.
Note that you're responsible yourself for reloading ressources like fonts, which may only get acquired once.
Note that you're responsible yourself for reloading resources like fonts, which may only get acquired once.
A lot of components of dwl will also only get run on a trigger (the tiling for example).
So not every change will be immediate.
Furthermore, any patch adding more global state to dwl cannot currently be reloaded properly since
@@ -47,13 +47,13 @@ From the cold part there are some newly available macros:
* `CSYM(T, v)` dynamically accesses the value of the symbol `v` of type `T` from the shared object. Use this to query values from config.h for example.
* `LISTEN_GLOBAL(E, L)` is similar to the `LISTEN` macro. `E` is an event and `L` the name of a global
listener. Current implementation is a bit messy and I may fix it if someone bothers me about it.
* `UNLISTEN(L)` takes a listener and unregisteres it. This is important for reloading.
* `UNLISTEN(L)` takes a listener and unregisters it. This is important for reloading.
When adding new code there are some considerations to be made. Since dwl decorates all symbols with `static` by default, we cannot access them as-is.
C's macro system is a bit too powerful though and we use this to our advantage. We will repeatedly define and
undefine a macro called `static` in order to replace the `static` keyword inside some sections.
This allows us to do less refactoring and preserve a lot of the original patch compatability since we're only
strategically adding lines. We're tring to be as minimally invasive as we can.
This allows us to do less refactoring and preserve a lot of the original patch compatibility since we're only
strategically adding lines. We're trying to be as minimally invasive as we can.
As a general guide:
* global state should be global for the cold part and `extern` in the cold part meaning it should be inside a block like this:
```C
@@ -87,7 +87,7 @@ Thus, we enclose them the same way we do functions:
#ifdef HOT
... // function definitions here
#endif
* enfore use of the `LISTEN_GLOBAL` and `UNLISTEN` macros (I know this sucks but what can I do, I need to get
* enforce use of the `LISTEN_GLOBAL` and `UNLISTEN` macros (I know this sucks but what can I do, I need to get
access to the callbacks somehow). So you want
* `wl_list_remove(listener.link)` to become `UNLISTEN(listener)` and
* `wl_signal_add(event, global_listener)` to become `LISTEN_GLOBAL(event, global_listener)`.
+1 -1
View File
@@ -509,7 +509,7 @@ index def2562..1c9ab67 100644
+ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL);
/* The Wayland display is managed by libwayland. It handles accepting
* clients from the Unix socket, manging Wayland globals, and so on. */
* clients from the Unix socket, managing Wayland globals, and so on. */
@@ -2454,7 +2576,7 @@ setup(void)
/* Initialize the scene graph used to lay out windows */
+1 -1
View File
@@ -637,7 +637,7 @@ index 4816159..70e99be 100644
+ wlr_log_init(CSYM(enum wlr_log_importance, log_level), NULL);
/* The Wayland display is managed by libwayland. It handles accepting
* clients from the Unix socket, manging Wayland globals, and so on. */
* clients from the Unix socket, managing Wayland globals, and so on. */
@@ -2463,7 +2602,7 @@ setup(void)
/* Initialize the scene graph used to lay out windows */
+2 -2
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
+188
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
+3 -2
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
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
+2 -1
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
+169
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
+1 -1
View File
@@ -7,7 +7,7 @@ To enable Xwayland support, you will need to enable it in the wlroots subproject
```sh
meson setup -Dwlroots:xwayland=enabled build
```
It is also reccomended to see the wlroots meson project configuration logs for any
It is also recommended to see the wlroots meson project configuration logs for any
unusual checks, such as requiring `hwdata` for the DRM backend.
### Download
+3 -1
View File
@@ -28,8 +28,10 @@ static const Modekey modekeys[] = {
```
### Download
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/modes/modes.patch)
- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/modes)
- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/modes/modes.patch)
- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/modes/modes-0.5.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad)
- [wochap](https://codeberg.org/wochap)
+165
View File
@@ -0,0 +1,165 @@
From a32b85018ff2cea0fc9f9137789860a4aadc3b3a Mon Sep 17 00:00:00 2001
From: wochap <gean.marroquin@gmail.com>
Date: Wed, 6 Mar 2024 07:31:17 -0500
Subject: [PATCH] implement modes
like sway/river modes
---
config.def.h | 20 ++++++++++++++++++++
dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
diff --git a/config.def.h b/config.def.h
index db0babc..1616136 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,6 +13,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff);
/* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */
static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* You can also use glsl colors */
+enum {
+ BROWSER,
+};
+const char *modes_labels[] = {
+ "browser",
+};
+
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -152,6 +159,8 @@ static const Key keys[] = {
TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8),
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} },
+ { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} },
+
/* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */
{ WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} },
/* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is
@@ -162,6 +171,17 @@ static const Key keys[] = {
CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12),
};
+static const Modekey modekeys[] = {
+ /* mode modifier key function argument */
+ { BROWSER, { 0, XKB_KEY_f, spawn, SHCMD("firefox") } },
+ { BROWSER, { 0, XKB_KEY_f, entermode, {.i = NORMAL} } },
+ { BROWSER, { 0, XKB_KEY_b, spawn, SHCMD("brave") } },
+ { BROWSER, { 0, XKB_KEY_b, entermode, {.i = NORMAL} } },
+ { BROWSER, { 0, XKB_KEY_g, spawn, SHCMD("google-chrome-stable") } },
+ { BROWSER, { 0, XKB_KEY_g, entermode, {.i = NORMAL} } },
+ { BROWSER, { 0, XKB_KEY_Escape, entermode, {.i = NORMAL} } },
+};
+
static const Button buttons[] = {
{ MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} },
{ MODKEY, BTN_MIDDLE, togglefloating, {0} },
diff --git a/dwl.c b/dwl.c
index ef27a1d..1ada006 100644
--- a/dwl.c
+++ b/dwl.c
@@ -139,6 +139,11 @@ typedef struct {
const Arg arg;
} Key;
+typedef struct {
+ int mode_index;
+ Key key;
+} Modekey;
+
typedef struct {
struct wl_list link;
struct wlr_keyboard *wlr_keyboard;
@@ -270,6 +275,7 @@ static void handlesig(int signo);
static void incnmaster(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
+static int modekeybinding(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);
static int keyrepeat(void *data);
@@ -327,6 +333,7 @@ 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 void entermode(const Arg *arg);
/* variables */
static const char broken[] = "broken";
@@ -377,6 +384,9 @@ static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static const int NORMAL = -1;
+static int active_mode_index = NORMAL;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -1372,6 +1382,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
*/
int handled = 0;
const Key *k;
+
+ if (active_mode_index >= 0) {
+ return modekeybinding(mods, sym);
+ }
+
for (k = keys; k < END(keys); k++) {
if (CLEANMASK(mods) == CLEANMASK(k->mod) &&
sym == k->keysym && k->func) {
@@ -1382,6 +1397,29 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
return handled;
}
+int
+modekeybinding(uint32_t mods, xkb_keysym_t sym)
+{
+ int handled = 0;
+ const Modekey *mk;
+ const Key *k;
+
+ for (mk = modekeys; mk < END(modekeys); mk++) {
+ if (active_mode_index != mk->mode_index) {
+ continue;
+ }
+
+ k = &mk->key;
+ if (CLEANMASK(mods) == CLEANMASK(k->mod) &&
+ sym == k->keysym && k->func) {
+ k->func(&k->arg);
+ handled = 1;
+ }
+ }
+
+ return handled;
+}
+
void
keypress(struct wl_listener *listener, void *data)
{
@@ -1851,6 +1889,7 @@ printstatus(void)
printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags],
sel, urg);
printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol);
+ printf("%s mode %s\n", m->wlr_output->name, modes_labels[active_mode_index] ? modes_labels[active_mode_index] : "");
}
fflush(stdout);
}
@@ -2746,6 +2785,13 @@ zoom(const Arg *arg)
arrange(selmon);
}
+void
+entermode(const Arg *arg)
+{
+ active_mode_index = arg->i;
+ printstatus();
+}
+
#ifdef XWAYLAND
void
activatex11(struct wl_listener *listener, void *data)
--
2.42.0
+37 -37
View File
@@ -1,21 +1,20 @@
From a32b85018ff2cea0fc9f9137789860a4aadc3b3a Mon Sep 17 00:00:00 2001
From: wochap <gean.marroquin@gmail.com>
Date: Wed, 6 Mar 2024 07:31:17 -0500
Subject: [PATCH] implement modes
From 915115151a4429bab38dd8cff2268f34ee23984f Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Mon, 2 Mar 2026 19:23:59 +0800
Subject: [PATCH] Patch: modes-0.8.patch
like sway/river modes
---
config.def.h | 20 ++++++++++++++++++++
dwl.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 66 insertions(+)
dwl.c | 47 +++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 67 insertions(+)
diff --git a/config.def.h b/config.def.h
index db0babc..1616136 100644
index 8a6eda0..bc88ea5 100644
--- a/config.def.h
+++ b/config.def.h
@@ -13,6 +13,13 @@ static const float urgentcolor[] = COLOR(0xff0000ff);
/* To conform the xdg-protocol, set the alpha to zero to restore the old behavior */
static const float fullscreen_bg[] = {0.1, 0.1, 0.1, 1.0}; /* You can also use glsl colors */
@@ -14,6 +14,13 @@ 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 */
+enum {
+ BROWSER,
@@ -27,16 +26,16 @@ index db0babc..1616136 100644
/* tagging - TAGCOUNT must be no greater than 31 */
#define TAGCOUNT (9)
@@ -152,6 +159,8 @@ static const Key keys[] = {
TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8),
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Q, quit, {0} },
@@ -155,6 +162,8 @@ static const Key keys[] = {
TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8),
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, quit, {0} },
+ { MODKEY, XKB_KEY_b, entermode, {.i = BROWSER} },
+
/* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */
{ WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} },
/* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is
@@ -162,6 +171,17 @@ static const Key keys[] = {
@@ -165,6 +174,17 @@ static const Key keys[] = {
CHVT(7), CHVT(8), CHVT(9), CHVT(10), CHVT(11), CHVT(12),
};
@@ -55,10 +54,10 @@ index db0babc..1616136 100644
{ MODKEY, BTN_LEFT, moveresize, {.ui = CurMove} },
{ MODKEY, BTN_MIDDLE, togglefloating, {0} },
diff --git a/dwl.c b/dwl.c
index ef27a1d..1ada006 100644
index 44f3ad9..9247541 100644
--- a/dwl.c
+++ b/dwl.c
@@ -139,6 +139,11 @@ typedef struct {
@@ -148,6 +148,11 @@ typedef struct {
const Arg arg;
} Key;
@@ -68,9 +67,9 @@ index ef27a1d..1ada006 100644
+} Modekey;
+
typedef struct {
struct wl_list link;
struct wlr_keyboard *wlr_keyboard;
@@ -270,6 +275,7 @@ static void handlesig(int signo);
struct wlr_keyboard_group *wlr_group;
@@ -292,6 +297,7 @@ static void handlesig(int signo);
static void incnmaster(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
@@ -78,27 +77,27 @@ index ef27a1d..1ada006 100644
static void keypress(struct wl_listener *listener, void *data);
static void keypressmod(struct wl_listener *listener, void *data);
static int keyrepeat(void *data);
@@ -327,6 +333,7 @@ static Monitor *xytomon(double x, double y);
@@ -351,6 +357,7 @@ 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 void entermode(const Arg *arg);
/* variables */
static const char broken[] = "broken";
@@ -377,6 +384,9 @@ static struct wlr_box sgeom;
static pid_t child_pid = -1;
@@ -406,6 +413,9 @@ static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static const int NORMAL = -1;
+static int active_mode_index = NORMAL;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -1372,6 +1382,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
static struct wl_listener cursor_button = {.notify = buttonpress};
@@ -1614,6 +1624,11 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
* processing.
*/
int handled = 0;
const Key *k;
+
+ if (active_mode_index >= 0) {
@@ -106,10 +105,10 @@ index ef27a1d..1ada006 100644
+ }
+
for (k = keys; k < END(keys); k++) {
if (CLEANMASK(mods) == CLEANMASK(k->mod) &&
sym == k->keysym && k->func) {
@@ -1382,6 +1397,29 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
return handled;
if (CLEANMASK(mods) == CLEANMASK(k->mod)
&& xkb_keysym_to_lower(sym) == xkb_keysym_to_lower(k->keysym)
@@ -1625,6 +1640,30 @@ keybinding(uint32_t mods, xkb_keysym_t sym)
return 0;
}
+int
@@ -134,19 +133,20 @@ index ef27a1d..1ada006 100644
+
+ return handled;
+}
+
+
void
keypress(struct wl_listener *listener, void *data)
{
@@ -1851,6 +1889,7 @@ printstatus(void)
printf("%s tags %u %u %u %u\n", m->wlr_output->name, occ, m->tagset[m->seltags],
sel, urg);
@@ -2119,6 +2158,7 @@ printstatus(void)
printf("%s tags %"PRIu32" %"PRIu32" %"PRIu32" %"PRIu32"\n",
m->wlr_output->name, occ, m->tagset[m->seltags], sel, urg);
printf("%s layout %s\n", m->wlr_output->name, m->ltsymbol);
+ printf("%s mode %s\n", m->wlr_output->name, modes_labels[active_mode_index] ? modes_labels[active_mode_index] : "");
}
fflush(stdout);
}
@@ -2746,6 +2785,13 @@ zoom(const Arg *arg)
@@ -3075,6 +3115,13 @@ zoom(const Arg *arg)
arrange(selmon);
}
@@ -161,5 +161,5 @@ index ef27a1d..1ada006 100644
void
activatex11(struct wl_listener *listener, void *data)
--
2.42.0
2.53.0
+2 -2
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)
+87
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
@@ -1,7 +1,7 @@
From e42ca1c539437d3098d80983cfe2ad6f938d7a08 Mon Sep 17 00:00:00 2001
From: Eldar Yusupov <eyusupov@gmail.com>
Date: Sun, 17 Mar 2024 19:12:29 +0300
Subject: [PATCH] Restore correct montior for client when it is reattached
Subject: [PATCH] Restore correct monitor for client when it is reattached
---
dwl.c | 24 ++++++++++++++++++++++--
+1 -1
View File
@@ -7,7 +7,7 @@ Most of this patch is stored in river-control.h, It contains a list of functions
This patch's main intended use case is to have a startup script that calls dwlctl a bunch to add all the binds/rules you want, without the need of restarting dwl if you make any changes to the list of binds/rules.
This patch also adds keybind modes which allow switching between a diffrent sets of keybinds on the fly.
This patch also adds keybind modes which allow switching between a different sets of keybinds on the fly.
Also you can set a keybind mode as oneshot (meaning as soon as a keybind is activated the mode is switched) by using...
`dwlctl oneshot-mode _layout_you_want_to_make_oneshot_here_ _layout_you_want_to_switch_to_after_keybind_pressed_`
Just make sure to set a mode as oneshot after creating a bind under it otherwise it won't work.
+3 -3
View File
@@ -120,7 +120,7 @@ index 95c2afa..ccc3edb 100644
-static const Key keys[] = {
+/* note keys gets cleared with riverctl clear-binds but the keys_always are excluded from being cleared
+ * this is to have a list of fallback keybinds if your riverctl script fails
+ * if you won't like to have keys[] declared commented out the KEYS_USED macro bellow to disable the functionality*/
+ * if you won't like to have keys[] declared commented out the KEYS_USED macro below to disable the functionality*/
+#define KEYS_USED
+static Key keys[] = {
/* Note that Shift changes certain key codes: c -> C, 2 -> at, etc. */
@@ -1267,7 +1267,7 @@ index 0000000..599cffe
+ .add_argument = zriver_control_add_argument,
+ .destroy = zriver_control_destroy,
+};
+static void zriver_control_handle_destory(struct wl_resource *resource) {
+static void zriver_control_handle_destroy(struct wl_resource *resource) {
+ struct zriver_arg_list_resource *zriver_arg_list_resource = wl_resource_get_user_data(resource);
+ free(zriver_arg_list_resource);
+ printf("handle destroy\n");
@@ -1281,7 +1281,7 @@ index 0000000..599cffe
+
+
+ wl_resource_set_implementation(resource, &zriver_control_interface,
+ zriver_arg_list_resource, zriver_control_handle_destory);
+ zriver_arg_list_resource, zriver_control_handle_destroy);
+}
--
2.51.0
+2 -1
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
+153
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
+22
View File
@@ -0,0 +1,22 @@
### Description
Shift to next/previous tag, with skipping occupied/unoccupied variants.
This patch is an alternative to
[shiftview](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/shiftview) with more controls:
| function | behavior |
| :--- | :---|
| shifttag | shift to next/previous tags |
| shifttag_occupied | skipping unoccupied tags |
| shifttag_unoccupied | skipping occupied tags |
`shifttag-bar` is a variant to be applied on top of the
[bar](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/bar/) patch.
### Download
- [shifttag-v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shifttag/shifttag.patch)
- [shifttag-bar-v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/shifttag/shifttag-bar.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad/)
+162
View File
@@ -0,0 +1,162 @@
From 8dff34be15664705654e5e4d41f990f8eba8b79d Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Mon, 23 Mar 2026 01:16:06 +0800
Subject: [PATCH] shifttag with filtering occupied/unoccupied tags support for
bar patch
---
config.def.h | 8 ++++
shifttag.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 123 insertions(+)
create mode 100644 shifttag.c
diff --git a/config.def.h b/config.def.h
index 7da50d2..9fbcf2a 100644
--- a/config.def.h
+++ b/config.def.h
@@ -124,6 +124,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA
static const char *termcmd[] = { "foot", NULL };
static const char *menucmd[] = { "wmenu-run", NULL };
+#include "shifttag.c"
+
static const Key keys[] = {
/* Note that Shift changes certain key codes: 2 -> at, etc. */
/* modifier key function argument */
@@ -138,6 +140,12 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
+ { MODKEY, XKB_KEY_apostrophe, shifttag_occupied, { .i = 1 } },
+ { MODKEY, XKB_KEY_semicolon, shifttag_occupied, { .i = -1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_quotedbl, shifttag, { .i = 1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_colon, shifttag, { .i = -1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_apostrophe, shifttag_unoccupied, { .i = 1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_semicolon, shifttag_unoccupied, { .i = -1 } },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} },
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
diff --git a/shifttag.c b/shifttag.c
new file mode 100644
index 0000000..5f1d5c2
--- /dev/null
+++ b/shifttag.c
@@ -0,0 +1,115 @@
+// "arg->i" stores the number of tags to shift right (positive value)
+// or left (negative value)
+
+static uint32_t
+get_occupied_tags(Monitor *m)
+{
+ uint32_t occupied = 0;
+ Client *c;
+ wl_list_for_each(c, &clients, link) {
+ if (c->mon == m)
+ occupied |= c->tags;
+ }
+ return occupied & TAGMASK;
+}
+
+static uint32_t
+find_next_tag(uint32_t current_tag, int direction, bool skip_unoccupied, bool skip_occupied)
+{
+ uint32_t occupied = get_occupied_tags(selmon);
+ uint32_t result = 0;
+
+ for (int i = 0; i < LENGTH(tags); i++) {
+ uint32_t bit = 1 << i;
+
+ if (current_tag & bit) {
+ uint32_t new_bit = bit;
+
+ if (!skip_unoccupied && !skip_occupied) {
+ if (direction > 0) {
+ if (bit << 1 && (bit << 1) <= TAGMASK)
+ new_bit = bit << 1;
+ else
+ new_bit = 1;
+ } else {
+ if (bit >> 1)
+ new_bit = bit >> 1;
+ else
+ new_bit = 1 << (LENGTH(tags) - 1);
+ }
+ } else {
+ uint32_t test_bit = bit;
+ uint32_t start_bit = bit;
+ int count = 0;
+
+ do {
+ if (direction > 0) {
+ if (test_bit << 1 && (test_bit << 1) <= TAGMASK)
+ test_bit = test_bit << 1;
+ else
+ test_bit = 1;
+ } else {
+ if (test_bit >> 1)
+ test_bit = test_bit >> 1;
+ else
+ test_bit = 1 << (LENGTH(tags) - 1);
+ }
+
+ int is_occupied = (occupied & test_bit) != 0;
+ int should_select = (skip_unoccupied && is_occupied) ||
+ (skip_occupied && !is_occupied);
+
+ if (should_select) {
+ new_bit = test_bit;
+ break;
+ }
+ count++;
+ } while (test_bit != start_bit && count < LENGTH(tags));
+ }
+
+ result |= new_bit;
+ }
+ }
+
+ return result;
+}
+
+static void
+shifttag_with_filter(const Arg *arg, bool skip_unoccupied, bool skip_occupied)
+{
+ Arg a;
+ if (!selmon)
+ return;
+
+ uint32_t occupied = get_occupied_tags(selmon);
+ if (skip_unoccupied && !skip_occupied && occupied == 0)
+ return;
+ if (skip_occupied && !skip_unoccupied && (occupied == TAGMASK))
+ return;
+
+ uint32_t curseltags = selmon->tagset[selmon->seltags];
+ uint32_t nextseltags = find_next_tag(curseltags, arg->i, skip_unoccupied, skip_occupied);
+
+ if (nextseltags != curseltags) {
+ a.i = nextseltags;
+ view(&a);
+ }
+}
+
+void
+shifttag(const Arg *arg)
+{
+ shifttag_with_filter(arg, false, false);
+}
+
+void
+shifttag_occupied(const Arg *arg)
+{
+ shifttag_with_filter(arg, true, false);
+}
+
+void
+shifttag_unoccupied(const Arg *arg)
+{
+ shifttag_with_filter(arg, false, true);
+}
--
2.53.0
+161
View File
@@ -0,0 +1,161 @@
From 2a4417a6344c9d4b83989d63df17d9cdb0f81aea Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Mon, 23 Mar 2026 00:50:54 +0800
Subject: [PATCH] shifttag with filtering occupied/unoccupied tags support
---
config.def.h | 8 ++++
shifttag.c | 115 +++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 123 insertions(+)
create mode 100644 shifttag.c
diff --git a/config.def.h b/config.def.h
index 8a6eda0..2377fa5 100644
--- a/config.def.h
+++ b/config.def.h
@@ -118,6 +118,8 @@ static const enum libinput_config_tap_button_map button_map = LIBINPUT_CONFIG_TA
static const char *termcmd[] = { "foot", NULL };
static const char *menucmd[] = { "wmenu-run", NULL };
+#include "shifttag.c"
+
static const Key keys[] = {
/* Note that Shift changes certain key codes: 2 -> at, etc. */
/* modifier key function argument */
@@ -131,6 +133,12 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_l, setmfact, {.f = +0.05f} },
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
+ { MODKEY, XKB_KEY_apostrophe, shifttag_occupied, { .i = 1 } },
+ { MODKEY, XKB_KEY_semicolon, shifttag_occupied, { .i = -1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_quotedbl, shifttag, { .i = 1 } },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_colon, shifttag, { .i = -1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_apostrophe, shifttag_unoccupied, { .i = 1 } },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_semicolon, shifttag_unoccupied, { .i = -1 } },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} },
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
diff --git a/shifttag.c b/shifttag.c
new file mode 100644
index 0000000..971216b
--- /dev/null
+++ b/shifttag.c
@@ -0,0 +1,115 @@
+// "arg->i" stores the number of tags to shift right (positive value)
+// or left (negative value)
+
+static uint32_t
+get_occupied_tags(Monitor *m)
+{
+ uint32_t occupied = 0;
+ Client *c;
+ wl_list_for_each(c, &clients, link) {
+ if (c->mon == m)
+ occupied |= c->tags;
+ }
+ return occupied & TAGMASK;
+}
+
+static uint32_t
+find_next_tag(uint32_t current_tag, int direction, bool skip_unoccupied, bool skip_occupied)
+{
+ uint32_t occupied = get_occupied_tags(selmon);
+ uint32_t result = 0;
+
+ for (int i = 0; i < TAGCOUNT; i++) {
+ uint32_t bit = 1 << i;
+
+ if (current_tag & bit) {
+ uint32_t new_bit = bit;
+
+ if (!skip_unoccupied && !skip_occupied) {
+ if (direction > 0) {
+ if (bit << 1 && (bit << 1) <= TAGMASK)
+ new_bit = bit << 1;
+ else
+ new_bit = 1;
+ } else {
+ if (bit >> 1)
+ new_bit = bit >> 1;
+ else
+ new_bit = 1 << (TAGCOUNT - 1);
+ }
+ } else {
+ uint32_t test_bit = bit;
+ uint32_t start_bit = bit;
+ int count = 0;
+
+ do {
+ if (direction > 0) {
+ if (test_bit << 1 && (test_bit << 1) <= TAGMASK)
+ test_bit = test_bit << 1;
+ else
+ test_bit = 1;
+ } else {
+ if (test_bit >> 1)
+ test_bit = test_bit >> 1;
+ else
+ test_bit = 1 << (TAGCOUNT - 1);
+ }
+
+ int is_occupied = (occupied & test_bit) != 0;
+ int should_select = (skip_unoccupied && is_occupied) ||
+ (skip_occupied && !is_occupied);
+
+ if (should_select) {
+ new_bit = test_bit;
+ break;
+ }
+ count++;
+ } while (test_bit != start_bit && count < TAGCOUNT);
+ }
+
+ result |= new_bit;
+ }
+ }
+
+ return result;
+}
+
+static void
+shifttag_with_filter(const Arg *arg, bool skip_unoccupied, bool skip_occupied)
+{
+ Arg a;
+ if (!selmon)
+ return;
+
+ uint32_t occupied = get_occupied_tags(selmon);
+ if (skip_unoccupied && !skip_occupied && occupied == 0)
+ return;
+ if (skip_occupied && !skip_unoccupied && (occupied == TAGMASK))
+ return;
+
+ uint32_t curseltags = selmon->tagset[selmon->seltags];
+ uint32_t nextseltags = find_next_tag(curseltags, arg->i, skip_unoccupied, skip_occupied);
+
+ if (nextseltags != curseltags) {
+ a.i = nextseltags;
+ view(&a);
+ }
+}
+
+void
+shifttag(const Arg *arg)
+{
+ shifttag_with_filter(arg, false, false);
+}
+
+void
+shifttag_occupied(const Arg *arg)
+{
+ shifttag_with_filter(arg, true, false);
+}
+
+void
+shifttag_unoccupied(const Arg *arg)
+{
+ shifttag_with_filter(arg, false, true);
+}
--
2.53.0
+2 -1
View File
@@ -19,7 +19,8 @@ Adds functions to add, toggle and remove clients to/from scratchpad client list.
### Download
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/simple_scratchpad)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simple_scratchpad/simple_scratchpad-v0.7.patch)
- [scratchpad-wlroots-next-f4249db.patch](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simple_scratchpad/simple_scratchpad-f4249db.patch)
- [scratchpad-0.8.patch](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/simple_scratchpad/simple_scratchpad-0.8.patch)
### Authors
- [julmajustus](https://codeberg.org/julmajustus)
@@ -1,34 +1,34 @@
From a8d29e03f565b54a68c6c2cb2da103366c627825 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Sat, 4 Jan 2025 13:22:12 +0200
Subject: [PATCH] add simple_scratchpad
From 0d603b179ccccbb4270fd202a14a2db18d0a5413 Mon Sep 17 00:00:00 2001
From: A Frederick Christensen <dwl@ivories.org>
Date: Thu, 26 Feb 2026 17:09:28 -0600
Subject: [PATCH] Apply simple_scratchpad patch
---
config.def.h | 3 ++
dwl.c | 34 +++++++++++++++++++++--
simple_scratchpad.c | 67 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 102 insertions(+), 2 deletions(-)
simple_scratchpad.c | 68 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+), 2 deletions(-)
create mode 100644 simple_scratchpad.c
diff --git a/config.def.h b/config.def.h
index 22d2171..83f19b3 100644
index 8a6eda0..6254fc9 100644
--- a/config.def.h
+++ b/config.def.h
@@ -136,6 +136,9 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_C, killclient, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Z, addscratchpad, {0} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, togglescratchpad, {0} },
+ { MODKEY, XKB_KEY_z, removescratchpad, {0} },
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
@@ -132,6 +132,9 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_z, addscratchpad, {0} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, togglescratchpad, {0} },
+ { MODKEY, XKB_KEY_z, removescratchpad, {0} },
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
diff --git a/dwl.c b/dwl.c
index a2711f6..85f4a51 100644
index 44f3ad9..215f630 100644
--- a/dwl.c
+++ b/dwl.c
@@ -141,6 +141,7 @@ typedef struct {
@@ -139,6 +139,7 @@ typedef struct {
uint32_t tags;
int isfloating, isurgent, isfullscreen;
uint32_t resize; /* configure serial of a pending resize */
@@ -36,7 +36,7 @@ index a2711f6..85f4a51 100644
} Client;
typedef struct {
@@ -243,6 +244,7 @@ typedef struct {
@@ -240,6 +241,7 @@ typedef struct {
} SessionLock;
/* function declarations */
@@ -44,7 +44,7 @@ index a2711f6..85f4a51 100644
static void applybounds(Client *c, struct wlr_box *bbox);
static void applyrules(Client *c);
static void arrange(Monitor *m);
@@ -317,6 +319,7 @@ static void printstatus(void);
@@ -314,6 +316,7 @@ static void printstatus(void);
static void powermgrsetmode(struct wl_listener *listener, void *data);
static void quit(const Arg *arg);
static void rendermon(struct wl_listener *listener, void *data);
@@ -52,7 +52,7 @@ index a2711f6..85f4a51 100644
static void requestdecorationmode(struct wl_listener *listener, void *data);
static void requeststartdrag(struct wl_listener *listener, void *data);
static void requestmonstate(struct wl_listener *listener, void *data);
@@ -340,6 +343,7 @@ static void tagmon(const Arg *arg);
@@ -336,6 +339,7 @@ static void tagmon(const Arg *arg);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
@@ -60,17 +60,17 @@ index a2711f6..85f4a51 100644
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -413,6 +417,9 @@ static struct wlr_box sgeom;
@@ -406,6 +410,9 @@ static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static struct wl_list scratchpad_clients;
+static int scratchpad_visible = 1;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -432,6 +439,8 @@ static xcb_atom_t netatom[NetLast];
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
static struct wl_listener cursor_button = {.notify = buttonpress};
@@ -455,6 +462,8 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
@@ -79,7 +79,7 @@ index a2711f6..85f4a51 100644
/* function implementations */
void
applybounds(Client *c, struct wlr_box *bbox)
@@ -1259,10 +1268,20 @@ void
@@ -1328,10 +1337,20 @@ void
destroynotify(struct wl_listener *listener, void *data)
{
/* Called when the xdg_toplevel is destroyed. */
@@ -101,7 +101,7 @@ index a2711f6..85f4a51 100644
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -2306,11 +2325,21 @@ setcursorshape(struct wl_listener *listener, void *data)
@@ -2333,11 +2352,21 @@ setcursorshape(struct wl_listener *listener, void *data)
void
setfloating(Client *c, int floating)
{
@@ -124,17 +124,17 @@ index a2711f6..85f4a51 100644
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
(p && p->isfullscreen) ? LyrFS
: c->isfloating ? LyrFloat : LyrTile]);
@@ -2534,6 +2563,7 @@ setup(void)
@@ -2554,6 +2583,7 @@ setup(void)
*/
wl_list_init(&clients);
wl_list_init(&fstack);
+ wl_list_init(&scratchpad_clients);
xdg_shell = wlr_xdg_shell_create(dpy, 6);
LISTEN_STATIC(&xdg_shell->events.new_toplevel, createnotify);
wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel);
diff --git a/simple_scratchpad.c b/simple_scratchpad.c
new file mode 100644
index 0000000..381f4b5
index 0000000..26d6b66
--- /dev/null
+++ b/simple_scratchpad.c
@@ -0,0 +1,68 @@
@@ -207,5 +207,5 @@ index 0000000..381f4b5
+ }
+}
--
2.45.2
2.52.0
@@ -0,0 +1,219 @@
From 94e649317328c7af091516fe3441ea80a814d1e4 Mon Sep 17 00:00:00 2001
From: A Frederick Christensen <dwl@ivories.org>
Date: Thu, 26 Feb 2026 17:18:17 -0600
Subject: [PATCH] Apply simple_scratchpad patch
---
config.def.h | 4 ++-
dwl.c | 34 +++++++++++++++++++++--
simple_scratchpad.c | 68 +++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 103 insertions(+), 3 deletions(-)
create mode 100644 simple_scratchpad.c
diff --git a/config.def.h b/config.def.h
index 8a6eda0..bd6cb40 100644
--- a/config.def.h
+++ b/config.def.h
@@ -132,6 +132,9 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_Return, zoom, {0} },
{ MODKEY, XKB_KEY_Tab, view, {0} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_c, killclient, {0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_z, addscratchpad, {0} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_z, togglescratchpad, {0} },
+ { MODKEY, XKB_KEY_z, removescratchpad, {0} },
{ MODKEY, XKB_KEY_t, setlayout, {.v = &layouts[0]} },
{ MODKEY, XKB_KEY_f, setlayout, {.v = &layouts[1]} },
{ MODKEY, XKB_KEY_m, setlayout, {.v = &layouts[2]} },
@@ -154,7 +157,6 @@ static const Key keys[] = {
TAGKEYS( XKB_KEY_8, XKB_KEY_asterisk, 7),
TAGKEYS( XKB_KEY_9, XKB_KEY_parenleft, 8),
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_q, quit, {0} },
-
/* Ctrl-Alt-Backspace and Ctrl-Alt-Fx used to be handled by X server */
{ WLR_MODIFIER_CTRL|WLR_MODIFIER_ALT,XKB_KEY_Terminate_Server, quit, {0} },
/* Ctrl-Alt-Fx is used to switch to another VT, if you don't know what a VT is
diff --git a/dwl.c b/dwl.c
index 8a9715d..6ae7707 100644
--- a/dwl.c
+++ b/dwl.c
@@ -141,6 +141,7 @@ typedef struct {
uint32_t tags;
int isfloating, isurgent, isfullscreen;
uint32_t resize; /* configure serial of a pending resize */
+ struct wl_list link_temp;
} Client;
typedef struct {
@@ -242,6 +243,7 @@ typedef struct {
} SessionLock;
/* function declarations */
+static void addscratchpad(const Arg *arg);
static void applybounds(Client *c, struct wlr_box *bbox);
static void applyrules(Client *c);
static void arrange(Monitor *m);
@@ -316,6 +318,7 @@ static void printstatus(void);
static void powermgrsetmode(struct wl_listener *listener, void *data);
static void quit(const Arg *arg);
static void rendermon(struct wl_listener *listener, void *data);
+static void removescratchpad(const Arg *arg);
static void requestdecorationmode(struct wl_listener *listener, void *data);
static void requeststartdrag(struct wl_listener *listener, void *data);
static void requestmonstate(struct wl_listener *listener, void *data);
@@ -338,6 +341,7 @@ static void tagmon(const Arg *arg);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
+static void togglescratchpad(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -408,6 +412,9 @@ static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static struct wl_list scratchpad_clients;
+static int scratchpad_visible = 1;
+
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
static struct wl_listener cursor_button = {.notify = buttonpress};
@@ -457,6 +464,8 @@ static struct wlr_xwayland *xwayland;
/* attempt to encapsulate suck into one file */
#include "client.h"
+#include "simple_scratchpad.c"
+
/* function implementations */
void
applybounds(Client *c, struct wlr_box *bbox)
@@ -1330,10 +1339,20 @@ void
destroynotify(struct wl_listener *listener, void *data)
{
/* Called when the xdg_toplevel is destroyed. */
- Client *c = wl_container_of(listener, c, destroy);
+ Client *sc, *c = wl_container_of(listener, c, destroy);
wl_list_remove(&c->destroy.link);
wl_list_remove(&c->set_title.link);
wl_list_remove(&c->fullscreen.link);
+ /* Check if destroyed client was part of scratchpad_clients
+ * and clean it from the list if so. */
+ if (c && wl_list_length(&scratchpad_clients) > 0) {
+ wl_list_for_each(sc, &scratchpad_clients, link_temp) {
+ if (sc == c) {
+ wl_list_remove(&c->link_temp);
+ break;
+ }
+ }
+ }
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -2335,11 +2354,21 @@ setcursorshape(struct wl_listener *listener, void *data)
void
setfloating(Client *c, int floating)
{
- Client *p = client_get_parent(c);
+ Client *sc, *p = client_get_parent(c);
c->isfloating = floating;
/* If in floating layout do not change the client's layer */
if (!c->mon || !client_surface(c)->mapped || !c->mon->lt[c->mon->sellt]->arrange)
return;
+ /* Check if unfloated client was part of scratchpad_clients
+ * and remove it from scratchpad_clients list if so */
+ if (!floating && wl_list_length(&scratchpad_clients) > 0) {
+ wl_list_for_each(sc, &scratchpad_clients, link_temp) {
+ if (sc == c) {
+ wl_list_remove(&c->link_temp);
+ break;
+ }
+ }
+ }
wlr_scene_node_reparent(&c->scene->node, layers[c->isfullscreen ||
(p && p->isfullscreen) ? LyrFS
: c->isfloating ? LyrFloat : LyrTile]);
@@ -2557,6 +2586,7 @@ setup(void)
*/
wl_list_init(&clients);
wl_list_init(&fstack);
+ wl_list_init(&scratchpad_clients);
xdg_shell = wlr_xdg_shell_create(dpy, 6);
wl_signal_add(&xdg_shell->events.new_toplevel, &new_xdg_toplevel);
diff --git a/simple_scratchpad.c b/simple_scratchpad.c
new file mode 100644
index 0000000..26d6b66
--- /dev/null
+++ b/simple_scratchpad.c
@@ -0,0 +1,68 @@
+/* ************************************************************************** */
+/* */
+/* ::: :::::::: */
+/* simple_scratchpad.c :+: :+: :+: */
+/* +:+ +:+ +:+ */
+/* By: jmakkone <jmakkone@student.hive.fi> +#+ +:+ +#+ */
+/* +#+#+#+#+#+ +#+ */
+/* Created: 2024/12/19 19:35:02 by jmakkone #+# #+# */
+/* Updated: 2025/01/04 13:35:50 by jmakkone ### ########.fr */
+/* */
+/* ************************************************************************** */
+
+void
+addscratchpad(const Arg *arg)
+{
+ Client *cc, *c = focustop(selmon);
+
+ if (!c)
+ return;
+ /* Check if the added client is already a scratchpad client */
+ wl_list_for_each(cc, &scratchpad_clients, link_temp) {
+ if (cc == c)
+ return;
+ }
+ if (!c->isfloating) {
+ setfloating(c, 1);
+ }
+ wl_list_insert(&scratchpad_clients, &c->link_temp);
+}
+
+void
+togglescratchpad(const Arg *arg)
+{
+ Client *c;
+ Monitor *m = selmon;
+
+ scratchpad_visible = !scratchpad_visible;
+ if (scratchpad_visible) {
+ wl_list_for_each(c, &scratchpad_clients, link_temp) {
+ c->mon = m;
+ c->tags = m->tagset[m->seltags];
+ arrange(m);
+ focusclient(c, 1);
+ }
+ } else {
+ wl_list_for_each(c, &scratchpad_clients, link_temp) {
+ c->tags = 0;
+ focusclient(focustop(m), 1);
+ arrange(m);
+ }
+ }
+}
+
+void
+removescratchpad(const Arg *arg)
+{
+ Client *sc, *c = focustop(selmon);
+
+ if (c && wl_list_length(&scratchpad_clients) > 0) {
+ /* Check if c is in scratchpad_clients */
+ wl_list_for_each(sc, &scratchpad_clients, link_temp) {
+ if (sc == c) {
+ wl_list_remove(&c->link_temp);
+ break;
+ }
+ }
+ }
+}
--
2.52.0
+1 -1
View File
@@ -1,6 +1,6 @@
### Description
Makes sticky work as expected with singletagset. The sticky window will
stay on original output until you explicitely put it to a different monitor.
stay on original output until you explicitly put it to a different monitor.
This patch expects both [singletagset](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/singletagset) and [sticky](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/sticky) patches to be already in
your tree committed. It applies onto them.
+100 -3
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)
+162
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
+1 -1
View File
@@ -13,7 +13,7 @@ tempdir="/tmp/screenshots"
mkdir -p "$tempdir"
file="$(mktemp -p "$tempdir" "XXXXXX.png")"
# Grab the screenshot! Very conviniently, GEOMETRY format matches the one
# Grab the screenshot! Very conveniently, GEOMETRY format matches the one
# expected by grim
grim -g "$GEOMETRY" "$file" || exit
+2 -1
View File
@@ -4,7 +4,8 @@ Adds a toggleable function that makes a sticky client that is visible on all tag
Originally based on [dwm sticky patch](https://dwm.suckless.org/patches/sticky).
### Download
- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch)
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/sticky/sticky.patch)
- [v0.7](https://codeberg.org/dwl/dwl-patches/raw/commit/898bc7a946c7be14034a665bd7a7b042632465ea/patches/sticky/sticky.patch)
- [v0.4](https://github.com/djpohly/dwl/compare/main...dm1tz:04-sticky.patch)
- [git branch](https://codeberg.org/Rutherther/dwl/src/branch/v0.7/sticky)
+14 -14
View File
@@ -1,17 +1,17 @@
From f113cdc0b4cecceaaf28679489852ae61a1aa3f5 Mon Sep 17 00:00:00 2001
From: Rutherther <rutherther@proton.me>
Date: Fri, 19 Jul 2024 16:29:43 +0200
Subject: [PATCH] sticky
From d36eee35fdc4b0e666fb04a580200e8c5d2b1f9e Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 22:55:33 +0800
Subject: [PATCH] Update sticky patch to v0.8
---
dwl.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/dwl.c b/dwl.c
index 5bf995e..820f4af 100644
index 44f3ad9..05070ef 100644
--- a/dwl.c
+++ b/dwl.c
@@ -73,7 +73,7 @@
@@ -74,7 +74,7 @@
#define MAX(A, B) ((A) > (B) ? (A) : (B))
#define MIN(A, B) ((A) < (B) ? (A) : (B))
#define CLEANMASK(mask) (mask & ~WLR_MODIFIER_CAPS)
@@ -20,7 +20,7 @@ index 5bf995e..820f4af 100644
#define LENGTH(X) (sizeof X / sizeof X[0])
#define END(A) ((A) + LENGTH(A))
#define TAGMASK ((1u << TAGCOUNT) - 1)
@@ -139,7 +139,7 @@ typedef struct {
@@ -137,7 +137,7 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@@ -29,15 +29,15 @@ index 5bf995e..820f4af 100644
uint32_t resize; /* configure serial of a pending resize */
} Client;
@@ -326,6 +326,7 @@ static void setcursor(struct wl_listener *listener, void *data);
@@ -323,6 +323,7 @@ static void setcursor(struct wl_listener *listener, void *data);
static void setcursorshape(struct wl_listener *listener, void *data);
static void setfloating(Client *c, int floating);
static void setfullscreen(Client *c, int fullscreen);
+static void setsticky(Client *c, int sticky);
static void setgamma(struct wl_listener *listener, void *data);
static void setlayout(const Arg *arg);
static void setmfact(const Arg *arg);
@@ -339,6 +340,7 @@ static void tag(const Arg *arg);
static void setmon(Client *c, Monitor *m, uint32_t newtags);
@@ -335,6 +336,7 @@ static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
@@ -45,8 +45,8 @@ index 5bf995e..820f4af 100644
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
@@ -2351,6 +2353,17 @@ setgamma(struct wl_listener *listener, void *data)
wlr_output_schedule_frame(m->wlr_output);
@@ -2368,6 +2370,17 @@ setfullscreen(Client *c, int fullscreen)
printstatus();
}
+void
@@ -63,7 +63,7 @@ index 5bf995e..820f4af 100644
void
setlayout(const Arg *arg)
{
@@ -2738,6 +2751,16 @@ togglefullscreen(const Arg *arg)
@@ -2760,6 +2773,16 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
@@ -81,5 +81,5 @@ index 5bf995e..820f4af 100644
toggletag(const Arg *arg)
{
--
2.45.2
2.53.0
+2 -1
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)
+350
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
+2
View File
@@ -6,9 +6,11 @@ Swap the focused window with the window (no floating) to the left, right, above,
**NOTE:** this patch uses the same algorithm that River uses to select the window in the given direction.
### Download
- [v0.8](https://codeberg.org/dwl/dwl-patches/raw/commit/a5f66f3d9cdbfc1a3fd7ae9c0288c35c8ec19479/patches/swapandfocusdir/swapandfocusdir.patch)
- [git branch](https://codeberg.org/wochap/dwl/src/branch/v0.5/swapandfocusdir)
- [2024-07-09](https://codeberg.org/dwl/dwl-patches/raw/commit/13d96b51b54500dd24544cf3a73c61b7a1414bc6/patches/swapandfocusdir/swapandfocusdir.patch)
- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/swapandfocusdir/swapandfocusdir.patch)
### Authors
- [unixchad](https://codeberg.org/unixchad)
- [wochap](https://codeberg.org/wochap)
+26 -26
View File
@@ -1,7 +1,7 @@
From 285470897406b653e77d732a77356aaf9a70b799 Mon Sep 17 00:00:00 2001
From: wochap <gean.marroquin@gmail.com>
Date: Fri, 5 Jul 2024 12:37:39 -0500
Subject: [PATCH] implement swapandfocusdir
From 6972f383921a892e30ac10b42a3d3ce47b2f93e5 Mon Sep 17 00:00:00 2001
From: nate zhou <gnuunixchad@outlook.com>
Date: Sat, 28 Feb 2026 20:48:27 +0800
Subject: [PATCH] Patch: swapandfocusdir.patch
---
config.def.h | 8 +++
@@ -9,29 +9,29 @@ Subject: [PATCH] implement swapandfocusdir
2 files changed, 172 insertions(+)
diff --git a/config.def.h b/config.def.h
index 22d2171..724e15e 100644
index 8a6eda0..6e7fc39 100644
--- a/config.def.h
+++ b/config.def.h
@@ -129,6 +129,14 @@ 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_CTRL, XKB_KEY_Left, focusdir, {.ui = 0} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, focusdir, {.ui = 1} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, focusdir, {.ui = 2} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, focusdir, {.ui = 3} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapdir, {.ui = 0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapdir, {.ui = 1} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapdir, {.ui = 2} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapdir, {.ui = 3} },
{ MODKEY, XKB_KEY_i, incnmaster, {.i = +1} },
{ MODKEY, XKB_KEY_d, incnmaster, {.i = -1} },
{ MODKEY, XKB_KEY_h, setmfact, {.f = -0.05f} },
@@ -125,6 +125,14 @@ 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_CTRL, XKB_KEY_Left, focusdir, {.ui = 0} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, focusdir, {.ui = 1} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, focusdir, {.ui = 2} },
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, focusdir, {.ui = 3} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapdir, {.ui = 0} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapdir, {.ui = 1} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapdir, {.ui = 2} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapdir, {.ui = 3} },
{ 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 dc0437e..844c1f5 100644
index 44f3ad9..1893fbd 100644
--- a/dwl.c
+++ b/dwl.c
@@ -241,6 +241,11 @@ typedef struct {
@@ -239,6 +239,11 @@ typedef struct {
struct wl_listener destroy;
} SessionLock;
@@ -51,8 +51,8 @@ index dc0437e..844c1f5 100644
+static void swapdir(const Arg *arg);
static Client *focustop(Monitor *m);
static void fullscreennotify(struct wl_listener *listener, void *data);
static void handlesig(int signo);
@@ -1425,6 +1432,163 @@ focusstack(const Arg *arg)
static void gpureset(struct wl_listener *listener, void *data);
@@ -1511,6 +1518,163 @@ focusstack(const Arg *arg)
focusclient(c, 1);
}
@@ -213,9 +213,9 @@ index dc0437e..844c1f5 100644
+ arrange(selmon);
+}
+
/* We probably should change the name of this, it sounds like
/* We probably should change the name of this: it sounds like it
* will focus the topmost client of this mon, when actually will
* only return that client */
--
2.45.1
2.53.0
-13
View File
@@ -1,13 +0,0 @@
### Description
This combines
[tablet-input](../tablet-input) and
[touch-input](../touch-input)
into a single appliable patch.
The two patches modify similar code areas in `dwl.c` and will conflict if one attempts to direcly apply both.
### Download
- [tablet-and-touch-wlroots-next-f4249db.patch](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-and-touch/tablet-and-touch-wlroots-next-f4249db.patch)
- [tablet-and-touch-0.8.patch](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/tablet-and-touch/tablet-and-touch-0.8.patch)
### Authors
- [fauxmight](https://codeberg.org/fauxmight)
@@ -1,584 +0,0 @@
From f0f797f8abebc4624c7e6bdfbb1ec2a3122f3914 Mon Sep 17 00:00:00 2001
From: A Frederick Christensen <dwl@ivories.org>
Date: Thu, 26 Feb 2026 23:20:03 -0600
Subject: [PATCH] Apply tablet-and-touch patch
---
Makefile | 6 +-
config.def.h | 1 +
dwl.c | 398 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 404 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 578194f..e0d1835 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,8 @@ dwl: dwl.o util.o
$(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \
- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h
+ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \
+ tablet-v2-protocol.h
util.o: util.c util.h
# wayland-scanner is a tool which generates C headers and rigging for Wayland
@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h:
xdg-shell-protocol.h:
$(WAYLAND_SCANNER) server-header \
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+tablet-v2-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@
config.h:
cp config.def.h $@
diff --git a/config.def.h b/config.def.h
index 8a6eda0..1f20dfd 100644
--- a/config.def.h
+++ b/config.def.h
@@ -4,6 +4,7 @@
((hex >> 8) & 0xFF) / 255.0f, \
(hex & 0xFF) / 255.0f }
/* appearance */
+static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */
static const int sloppyfocus = 1; /* focus follows mouse */
static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */
static const unsigned int borderpx = 1; /* border pixel of windows */
diff --git a/dwl.c b/dwl.c
index 44f3ad9..5ed467d 100644
--- a/dwl.c
+++ b/dwl.c
@@ -51,6 +51,10 @@
#include <wlr/types/wlr_session_lock_v1.h>
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_tablet_pad.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
@@ -161,6 +165,12 @@ typedef struct {
struct wl_listener destroy;
} KeyboardGroup;
+typedef struct TouchGroup {
+ struct wl_list link;
+ struct wlr_touch *touch;
+ Monitor *m;
+} TouchGroup;
+
typedef struct {
/* Must keep this field first */
unsigned int type; /* LayerShell */
@@ -268,7 +278,10 @@ static void createnotify(struct wl_listener *listener, void *data);
static void createpointer(struct wlr_pointer *pointer);
static void createpointerconstraint(struct wl_listener *listener, void *data);
static void createpopup(struct wl_listener *listener, void *data);
+static void createtablet(struct wlr_input_device *device);
+static void createtouch(struct wlr_touch *touch);
static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
+static void createtouch(struct wlr_touch *touch);
static void cursorframe(struct wl_listener *listener, void *data);
static void cursorwarptohint(void);
static void destroydecoration(struct wl_listener *listener, void *data);
@@ -281,6 +294,9 @@ static void destroynotify(struct wl_listener *listener, void *data);
static void destroypointerconstraint(struct wl_listener *listener, void *data);
static void destroysessionlock(struct wl_listener *listener, void *data);
static void destroykeyboardgroup(struct wl_listener *listener, void *data);
+static void destroytablet(struct wl_listener *listener, void *data);
+static void destroytabletsurfacenotify(struct wl_listener *listener, void *data);
+static void destroytablettool(struct wl_listener *listener, void *data);
static Monitor *dirtomon(enum wlr_direction dir);
static void focusclient(Client *c, int lift);
static void focusmon(const Arg *arg);
@@ -333,11 +349,20 @@ static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
+static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy);
+static void tablettoolproximity(struct wl_listener *listener, void *data);
+static void tablettoolaxis(struct wl_listener *listener, void *data);
+static void tablettoolbutton(struct wl_listener *listener, void *data);
+static void tablettooltip(struct wl_listener *listener, void *data);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
+static void touchdown(struct wl_listener *listener, void *data);
+static void touchup(struct wl_listener *listener, void *data);
+static void touchframe(struct wl_listener *listener, void *data);
+static void touchmotion(struct wl_listener *listener, void *data);
static void unlocksession(struct wl_listener *listener, void *data);
static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
static void unmapnotify(struct wl_listener *listener, void *data);
@@ -390,6 +415,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint;
static struct wlr_cursor *cursor;
static struct wlr_xcursor_manager *cursor_mgr;
+static struct wlr_tablet_manager_v2 *tablet_mgr;
+static struct wlr_tablet_v2_tablet *tablet = NULL;
+static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL;
+static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL;
+static struct wlr_surface *tablet_curr_surface = NULL;
+static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify};
+
static struct wlr_scene_rect *root_bg;
static struct wlr_session_lock_manager_v1 *session_lock_mgr;
static struct wlr_scene_rect *locked_bg;
@@ -405,6 +437,7 @@ static struct wlr_output_layout *output_layout;
static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static struct wl_list touches;
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
@@ -412,6 +445,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress};
static struct wl_listener cursor_frame = {.notify = cursorframe};
static struct wl_listener cursor_motion = {.notify = motionrelative};
static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute};
+static struct wl_listener tablet_device_destroy = {.notify = destroytablet};
+static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis};
+static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton};
+static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool};
+static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity};
+static struct wl_listener tablet_tool_tip = {.notify = tablettooltip};
static struct wl_listener gpu_reset = {.notify = gpureset};
static struct wl_listener layout_change = {.notify = updatemons};
static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor};
@@ -434,6 +473,10 @@ static struct wl_listener request_set_sel = {.notify = setsel};
static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
+static struct wl_listener touch_down = {.notify = touchdown};
+static struct wl_listener touch_frame = {.notify = touchframe};
+static struct wl_listener touch_motion = {.notify = touchmotion};
+static struct wl_listener touch_up = {.notify = touchup};
static struct wl_listener new_session_lock = {.notify = locksession};
#ifdef XWAYLAND
@@ -781,6 +824,10 @@ cleanuplisteners(void)
wl_list_remove(&request_set_cursor_shape.link);
wl_list_remove(&request_start_drag.link);
wl_list_remove(&start_drag.link);
+ wl_list_remove(&touch_down.link);
+ wl_list_remove(&touch_frame.link);
+ wl_list_remove(&touch_motion.link);
+ wl_list_remove(&touch_up.link);
wl_list_remove(&new_session_lock.link);
#ifdef XWAYLAND
wl_list_remove(&new_xwayland_surface.link);
@@ -1199,6 +1246,38 @@ createpopup(struct wl_listener *listener, void *data)
LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup);
}
+void
+createtablet(struct wlr_input_device *device)
+{
+ if (!tablet) {
+ struct libinput_device *device_handle = NULL;
+ if (!wlr_input_device_is_libinput(device) ||
+ !(device_handle = wlr_libinput_get_device_handle(device)))
+ return;
+
+ tablet = wlr_tablet_create(tablet_mgr, seat, device);
+ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy);
+ if (libinput_device_config_send_events_get_modes(device_handle)) {
+ libinput_device_config_send_events_set_mode(device_handle, send_events_mode);
+ wlr_cursor_attach_input_device(cursor, device);
+ }
+ } else if (device == tablet->wlr_device) {
+ wlr_log(WLR_ERROR, "createtablet: duplicate device");
+ } else {
+ wlr_log(WLR_ERROR, "createtablet: already have one tablet");
+ }
+}
+
+void
+createtouch(struct wlr_touch *wlr_touch)
+{
+ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup));
+
+ touch->touch = wlr_touch;
+ wl_list_insert(&touches, &touch->link);
+ wlr_cursor_attach_input_device(cursor, &wlr_touch->base);
+}
+
void
cursorconstrain(struct wlr_pointer_constraint_v1 *constraint)
{
@@ -1383,6 +1462,29 @@ destroykeyboardgroup(struct wl_listener *listener, void *data)
free(group);
}
+void
+destroytablet(struct wl_listener *listener, void *data)
+{
+ wl_list_remove(&tablet_device_destroy.link);
+ wlr_cursor_detach_input_device(cursor, tablet->wlr_device);
+ tablet = NULL;
+}
+
+void
+destroytabletsurfacenotify(struct wl_listener *listener, void *data)
+{
+ if (tablet_curr_surface)
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ tablet_curr_surface = NULL;
+}
+
+void
+destroytablettool(struct wl_listener *listener, void *data)
+{
+ destroytabletsurfacenotify(NULL, NULL);
+ tablet_tool = NULL;
+}
+
Monitor *
dirtomon(enum wlr_direction dir)
{
@@ -1590,6 +1692,15 @@ inputdevice(struct wl_listener *listener, void *data)
case WLR_INPUT_DEVICE_POINTER:
createpointer(wlr_pointer_from_input_device(device));
break;
+ case WLR_INPUT_DEVICE_TABLET:
+ createtablet(device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ createtouch(wlr_touch_from_input_device(device));
+ break;
default:
/* TODO handle other input device types */
break;
@@ -1602,6 +1713,8 @@ inputdevice(struct wl_listener *listener, void *data)
caps = WL_SEAT_CAPABILITY_POINTER;
if (!wl_list_empty(&kb_group->wlr_group->devices))
caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ if (!wl_list_empty(&touches))
+ caps |= WL_SEAT_CAPABILITY_TOUCH;
wlr_seat_set_capabilities(seat, caps);
}
@@ -2585,6 +2698,8 @@ setup(void)
relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy);
+ tablet_mgr = wlr_tablet_v2_create(dpy);
+
/*
* Creates a cursor, which is a wlroots utility for tracking the cursor
* image shown on screen.
@@ -2614,6 +2729,18 @@ setup(void)
wl_signal_add(&cursor->events.button, &cursor_button);
wl_signal_add(&cursor->events.axis, &cursor_axis);
wl_signal_add(&cursor->events.frame, &cursor_frame);
+ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity);
+ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis);
+ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button);
+ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip);
+
+
+ wl_list_init(&touches);
+
+ wl_signal_add(&cursor->events.touch_down, &touch_down);
+ wl_signal_add(&cursor->events.touch_frame, &touch_frame);
+ wl_signal_add(&cursor->events.touch_motion, &touch_motion);
+ wl_signal_add(&cursor->events.touch_up, &touch_up);
cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1);
wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape);
@@ -2709,6 +2836,163 @@ tagmon(const Arg *arg)
setmon(sel, dirtomon(arg->i), 0);
}
+void
+tabletapplymap(double x, double y, struct wlr_input_device *dev)
+{
+ Client *p;
+ struct wlr_box geom = {0};
+ if (tabletmaptosurface && tablet_curr_surface) {
+ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL);
+ if (p) {
+ for (; client_get_parent(p); p = client_get_parent(p));
+ geom.x = p->geom.x + p->bw;
+ geom.y = p->geom.y + p->bw;
+ geom.width = p->geom.width - 2 * p->bw;
+ geom.height = p->geom.height - 2 * p->bw;
+ }
+ }
+ wlr_cursor_map_input_to_region(cursor, dev, &geom);
+ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output);
+}
+
+void
+tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y,
+ double x, double y, double dx, double dy)
+{
+ struct wlr_surface *surface = NULL;
+ double sx, sy;
+
+ if (!change_x && !change_y)
+ return;
+
+ tabletapplymap(x, y, tablet->wlr_device);
+
+ // TODO: apply constraints
+ switch (tablet_tool->wlr_tool->type) {
+ case WLR_TABLET_TOOL_TYPE_LENS:
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN);
+ break;
+ }
+
+ motionnotify(0, NULL, 0, 0, 0, 0);
+
+ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy);
+ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet))
+ surface = NULL;
+
+ if (surface != tablet_curr_surface) {
+ if (tablet_curr_surface) {
+ // TODO: wait until all buttons released before leaving
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface);
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ }
+ if (surface) {
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface);
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface);
+ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener);
+ }
+ tablet_curr_surface = surface;
+ }
+
+ if (surface)
+ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy);
+}
+
+void
+tablettoolproximity(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_proximity_event *event = data;
+ struct wlr_tablet_tool *tool = event->tool;
+
+ if (!tablet_tool) {
+ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool);
+ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy);
+ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor);
+ }
+
+ switch (event->state) {
+ case WLR_TABLET_TOOL_PROXIMITY_OUT:
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ destroytabletsurfacenotify(NULL, NULL);
+ break;
+ case WLR_TABLET_TOOL_PROXIMITY_IN:
+ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0);
+ break;
+ }
+}
+
+double tilt_x = 0;
+double tilt_y = 0;
+
+void
+tablettoolaxis(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_axis_event *event = data;
+
+ tablettoolmotion(tablet_tool,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
+ event->x, event->y, event->dx, event->dy);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
+ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
+ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
+ tilt_x = event->tilt_x;
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
+ tilt_y = event->tilt_y;
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y))
+ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, tilt_x, tilt_y);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
+ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
+ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
+ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0);
+}
+
+void
+tablettoolbutton(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_button_event *event = data;
+ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+void
+tablettooltip(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_tip_event *event = data;
+
+ if (!tablet_curr_surface) {
+ struct wlr_pointer_button_event fakeptrbtnevent = {
+ .button = BTN_LEFT,
+ .state = event->state == WLR_TABLET_TOOL_TIP_UP ?
+ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED,
+ .time_msec = event->time_msec,
+ };
+ buttonpress(NULL, (void *)&fakeptrbtnevent);
+ }
+
+ if (event->state == WLR_TABLET_TOOL_TIP_UP) {
+ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool);
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool);
+}
+
void
tile(Monitor *m)
{
@@ -2787,6 +3071,120 @@ toggleview(const Arg *arg)
printstatus();
}
+void
+touchdown(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_down_event *event = data;
+ double lx, ly;
+ double sx, sy;
+ struct wlr_surface *surface;
+ Client *c = NULL;
+ uint32_t serial = 0;
+ Monitor *m;
+
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+
+ // Map the input to the appropriate output, to ensure that rotation is
+ // handled.
+ wl_list_for_each(m, &mons, link) {
+ if (m == NULL || m->wlr_output == NULL) {
+ continue;
+ }
+ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) {
+ continue;
+ }
+
+ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output);
+ }
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
+
+ /* Find the client under the pointer and send the event along. */
+ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
+ if (sloppyfocus)
+ focusclient(c, 0);
+
+ if (surface != NULL) {
+ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy);
+ }
+
+ if (serial && wlr_seat_touch_num_points(seat) == 1) {
+ /* Emulate a mouse click if the touch event wasn't handled */
+ struct wlr_pointer_button_event *button_event = data;
+ struct wlr_pointer_motion_absolute_event *motion_event = data;
+ double dx, dy;
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly);
+ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly);
+ dx = lx - cursor->x;
+ dy = ly - cursor->y;
+ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy);
+
+ button_event->button = BTN_LEFT;
+ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED;
+ buttonpress(listener, button_event);
+ }
+}
+
+void
+touchup(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_up_event *event = data;
+
+ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
+ return;
+ }
+
+ if (wlr_seat_touch_num_points(seat) == 1) {
+ struct wlr_pointer_button_event *button_event = data;
+
+ button_event->button = BTN_LEFT;
+ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED;
+ buttonpress(listener, button_event);
+ }
+
+ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id);
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+void
+touchframe(struct wl_listener *listener, void *data)
+{
+ wlr_seat_touch_notify_frame(seat);
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+void
+touchmotion(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_motion_event *event = data;
+ double lx, ly;
+ double sx, sy;
+ struct wlr_surface *surface;
+ Client *c = NULL;
+
+ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
+ return;
+ }
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
+ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
+
+ if (c != NULL && surface != NULL) {
+ if (sloppyfocus)
+ focusclient(c, 0);
+ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy);
+ } else {
+ if (sloppyfocus)
+ focusclient(NULL, 0);
+ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id);
+ }
+ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy);
+
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+
void
unlocksession(struct wl_listener *listener, void *data)
{
--
2.52.0
@@ -1,584 +0,0 @@
From 78eda59b86ca01188fd645fe4062dda75d60acb1 Mon Sep 17 00:00:00 2001
From: A Frederick Christensen <dwl@ivories.org>
Date: Thu, 26 Feb 2026 23:23:49 -0600
Subject: [PATCH] Apply tablet-and-touch patch
---
Makefile | 6 +-
config.def.h | 1 +
dwl.c | 398 +++++++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 404 insertions(+), 1 deletion(-)
diff --git a/Makefile b/Makefile
index 578194f..e0d1835 100644
--- a/Makefile
+++ b/Makefile
@@ -21,7 +21,8 @@ dwl: dwl.o util.o
$(CC) dwl.o util.o $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@
dwl.o: dwl.c client.h config.h config.mk cursor-shape-v1-protocol.h \
pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \
- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h
+ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \
+ tablet-v2-protocol.h
util.o: util.c util.h
# wayland-scanner is a tool which generates C headers and rigging for Wayland
@@ -45,6 +46,9 @@ wlr-output-power-management-unstable-v1-protocol.h:
xdg-shell-protocol.h:
$(WAYLAND_SCANNER) server-header \
$(WAYLAND_PROTOCOLS)/stable/xdg-shell/xdg-shell.xml $@
+tablet-v2-protocol.h:
+ $(WAYLAND_SCANNER) server-header \
+ $(WAYLAND_PROTOCOLS)/unstable/tablet/tablet-unstable-v2.xml $@
config.h:
cp config.def.h $@
diff --git a/config.def.h b/config.def.h
index 8a6eda0..1f20dfd 100644
--- a/config.def.h
+++ b/config.def.h
@@ -4,6 +4,7 @@
((hex >> 8) & 0xFF) / 255.0f, \
(hex & 0xFF) / 255.0f }
/* appearance */
+static const int tabletmaptosurface = 0; /* map tablet input to surface(1) or monitor(0) */
static const int sloppyfocus = 1; /* focus follows mouse */
static const int bypass_surface_visibility = 0; /* 1 means idle inhibitors will disable idle tracking even if it's surface isn't visible */
static const unsigned int borderpx = 1; /* border pixel of windows */
diff --git a/dwl.c b/dwl.c
index 8a9715d..6dc462a 100644
--- a/dwl.c
+++ b/dwl.c
@@ -52,6 +52,10 @@
#include <wlr/types/wlr_session_lock_v1.h>
#include <wlr/types/wlr_single_pixel_buffer_v1.h>
#include <wlr/types/wlr_subcompositor.h>
+#include <wlr/types/wlr_tablet_pad.h>
+#include <wlr/types/wlr_tablet_tool.h>
+#include <wlr/types/wlr_tablet_v2.h>
+#include <wlr/types/wlr_touch.h>
#include <wlr/types/wlr_viewporter.h>
#include <wlr/types/wlr_virtual_keyboard_v1.h>
#include <wlr/types/wlr_virtual_pointer_v1.h>
@@ -163,6 +167,12 @@ typedef struct {
struct wl_listener destroy;
} KeyboardGroup;
+typedef struct TouchGroup {
+ struct wl_list link;
+ struct wlr_touch *touch;
+ Monitor *m;
+} TouchGroup;
+
typedef struct {
/* Must keep this field first */
unsigned int type; /* LayerShell */
@@ -270,7 +280,10 @@ static void createnotify(struct wl_listener *listener, void *data);
static void createpointer(struct wlr_pointer *pointer);
static void createpointerconstraint(struct wl_listener *listener, void *data);
static void createpopup(struct wl_listener *listener, void *data);
+static void createtablet(struct wlr_input_device *device);
+static void createtouch(struct wlr_touch *touch);
static void cursorconstrain(struct wlr_pointer_constraint_v1 *constraint);
+static void createtouch(struct wlr_touch *touch);
static void cursorframe(struct wl_listener *listener, void *data);
static void cursorwarptohint(void);
static void destroydecoration(struct wl_listener *listener, void *data);
@@ -283,6 +296,9 @@ static void destroynotify(struct wl_listener *listener, void *data);
static void destroypointerconstraint(struct wl_listener *listener, void *data);
static void destroysessionlock(struct wl_listener *listener, void *data);
static void destroykeyboardgroup(struct wl_listener *listener, void *data);
+static void destroytablet(struct wl_listener *listener, void *data);
+static void destroytabletsurfacenotify(struct wl_listener *listener, void *data);
+static void destroytablettool(struct wl_listener *listener, void *data);
static Monitor *dirtomon(enum wlr_direction dir);
static void focusclient(Client *c, int lift);
static void focusmon(const Arg *arg);
@@ -335,11 +351,20 @@ static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
static void tagmon(const Arg *arg);
+static void tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y, double x, double y, double dx, double dy);
+static void tablettoolproximity(struct wl_listener *listener, void *data);
+static void tablettoolaxis(struct wl_listener *listener, void *data);
+static void tablettoolbutton(struct wl_listener *listener, void *data);
+static void tablettooltip(struct wl_listener *listener, void *data);
static void tile(Monitor *m);
static void togglefloating(const Arg *arg);
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
+static void touchdown(struct wl_listener *listener, void *data);
+static void touchup(struct wl_listener *listener, void *data);
+static void touchframe(struct wl_listener *listener, void *data);
+static void touchmotion(struct wl_listener *listener, void *data);
static void unlocksession(struct wl_listener *listener, void *data);
static void unmaplayersurfacenotify(struct wl_listener *listener, void *data);
static void unmapnotify(struct wl_listener *listener, void *data);
@@ -392,6 +417,13 @@ static struct wlr_pointer_constraint_v1 *active_constraint;
static struct wlr_cursor *cursor;
static struct wlr_xcursor_manager *cursor_mgr;
+static struct wlr_tablet_manager_v2 *tablet_mgr;
+static struct wlr_tablet_v2_tablet *tablet = NULL;
+static struct wlr_tablet_v2_tablet_tool *tablet_tool = NULL;
+static struct wlr_tablet_v2_tablet_pad *tablet_pad = NULL;
+static struct wlr_surface *tablet_curr_surface = NULL;
+static struct wl_listener destroy_tablet_surface_listener = {.notify = destroytabletsurfacenotify};
+
static struct wlr_scene_rect *root_bg;
static struct wlr_session_lock_manager_v1 *session_lock_mgr;
static struct wlr_scene_rect *locked_bg;
@@ -407,6 +439,7 @@ static struct wlr_output_layout *output_layout;
static struct wlr_box sgeom;
static struct wl_list mons;
static Monitor *selmon;
+static struct wl_list touches;
/* global event handlers */
static struct wl_listener cursor_axis = {.notify = axisnotify};
@@ -414,6 +447,12 @@ static struct wl_listener cursor_button = {.notify = buttonpress};
static struct wl_listener cursor_frame = {.notify = cursorframe};
static struct wl_listener cursor_motion = {.notify = motionrelative};
static struct wl_listener cursor_motion_absolute = {.notify = motionabsolute};
+static struct wl_listener tablet_device_destroy = {.notify = destroytablet};
+static struct wl_listener tablet_tool_axis = {.notify = tablettoolaxis};
+static struct wl_listener tablet_tool_button = {.notify = tablettoolbutton};
+static struct wl_listener tablet_tool_destroy = {.notify = destroytablettool};
+static struct wl_listener tablet_tool_proximity = {.notify = tablettoolproximity};
+static struct wl_listener tablet_tool_tip = {.notify = tablettooltip};
static struct wl_listener gpu_reset = {.notify = gpureset};
static struct wl_listener layout_change = {.notify = updatemons};
static struct wl_listener new_idle_inhibitor = {.notify = createidleinhibitor};
@@ -436,6 +475,10 @@ static struct wl_listener request_set_sel = {.notify = setsel};
static struct wl_listener request_set_cursor_shape = {.notify = setcursorshape};
static struct wl_listener request_start_drag = {.notify = requeststartdrag};
static struct wl_listener start_drag = {.notify = startdrag};
+static struct wl_listener touch_down = {.notify = touchdown};
+static struct wl_listener touch_frame = {.notify = touchframe};
+static struct wl_listener touch_motion = {.notify = touchmotion};
+static struct wl_listener touch_up = {.notify = touchup};
static struct wl_listener new_session_lock = {.notify = locksession};
#ifdef XWAYLAND
@@ -783,6 +826,10 @@ cleanuplisteners(void)
wl_list_remove(&request_set_cursor_shape.link);
wl_list_remove(&request_start_drag.link);
wl_list_remove(&start_drag.link);
+ wl_list_remove(&touch_down.link);
+ wl_list_remove(&touch_frame.link);
+ wl_list_remove(&touch_motion.link);
+ wl_list_remove(&touch_up.link);
wl_list_remove(&new_session_lock.link);
#ifdef XWAYLAND
wl_list_remove(&new_xwayland_surface.link);
@@ -1201,6 +1248,38 @@ createpopup(struct wl_listener *listener, void *data)
LISTEN_STATIC(&popup->base->surface->events.commit, commitpopup);
}
+void
+createtablet(struct wlr_input_device *device)
+{
+ if (!tablet) {
+ struct libinput_device *device_handle = NULL;
+ if (!wlr_input_device_is_libinput(device) ||
+ !(device_handle = wlr_libinput_get_device_handle(device)))
+ return;
+
+ tablet = wlr_tablet_create(tablet_mgr, seat, device);
+ wl_signal_add(&tablet->wlr_device->events.destroy, &tablet_device_destroy);
+ if (libinput_device_config_send_events_get_modes(device_handle)) {
+ libinput_device_config_send_events_set_mode(device_handle, send_events_mode);
+ wlr_cursor_attach_input_device(cursor, device);
+ }
+ } else if (device == tablet->wlr_device) {
+ wlr_log(WLR_ERROR, "createtablet: duplicate device");
+ } else {
+ wlr_log(WLR_ERROR, "createtablet: already have one tablet");
+ }
+}
+
+void
+createtouch(struct wlr_touch *wlr_touch)
+{
+ TouchGroup *touch = ecalloc(1, sizeof(TouchGroup));
+
+ touch->touch = wlr_touch;
+ wl_list_insert(&touches, &touch->link);
+ wlr_cursor_attach_input_device(cursor, &wlr_touch->base);
+}
+
void
cursorconstrain(struct wlr_pointer_constraint_v1 *constraint)
{
@@ -1385,6 +1464,29 @@ destroykeyboardgroup(struct wl_listener *listener, void *data)
free(group);
}
+void
+destroytablet(struct wl_listener *listener, void *data)
+{
+ wl_list_remove(&tablet_device_destroy.link);
+ wlr_cursor_detach_input_device(cursor, tablet->wlr_device);
+ tablet = NULL;
+}
+
+void
+destroytabletsurfacenotify(struct wl_listener *listener, void *data)
+{
+ if (tablet_curr_surface)
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ tablet_curr_surface = NULL;
+}
+
+void
+destroytablettool(struct wl_listener *listener, void *data)
+{
+ destroytabletsurfacenotify(NULL, NULL);
+ tablet_tool = NULL;
+}
+
Monitor *
dirtomon(enum wlr_direction dir)
{
@@ -1592,6 +1694,15 @@ inputdevice(struct wl_listener *listener, void *data)
case WLR_INPUT_DEVICE_POINTER:
createpointer(wlr_pointer_from_input_device(device));
break;
+ case WLR_INPUT_DEVICE_TABLET:
+ createtablet(device);
+ break;
+ case WLR_INPUT_DEVICE_TABLET_PAD:
+ tablet_pad = wlr_tablet_pad_create(tablet_mgr, seat, device);
+ break;
+ case WLR_INPUT_DEVICE_TOUCH:
+ createtouch(wlr_touch_from_input_device(device));
+ break;
default:
/* TODO handle other input device types */
break;
@@ -1604,6 +1715,8 @@ inputdevice(struct wl_listener *listener, void *data)
caps = WL_SEAT_CAPABILITY_POINTER;
if (!wl_list_empty(&kb_group->wlr_group->devices))
caps |= WL_SEAT_CAPABILITY_KEYBOARD;
+ if (!wl_list_empty(&touches))
+ caps |= WL_SEAT_CAPABILITY_TOUCH;
wlr_seat_set_capabilities(seat, caps);
}
@@ -2588,6 +2701,8 @@ setup(void)
relative_pointer_mgr = wlr_relative_pointer_manager_v1_create(dpy);
+ tablet_mgr = wlr_tablet_v2_create(dpy);
+
/*
* Creates a cursor, which is a wlroots utility for tracking the cursor
* image shown on screen.
@@ -2617,6 +2732,18 @@ setup(void)
wl_signal_add(&cursor->events.button, &cursor_button);
wl_signal_add(&cursor->events.axis, &cursor_axis);
wl_signal_add(&cursor->events.frame, &cursor_frame);
+ wl_signal_add(&cursor->events.tablet_tool_proximity, &tablet_tool_proximity);
+ wl_signal_add(&cursor->events.tablet_tool_axis, &tablet_tool_axis);
+ wl_signal_add(&cursor->events.tablet_tool_button, &tablet_tool_button);
+ wl_signal_add(&cursor->events.tablet_tool_tip, &tablet_tool_tip);
+
+
+ wl_list_init(&touches);
+
+ wl_signal_add(&cursor->events.touch_down, &touch_down);
+ wl_signal_add(&cursor->events.touch_frame, &touch_frame);
+ wl_signal_add(&cursor->events.touch_motion, &touch_motion);
+ wl_signal_add(&cursor->events.touch_up, &touch_up);
cursor_shape_mgr = wlr_cursor_shape_manager_v1_create(dpy, 1);
wl_signal_add(&cursor_shape_mgr->events.request_set_shape, &request_set_cursor_shape);
@@ -2712,6 +2839,163 @@ tagmon(const Arg *arg)
setmon(sel, dirtomon(arg->i), 0);
}
+void
+tabletapplymap(double x, double y, struct wlr_input_device *dev)
+{
+ Client *p;
+ struct wlr_box geom = {0};
+ if (tabletmaptosurface && tablet_curr_surface) {
+ toplevel_from_wlr_surface(tablet_curr_surface, &p, NULL);
+ if (p) {
+ for (; client_get_parent(p); p = client_get_parent(p));
+ geom.x = p->geom.x + p->bw;
+ geom.y = p->geom.y + p->bw;
+ geom.width = p->geom.width - 2 * p->bw;
+ geom.height = p->geom.height - 2 * p->bw;
+ }
+ }
+ wlr_cursor_map_input_to_region(cursor, dev, &geom);
+ wlr_cursor_map_input_to_output(cursor, dev, selmon->wlr_output);
+}
+
+void
+tablettoolmotion(struct wlr_tablet_v2_tablet_tool *tool, bool change_x, bool change_y,
+ double x, double y, double dx, double dy)
+{
+ struct wlr_surface *surface = NULL;
+ double sx, sy;
+
+ if (!change_x && !change_y)
+ return;
+
+ tabletapplymap(x, y, tablet->wlr_device);
+
+ // TODO: apply constraints
+ switch (tablet_tool->wlr_tool->type) {
+ case WLR_TABLET_TOOL_TYPE_LENS:
+ case WLR_TABLET_TOOL_TYPE_MOUSE:
+ wlr_cursor_move(cursor, tablet->wlr_device, dx, dy);
+ break;
+ default:
+ wlr_cursor_warp_absolute(cursor, tablet->wlr_device, change_x ? x : NAN, change_y ? y : NAN);
+ break;
+ }
+
+ motionnotify(0, NULL, 0, 0, 0, 0);
+
+ xytonode(cursor->x, cursor->y, &surface, NULL, NULL, &sx, &sy);
+ if (surface && !wlr_surface_accepts_tablet_v2(surface, tablet))
+ surface = NULL;
+
+ if (surface != tablet_curr_surface) {
+ if (tablet_curr_surface) {
+ // TODO: wait until all buttons released before leaving
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_leave(tablet_pad, tablet_curr_surface);
+ wl_list_remove(&destroy_tablet_surface_listener.link);
+ }
+ if (surface) {
+ if (tablet_pad)
+ wlr_tablet_v2_tablet_pad_notify_enter(tablet_pad, tablet, surface);
+ if (tablet_tool)
+ wlr_tablet_v2_tablet_tool_notify_proximity_in(tablet_tool, tablet, surface);
+ wl_signal_add(&surface->events.destroy, &destroy_tablet_surface_listener);
+ }
+ tablet_curr_surface = surface;
+ }
+
+ if (surface)
+ wlr_tablet_v2_tablet_tool_notify_motion(tablet_tool, sx, sy);
+}
+
+void
+tablettoolproximity(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_proximity_event *event = data;
+ struct wlr_tablet_tool *tool = event->tool;
+
+ if (!tablet_tool) {
+ tablet_tool = wlr_tablet_tool_create(tablet_mgr, seat, tool);
+ wl_signal_add(&tablet_tool->wlr_tool->events.destroy, &tablet_tool_destroy);
+ wl_signal_add(&tablet_tool->events.set_cursor, &request_cursor);
+ }
+
+ switch (event->state) {
+ case WLR_TABLET_TOOL_PROXIMITY_OUT:
+ wlr_tablet_v2_tablet_tool_notify_proximity_out(tablet_tool);
+ destroytabletsurfacenotify(NULL, NULL);
+ break;
+ case WLR_TABLET_TOOL_PROXIMITY_IN:
+ tablettoolmotion(tablet_tool, true, true, event->x, event->y, 0, 0);
+ break;
+ }
+}
+
+double tilt_x = 0;
+double tilt_y = 0;
+
+void
+tablettoolaxis(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_axis_event *event = data;
+
+ tablettoolmotion(tablet_tool,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_X,
+ event->updated_axes & WLR_TABLET_TOOL_AXIS_Y,
+ event->x, event->y, event->dx, event->dy);
+
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_PRESSURE)
+ wlr_tablet_v2_tablet_tool_notify_pressure(tablet_tool, event->pressure);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_DISTANCE)
+ wlr_tablet_v2_tablet_tool_notify_distance(tablet_tool, event->distance);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_X)
+ tilt_x = event->tilt_x;
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_TILT_Y)
+ tilt_y = event->tilt_y;
+ if (event->updated_axes & (WLR_TABLET_TOOL_AXIS_TILT_X | WLR_TABLET_TOOL_AXIS_TILT_Y))
+ wlr_tablet_v2_tablet_tool_notify_tilt(tablet_tool, tilt_x, tilt_y);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_ROTATION)
+ wlr_tablet_v2_tablet_tool_notify_rotation(tablet_tool, event->rotation);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_SLIDER)
+ wlr_tablet_v2_tablet_tool_notify_slider(tablet_tool, event->slider);
+ if (event->updated_axes & WLR_TABLET_TOOL_AXIS_WHEEL)
+ wlr_tablet_v2_tablet_tool_notify_wheel(tablet_tool, event->wheel_delta, 0);
+}
+
+void
+tablettoolbutton(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_button_event *event = data;
+ wlr_tablet_v2_tablet_tool_notify_button(tablet_tool, event->button,
+ (enum zwp_tablet_pad_v2_button_state)event->state);
+}
+
+void
+tablettooltip(struct wl_listener *listener, void *data)
+{
+ struct wlr_tablet_tool_tip_event *event = data;
+
+ if (!tablet_curr_surface) {
+ struct wlr_pointer_button_event fakeptrbtnevent = {
+ .button = BTN_LEFT,
+ .state = event->state == WLR_TABLET_TOOL_TIP_UP ?
+ WL_POINTER_BUTTON_STATE_RELEASED : WL_POINTER_BUTTON_STATE_PRESSED,
+ .time_msec = event->time_msec,
+ };
+ buttonpress(NULL, (void *)&fakeptrbtnevent);
+ }
+
+ if (event->state == WLR_TABLET_TOOL_TIP_UP) {
+ wlr_tablet_v2_tablet_tool_notify_up(tablet_tool);
+ return;
+ }
+
+ wlr_tablet_v2_tablet_tool_notify_down(tablet_tool);
+ wlr_tablet_tool_v2_start_implicit_grab(tablet_tool);
+}
+
void
tile(Monitor *m)
{
@@ -2790,6 +3074,120 @@ toggleview(const Arg *arg)
printstatus();
}
+void
+touchdown(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_down_event *event = data;
+ double lx, ly;
+ double sx, sy;
+ struct wlr_surface *surface;
+ Client *c = NULL;
+ uint32_t serial = 0;
+ Monitor *m;
+
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+
+ // Map the input to the appropriate output, to ensure that rotation is
+ // handled.
+ wl_list_for_each(m, &mons, link) {
+ if (m == NULL || m->wlr_output == NULL) {
+ continue;
+ }
+ if (event->touch->output_name != NULL && 0 != strcmp(event->touch->output_name, m->wlr_output->name)) {
+ continue;
+ }
+
+ wlr_cursor_map_input_to_output(cursor, &event->touch->base, m->wlr_output);
+ }
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
+
+ /* Find the client under the pointer and send the event along. */
+ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
+ if (sloppyfocus)
+ focusclient(c, 0);
+
+ if (surface != NULL) {
+ serial = wlr_seat_touch_notify_down(seat, surface, event->time_msec, event->touch_id, sx, sy);
+ }
+
+ if (serial && wlr_seat_touch_num_points(seat) == 1) {
+ /* Emulate a mouse click if the touch event wasn't handled */
+ struct wlr_pointer_button_event *button_event = data;
+ struct wlr_pointer_motion_absolute_event *motion_event = data;
+ double dx, dy;
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &motion_event->pointer->base, motion_event->x, motion_event->y, &lx, &ly);
+ wlr_cursor_warp_closest(cursor, &motion_event->pointer->base, lx, ly);
+ dx = lx - cursor->x;
+ dy = ly - cursor->y;
+ motionnotify(motion_event->time_msec, &motion_event->pointer->base, dx, dy, dx, dy);
+
+ button_event->button = BTN_LEFT;
+ button_event->state = WL_POINTER_BUTTON_STATE_PRESSED;
+ buttonpress(listener, button_event);
+ }
+}
+
+void
+touchup(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_up_event *event = data;
+
+ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
+ return;
+ }
+
+ if (wlr_seat_touch_num_points(seat) == 1) {
+ struct wlr_pointer_button_event *button_event = data;
+
+ button_event->button = BTN_LEFT;
+ button_event->state = WL_POINTER_BUTTON_STATE_RELEASED;
+ buttonpress(listener, button_event);
+ }
+
+ wlr_seat_touch_notify_up(seat, event->time_msec, event->touch_id);
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+void
+touchframe(struct wl_listener *listener, void *data)
+{
+ wlr_seat_touch_notify_frame(seat);
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+void
+touchmotion(struct wl_listener *listener, void *data)
+{
+ struct wlr_touch_motion_event *event = data;
+ double lx, ly;
+ double sx, sy;
+ struct wlr_surface *surface;
+ Client *c = NULL;
+
+ if (!wlr_seat_touch_get_point(seat, event->touch_id)) {
+ return;
+ }
+
+ wlr_cursor_absolute_to_layout_coords(cursor, &event->touch->base, event->x, event->y, &lx, &ly);
+ xytonode(lx, ly, &surface, &c, NULL, &sx, &sy);
+
+ if (c != NULL && surface != NULL) {
+ if (sloppyfocus)
+ focusclient(c, 0);
+ wlr_seat_touch_point_focus(seat, surface, event->time_msec, event->touch_id, sx, sy);
+ } else {
+ if (sloppyfocus)
+ focusclient(NULL, 0);
+ wlr_seat_touch_point_clear_focus(seat, event->time_msec, event->touch_id);
+ }
+ wlr_seat_touch_notify_motion(seat, event->time_msec, event->touch_id, sx, sy);
+
+ wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
+}
+
+
void
unlocksession(struct wl_listener *listener, void *data)
{
--
2.52.0
+4 -1
View File
@@ -1,8 +1,11 @@
### Description
Switch between multiple keyboard layouts, variants, and options at runtime.
Supports both assigning a specific keyboard to a shortcut and cycling through
keyboards.
### Download
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch)
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.8.patch)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions-0.7.patch)
### Authors
- [LaKato](https://codeberg.org/LaKato)
@@ -0,0 +1,160 @@
From 6c028b4cacd21f9e6a195ce7b0e1987868fb3e62 Mon Sep 17 00:00:00 2001
From: LaKato <lakato@noreply.codeberg.org>
Date: Wed, 18 Mar 2026 20:47:30 -0400
Subject: [PATCH] Rebase togglekblayoutandoptions to v0.8
No actual changes, and the previous patch applied just fine if
whitespace was ignored. Just adding this to explicitly advertise
support for v0.8 and reduce noise about patches being applied at an
offset.
---
config.def.h | 17 +++++++++++------
dwl.c | 44 +++++++++++++++++++++++++++++++++-----------
2 files changed, 44 insertions(+), 17 deletions(-)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..850f322 100644
--- a/config.def.h
+++ b/config.def.h
@@ -48,12 +48,15 @@ static const MonitorRule monrules[] = {
};
/* keyboard */
-static const struct xkb_rule_names xkb_rules = {
- /* can specify fields: rules, model, layout, variant, options */
- /* example:
- .options = "ctrl:nocaps",
- */
- .options = NULL,
+static const struct xkb_rule_names xkb_rules[] = {
+ {
+ .layout = "us",
+ },
+ /*{
+ .layout = "us",
+ .variant = "dvp",
+ .options = "compose:102,numpad:shift3,kpdl:semi,keypad:atm,caps:super"
+ }*/
};
static const int repeat_rate = 25;
@@ -144,6 +147,8 @@ static const Key keys[] = {
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_A, incxkbrules, {.i = +1} },
+ /*{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_E, setxkbrules, {.i = +1} },*/
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
diff --git a/dwl.c b/dwl.c
index 44f3ad9..a86c692 100644
--- a/dwl.c
+++ b/dwl.c
@@ -246,6 +246,7 @@ static void arrange(Monitor *m);
static void arrangelayer(Monitor *m, struct wl_list *list,
struct wlr_box *usable_area, int exclusive);
static void arrangelayers(Monitor *m);
+static void assignkeymap(struct wlr_keyboard *keyboard);
static void axisnotify(struct wl_listener *listener, void *data);
static void buttonpress(struct wl_listener *listener, void *data);
static void chvt(const Arg *arg);
@@ -290,6 +291,7 @@ static void fullscreennotify(struct wl_listener *listener, void *data);
static void gpureset(struct wl_listener *listener, void *data);
static void handlesig(int signo);
static void incnmaster(const Arg *arg);
+static void incxkbrules(const Arg *arg);
static void inputdevice(struct wl_listener *listener, void *data);
static int keybinding(uint32_t mods, xkb_keysym_t sym);
static void keypress(struct wl_listener *listener, void *data);
@@ -329,6 +331,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 setxkbrules(const Arg *arg);
static void spawn(const Arg *arg);
static void startdrag(struct wl_listener *listener, void *data);
static void tag(const Arg *arg);
@@ -397,6 +400,7 @@ static struct wlr_session_lock_v1 *cur_lock;
static struct wlr_seat *seat;
static KeyboardGroup *kb_group;
+static unsigned int kblayout = 0;
static unsigned int cursor_mode;
static Client *grabc;
static int grabcx, grabcy; /* client-relative */
@@ -603,6 +607,20 @@ arrangelayers(Monitor *m)
}
}
+void
+assignkeymap(struct wlr_keyboard *keyboard) {
+ struct xkb_context *context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
+ struct xkb_keymap *keymap = xkb_keymap_new_from_names(context, &xkb_rules[kblayout],
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ if (!keymap)
+ die("failed to compile keymap");
+
+ wlr_keyboard_set_keymap(keyboard, keymap);
+ xkb_keymap_unref(keymap);
+ xkb_context_unref(context);
+}
+
void
axisnotify(struct wl_listener *listener, void *data)
{
@@ -951,21 +969,11 @@ KeyboardGroup *
createkeyboardgroup(void)
{
KeyboardGroup *group = ecalloc(1, sizeof(*group));
- struct xkb_context *context;
- struct xkb_keymap *keymap;
group->wlr_group = wlr_keyboard_group_create();
group->wlr_group->data = group;
- /* Prepare an XKB keymap and assign it to the keyboard group. */
- context = xkb_context_new(XKB_CONTEXT_NO_FLAGS);
- if (!(keymap = xkb_keymap_new_from_names(context, &xkb_rules,
- XKB_KEYMAP_COMPILE_NO_FLAGS)))
- die("failed to compile keymap");
-
- wlr_keyboard_set_keymap(&group->wlr_group->keyboard, keymap);
- xkb_keymap_unref(keymap);
- xkb_context_unref(context);
+ assignkeymap(&group->wlr_group->keyboard);
wlr_keyboard_set_repeat_info(&group->wlr_group->keyboard, repeat_rate, repeat_delay);
@@ -1575,6 +1583,13 @@ incnmaster(const Arg *arg)
arrange(selmon);
}
+void
+incxkbrules(const Arg *arg)
+{
+ kblayout = (kblayout + arg->i) % LENGTH(xkb_rules);
+ assignkeymap(&kb_group->wlr_group->keyboard);
+}
+
void
inputdevice(struct wl_listener *listener, void *data)
{
@@ -2666,6 +2681,13 @@ setup(void)
#endif
}
+void
+setxkbrules(const Arg *arg)
+{
+ kblayout = arg->i;
+ assignkeymap(&kb_group->wlr_group->keyboard);
+}
+
void
spawn(const Arg *arg)
{
--
2.52.0
+1 -1
View File
@@ -60,7 +60,7 @@ index 775dadf..621779e 100644
- /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented
- * by checking the event's orientation and the delta of the event */
+ handlecursoractivity();
+ /* TODO: allow usage of scroll whell for mousebindings, it can be implemented
+ /* TODO: allow usage of scroll wheel for mousebindings, it can be implemented
+ * checking the event's orientation and the delta of the event */
/* Notify the client with pointer focus of the axis event. */
wlr_seat_pointer_notify_axis(seat,
+2 -2
View File
@@ -1,7 +1,7 @@
### Description
implement wlr_virtual_pointer_v1 for things like wayvnc server to work
**NOTE:** no longer neccessary if you are using a DWL version after https://codeberg.org/dwl/dwl/commit/ac6074f4fdb8cc263c877f08e16a5805d3bb22d2
**NOTE:** no longer necessary if you are using a DWL version after https://codeberg.org/dwl/dwl/commit/ac6074f4fdb8cc263c877f08e16a5805d3bb22d2
### Download
- [git branch](https://codeberg.org/wochap/dwl/src/v0.5/virtual-pointer)
@@ -9,4 +9,4 @@ implement wlr_virtual_pointer_v1 for things like wayvnc server to work
### Authors
- [wochap](https://codeberg.org/wochap)
- [youbitchoc](https://github.com/youbitchoc)
- [youbitchoc](https://github.com/youbitchoc)
+3 -2
View File
@@ -3,7 +3,7 @@ Implements the function `winview` which switches the visible tags to the tags on
This patch is inspired from <https://dwm.suckless.org/patches/winview/>. Citing the description of the dwm patch:
> Dwm tags are a powerfull feature that allows organizing windows in workspaces. Sometime it can be difficult to remember the tag to activate to unhide a window. With the winview patch the window to unhide can be selected from the all-window view. The user switches to the all-window view (Mod1-0), selects the window (Mod1-j/k or using the mouse) and press Mod1-o. The key Mod1-o switches the view to the selected window tag.
> Dwm tags are a powerful feature that allows organizing windows in workspaces. Sometime it can be difficult to remember the tag to activate to unhide a window. With the winview patch the window to unhide can be selected from the all-window view. The user switches to the all-window view (Mod1-0), selects the window (Mod1-j/k or using the mouse) and press Mod1-o. The key Mod1-o switches the view to the selected window tag.
>
> #### Recommend patches
>
@@ -26,7 +26,8 @@ This patch is inspired from <https://dwm.suckless.org/patches/winview/>. Citing
> An example of how to insert this line can be found in the default config file template, config.def.h.
### Download
- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview.patch)
- [2026-03-14 dwl 0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview_dwl_08.patch)
- [2024-09-18](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview_dwl_07.patch)
- [2024-06-06](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/winview/winview-20240606.patch)
- [git branch](https://codeberg.org/dhruva_sambrani/dwl/src/branch/winview)
+55
View File
@@ -0,0 +1,55 @@
From d53f64b3baee1e14c59b9b6cead024b91e185bd7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Andr=C3=A9=20Desgualdo=20Pereira?= <desgua@gmail.com>
Date: Sat, 14 Mar 2026 18:34:36 -0300
Subject: [PATCH] update to dwl 0.8
---
config.def.h | 1 +
dwl.c | 12 ++++++++++++
2 files changed, 13 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..3c1f16b 100644
--- a/config.def.h
+++ b/config.def.h
@@ -139,6 +139,7 @@ static const Key keys[] = {
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_space, togglefloating, {0} },
{ MODKEY, XKB_KEY_e, togglefullscreen, {0} },
{ MODKEY, XKB_KEY_0, view, {.ui = ~0} },
+ { MODKEY, XKB_KEY_o, winview, {0}},
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_parenright, tag, {.ui = ~0} },
{ MODKEY, XKB_KEY_comma, focusmon, {.i = WLR_DIRECTION_LEFT} },
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
diff --git a/dwl.c b/dwl.c
index 101a45f..f3b75e0 100644
--- a/dwl.c
+++ b/dwl.c
@@ -348,6 +348,7 @@ static void urgent(struct wl_listener *listener, void *data);
static void view(const Arg *arg);
static void virtualkeyboard(struct wl_listener *listener, void *data);
static void virtualpointer(struct wl_listener *listener, void *data);
+static void winview(const Arg *a);
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);
@@ -3006,6 +3007,17 @@ virtualpointer(struct wl_listener *listener, void *data)
wlr_cursor_map_input_to_output(cursor, device, event->suggested_output);
}
+void
+winview(const Arg *a) {
+ Arg b = {0};
+ Client *sel = focustop(selmon);
+ if(!sel)
+ return;
+ b.ui = sel -> tags;
+ view(&b);
+ return;
+}
+
Monitor *
xytomon(double x, double y)
{
--
2.51.0
+2 -2
View File
@@ -1,5 +1,5 @@
### Description
Implements `swaymsg create_output` command, it allows you to create virtual/headless outputs. But in combination with a VNC server (for example wayvnc), this allows you to essentially have additional monitors, by connecting to the VNC server with an appropiate client (for example on an tablet or laptop).
Implements `swaymsg create_output` command, it allows you to create virtual/headless outputs. But in combination with a VNC server (for example wayvnc), this allows you to essentially have additional monitors, by connecting to the VNC server with an appropriate client (for example on an tablet or laptop).
If you plan to use wayvnc, you'll need [virtual-pointer](https://codeberg.org/dwl/dwl-patches/wiki/virtual-pointer.-) patch as well
@@ -8,4 +8,4 @@ If you plan to use wayvnc, you'll need [virtual-pointer](https://codeberg.org/dw
- [v0.5](https://codeberg.org/dwl/dwl-patches/raw/commit/0096e49402bc59b4050e12cdb9befb79d0011006/headless/headless.patch)
### Authors
- [wochap](https://codeberg.org/wochap)
- [wochap](https://codeberg.org/wochap)
+10 -10
View File
@@ -417,7 +417,7 @@ index 0000000..0a6e7e5
+I would probably just submit raphi's patchset but I don't think that would be polite.
+-->
+<protocol name="dwl_ipc_unstable_v2">
+ <description summary="inter-proccess-communication about dwl's state">
+ <description summary="inter-process-communication about dwl's state">
+ This protocol allows clients to update and get updates from dwl.
+
+ Warning! The protocol described in this file is experimental and
@@ -437,7 +437,7 @@ index 0000000..0a6e7e5
+ This interface is exposed as a global in wl_registry.
+
+ Clients can use this interface to get a dwl_ipc_output.
+ After binding the client will recieve the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
+ After binding the client will receive the dwl_ipc_manager.tags and dwl_ipc_manager.layout events.
+ The dwl_ipc_manager.tags and dwl_ipc_manager.layout events expose tags and layouts to the client.
+ </description>
+
@@ -449,8 +449,8 @@ index 0000000..0a6e7e5
+ </request>
+
+ <request name="get_output">
+ <description summary="get a dwl_ipc_outout for a wl_output">
+ Get a dwl_ipc_outout for the specified wl_output.
+ <description summary="get a dwl_ipc_output for a wl_output">
+ Get a dwl_ipc_output for the specified wl_output.
+ </description>
+ <arg name="id" type="new_id" interface="zdwl_ipc_output_v2"/>
+ <arg name="output" type="object" interface="wl_output"/>
@@ -459,7 +459,7 @@ index 0000000..0a6e7e5
+ <event name="tags">
+ <description summary="Announces tag amount">
+ This event is sent after binding.
+ A roundtrip after binding guarantees the client recieved all tags.
+ A roundtrip after binding guarantees the client received all tags.
+ </description>
+ <arg name="amount" type="uint"/>
+ </event>
@@ -467,7 +467,7 @@ index 0000000..0a6e7e5
+ <event name="layout">
+ <description summary="Announces a layout">
+ This event is sent after binding.
+ A roundtrip after binding guarantees the client recieved all layouts.
+ A roundtrip after binding guarantees the client received all layouts.
+ </description>
+ <arg name="name" type="string"/>
+ </event>
@@ -491,13 +491,13 @@ index 0000000..0a6e7e5
+ </enum>
+
+ <request name="release" type="destructor">
+ <description summary="release dwl_ipc_outout">
+ <description summary="release dwl_ipc_output">
+ Indicates to that the client no longer needs this dwl_ipc_output.
+ </description>
+ </request>
+
+ <event name="toggle_visibility">
+ <description summary="Toggle client visibilty">
+ <description summary="Toggle client visibility">
+ Indicates the client should hide or show themselves.
+ If the client is visible then hide, if hidden then show.
+ </description>
@@ -544,7 +544,7 @@ index 0000000..0a6e7e5
+ <event name="layout_symbol" since="1">
+ <description summary="Update the current layout symbol">
+ Indicates the layout has changed. Since layout symbols are dynamic.
+ As opposed to the zdwl_ipc_manager.layout event, this should take precendence when displaying.
+ As opposed to the zdwl_ipc_manager.layout event, this should take precedence when displaying.
+ You can ignore the zdwl_ipc_output.layout event.
+ </description>
+ <arg name="layout" type="string" summary="The new layout"/>
@@ -573,7 +573,7 @@ index 0000000..0a6e7e5
+
+ <request name="set_layout">
+ <description summary="Set the layout of this output"/>
+ <arg name="index" type="uint" summary="index of a layout recieved by dwl_ipc_manager.layout"/>
+ <arg name="index" type="uint" summary="index of a layout received by dwl_ipc_manager.layout"/>
+ </request>
+
+ <!-- Version 2 -->