diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md index d187f5f..7e679c4 100644 --- a/patches/btrtile/README.md +++ b/patches/btrtile/README.md @@ -96,8 +96,10 @@ If mouse resizing feels sluggish, you can try compiling dwl with more aggressive ### Download - [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev) -- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) -- [0.7 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gaps.patch) +- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8.patch) +- [0.8 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8-gaps.patch) +- [wlroots-next](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch) +- [wlroots-next WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch) ### Authors - [julmajustus](https://codeberg.org/julmajustus) diff --git a/patches/btrtile/btrtile-v0.7-gaps.patch b/patches/btrtile/btrtile-v0.8-gaps.patch similarity index 89% rename from patches/btrtile/btrtile-v0.7-gaps.patch rename to patches/btrtile/btrtile-v0.8-gaps.patch index dc4e9cf..17cc5ca 100644 --- a/patches/btrtile/btrtile-v0.7-gaps.patch +++ b/patches/btrtile/btrtile-v0.8-gaps.patch @@ -1,30 +1,32 @@ -From 858ef20d36c2d5e6a23a69b3b5909a80fab05f97 Mon Sep 17 00:00:00 2001 +From f5d7fe287bd4405879a92d649243d3f646652617 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Thu, 13 Feb 2025 23:25:20 +0200 -Subject: [PATCH] btrtile-gaps with multi-tag support +Date: Thu, 19 Mar 2026 00:08:10 +0200 +Subject: [PATCH] Refactor btrtile-gaps to v0.8 --- - btrtile.c | 582 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 584 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 ++ - dwl.c | 152 +++++++++++--- - 3 files changed, 717 insertions(+), 29 deletions(-) + dwl.c | 148 ++++++++++--- + 3 files changed, 717 insertions(+), 27 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 -index 0000000..650cab5 +index 0000000..afe3c8f --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,582 @@ +@@ -0,0 +1,584 @@ +/* ************************************************************************** */ -+/* */ -+/* ::: :::::::: */ -+/* btrtile.c :+: :+: :+: */ -+/* +:+ +:+ +:+ */ -+/* By: jmakkone +#+ +:+ +#+ */ -+/* +#+#+#+#+#+ +#+ */ -+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2025/02/13 23:25:03 by jmakkone ### ########.fr */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/19 00:07:19 by julmajustus : : : : : : */ +/* */ +/* ************************************************************************** */ + @@ -74,7 +76,7 @@ index 0000000..650cab5 + if (!node) + return; + -+ if (is_root && e) { ++ if (is_root && e) { + area.x += gappx; + area.y += gappx; + area.width -= 2 * gappx; @@ -127,7 +129,7 @@ index 0000000..650cab5 + right_area.width = area.width - mid; + right_area.height = area.height; + -+ if (e) { ++ if (e) { + left_area.width -= gappx / 2; + right_area.x += gappx / 2; + right_area.width -= gappx / 2; @@ -145,7 +147,7 @@ index 0000000..650cab5 + right_area.width = area.width; + right_area.height= area.height - mid; + -+ if (e) { ++ if (e) { + left_area.height -= gappx / 2; + right_area.y += gappx / 2; + right_area.height -= gappx / 2; @@ -599,21 +601,21 @@ index 0000000..650cab5 + return closest; +} diff --git a/config.def.h b/config.def.h -index 22d2171..92f3ad6 100644 +index 8a6eda0..bc04e3f 100644 --- a/config.def.h +++ b/config.def.h @@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); static const float urgentcolor[] = COLOR(0xff0000ff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ - static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ -+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */ + 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) -@@ -31,6 +34,7 @@ static const Rule rules[] = { +@@ -30,6 +33,7 @@ static const Rule rules[] = { /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ @@ -621,23 +623,23 @@ index 22d2171..92f3ad6 100644 { "[]=", tile }, { "><>", NULL }, /* no layout function means floating behavior */ { "[M]", monocle }, -@@ -148,6 +152,14 @@ static const Key keys[] = { - { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, - TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), - TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), - TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), diff --git a/dwl.c b/dwl.c -index a2711f6..e49a061 100644 +index 44f3ad9..f74d9a1 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -648,15 +650,15 @@ index a2711f6..e49a061 100644 #include #include #include -@@ -103,6 +104,7 @@ typedef struct { +@@ -100,6 +101,7 @@ typedef struct { const Arg arg; } Button; +typedef struct LayoutNode LayoutNode; typedef struct Monitor Monitor; typedef struct { - /* Must keep these three elements in this order */ -@@ -139,8 +141,9 @@ typedef struct { + /* Must keep this field first */ +@@ -137,8 +139,9 @@ typedef struct { #endif unsigned int bw; uint32_t tags; @@ -667,7 +669,7 @@ index a2711f6..e49a061 100644 } Client; typedef struct { -@@ -208,6 +211,7 @@ struct Monitor { +@@ -205,6 +208,7 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; @@ -675,7 +677,7 @@ index a2711f6..e49a061 100644 }; typedef struct { -@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -247,6 +251,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); @@ -683,7 +685,7 @@ index a2711f6..e49a061 100644 static void buttonpress(struct wl_listener *listener, void *data); static void chvt(const Arg *arg); static void checkidleinhibitor(struct wlr_surface *exclude); -@@ -333,6 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); +@@ -329,6 +334,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags); static void setpsel(struct wl_listener *listener, void *data); static void setsel(struct wl_listener *listener, void *data); static void setup(void); @@ -693,7 +695,7 @@ index a2711f6..e49a061 100644 static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); -@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; +@@ -454,6 +462,7 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" @@ -701,7 +703,7 @@ index a2711f6..e49a061 100644 /* function implementations */ void -@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -624,7 +633,7 @@ buttonpress(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; @@ -710,7 +712,7 @@ index a2711f6..e49a061 100644 const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); -@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -645,7 +654,7 @@ buttonpress(struct wl_listener *listener, void *data) mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && @@ -719,9 +721,9 @@ index a2711f6..e49a061 100644 b->func(&b->arg); return; } -@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -655,6 +664,27 @@ buttonpress(struct wl_listener *listener, void *data) /* If you released any buttons, we exit interactive move/resize mode. */ - /* TODO should reset to the pointer focus's current setcursor */ + /* TODO: should reset to the pointer focus's current setcursor */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + c = grabc; + if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { @@ -747,18 +749,7 @@ index a2711f6..e49a061 100644 wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); cursor_mode = CurNormal; /* Drop the window off on its new monitor */ - selmon = xytomon(cursor->x, cursor->y); - setmon(grabc, selmon, 0); -+ grabc = NULL; - return; -- } else { -- cursor_mode = CurNormal; - } -+ cursor_mode = CurNormal; - break; - } - /* If the event wasn't handled by the compositor, notify the client with -@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) +@@ -746,6 +776,7 @@ cleanupmon(struct wl_listener *listener, void *data) wlr_output_layout_remove(output_layout, m->wlr_output); wlr_scene_output_destroy(m->scene_output); @@ -766,7 +757,7 @@ index a2711f6..e49a061 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -1090,6 +1121,7 @@ createmon(struct wl_listener *listener, void *data) wl_list_insert(&mons, &m->link); printstatus(); @@ -774,7 +765,7 @@ index a2711f6..e49a061 100644 /* The xdg-protocol specifies: * -@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -1332,6 +1364,10 @@ destroynotify(struct wl_listener *listener, void *data) wl_list_remove(&c->destroy.link); wl_list_remove(&c->set_title.link); wl_list_remove(&c->fullscreen.link); @@ -785,7 +776,7 @@ index a2711f6..e49a061 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1845,8 @@ void +@@ -1862,7 +1898,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { @@ -795,7 +786,7 @@ index a2711f6..e49a061 100644 Client *c = NULL, *w = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; -@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d +@@ -1916,18 +1953,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); @@ -859,7 +850,7 @@ index a2711f6..e49a061 100644 /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ -@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) +@@ -1961,22 +2036,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -869,7 +860,7 @@ index a2711f6..e49a061 100644 - case CurMove: - grabcx = (int)round(cursor->x) - grabc->geom.x; - grabcy = (int)round(cursor->y) - grabc->geom.y; -- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); - break; - case CurResize: - /* Doesn't work for X11 output - the next absolute motion event @@ -918,5 +909,5 @@ index a2711f6..e49a061 100644 } -- -2.45.3 +2.52.0 diff --git a/patches/btrtile/btrtile-v0.8.patch b/patches/btrtile/btrtile-v0.8.patch new file mode 100644 index 0000000..f43312b --- /dev/null +++ b/patches/btrtile/btrtile-v0.8.patch @@ -0,0 +1,894 @@ +From 1b75163aac16942a715c6fc0aaf5d508e778dd6a Mon Sep 17 00:00:00 2001 +From: julmajustus +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 !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 8a6eda0..bc04e3f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +33,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index 44f3ad9..f74d9a1 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -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 + diff --git a/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch new file mode 100644 index 0000000..3177c56 --- /dev/null +++ b/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch @@ -0,0 +1,913 @@ +From a9329abe48083a4c5b64eee726ceefdb4bf0bb9f Mon Sep 17 00:00:00 2001 +From: julmajustus +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 !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/19 22:59:12 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static LayoutNode *create_client_node(Client *c); ++static LayoutNode *create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node(LayoutNode *node); ++static void destroy_tree(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_suitable_split(LayoutNode *start, unsigned int need_vert); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *focused_client, Client *new_client); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static Client *xytoclient(double x, double y); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ Client *c; ++ float ratio; ++ unsigned int left_count, right_count, mid, e = m->gaps; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ if (is_root && e) { ++ area.x += gappx; ++ area.y += gappx; ++ area.width -= 2 * gappx; ++ area.height -= 2 * gappx; ++ } ++ ++ /* If this node is a client node, check if it is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) ++ return; ++ resize(c, area, 0); ++ c->old_geom = area; ++ return; ++ } ++ ++ /* For a split node, we see how many visible children are on each side: */ ++ left_count = visible_count(node->left, m); ++ right_count = visible_count(node->right, m); ++ ++ if (left_count == 0 && right_count == 0) { ++ return; ++ } else if (left_count > 0 && right_count == 0) { ++ apply_layout(m, node->left, area, 0); ++ return; ++ } else if (left_count == 0 && right_count > 0) { ++ apply_layout(m, node->right, area, 0); ++ return; ++ } ++ ++ /* If we’re here, we have visible clients in both subtrees. */ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ memset(&left_area, 0, sizeof(left_area)); ++ memset(&right_area, 0, sizeof(right_area)); ++ ++ if (node->is_split_vertically) { ++ mid = (unsigned int)(area.width * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = mid; ++ left_area.height = area.height; ++ ++ right_area.x = area.x + mid; ++ right_area.y = area.y; ++ right_area.width = area.width - mid; ++ right_area.height = area.height; ++ ++ if (e) { ++ left_area.width -= gappx / 2; ++ right_area.x += gappx / 2; ++ right_area.width -= gappx / 2; ++ } ++ } else { ++ /* horizontal split */ ++ mid = (unsigned int)(area.height * ratio); ++ left_area.x = area.x; ++ left_area.y = area.y; ++ left_area.width = area.width; ++ left_area.height = mid; ++ ++ right_area.x = area.x; ++ right_area.y = area.y + mid; ++ right_area.width = area.width; ++ right_area.height= area.height - mid; ++ ++ if (e) { ++ left_area.height -= gappx / 2; ++ right_area.y += gappx / 2; ++ right_area.height -= gappx / 2; ++ } ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *focused = NULL; ++ int n = 0; ++ LayoutNode *found; ++ struct wlr_box full_area; ++ ++ if (!m || !m->root) ++ return; ++ ++ /* Remove non tiled clients from tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon == m && !c->isfloating && !c->isfullscreen) { ++ } else { ++ remove_client(m, c); ++ } ++ } ++ ++ /* If no client is found under cursor, fallback to focustop(m) */ ++ if (!(focused = xytoclient(cursor->x, cursor->y))) ++ focused = focustop(m); ++ ++ /* Insert visible clients that are not part of the tree. */ ++ wl_list_for_each(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) { ++ found = find_client_node(m->root, c); ++ if (!found) { ++ insert_client(m, focused, c); ++ } ++ n++; ++ } ++ } ++ ++ if (n == 0) ++ return; ++ ++ full_area = m->w; ++ apply_layout(m, m->root, full_area, 1); ++} ++ ++LayoutNode * ++create_client_node(Client *c) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 1; ++ node->split_ratio = 0.5f; ++ node->client = c; ++ return node; ++} ++ ++LayoutNode * ++create_split_node(unsigned int is_split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_ratio = 0.5f; ++ node->is_split_vertically = is_split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++void ++destroy_node(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node(node->left); ++ destroy_node(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree(Monitor *m) ++{ ++ if (!m || !m->root) ++ return; ++ destroy_node(m->root); ++ m->root = NULL; ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node || !c) ++ return NULL; ++ if (node->is_client_node) { ++ return (node->client == c) ? node : NULL; ++ } ++ res = find_client_node(node->left, c); ++ return res ? res : find_client_node(node->right, c); ++} ++ ++LayoutNode * ++find_suitable_split(LayoutNode *start_node, unsigned int need_vertical) ++{ ++ LayoutNode *n = start_node; ++ /* if we started from a client node, jump to its parent: */ ++ if (n && n->is_client_node) ++ n = n->split_node; ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical && ++ visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0) ++ return n; ++ n = n->split_node; ++ } ++ return NULL; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->root = calloc(1, sizeof(LayoutNode)); ++ if (!m->root) ++ m->root = NULL; ++} ++ ++void ++insert_client(Monitor *m, Client *focused_client, Client *new_client) ++{ ++ Client *old_client; ++ LayoutNode **root = &m->root, *old_root, ++ *focused_node, *new_client_node, *old_client_node; ++ unsigned int wider, mid_x, mid_y; ++ ++ /* If no root , new client becomes the root. */ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ return; ++ } ++ ++ /* Find the focused_client node, ++ * if not found split the root. */ ++ focused_node = focused_client ? ++ find_client_node(*root, focused_client) : NULL; ++ if (!focused_node) { ++ old_root = *root; ++ new_client_node = create_client_node(new_client); ++ *root = create_split_node(1, old_root, new_client_node); ++ return; ++ } ++ ++ /* Turn focused node from a client node into a split node, ++ * and attach old_client + new_client. */ ++ old_client = focused_node->client; ++ old_client_node = create_client_node(old_client); ++ new_client_node = create_client_node(new_client); ++ ++ /* Decide split direction. */ ++ wider = (focused_client->geom.width >= focused_client->geom.height); ++ focused_node->is_client_node = 0; ++ focused_node->client = NULL; ++ focused_node->is_split_vertically = (wider ? 1 : 0); ++ ++ /* Pick new_client side depending on the cursor position. */ ++ mid_x = focused_client->geom.x + focused_client->geom.width / 2; ++ mid_y = focused_client->geom.y + focused_client->geom.height / 2; ++ ++ if (wider) { ++ /* vertical split => left vs right */ ++ if (cursor->x <= mid_x) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } else { ++ /* horizontal split => top vs bottom */ ++ if (cursor->y <= mid_y) { ++ focused_node->left = new_client_node; ++ focused_node->right = old_client_node; ++ } else { ++ focused_node->left = old_client_node; ++ focused_node->right = new_client_node; ++ } ++ } ++ old_client_node->split_node = focused_node; ++ new_client_node->split_node = focused_node; ++ focused_node->split_ratio = 0.5f; ++} ++ ++LayoutNode * ++remove_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *tmp; ++ if (!node) ++ return NULL; ++ if (node->is_client_node) { ++ /* If this client_node is the client we're removing, ++ * return NULL to remove it */ ++ if (node->client == c) { ++ free(node); ++ return NULL; ++ } ++ return node; ++ } ++ ++ node->left = remove_client_node(node->left, c); ++ node->right = remove_client_node(node->right, c); ++ ++ /* If one of the client node is NULL after removal and the other is not, ++ * we "lift" the other client node up to replace this split node. */ ++ if (!node->left && node->right) { ++ tmp = node->right; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ if (!node->right && node->left) { ++ tmp = node->left; ++ ++ /* Save pointer to split node */ ++ if (tmp) ++ tmp->split_node = node->split_node; ++ ++ free(node); ++ return tmp; ++ } ++ ++ /* If both children exist or both are NULL (empty tree), ++ * return node as is. */ ++ return node; ++} ++ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (!m->root || !c) ++ return; ++ m->root = remove_client_node(m->root, c); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 1); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *split_node; ++ float new_ratio; ++ ++ if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->root, sel); ++ if (!client_node) ++ return; ++ ++ split_node = find_suitable_split(client_node, 0); ++ if (!split_node) ++ return; ++ ++ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) ++ new_ratio = 0.05f; ++ if (new_ratio > 0.95f) ++ new_ratio = 0.95f; ++ split_node->split_ratio = new_ratio; ++ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void swapclients(const Arg *arg) { ++ Client *c, *tmp, *target = NULL, *sel = focustop(selmon); ++ LayoutNode *sel_node, *target_node; ++ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y, ++ cand_center_x, cand_center_y; ++ ++ if (!sel || sel->isfullscreen || ++ !selmon->root || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ ++ /* Get the center coordinates of the selected client */ ++ sel_center_x = sel->geom.x + sel->geom.width / 2; ++ sel_center_y = sel->geom.y + sel->geom.height / 2; ++ ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel) ++ continue; ++ ++ /* Get the center of candidate client */ ++ cand_center_x = c->geom.x + c->geom.width / 2; ++ cand_center_y = c->geom.y + c->geom.height / 2; ++ ++ /* Check that the candidate lies in the requested direction. */ ++ switch (arg->ui) { ++ case 0: ++ if (cand_center_x >= sel_center_x) ++ continue; ++ break; ++ case 1: ++ if (cand_center_x <= sel_center_x) ++ continue; ++ break; ++ case 2: ++ if (cand_center_y >= sel_center_y) ++ continue; ++ break; ++ case 3: ++ if (cand_center_y <= sel_center_y) ++ continue; ++ break; ++ default: ++ continue; ++ } ++ ++ /* Get distance between the centers */ ++ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y); ++ if (dist < closest_dist) { ++ closest_dist = dist; ++ target = c; ++ } ++ } ++ ++ /* If target is found, swap the two clients’ positions in the layout tree */ ++ if (target) { ++ sel_node = find_client_node(selmon->root, sel); ++ target_node = find_client_node(selmon->root, target); ++ if (sel_node && target_node) { ++ tmp = sel_node->client; ++ sel_node->client = target_node->client; ++ target_node->client = tmp; ++ arrange(selmon); ++ } ++ } ++} ++ ++unsigned int ++visible_count(LayoutNode *node, Monitor *m) ++{ ++ Client *c; ++ ++ if (!node) ++ return 0; ++ /* Check if this client is visible. */ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) ++ return 1; ++ return 0; ++ } ++ /* Else it’s a split node. */ ++ return visible_count(node->left, m) + visible_count(node->right, m); ++} ++ ++Client * ++xytoclient(double x, double y) { ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen && ++ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && ++ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ ++ return c; ++ } ++ } ++ ++ /* If no client was found at cursor position fallback to closest. */ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, selmon) && !c->isfloating && !c->isfullscreen) { ++ dx = 0, dy = 0; ++ ++ if (x < c->geom.x) ++ dx = c->geom.x - x; ++ else if (x > (c->geom.x + c->geom.width)) ++ dx = x - (c->geom.x + c->geom.width); ++ ++ if (y < c->geom.y) ++ dy = c->geom.y - y; ++ else if (y > (c->geom.y + c->geom.height)) ++ dy = y - (c->geom.y + c->geom.height); ++ ++ dist = sqrt(dx * dx + dy * dy); ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 8a6eda0..bc04e3f 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +33,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index 8101ffa..e4e7074 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -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 + diff --git a/patches/btrtile/btrtile-v0.7.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch similarity index 89% rename from patches/btrtile/btrtile-v0.7.patch rename to patches/btrtile/btrtile-wlroots-next-d41ecb7.patch index 0396500..87f4e99 100644 --- a/patches/btrtile/btrtile-v0.7.patch +++ b/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch @@ -1,30 +1,32 @@ -From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001 +From 912701a41b19c013a9adcb176a7b6503aa1cf9c1 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Thu, 13 Feb 2025 23:23:40 +0200 -Subject: [PATCH] btrtile with multi-tag support +Date: Thu, 19 Mar 2026 22:56:45 +0200 +Subject: [PATCH] Refactor btrtile to wlroots-next --- - btrtile.c | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 565 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 ++ - dwl.c | 152 +++++++++++--- - 3 files changed, 698 insertions(+), 29 deletions(-) + dwl.c | 148 +++++++++++--- + 3 files changed, 698 insertions(+), 27 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 -index 0000000..03f4680 +index 0000000..d706c3b --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,563 @@ +@@ -0,0 +1,565 @@ +/* ************************************************************************** */ -+/* */ -+/* ::: :::::::: */ -+/* btrtile.c :+: :+: :+: */ -+/* +:+ +:+ +:+ */ -+/* By: jmakkone +#+ +:+ +#+ */ -+/* +#+#+#+#+#+ +#+ */ -+/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2025/02/13 23:22:33 by jmakkone ### ########.fr */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.c @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/03/18 20:26:17 by julmajustus : : : : : : */ +/* */ +/* ************************************************************************** */ + @@ -580,21 +582,21 @@ index 0000000..03f4680 + return closest; +} diff --git a/config.def.h b/config.def.h -index 22d2171..92f3ad6 100644 +index 8a6eda0..bc04e3f 100644 --- a/config.def.h +++ b/config.def.h @@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); static const float urgentcolor[] = COLOR(0xff0000ff); /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ - static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ -+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensitivity. */ + 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) -@@ -31,6 +34,7 @@ static const Rule rules[] = { +@@ -30,6 +33,7 @@ static const Rule rules[] = { /* layout(s) */ static const Layout layouts[] = { /* symbol arrange function */ @@ -602,23 +604,23 @@ index 22d2171..92f3ad6 100644 { "[]=", tile }, { "><>", NULL }, /* no layout function means floating behavior */ { "[M]", monocle }, -@@ -148,6 +152,14 @@ static const Key keys[] = { - { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, - { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, -+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, -+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, - TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), - TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), - TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +@@ -144,6 +148,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), diff --git a/dwl.c b/dwl.c -index a2711f6..e49a061 100644 +index 8101ffa..e4e7074 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -629,15 +631,15 @@ index a2711f6..e49a061 100644 #include #include #include -@@ -103,6 +104,7 @@ typedef struct { +@@ -104,6 +105,7 @@ typedef struct { const Arg arg; } Button; +typedef struct LayoutNode LayoutNode; typedef struct Monitor Monitor; typedef struct { - /* Must keep these three elements in this order */ -@@ -139,8 +141,9 @@ typedef struct { + /* Must keep this field first */ +@@ -141,8 +143,9 @@ typedef struct { #endif unsigned int bw; uint32_t tags; @@ -648,7 +650,7 @@ index a2711f6..e49a061 100644 } Client; typedef struct { -@@ -208,6 +211,7 @@ struct Monitor { +@@ -209,6 +212,7 @@ struct Monitor { int nmaster; char ltsymbol[16]; int asleep; @@ -656,7 +658,7 @@ index a2711f6..e49a061 100644 }; typedef struct { -@@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, +@@ -251,6 +255,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list, struct wlr_box *usable_area, int exclusive); static void arrangelayers(Monitor *m); static void axisnotify(struct wl_listener *listener, void *data); @@ -674,7 +676,7 @@ index a2711f6..e49a061 100644 static void spawn(const Arg *arg); static void startdrag(struct wl_listener *listener, void *data); static void tag(const Arg *arg); -@@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast]; +@@ -458,6 +466,7 @@ static struct wlr_xwayland *xwayland; /* attempt to encapsulate suck into one file */ #include "client.h" @@ -682,7 +684,7 @@ index a2711f6..e49a061 100644 /* function implementations */ void -@@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -628,7 +637,7 @@ buttonpress(struct wl_listener *listener, void *data) struct wlr_pointer_button_event *event = data; struct wlr_keyboard *keyboard; uint32_t mods; @@ -691,7 +693,7 @@ index a2711f6..e49a061 100644 const Button *b; wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); -@@ -622,7 +631,7 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -649,7 +658,7 @@ buttonpress(struct wl_listener *listener, void *data) mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0; for (b = buttons; b < END(buttons); b++) { if (CLEANMASK(mods) == CLEANMASK(b->mod) && @@ -700,9 +702,9 @@ index a2711f6..e49a061 100644 b->func(&b->arg); return; } -@@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data) +@@ -659,6 +668,27 @@ buttonpress(struct wl_listener *listener, void *data) /* If you released any buttons, we exit interactive move/resize mode. */ - /* TODO should reset to the pointer focus's current setcursor */ + /* TODO: should reset to the pointer focus's current setcursor */ if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { + c = grabc; + if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { @@ -728,18 +730,7 @@ index a2711f6..e49a061 100644 wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); cursor_mode = CurNormal; /* Drop the window off on its new monitor */ - selmon = xytomon(cursor->x, cursor->y); - setmon(grabc, selmon, 0); -+ grabc = NULL; - return; -- } else { -- cursor_mode = CurNormal; - } -+ cursor_mode = CurNormal; - break; - } - /* If the event wasn't handled by the compositor, notify the client with -@@ -720,6 +750,7 @@ cleanupmon(struct wl_listener *listener, void *data) +@@ -750,6 +780,7 @@ cleanupmon(struct wl_listener *listener, void *data) wlr_output_layout_remove(output_layout, m->wlr_output); wlr_scene_output_destroy(m->scene_output); @@ -747,7 +738,7 @@ index a2711f6..e49a061 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -1094,6 +1125,7 @@ createmon(struct wl_listener *listener, void *data) wl_list_insert(&mons, &m->link); printstatus(); @@ -755,7 +746,7 @@ index a2711f6..e49a061 100644 /* The xdg-protocol specifies: * -@@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -1336,6 +1368,10 @@ destroynotify(struct wl_listener *listener, void *data) wl_list_remove(&c->destroy.link); wl_list_remove(&c->set_title.link); wl_list_remove(&c->fullscreen.link); @@ -766,7 +757,7 @@ index a2711f6..e49a061 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1845,8 @@ void +@@ -1866,7 +1902,8 @@ void motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, double dx_unaccel, double dy_unaccel) { @@ -776,7 +767,7 @@ index a2711f6..e49a061 100644 Client *c = NULL, *w = NULL; LayerSurface *l = NULL; struct wlr_surface *surface = NULL; -@@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d +@@ -1920,18 +1957,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d /* Update drag icon's position */ wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); @@ -840,7 +831,7 @@ index a2711f6..e49a061 100644 /* If there's no client surface under the cursor, set the cursor image to a * default. This is what makes the cursor image appear when you move it * off of a client or over its border. */ -@@ -1908,22 +1983,41 @@ moveresize(const Arg *arg) +@@ -1965,22 +2040,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -850,7 +841,7 @@ index a2711f6..e49a061 100644 - case CurMove: - grabcx = (int)round(cursor->x) - grabc->geom.x; - grabcy = (int)round(cursor->y) - grabc->geom.y; -- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll"); - break; - case CurResize: - /* Doesn't work for X11 output - the next absolute motion event @@ -899,5 +890,5 @@ index a2711f6..e49a061 100644 } -- -2.45.3 +2.52.0