Compare commits

...

2 Commits

Author SHA1 Message Date
julmajustus 7f06b4c1a3 fullscreenadpativesync: update & minor bug fixes
- Fixed logic error inside unmapnotify
- Added no-op guards inside set_adaptive_sync
- Added extra check for the config commit success
2026-05-18 00:29:39 +03:00
julmajustus d7471ef29e btrtile: Refactored the tiled client resizing logic
Improved detection of the node to resize. btrtile now handles client resizing based on the nearest client edge in the resize direction. It also handles resizing of clientless split nodes, making the behavior feel more intuitive.
2026-05-17 23:44:33 +03:00
8 changed files with 731 additions and 341 deletions
+101 -82
View File
@@ -1,21 +1,23 @@
From f5d7fe287bd4405879a92d649243d3f646652617 Mon Sep 17 00:00:00 2001
From ad32bb92157d5238340eb53322254dc8127477db Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Thu, 19 Mar 2026 00:08:10 +0200
Subject: [PATCH] Refactor btrtile-gaps to v0.8
Date: Sun, 17 May 2026 22:49:07 +0300
Subject: [PATCH] btrtile resizing refactor
- Refactored the tiled client resizing logic to be more logical. Insted
of resizing based on clients split nodes, now btrtile handles client resizing based on the nearest edge client edge on the resize direction. Now it handles also resizing of the clientless splitnodes and should feel more intuitive.
---
btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 ++++++++++---
3 files changed, 717 insertions(+), 27 deletions(-)
dwl.c | 160 +++++++++++---
3 files changed, 727 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..afe3c8f
index 0000000..c63e59f
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,584 @@
@@ -0,0 +1,582 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
@@ -26,7 +28,7 @@ index 0000000..afe3c8f
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/19 00:07:19 by julmajustus : : : : : : */
+/* Updated: 2026/05/17 22:00:31 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -49,7 +51,8 @@ index 0000000..afe3c8f
+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 LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
+ unsigned int need_vertical, int focused_on_left);
+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);
@@ -76,7 +79,7 @@ index 0000000..afe3c8f
+ if (!node)
+ return;
+
+ if (is_root && e) {
+ if (is_root && e) {
+ area.x += gappx;
+ area.y += gappx;
+ area.width -= 2 * gappx;
@@ -129,7 +132,7 @@ index 0000000..afe3c8f
+ 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;
@@ -147,7 +150,7 @@ index 0000000..afe3c8f
+ 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;
@@ -166,16 +169,16 @@ index 0000000..afe3c8f
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+ if (!m)
+ 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);
+ }
+ }
+ /* 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)))
@@ -268,30 +271,37 @@ index 0000000..afe3c8f
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+find_suitable_split(Monitor *m, LayoutNode *start_node,
+ unsigned int need_vertical, int focused_on_left)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ LayoutNode *n = start_node, *child = NULL;
+
+ if (!m)
+ return NULL;
+
+ if (n && n->is_client_node) {
+ child = n;
+ 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;
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
+ && visible_count(n->left, m) > 0
+ && visible_count(n->right, m) > 0) {
+ if ((focused_on_left && n->left == child) ||
+ (!focused_on_left && n->right == child))
+ return n;
+ }
+ child = 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;
+ if (m)
+ m->root = NULL;
+}
+
+void
@@ -415,21 +425,31 @@ index 0000000..afe3c8f
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+static void
+setratio(unsigned int need_vertical, const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ Client *sel;
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+ int focused_on_left;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ sel = focustop(selmon);
+ if (!sel)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ focused_on_left = (arg->f >= 0.0f);
+
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
+
+ if (!split_node)
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
+ if (!split_node)
+ return;
+
@@ -440,43 +460,22 @@ index 0000000..afe3c8f
+ 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) {
+ /* Skip the arrange when called from motionnotify; that path calls
+ * arrange itself after rate-limiting. */
+ if (!resizing_from_mouse)
+ arrange(selmon);
+ }
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ setratio(1, arg);
+}
+
+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);
+ }
+ setratio(0, arg);
+}
+
+void swapclients(const Arg *arg) {
@@ -565,11 +564,12 @@ index 0000000..afe3c8f
+
+Client *
+xytoclient(double x, double y) {
+ Monitor *m = xytomon(x, 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 &&
+ if (VISIBLEON(c, m) && !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;
@@ -578,7 +578,7 @@ index 0000000..afe3c8f
+
+ /* 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) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
@@ -591,7 +591,7 @@ index 0000000..afe3c8f
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ dist = dx * dx + dy * dy;
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
@@ -639,7 +639,7 @@ index 8a6eda0..bc04e3f 100644
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
index 44f3ad9..d37e235 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -765,18 +765,22 @@ index 44f3ad9..f74d9a1 100644
/* The xdg-protocol specifies:
*
@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1332,6 +1364,14 @@ 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);
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1862,7 +1898,8 @@ void
@@ -1862,7 +1902,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -786,7 +790,7 @@ index 44f3ad9..f74d9a1 100644
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
@@ -1916,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));
@@ -850,7 +854,7 @@ index 44f3ad9..f74d9a1 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. */
@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg)
@@ -1961,22 +2040,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -908,6 +912,21 @@ index 44f3ad9..f74d9a1 100644
}
}
@@ -2826,6 +2924,14 @@ unmapnotify(struct wl_listener *listener, void *data)
focusclient(focustop(selmon), 1);
}
} else {
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
+
wl_list_remove(&c->link);
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
--
2.52.0
2.53.0
+98 -79
View File
@@ -1,21 +1,23 @@
From 1b75163aac16942a715c6fc0aaf5d508e778dd6a Mon Sep 17 00:00:00 2001
From 7ef8c161eb3cd801f608784e7d888fe12e94b9d4 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
Date: Sun, 17 May 2026 22:39:30 +0300
Subject: [PATCH] btrtile resizing refactor
- Refactored the tiled client resizing logic to be more logical. Insted
of resizing based on clients split nodes, now btrtile handles client resizing based on the nearest edge client edge on the resize direction. Now it handles also resizing of the clientless splitnodes and should feel more intuitive.
---
btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 +++++++++++---
3 files changed, 698 insertions(+), 27 deletions(-)
dwl.c | 160 ++++++++++++---
3 files changed, 708 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..d706c3b
index 0000000..ad810bf
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,565 @@
@@ -0,0 +1,563 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
@@ -26,7 +28,7 @@ index 0000000..d706c3b
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */
+/* Updated: 2026/05/17 22:09:15 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -49,7 +51,8 @@ index 0000000..d706c3b
+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 LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
+ unsigned int need_vertical, int focused_on_left);
+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);
@@ -147,16 +150,16 @@ index 0000000..d706c3b
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+ if (!m)
+ 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);
+ }
+ }
+ /* 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)))
@@ -249,30 +252,37 @@ index 0000000..d706c3b
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+find_suitable_split(Monitor *m, LayoutNode *start_node,
+ unsigned int need_vertical, int focused_on_left)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ LayoutNode *n = start_node, *child = NULL;
+
+ if (!m)
+ return NULL;
+
+ if (n && n->is_client_node) {
+ child = n;
+ 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;
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
+ && visible_count(n->left, m) > 0
+ && visible_count(n->right, m) > 0) {
+ if ((focused_on_left && n->left == child) ||
+ (!focused_on_left && n->right == child))
+ return n;
+ }
+ child = 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;
+ if (m)
+ m->root = NULL;
+}
+
+void
@@ -396,21 +406,31 @@ index 0000000..d706c3b
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+static void
+setratio(unsigned int need_vertical, const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ Client *sel;
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+ int focused_on_left;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ sel = focustop(selmon);
+ if (!sel)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ focused_on_left = (arg->f >= 0.0f);
+
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
+
+ if (!split_node)
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
+ if (!split_node)
+ return;
+
@@ -421,43 +441,22 @@ index 0000000..d706c3b
+ 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) {
+ /* Skip the arrange when called from motionnotify; that path calls
+ * arrange itself after rate-limiting. */
+ if (!resizing_from_mouse)
+ arrange(selmon);
+ }
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ setratio(1, arg);
+}
+
+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);
+ }
+ setratio(0, arg);
+}
+
+void swapclients(const Arg *arg) {
@@ -546,11 +545,12 @@ index 0000000..d706c3b
+
+Client *
+xytoclient(double x, double y) {
+ Monitor *m = xytomon(x, 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 &&
+ if (VISIBLEON(c, m) && !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;
@@ -559,7 +559,7 @@ index 0000000..d706c3b
+
+ /* 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) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
@@ -572,7 +572,7 @@ index 0000000..d706c3b
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ dist = dx * dx + dy * dy;
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
@@ -620,7 +620,7 @@ index 8a6eda0..bc04e3f 100644
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
index 44f3ad9..d37e235 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -746,18 +746,22 @@ index 44f3ad9..f74d9a1 100644
/* The xdg-protocol specifies:
*
@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1332,6 +1364,14 @@ 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);
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1862,7 +1898,8 @@ void
@@ -1862,7 +1902,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -767,7 +771,7 @@ index 44f3ad9..f74d9a1 100644
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
@@ -1916,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));
@@ -831,7 +835,7 @@ index 44f3ad9..f74d9a1 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. */
@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg)
@@ -1961,22 +2040,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -889,6 +893,21 @@ index 44f3ad9..f74d9a1 100644
}
}
@@ -2826,6 +2924,14 @@ unmapnotify(struct wl_listener *listener, void *data)
focusclient(focustop(selmon), 1);
}
} else {
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
+
wl_list_remove(&c->link);
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
--
2.52.0
2.53.0
@@ -1,21 +1,23 @@
From a9329abe48083a4c5b64eee726ceefdb4bf0bb9f Mon Sep 17 00:00:00 2001
From b8d84c87f13681118865b83312387a881ab10b73 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
Date: Sun, 17 May 2026 23:10:49 +0300
Subject: [PATCH] btrtile resizing refactor
- Refactored the tiled client resizing logic to be more logical. Insted
of resizing based on clients split nodes, now btrtile handles client resizing based on the nearest edge client edge on the resize direction. Now it handles also resizing of the clientless splitnodes and should feel more intuitive.
---
btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 ++++++++++---
3 files changed, 717 insertions(+), 27 deletions(-)
dwl.c | 161 +++++++++++---
3 files changed, 728 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..8703617
index 0000000..c63e59f
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,584 @@
@@ -0,0 +1,582 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
@@ -26,7 +28,7 @@ index 0000000..8703617
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/19 22:59:12 by julmajustus : : : : : : */
+/* Updated: 2026/05/17 22:00:31 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -49,7 +51,8 @@ index 0000000..8703617
+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 LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
+ unsigned int need_vertical, int focused_on_left);
+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);
@@ -129,10 +132,10 @@ index 0000000..8703617
+ 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;
+ if (e) {
+ left_area.width -= gappx / 2;
+ right_area.x += gappx / 2;
+ right_area.width -= gappx / 2;
+ }
+ } else {
+ /* horizontal split */
@@ -166,16 +169,16 @@ index 0000000..8703617
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+ if (!m)
+ 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);
+ }
+ }
+ /* 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)))
@@ -268,30 +271,37 @@ index 0000000..8703617
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+find_suitable_split(Monitor *m, LayoutNode *start_node,
+ unsigned int need_vertical, int focused_on_left)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ LayoutNode *n = start_node, *child = NULL;
+
+ if (!m)
+ return NULL;
+
+ if (n && n->is_client_node) {
+ child = n;
+ 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;
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
+ && visible_count(n->left, m) > 0
+ && visible_count(n->right, m) > 0) {
+ if ((focused_on_left && n->left == child) ||
+ (!focused_on_left && n->right == child))
+ return n;
+ }
+ child = 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;
+ if (m)
+ m->root = NULL;
+}
+
+void
@@ -415,21 +425,31 @@ index 0000000..8703617
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+static void
+setratio(unsigned int need_vertical, const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ Client *sel;
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+ int focused_on_left;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ sel = focustop(selmon);
+ if (!sel)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ focused_on_left = (arg->f >= 0.0f);
+
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
+
+ if (!split_node)
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
+ if (!split_node)
+ return;
+
@@ -440,43 +460,22 @@ index 0000000..8703617
+ 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) {
+ /* Skip the arrange when called from motionnotify; that path calls
+ * arrange itself after rate-limiting. */
+ if (!resizing_from_mouse)
+ arrange(selmon);
+ }
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ setratio(1, arg);
+}
+
+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);
+ }
+ setratio(0, arg);
+}
+
+void swapclients(const Arg *arg) {
@@ -565,11 +564,12 @@ index 0000000..8703617
+
+Client *
+xytoclient(double x, double y) {
+ Monitor *m = xytomon(x, 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 &&
+ if (VISIBLEON(c, m) && !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;
@@ -578,7 +578,7 @@ index 0000000..8703617
+
+ /* 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) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
@@ -591,7 +591,7 @@ index 0000000..8703617
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ dist = dx * dx + dy * dy;
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
@@ -639,7 +639,7 @@ index 8a6eda0..bc04e3f 100644
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
index 8101ffa..32a94c4 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -765,18 +765,23 @@ index 8101ffa..e4e7074 100644
/* The xdg-protocol specifies:
*
@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1336,6 +1368,15 @@ 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);
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1866,7 +1902,8 @@ void
@@ -1866,7 +1907,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -786,7 +791,7 @@ index 8101ffa..e4e7074 100644
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
@@ -1920,18 +1962,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));
@@ -850,7 +855,7 @@ index 8101ffa..e4e7074 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. */
@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg)
@@ -1965,22 +2045,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -908,6 +913,21 @@ index 8101ffa..e4e7074 100644
}
}
@@ -2833,6 +2932,14 @@ unmapnotify(struct wl_listener *listener, void *data)
focusclient(focustop(selmon), 1);
}
} else {
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
+
wl_list_remove(&c->link);
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
--
2.52.0
2.53.0
@@ -1,21 +1,23 @@
From 912701a41b19c013a9adcb176a7b6503aa1cf9c1 Mon Sep 17 00:00:00 2001
From 3c78ee7e48fb24276d1a8d722ca1e715ea07d17f Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Thu, 19 Mar 2026 22:56:45 +0200
Subject: [PATCH] Refactor btrtile to wlroots-next
Date: Sun, 17 May 2026 23:28:41 +0300
Subject: [PATCH] btrtile resizing refactor
- Refactored the tiled client resizing logic to be more logical. Insted
of resizing based on clients split nodes, now btrtile handles client resizing based on the nearest edge client edge on the resize direction. Now it handles also resizing of the clientless splitnodes and should feel more intuitive.
---
btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 +++++++++++---
3 files changed, 698 insertions(+), 27 deletions(-)
dwl.c | 161 ++++++++++++---
3 files changed, 709 insertions(+), 27 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..d706c3b
index 0000000..7e70478
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,565 @@
@@ -0,0 +1,563 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
@@ -26,7 +28,7 @@ index 0000000..d706c3b
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
+/* ::! :!: !:! */
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
+/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */
+/* Updated: 2026/05/17 23:26:25 by julmajustus : : : : : : */
+/* */
+/* ************************************************************************** */
+
@@ -49,7 +51,8 @@ index 0000000..d706c3b
+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 LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
+ unsigned int need_vertical, int focused_on_left);
+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);
@@ -147,16 +150,16 @@ index 0000000..d706c3b
+ LayoutNode *found;
+ struct wlr_box full_area;
+
+ if (!m || !m->root)
+ return;
+ if (!m)
+ 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);
+ }
+ }
+ /* 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)))
@@ -249,30 +252,37 @@ index 0000000..d706c3b
+}
+
+LayoutNode *
+find_suitable_split(LayoutNode *start_node, unsigned int need_vertical)
+find_suitable_split(Monitor *m, LayoutNode *start_node,
+ unsigned int need_vertical, int focused_on_left)
+{
+ LayoutNode *n = start_node;
+ /* if we started from a client node, jump to its parent: */
+ if (n && n->is_client_node)
+ LayoutNode *n = start_node, *child = NULL;
+
+ if (!m)
+ return NULL;
+
+ if (n && n->is_client_node) {
+ child = n;
+ 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;
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
+ && visible_count(n->left, m) > 0
+ && visible_count(n->right, m) > 0) {
+ if ((focused_on_left && n->left == child) ||
+ (!focused_on_left && n->right == child))
+ return n;
+ }
+ child = 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;
+ if (m)
+ m->root = NULL;
+}
+
+void
@@ -396,21 +406,31 @@ index 0000000..d706c3b
+ m->root = remove_client_node(m->root, c);
+}
+
+void
+setratio_h(const Arg *arg)
+static void
+setratio(unsigned int need_vertical, const Arg *arg)
+{
+ Client *sel = focustop(selmon);
+ Client *sel;
+ LayoutNode *client_node, *split_node;
+ float new_ratio;
+ int focused_on_left;
+
+ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
+ return;
+
+ sel = focustop(selmon);
+ if (!sel)
+ return;
+
+ client_node = find_client_node(selmon->root, sel);
+ if (!client_node)
+ return;
+
+ split_node = find_suitable_split(client_node, 1);
+ focused_on_left = (arg->f >= 0.0f);
+
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
+
+ if (!split_node)
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
+ if (!split_node)
+ return;
+
@@ -421,43 +441,22 @@ index 0000000..d706c3b
+ 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) {
+ /* Skip the arrange when called from motionnotify; that path calls
+ * arrange itself after rate-limiting. */
+ if (!resizing_from_mouse)
+ arrange(selmon);
+ }
+}
+
+void
+setratio_h(const Arg *arg)
+{
+ setratio(1, arg);
+}
+
+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);
+ }
+ setratio(0, arg);
+}
+
+void swapclients(const Arg *arg) {
@@ -546,11 +545,12 @@ index 0000000..d706c3b
+
+Client *
+xytoclient(double x, double y) {
+ Monitor *m = xytomon(x, 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 &&
+ if (VISIBLEON(c, m) && !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;
@@ -559,7 +559,7 @@ index 0000000..d706c3b
+
+ /* 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) {
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
+ dx = 0, dy = 0;
+
+ if (x < c->geom.x)
@@ -572,7 +572,7 @@ index 0000000..d706c3b
+ else if (y > (c->geom.y + c->geom.height))
+ dy = y - (c->geom.y + c->geom.height);
+
+ dist = sqrt(dx * dx + dy * dy);
+ dist = dx * dx + dy * dy;
+ if (dist < mindist) {
+ mindist = dist;
+ closest = c;
@@ -620,7 +620,7 @@ index 8a6eda0..bc04e3f 100644
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
index 8101ffa..32a94c4 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@@ -746,18 +746,23 @@ index 8101ffa..e4e7074 100644
/* The xdg-protocol specifies:
*
@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1336,6 +1368,15 @@ 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);
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1866,7 +1902,8 @@ void
@@ -1866,7 +1907,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@@ -767,7 +772,7 @@ index 8101ffa..e4e7074 100644
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
@@ -1920,18 +1962,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));
@@ -831,7 +836,7 @@ index 8101ffa..e4e7074 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. */
@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg)
@@ -1965,22 +2045,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@@ -889,6 +894,21 @@ index 8101ffa..e4e7074 100644
}
}
@@ -2833,6 +2932,14 @@ unmapnotify(struct wl_listener *listener, void *data)
focusclient(focustop(selmon), 1);
}
} else {
+ // btrtile remove clients for each monitor
+ Monitor *mon;
+ wl_list_for_each(mon, &mons, link) {
+ if (mon->root) {
+ remove_client(mon, c);
+ }
+ }
+
wl_list_remove(&c->link);
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
--
2.52.0
2.53.0
+4 -2
View File
@@ -22,8 +22,10 @@ Adds a function that automatically enables adaptive sync/VRR when a fullscreen c
- Enabled by default.
### Download
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/fullscreenadaptivesync)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch)
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/fullscreenadaptivesync-wlroots-next)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.7.patch)
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-v0.8.patch)
- [wlroots-next-d41ecb7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/fullscreenadaptivesync/fullscreenadaptivesync-wlroots-next-d41ecb7.patch)
### Authors
- [julmajustus](https://codeberg.org/julmajustus)
@@ -1,12 +1,15 @@
From c003f450c197a0c960bbb355511f8dca7a35e3c3 Mon Sep 17 00:00:00 2001
From 85dccbec7e6d2e967646a8c182a7a24224632c68 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Sat, 4 Jan 2025 14:24:59 +0200
Subject: [PATCH] add fullscreenadaptivesync
Date: Mon, 18 May 2026 00:03:41 +0300
Subject: [PATCH] fullscreen_adaptive_sync: Minor bug fixes
- Fixed logic error inside unmapnotify
- Added no-op guards inside set_adaptive_sync
- Added extra check for the config commit success
---
config.def.h | 1 +
dwl.c | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 41 insertions(+)
dwl.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/config.def.h b/config.def.h
index 22d2171..886f1ab 100644
@@ -21,7 +24,7 @@ index 22d2171..886f1ab 100644
{ 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 a2711f6..7be05ef 100644
index a2711f6..1b0682d 100644
--- a/dwl.c
+++ b/dwl.c
@@ -322,6 +322,7 @@ static void requeststartdrag(struct wl_listener *listener, void *data);
@@ -49,11 +52,10 @@ index a2711f6..7be05ef 100644
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -2269,6 +2273,31 @@ run(char *startup_cmd)
@@ -2269,6 +2273,42 @@ run(char *startup_cmd)
wl_display_run(dpy);
}
+void
+set_adaptive_sync(Monitor *m, int enable)
+{
+ struct wlr_output_state state;
@@ -64,14 +66,26 @@ index a2711f6..7be05ef 100644
+ || !fullscreen_adaptive_sync_enabled)
+ return;
+
+ if (enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)
+ return;
+
+ if (!enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED)
+ return;
+
+ config = wlr_output_configuration_v1_create();
+ config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output);
+
+ /* Set and commit the adaptive sync state change */
+ wlr_output_state_init(&state);
+ wlr_output_state_set_adaptive_sync_enabled(&state, enable);
+ wlr_output_commit_state(m->wlr_output, &state);
+ wlr_output_state_finish(&state);
+ if (!wlr_output_commit_state(m->wlr_output, &state)) {
+ wlr_output_state_finish(&state);
+ wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+ wlr_output_state_finish(&state);
+
+ /* Broadcast the adaptive sync state change to output_mgr */
+ config_head->state.adaptive_sync_enabled = enable;
@@ -81,7 +95,7 @@ index a2711f6..7be05ef 100644
void
setcursor(struct wl_listener *listener, void *data)
{
@@ -2332,10 +2361,12 @@ setfullscreen(Client *c, int fullscreen)
@@ -2332,10 +2372,12 @@ setfullscreen(Client *c, int fullscreen)
if (fullscreen) {
c->prev = c->geom;
resize(c, c->mon->m, 0);
@@ -94,7 +108,7 @@ index a2711f6..7be05ef 100644
}
arrange(c->mon);
printstatus();
@@ -2739,6 +2770,12 @@ togglefullscreen(const Arg *arg)
@@ -2739,6 +2781,12 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
@@ -107,16 +121,24 @@ index a2711f6..7be05ef 100644
void
toggletag(const Arg *arg)
{
@@ -2809,6 +2846,9 @@ unmapnotify(struct wl_listener *listener, void *data)
@@ -2794,6 +2842,7 @@ unmapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is unmapped, and should no longer be shown. */
Client *c = wl_container_of(listener, c, unmap);
+ Monitor *lastmon = c->mon; // fullscreen_adaptive_sync
if (c == grabc) {
cursor_mode = CurNormal;
grabc = NULL;
@@ -2809,6 +2858,9 @@ unmapnotify(struct wl_listener *listener, void *data)
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
}
+ /* Toggle adaptive sync off when fullscreen client is unmapped */
+ if (c->isfullscreen)
+ set_adaptive_sync(selmon, 0);
+ if (c->isfullscreen)
+ set_adaptive_sync(lastmon, 0);
wlr_scene_node_destroy(&c->scene->node);
printstatus();
--
2.45.2
2.53.0
@@ -0,0 +1,144 @@
From 60af6f9dab09710234475b51393a104c21918db3 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Mon, 18 May 2026 00:12:31 +0300
Subject: [PATCH] fullscreen_adaptive_sync: Minor bug fixes
- Fixed logic error inside unmapnotify
- Added no-op guards inside set_adaptive_sync
- Added extra check for the config commit success
---
config.def.h | 1 +
dwl.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..06b3153 100644
--- a/config.def.h
+++ b/config.def.h
@@ -138,6 +138,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, XKB_KEY_F5, togglefullscreenadaptivesync, {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..737f089 100644
--- a/dwl.c
+++ b/dwl.c
@@ -319,6 +319,7 @@ 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 run(char *startup_cmd);
+static void set_adaptive_sync(Monitor *m, int enabled);
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);
@@ -336,6 +337,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 togglefullscreenadaptivesync(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -436,6 +438,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 int fullscreen_adaptive_sync_enabled = 1;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -2296,6 +2300,42 @@ run(char *startup_cmd)
wl_display_run(dpy);
}
+set_adaptive_sync(Monitor *m, int enable)
+{
+ struct wlr_output_state state;
+ struct wlr_output_configuration_v1 *config;
+ struct wlr_output_configuration_head_v1 *config_head;
+
+ if (!m || !m->wlr_output || !m->wlr_output->enabled
+ || !fullscreen_adaptive_sync_enabled)
+ return;
+
+ if (enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)
+ return;
+
+ if (!enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED)
+ return;
+
+ config = wlr_output_configuration_v1_create();
+ config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output);
+
+ /* Set and commit the adaptive sync state change */
+ wlr_output_state_init(&state);
+ wlr_output_state_set_adaptive_sync_enabled(&state, enable);
+ if (!wlr_output_commit_state(m->wlr_output, &state)) {
+ wlr_output_state_finish(&state);
+ wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+ wlr_output_state_finish(&state);
+
+ /* Broadcast the adaptive sync state change to output_mgr */
+ config_head->state.adaptive_sync_enabled = enable;
+ wlr_output_manager_v1_set_configuration(output_mgr, config);
+}
+
void
setcursor(struct wl_listener *listener, void *data)
{
@@ -2359,10 +2399,12 @@ setfullscreen(Client *c, int fullscreen)
if (fullscreen) {
c->prev = c->geom;
resize(c, c->mon->m, 0);
+ set_adaptive_sync(c->mon, 1);
} else {
/* restore previous size instead of arrange for floating windows since
* client positions are set by the user and cannot be recalculated */
resize(c, c->prev, 0);
+ set_adaptive_sync(c->mon, 0);
}
arrange(c->mon);
printstatus();
@@ -2760,6 +2802,12 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
+void
+togglefullscreenadaptivesync(const Arg *arg)
+{
+ fullscreen_adaptive_sync_enabled = !fullscreen_adaptive_sync_enabled;
+}
+
void
toggletag(const Arg *arg)
{
@@ -2815,6 +2863,7 @@ unmapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is unmapped, and should no longer be shown. */
Client *c = wl_container_of(listener, c, unmap);
+ Monitor *lastmon = c->mon; // fullscreen_adaptive_sync
if (c == grabc) {
cursor_mode = CurNormal;
grabc = NULL;
@@ -2830,6 +2879,9 @@ unmapnotify(struct wl_listener *listener, void *data)
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
}
+ /* Toggle adaptive sync off when fullscreen client is unmapped */
+ if (c->isfullscreen)
+ set_adaptive_sync(lastmon, 0);
wlr_scene_node_destroy(&c->scene->node);
printstatus();
--
2.53.0
@@ -0,0 +1,144 @@
From cf817259dbb1235d1ffc073d4e0f128676552666 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
Date: Mon, 18 May 2026 00:15:18 +0300
Subject: [PATCH] fullscreen_adaptive_sync: Minor bug fixes
- Fixed logic error inside unmapnotify
- Added no-op guards inside set_adaptive_sync
- Added extra check for the config commit success
---
config.def.h | 1 +
dwl.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 53 insertions(+)
diff --git a/config.def.h b/config.def.h
index 8a6eda0..06b3153 100644
--- a/config.def.h
+++ b/config.def.h
@@ -138,6 +138,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, XKB_KEY_F5, togglefullscreenadaptivesync, {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 8101ffa..2f1c80b 100644
--- a/dwl.c
+++ b/dwl.c
@@ -323,6 +323,7 @@ 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 run(char *startup_cmd);
+static void set_adaptive_sync(Monitor *m, int enabled);
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);
@@ -340,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 togglefullscreenadaptivesync(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
static void unlocksession(struct wl_listener *listener, void *data);
@@ -440,6 +442,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 int fullscreen_adaptive_sync_enabled = 1;
+
#ifdef XWAYLAND
static void activatex11(struct wl_listener *listener, void *data);
static void associatex11(struct wl_listener *listener, void *data);
@@ -2300,6 +2304,42 @@ run(char *startup_cmd)
wl_display_run(dpy);
}
+set_adaptive_sync(Monitor *m, int enable)
+{
+ struct wlr_output_state state;
+ struct wlr_output_configuration_v1 *config;
+ struct wlr_output_configuration_head_v1 *config_head;
+
+ if (!m || !m->wlr_output || !m->wlr_output->enabled
+ || !fullscreen_adaptive_sync_enabled)
+ return;
+
+ if (enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_ENABLED)
+ return;
+
+ if (!enable && m->wlr_output->adaptive_sync_status
+ == WLR_OUTPUT_ADAPTIVE_SYNC_DISABLED)
+ return;
+
+ config = wlr_output_configuration_v1_create();
+ config_head = wlr_output_configuration_head_v1_create(config, m->wlr_output);
+
+ /* Set and commit the adaptive sync state change */
+ wlr_output_state_init(&state);
+ wlr_output_state_set_adaptive_sync_enabled(&state, enable);
+ if (!wlr_output_commit_state(m->wlr_output, &state)) {
+ wlr_output_state_finish(&state);
+ wlr_output_configuration_v1_destroy(config);
+ return;
+ }
+ wlr_output_state_finish(&state);
+
+ /* Broadcast the adaptive sync state change to output_mgr */
+ config_head->state.adaptive_sync_enabled = enable;
+ wlr_output_manager_v1_set_configuration(output_mgr, config);
+}
+
void
setcursor(struct wl_listener *listener, void *data)
{
@@ -2363,10 +2403,12 @@ setfullscreen(Client *c, int fullscreen)
if (fullscreen) {
c->prev = c->geom;
resize(c, c->mon->m, 0);
+ set_adaptive_sync(c->mon, 1);
} else {
/* restore previous size instead of arrange for floating windows since
* client positions are set by the user and cannot be recalculated */
resize(c, c->prev, 0);
+ set_adaptive_sync(c->mon, 0);
}
arrange(c->mon);
printstatus();
@@ -2767,6 +2809,12 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
+void
+togglefullscreenadaptivesync(const Arg *arg)
+{
+ fullscreen_adaptive_sync_enabled = !fullscreen_adaptive_sync_enabled;
+}
+
void
toggletag(const Arg *arg)
{
@@ -2822,6 +2870,7 @@ unmapnotify(struct wl_listener *listener, void *data)
{
/* Called when the surface is unmapped, and should no longer be shown. */
Client *c = wl_container_of(listener, c, unmap);
+ Monitor *lastmon = c->mon; // fullscreen_adaptive_sync
if (c == grabc) {
cursor_mode = CurNormal;
grabc = NULL;
@@ -2837,6 +2886,9 @@ unmapnotify(struct wl_listener *listener, void *data)
setmon(c, NULL, 0);
wl_list_remove(&c->flink);
}
+ /* Toggle adaptive sync off when fullscreen client is unmapped */
+ if (c->isfullscreen)
+ set_adaptive_sync(lastmon, 0);
wlr_scene_node_destroy(&c->scene->node);
printstatus();
--
2.53.0