Compare commits

..

No commits in common. "c5d5f9fed4ba35b096f0717da44e4333d26e38de" and "85427bdb0100a1ffabd6cc176b448e98f152a2e8" have entirely different histories.

13 changed files with 152 additions and 2452 deletions

View File

@ -96,10 +96,8 @@ 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.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)
- [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)
### Authors
- [julmajustus](https://codeberg.org/julmajustus)

View File

@ -1,32 +1,30 @@
From f5d7fe287bd4405879a92d649243d3f646652617 Mon Sep 17 00:00:00 2001
From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 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: Thu, 13 Feb 2025 23:25:20 +0200
Subject: [PATCH] btrtile-gaps with multi-tag support
---
btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 ++++++++++---
3 files changed, 717 insertions(+), 27 deletions(-)
dwl.c | 152 +++++++++++---
3 files changed, 717 insertions(+), 29 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..afe3c8f
index 0000000..650cab5
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,584 @@
@@ -0,0 +1,582 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* 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 : : : : : : */
+/* */
+/* ::: :::::::: */
+/* 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 */
+/* */
+/* ************************************************************************** */
+
@ -76,7 +74,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 +127,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 +145,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;
@ -601,21 +599,21 @@ index 0000000..afe3c8f
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 8a6eda0..bc04e3f 100644
index 22d2171..92f3ad6 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 float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */
+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[] = {
@@ -31,6 +34,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
@ -623,23 +621,23 @@ index 8a6eda0..bc04e3f 100644
{ "[]=", 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),
@@ -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),
diff --git a/dwl.c b/dwl.c
index 44f3ad9..f74d9a1 100644
index a2711f6..e49a061 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@ -650,15 +648,15 @@ index 44f3ad9..f74d9a1 100644
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -100,6 +101,7 @@ typedef struct {
@@ -103,6 +104,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 {
/* Must keep these three elements in this order */
@@ -139,8 +141,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@ -669,7 +667,7 @@ index 44f3ad9..f74d9a1 100644
} Client;
typedef struct {
@@ -205,6 +208,7 @@ struct Monitor {
@@ -208,6 +211,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
@ -677,7 +675,7 @@ index 44f3ad9..f74d9a1 100644
};
typedef struct {
@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -250,6 +254,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);
@ -685,7 +683,7 @@ index 44f3ad9..f74d9a1 100644
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);
@@ -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);
@ -695,7 +693,7 @@ index 44f3ad9..f74d9a1 100644
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;
@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
/* attempt to encapsulate suck into one file */
#include "client.h"
@ -703,7 +701,7 @@ index 44f3ad9..f74d9a1 100644
/* function implementations */
void
@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
@ -712,7 +710,7 @@ index 44f3ad9..f74d9a1 100644
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -622,7 +631,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) &&
@ -721,9 +719,9 @@ index 44f3ad9..f74d9a1 100644
b->func(&b->arg);
return;
}
@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -632,15 +641,36 @@ 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|")) {
@ -749,7 +747,18 @@ index 44f3ad9..f74d9a1 100644
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)
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)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
@ -757,7 +766,7 @@ index 44f3ad9..f74d9a1 100644
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data)
@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
@ -765,7 +774,7 @@ index 44f3ad9..f74d9a1 100644
/* The xdg-protocol specifies:
*
@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1263,6 +1295,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);
@ -776,7 +785,7 @@ index 44f3ad9..f74d9a1 100644
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1862,7 +1898,8 @@ void
@@ -1809,7 +1845,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@ -786,7 +795,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
@@ -1863,18 +1900,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 +859,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)
@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@ -860,7 +869,7 @@ index 44f3ad9..f74d9a1 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, "all-scroll");
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
@ -909,5 +918,5 @@ index 44f3ad9..f74d9a1 100644
}
--
2.52.0
2.45.3

View File

@ -1,32 +1,30 @@
From 912701a41b19c013a9adcb176a7b6503aa1cf9c1 Mon Sep 17 00:00:00 2001
From b9789420f166c20579f29ecd171a8956c681848d 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: Thu, 13 Feb 2025 23:23:40 +0200
Subject: [PATCH] btrtile with multi-tag support
---
btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++
btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++
config.def.h | 12 ++
dwl.c | 148 +++++++++++---
3 files changed, 698 insertions(+), 27 deletions(-)
dwl.c | 152 +++++++++++---
3 files changed, 698 insertions(+), 29 deletions(-)
create mode 100644 btrtile.c
diff --git a/btrtile.c b/btrtile.c
new file mode 100644
index 0000000..d706c3b
index 0000000..03f4680
--- /dev/null
+++ b/btrtile.c
@@ -0,0 +1,565 @@
@@ -0,0 +1,563 @@
+/* ************************************************************************** */
+/* @@@ @@@@@@@@ */
+/* @@@ @@@@@@@@@@ */
+/* @@! @@! @@@@ */
+/* !@! !@! @!@!@ */
+/* 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 : : : : : : */
+/* */
+/* ::: :::::::: */
+/* 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 */
+/* */
+/* ************************************************************************** */
+
@ -582,21 +580,21 @@ index 0000000..d706c3b
+ return closest;
+}
diff --git a/config.def.h b/config.def.h
index 8a6eda0..bc04e3f 100644
index 22d2171..92f3ad6 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 float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */
+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[] = {
@@ -31,6 +34,7 @@ static const Rule rules[] = {
/* layout(s) */
static const Layout layouts[] = {
/* symbol arrange function */
@ -604,23 +602,23 @@ index 8a6eda0..bc04e3f 100644
{ "[]=", 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),
@@ -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),
diff --git a/dwl.c b/dwl.c
index 8101ffa..e4e7074 100644
index a2711f6..e49a061 100644
--- a/dwl.c
+++ b/dwl.c
@@ -1,6 +1,7 @@
@ -631,15 +629,15 @@ index 8101ffa..e4e7074 100644
#include <getopt.h>
#include <libinput.h>
#include <linux/input-event-codes.h>
@@ -104,6 +105,7 @@ typedef struct {
@@ -103,6 +104,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 {
/* Must keep these three elements in this order */
@@ -139,8 +141,9 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@ -650,7 +648,7 @@ index 8101ffa..e4e7074 100644
} Client;
typedef struct {
@@ -209,6 +212,7 @@ struct Monitor {
@@ -208,6 +211,7 @@ struct Monitor {
int nmaster;
char ltsymbol[16];
int asleep;
@ -658,7 +656,7 @@ index 8101ffa..e4e7074 100644
};
typedef struct {
@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
@@ -250,6 +254,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);
@ -676,7 +674,7 @@ index 8101ffa..e4e7074 100644
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;
@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
/* attempt to encapsulate suck into one file */
#include "client.h"
@ -684,7 +682,7 @@ index 8101ffa..e4e7074 100644
/* function implementations */
void
@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data)
struct wlr_pointer_button_event *event = data;
struct wlr_keyboard *keyboard;
uint32_t mods;
@ -693,7 +691,7 @@ index 8101ffa..e4e7074 100644
const Button *b;
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -622,7 +631,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) &&
@ -702,9 +700,9 @@ index 8101ffa..e4e7074 100644
b->func(&b->arg);
return;
}
@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data)
@@ -632,15 +641,36 @@ 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|")) {
@ -730,7 +728,18 @@ index 8101ffa..e4e7074 100644
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)
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)
wlr_output_layout_remove(output_layout, m->wlr_output);
wlr_scene_output_destroy(m->scene_output);
@ -738,7 +747,7 @@ index 8101ffa..e4e7074 100644
closemon(m);
wlr_scene_node_destroy(&m->fullscreen_bg->node);
free(m);
@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data)
@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
wl_list_insert(&mons, &m->link);
printstatus();
@ -746,7 +755,7 @@ index 8101ffa..e4e7074 100644
/* The xdg-protocol specifies:
*
@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data)
@@ -1263,6 +1295,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);
@ -757,7 +766,7 @@ index 8101ffa..e4e7074 100644
#ifdef XWAYLAND
if (c->type != XDGShell) {
wl_list_remove(&c->activate.link);
@@ -1866,7 +1902,8 @@ void
@@ -1809,7 +1845,8 @@ void
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
double dx_unaccel, double dy_unaccel)
{
@ -767,7 +776,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
@@ -1863,18 +1900,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 +840,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)
@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
return;
@ -841,7 +850,7 @@ index 8101ffa..e4e7074 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, "all-scroll");
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
- break;
- case CurResize:
- /* Doesn't work for X11 output - the next absolute motion event
@ -890,5 +899,5 @@ index 8101ffa..e4e7074 100644
}
--
2.52.0
2.45.3

View File

@ -1,894 +0,0 @@
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 were 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 its 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

View File

@ -1,913 +0,0 @@
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 were 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 its 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

View File

@ -1,22 +0,0 @@
### 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/)

View File

@ -1,162 +0,0 @@
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

View File

@ -1,161 +0,0 @@
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

View File

@ -4,8 +4,7 @@ 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
- [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)
- [2024-07-26](https://codeberg.org/dwl/dwl-patches/raw/branch/main/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)

View File

@ -1,17 +1,17 @@
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
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
---
dwl.c | 27 +++++++++++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/dwl.c b/dwl.c
index 44f3ad9..05070ef 100644
index 5bf995e..820f4af 100644
--- a/dwl.c
+++ b/dwl.c
@@ -74,7 +74,7 @@
@@ -73,7 +73,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 44f3ad9..05070ef 100644
#define LENGTH(X) (sizeof X / sizeof X[0])
#define END(A) ((A) + LENGTH(A))
#define TAGMASK ((1u << TAGCOUNT) - 1)
@@ -137,7 +137,7 @@ typedef struct {
@@ -139,7 +139,7 @@ typedef struct {
#endif
unsigned int bw;
uint32_t tags;
@ -29,15 +29,15 @@ index 44f3ad9..05070ef 100644
uint32_t resize; /* configure serial of a pending resize */
} Client;
@@ -323,6 +323,7 @@ static void setcursor(struct wl_listener *listener, void *data);
@@ -326,6 +326,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);
static void setmon(Client *c, Monitor *m, uint32_t newtags);
@@ -335,6 +336,7 @@ static void tag(const Arg *arg);
@@ -339,6 +340,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 44f3ad9..05070ef 100644
static void togglefullscreen(const Arg *arg);
static void toggletag(const Arg *arg);
static void toggleview(const Arg *arg);
@@ -2368,6 +2370,17 @@ setfullscreen(Client *c, int fullscreen)
printstatus();
@@ -2351,6 +2353,17 @@ setgamma(struct wl_listener *listener, void *data)
wlr_output_schedule_frame(m->wlr_output);
}
+void
@ -63,7 +63,7 @@ index 44f3ad9..05070ef 100644
void
setlayout(const Arg *arg)
{
@@ -2760,6 +2773,16 @@ togglefullscreen(const Arg *arg)
@@ -2738,6 +2751,16 @@ togglefullscreen(const Arg *arg)
setfullscreen(sel, !sel->isfullscreen);
}
@ -81,5 +81,5 @@ index 44f3ad9..05070ef 100644
toggletag(const Arg *arg)
{
--
2.53.0
2.45.2

View File

@ -1,11 +1,8 @@
### 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.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)
- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/togglekblayoutandoptions/togglekblayoutandoptions.patch)
### Authors
- [LaKato](https://codeberg.org/LaKato)

View File

@ -1,160 +0,0 @@
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