From 0d41b7573730b9ccbc9376027daf2951a7aa0ff4 Mon Sep 17 00:00:00 2001 From: julmajustus Date: Sun, 20 Sep 2026 03:18:11 +0300 Subject: [PATCH] btrtile: update and minor changes - Updated to current main - Added more user toggles to control the behaviour of btrtile into config.h - Simplified the btrtile gapps logic - Simplified the changes to dwl.c - Added two keybind functions to swap and toggle splits - Minor code cleanups - Added functionality to respect the client size hints --- patches/btrtile/README.md | 89 +- patches/btrtile/btrtile-d16036e.patch | 1243 ++++++++++++++++ patches/btrtile/btrtile-gaps-d16036e.patch | 1244 +++++++++++++++++ .../btrtile-wlroots-next-d41ecb7-gaps.patch | 922 ------------ .../btrtile-wlroots-next-d41ecb7.patch | 900 ------------ 5 files changed, 2521 insertions(+), 1877 deletions(-) create mode 100644 patches/btrtile/btrtile-d16036e.patch create mode 100644 patches/btrtile/btrtile-gaps-d16036e.patch delete mode 100644 patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch delete mode 100644 patches/btrtile/btrtile-wlroots-next-d41ecb7.patch diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md index fb381ee..cc91ef7 100644 --- a/patches/btrtile/README.md +++ b/patches/btrtile/README.md @@ -1,6 +1,6 @@ ### Description -# btrtile — A Focus-Driven Tiling Layout +# btrtile - A Focus-Driven Tiling Layout It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized. @@ -14,39 +14,35 @@ While dwl’s patches folder is full of different layouts, I couldn't find suita --- -# Features - -- **Combined Tiling and Management** - Combines tiling layout and management of clients under one patchset. - -- **Focus-Driven Splits** - When you add a new client, btrtile checks where your pointer is relative to the focused client’s geometry. - - If the pointer is on the left half (for a horizontally large client), the new client spawns on the left side, and vice versa. - - By default, new splits are 50/50. - -- **Adaptive Splitting** - - If the area to be split is wider than its height, btrtile does a vertical split. - - Otherwise, it does a horizontal split. - -- **Keyboard and Mouse Driven** - - Supports keyboard-based commands for quick ratio adjustments and client swapping. - - Mouse-based resizing and moving are integrated for more intuitive manipulation. - ---- - -# How It Works - -btrtile organizes clients using a binary tree data structure that represents splits either vertically or horizontally. - -When a new client appears: -1. **Focused Client Detection** - btrtile checks your pointer location to find which client (if any) you’re interacting with. -2. **Split Creation** - - If there’s a focused client, btrtile creates a split node around it, placing the new client on the side where your pointer is. - -3. **Ratio Management** - Each split node has a `split_ratio` (defaulting to 0.5). This ratio defines how much space is allocated to each child node. You can adjust this ratio using keyboard or mouse actions. +# How it works + +Clients live in a binary tree, every split has a ratio (50/50 by default). + +When a new client opens, it splits the client you last focused. The pointer decides which side it lands on: left half of the client puts it on the left, and so on. If the area is wider than it is tall the split is vertical, otherwise horizontal. When several clients open at once, each one splits the previous one, so you get a nice spiral instead of a pile in one corner. + +- **Mouse** + Drag a tiled client with `MODKEY` + left button to float it, drop it on another tiled client to place it next to it. + `MODKEY` + right button resizes the divider next to the client you grabbed, it follows the pointer 1:1. +- **Keyboard** + `setratio_h` / `setratio_v` move a divider, `swapclients` swaps a client with its neighbour in a direction, + `togglesplit` flips the direction of the split you're in (and keeps it that way), + `swapsplit` mirrors the split. +**config.h:** +```c +use_active_for_splits = 1; /* 1: split the last focused client, 0: the one under the pointer */ +force_split = 0; /* 0: new client on the pointer's side, 1: always left/top, 2: always right/bottom */ +preserve_split = 0; /* 1: splits keep their direction, 0: direction follows the shape of the area */ +split_width_multiplier = 1.0f; /* split side by side when width >= height * this */ +resize_interval_ms = 16; /* limits mouse resize updates, 16ms is ~60 per second */ +``` + +`force_split = 2` gives you a predictable spiral if you don't want the pointer to matter. +`split_width_multiplier` is handy on wide monitors, where windows tend to stay wider than tall for a long time. + +Smoother mouse resizing costs some cpu, so tune `resize_interval_ms` to your refresh rate. +If resizing feels sluggish, try compiling dwl with -O2/-O3. + --- # What It Doesn’t Handle @@ -56,23 +52,6 @@ When a new client appears: --- -# Configuring btrtile - -btrtile adds couple variables to config.h to fine tune the mouse resizing of tiled clients. - -1. **resize_factor** - - A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensitivity. - Defaults to 0.0002f. - -2. **resize_interval_ms** - - A time based resize call limiter. Depends on framerate and screen refresh rate. - Defaults to 16ms. (~60 resize updates per second) - -Fine tune these values to find the best values for your setup, smoother resizing can significally increase cpu overhead. -If mouse resizing feels sluggish, you can try compiling dwl with more aggressive optimization flags like -O2/-O3. - ---- - # Patch recommendations 1. **Patches that I use with my btrtile** @@ -95,11 +74,11 @@ 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) +- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-0.9) +- [0.8](./btrtile-v0.8.patch) +- [0.8 WITH gaps](./btrtile-v0.8-gaps.patch) +- [main-d16036e](./btrtile-d16036e.patch) +- [main-d16036e WITH gaps](./btrtile-gaps-d16036e.patch) ### Authors - [julmajustus](https://codeberg.org/julmajustus) diff --git a/patches/btrtile/btrtile-d16036e.patch b/patches/btrtile/btrtile-d16036e.patch new file mode 100644 index 0000000..45cc1e8 --- /dev/null +++ b/patches/btrtile/btrtile-d16036e.patch @@ -0,0 +1,1243 @@ +From fd672091bce3ab7db7ab89725db94df8a7bb5a6d Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sun, 20 Sep 2026 01:30:50 +0300 +Subject: [PATCH] btrtile: refactor and few features and code simplification + + - Refactor to current upstream main + - Added more user toggles to control the behaviour of btrtile into + config.h + - Simplified the btrtile gapps logic + - Simplified the changes to dwl.c + - Added two keybind functions to swap and toggle splits + - Minor code cleanups +--- + Makefile | 3 +- + btrtile.h | 884 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 20 +- + dwl.c | 149 +++++++-- + 4 files changed, 1019 insertions(+), 37 deletions(-) + create mode 100644 btrtile.h + +diff --git a/Makefile b/Makefile +index 8304245..063ba0a 100644 +--- a/Makefile ++++ b/Makefile +@@ -20,7 +20,8 @@ all: dwl + dwl: dwl.c client.h config.h ime.h util.h config.mk cursor-shape-v1-protocol.h \ + ext-image-copy-capture-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ btrtile.h + $(CC) dwl.c $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +diff --git a/btrtile.h b/btrtile.h +new file mode 100644 +index 0000000..9f91be2 +--- /dev/null ++++ b/btrtile.h +@@ -0,0 +1,884 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.h @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/09/20 01:07:46 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ unsigned int keep_dir; ++ float split_ratio; ++ struct wlr_box box; ++ struct LayoutNode *left; /* left or top */ ++ struct LayoutNode *right; /* right or bottom */ ++ struct LayoutNode *split_node; /* parent */ ++ Client *client; ++} LayoutNode; ++ ++static void apply_boxes(Monitor *m, LayoutNode *node); ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static int clamp_split(int total, int mid, int lmin, int rmin, int g); ++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_suitable_split(Monitor *m, LayoutNode *start, ++ unsigned int need_vertical, int focused_on_left); ++static int half_gap(Monitor *m); ++static int in_tree(Monitor *m, Client *c); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *target, Client *new_client); ++static void layout_boxes(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void layout_node(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void node_min_size(Monitor *m, LayoutNode *node, int *w, int *h); ++static int outer_gap(Monitor *m); ++static void remove_client(Monitor *m, Client *c); ++static void remove_leaf(Monitor *m, Client *c); ++static void resize_split(unsigned int need_vertical, double delta, int in_pixels); ++static void resize_split_px(unsigned int need_vertical, double pixels); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static Client *split_target(Monitor *m); ++static void swapclients(const Arg *arg); ++static void swapsplit(const Arg *arg); ++static void togglesplit(const Arg *arg); ++static LayoutNode *tree_root(LayoutNode *n); ++static void unlink_client(Client *c); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static LayoutNode *visible_parent(Monitor *m, LayoutNode *n); ++static int wants_vertical(int width, int height); ++static Client *xytoclient(double x, double y); ++ ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++static int dir_changed; ++ ++/* give every visible client the box of its leaf */ ++void ++apply_boxes(Monitor *m, LayoutNode *node) ++{ ++ Client *c; ++ ++ if (!node) { ++ return; ++ } ++ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ resize(c, node->box, 0); ++ } ++ ++ return; ++ } ++ ++ apply_boxes(m, node->left); ++ apply_boxes(m, node->right); ++} ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ layout_boxes(m, node, area, is_root); ++ apply_boxes(m, node); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *target; ++ int n = 0; ++ ++ if (!m) { ++ return; ++ } ++ ++ /* Remove floating and moved clients */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m || c->isfloating) { ++ remove_client(m, c); ++ } ++ } ++ ++ layout_boxes(m, m->root, m->w, 1); ++ ++ /* Insert visible clients that are not part of the tree */ ++ target = split_target(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (c->node && !in_tree(m, c)) { ++ unlink_client(c); ++ } ++ ++ if (!c->node) { ++ insert_client(m, target, c); ++ layout_boxes(m, m->root, m->w, 1); ++ target = c; ++ } ++ n++; ++ } ++ ++ if (n == 0) { ++ return; ++ } ++ ++ apply_boxes(m, m->root); ++} ++ ++/* Keep both sides of a split at least as large as min hint size */ ++int ++clamp_split(int total, int mid, int lmin, int rmin, int g) ++{ ++ int lo = lmin + g; ++ int hi = total - rmin - g; ++ ++ if (lo <= hi) { ++ return MAX(lo, MIN(mid, hi)); ++ } ++ ++ if (lmin + rmin <= 0) { ++ return mid; ++ } ++ ++ return (int)((double)total * lmin / (lmin + rmin)); ++} ++ ++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; ++ c->node = node; ++ ++ 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) { ++ if (node->client) { ++ node->client->node = NULL; ++ } ++ } ++ else { ++ 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_suitable_split(Monitor *m, LayoutNode *start_node, ++ unsigned int need_vertical, int focused_on_left) ++{ ++ LayoutNode *n = start_node, *child = NULL; ++ ++ if (!m) { ++ return NULL; ++ } ++ ++ if (n && n->is_client_node) { ++ child = n; ++ n = n->split_node; ++ } ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical ++ && visible_count(n->left, m) > 0 ++ && visible_count(n->right, m) > 0) { ++ if ((focused_on_left && n->left == child) || ++ (!focused_on_left && n->right == child)) ++ return n; ++ } ++ child = n; ++ n = n->split_node; ++ } ++ ++ return NULL; ++} ++ ++#ifdef BTRTILE_GAPS ++int ++half_gap(Monitor *m) ++{ ++ return m->gaps ? (int)gappx / 2 : 0; ++} ++#else ++int ++half_gap(Monitor *m) ++{ ++ return 0; ++} ++#endif ++ ++int ++in_tree(Monitor *m, Client *c) ++{ ++ return m && m->root && c && c->node && tree_root(c->node) == m->root; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (m) { ++ m->root = NULL; ++ } ++} ++ ++ ++void ++insert_client(Monitor *m, Client *target, Client *new_client) ++{ ++ LayoutNode *leaf, *tnode, *split, *parent; ++ struct wlr_box b; ++ int vertical, new_first; ++ ++ if (new_client->node) { ++ unlink_client(new_client); ++ } ++ ++ if (!(leaf = create_client_node(new_client))) { ++ return; ++ } ++ ++ /* If no root, new client becomes the root. */ ++ if (!m->root) { ++ m->root = leaf; ++ return; ++ } ++ ++ if (!in_tree(m, target)) { ++ vertical = wants_vertical(m->w.width, m->w.height); ++ if (!(split = create_split_node(vertical, m->root, leaf))) { ++ goto fail; ++ } ++ ++ m->root = split; ++ return; ++ } ++ ++ tnode = target->node; ++ b = tnode->box; ++ vertical = wants_vertical(b.width, b.height); ++ ++ /* Pick the side of the new client */ ++ if (force_split == 1) { ++ new_first = 1; ++ } ++ else if (force_split == 2) { ++ new_first = 0; ++ } ++ else if (vertical) { ++ new_first = cursor->x <= b.x + b.width / 2.0; ++ } ++ else { ++ new_first = cursor->y <= b.y + b.height / 2.0; ++ } ++ ++ parent = tnode->split_node; ++ split = new_first ? create_split_node(vertical, leaf, tnode) ++ : create_split_node(vertical, tnode, leaf); ++ if (!split) { ++ goto fail; ++ } ++ ++ split->split_node = parent; ++ if (!parent) { ++ m->root = split; ++ } ++ else if (parent->left == tnode) { ++ parent->left = split; ++ } ++ else { ++ parent->right = split; ++ } ++ ++ return; ++ ++fail: ++ new_client->node = NULL; ++ free(leaf); ++} ++ ++/* compute the box of every node */ ++void ++layout_boxes(Monitor *m, LayoutNode *node, struct wlr_box area, unsigned int is_root) ++{ ++ int i; ++ ++ for (i = 0; i < 4; i++) { ++ dir_changed = 0; ++ layout_node(m, node, area, is_root); ++ ++ if (!dir_changed) { ++ break; ++ } ++ } ++} ++ ++void ++layout_node(Monitor *m, LayoutNode *node, struct wlr_box area, unsigned int is_root) ++{ ++ unsigned int left_count, right_count; ++ int mid, lw, lh, rw, rh, vertical, og, g = half_gap(m); ++ float ratio; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) { ++ return; ++ } ++ ++ if (is_root) { ++ og = outer_gap(m); ++ area.x += og; ++ area.y += og; ++ area.width -= 2 * og; ++ area.height -= 2 * og; ++ } ++ node->box = area; ++ ++ if (node->is_client_node) { ++ return; ++ } ++ ++ /* For a split node, see how many visible clients 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; ++ } ++ ++ if (right_count == 0) { ++ layout_node(m, node->left, area, 0); ++ return; ++ } ++ ++ if (left_count == 0) { ++ layout_node(m, node->right, area, 0); ++ return; ++ } ++ ++ /* Visible clients on both sides */ ++ if (!preserve_split && !node->keep_dir) { ++ vertical = wants_vertical(area.width, area.height); ++ if ((unsigned int)vertical != node->is_split_vertically) { ++ dir_changed = 1; ++ } ++ node->is_split_vertically = vertical; ++ } ++ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) { ++ ratio = 0.05f; ++ } ++ ++ if (ratio > 0.95f) { ++ ratio = 0.95f; ++ } ++ ++ node_min_size(m, node->left, &lw, &lh); ++ node_min_size(m, node->right, &rw, &rh); ++ left_area = right_area = area; ++ ++ if (node->is_split_vertically) { ++ mid = clamp_split(area.width, (int)(area.width * ratio), lw, rw, g); ++ left_area.width = mid - g; ++ right_area.x = area.x + mid + g; ++ right_area.width = area.width - mid - g; ++ } else { ++ mid = clamp_split(area.height, (int)(area.height * ratio), lh, rh, g); ++ left_area.height = mid - g; ++ right_area.y = area.y + mid + g; ++ right_area.height = area.height - mid - g; ++ } ++ ++ layout_node(m, node->left, left_area, 0); ++ layout_node(m, node->right, right_area, 0); ++} ++ ++/* Smallest size a subtree can be given without violating size hints */ ++void ++node_min_size(Monitor *m, LayoutNode *node, int *w, int *h) ++{ ++ Client *c; ++ struct wlr_box min = {0}, max = {0}; ++ int lw, lh, rw, rh, g; ++ ++ *w = *h = 0; ++ if (!node) { ++ return; ++ } ++ ++ if (node->is_client_node) { ++ c = node->client; ++ ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ return; ++ } ++ ++ client_get_size_hints(c, &max, &min); ++ *w = MAX(min.width, 1) + 2 * (int)c->bw; ++ *h = MAX(min.height, 1) + 2 * (int)c->bw; ++ ++ return; ++ } ++ ++ node_min_size(m, node->left, &lw, &lh); ++ node_min_size(m, node->right, &rw, &rh); ++ if (!lw) { ++ *w = rw; ++ *h = rh; ++ return; ++ } ++ if (!rw) { ++ *w = lw; ++ *h = lh; ++ return; ++ } ++ ++ g = 2 * half_gap(m); ++ if (node->is_split_vertically) { ++ *w = lw + rw + g; ++ *h = MAX(lh, rh); ++ } ++ else { ++ *w = MAX(lw, rw); ++ *h = lh + rh + g; ++ } ++} ++ ++#ifdef BTRTILE_GAPS ++int ++outer_gap(Monitor *m) ++{ ++ return m->gaps ? (int)gappx : 0; ++} ++ ++#else ++int ++outer_gap(Monitor *m) ++{ ++ return 0; ++} ++#endif ++ ++/* Remove c if in the tree */ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (in_tree(m, c)) { ++ remove_leaf(m, c); ++ } ++} ++ ++void ++remove_leaf(Monitor *m, Client *c) ++{ ++ LayoutNode *n = c->node, *p, *sib, *gp; ++ ++ if (!n) { ++ return; ++ } ++ ++ c->node = NULL; ++ ++ if (!(p = n->split_node)) { ++ m->root = NULL; ++ free(n); ++ return; ++ } ++ ++ sib = (p->left == n) ? p->right : p->left; ++ gp = p->split_node; ++ sib->split_node = gp; ++ if (!gp) { ++ m->root = sib; ++ } ++ else if (gp->left == p) { ++ gp->left = sib; ++ } ++ else { ++ gp->right = sib; ++ } ++ ++ free(n); ++ free(p); ++} ++ ++/* In pixel mode delta is a pointer movement, otherwise it is a change of the split ratio. */ ++void ++resize_split(unsigned int need_vertical, double delta, int in_pixels) ++{ ++ Client *sel; ++ LayoutNode *split; ++ double extent, ratio; ++ ++ if (!selmon || !selmon->lt[selmon->sellt]->arrange) { ++ return; ++ } ++ ++ sel = focustop(selmon); ++ if (!in_tree(selmon, sel)) { ++ return; ++ } ++ ++ if (in_pixels && delta == 0.0) { ++ return; ++ } ++ ++ split = find_suitable_split(selmon, sel->node, need_vertical, delta >= 0.0); ++ if (!split) { ++ split = find_suitable_split(selmon, sel->node, need_vertical, delta < 0.0); ++ } ++ ++ if (!split) { ++ return; ++ } ++ ++ if (in_pixels) { ++ extent = need_vertical ? split->box.width : split->box.height; ++ if (extent <= 0.0) { ++ return; ++ } ++ ++ delta /= extent; ++ } ++ ++ ratio = (delta != 0.0) ? split->split_ratio + delta : 0.5; ++ ratio = MAX(0.05, MIN(ratio, 0.95)); ++ split->split_ratio = (float)ratio; ++ ++ apply_layout(selmon, selmon->root, selmon->w, 1); ++} ++ ++void ++resize_split_px(unsigned int need_vertical, double pixels) ++{ ++ resize_split(need_vertical, pixels, 1); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ resize_split(1, arg->f, 0); ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ resize_split(0, arg->f, 0); ++} ++ ++Client * ++split_target(Monitor *m) ++{ ++ Client *c; ++ ++ if (!use_active_for_splits && (c = xytoclient(cursor->x, cursor->y)) ++ && in_tree(m, c)) { ++ return c; ++ } ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && in_tree(m, c)) { ++ return c; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++swapclients(const Arg *arg) ++{ ++ Client *c, *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 || !in_tree(selmon, sel) ++ || !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' leaves in the layout tree */ ++ if (target && in_tree(selmon, target)) { ++ sel_node = sel->node; ++ target_node = target->node; ++ sel_node->client = target; ++ target_node->client = sel; ++ sel->node = target_node; ++ target->node = sel_node; ++ arrange(selmon); ++ } ++} ++ ++/* Mirror the split */ ++void ++swapsplit(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *p, *tmp; ++ ++ if (!in_tree(selmon, sel) || !(p = visible_parent(selmon, sel->node))) { ++ return; ++ } ++ ++ tmp = p->left; ++ p->left = p->right; ++ p->right = tmp; ++ p->split_ratio = 1.0f - p->split_ratio; ++ arrange(selmon); ++} ++ ++/* Flip the split direction of the split node */ ++void ++togglesplit(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *p; ++ ++ if (!in_tree(selmon, sel) || !(p = visible_parent(selmon, sel->node))) { ++ return; ++ } ++ ++ p->is_split_vertically = !p->is_split_vertically; ++ p->keep_dir = 1; ++ arrange(selmon); ++} ++ ++LayoutNode * ++tree_root(LayoutNode *n) ++{ ++ while (n && n->split_node) { ++ n = n->split_node; ++ } ++ ++ return n; ++} ++ ++/* Remove c */ ++void ++unlink_client(Client *c) ++{ ++ LayoutNode *root; ++ Monitor *m; ++ ++ if (!c || !c->node) { ++ return; ++ } ++ ++ root = tree_root(c->node); ++ wl_list_for_each(m, &mons, link) { ++ if (m->root == root) { ++ remove_leaf(m, c); ++ return; ++ } ++ } ++ ++ c->node->client = NULL; ++ c->node = NULL; ++} ++ ++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); ++} ++ ++/* get nearest ancestor of node that splits visible clients on both sides */ ++LayoutNode * ++visible_parent(Monitor *m, LayoutNode *n) ++{ ++ for (n = n->split_node; n; n = n->split_node) { ++ if (visible_count(n->left, m) > 0 && visible_count(n->right, m) > 0) { ++ return n; ++ } ++ } ++ ++ return NULL; ++} ++ ++int ++wants_vertical(int width, int height) ++{ ++ return (double)width >= (double)height * split_width_multiplier; ++} ++ ++/* get tiled client under the point, or the closest one */ ++Client * ++xytoclient(double x, double y) { ++ Monitor *m = xytomon(x, y); ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->node && ++ 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, m) && !c->isfloating && !c->isfullscreen && c->node) { ++ 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 = dx * dx + dy * dy; ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 13c5322..6233934 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,7 +14,14 @@ 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 */ +- ++/* btrtile */ ++static const int use_active_for_splits = 1; /* 0: split the client under the cursor */ ++static const int force_split = 0; /* 0: new window on the cursor's side, 1: left/top, 2: right/bottom */ ++static const int preserve_split = 1; /* 1: splits keep the direction they were created with */ ++static const float split_width_multiplier = 1.0f; /* side by side when width >= height * multiplier */ ++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 +38,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 +152,16 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_period, 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} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_t, togglesplit, {0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, swapsplit, {0} }, + TAGKEYS( XKB_KEY_1, 0), + TAGKEYS( XKB_KEY_2, 1), + TAGKEYS( XKB_KEY_3, 2), +diff --git a/dwl.c b/dwl.c +index 4cfd0fd..8f59bef 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -113,6 +114,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -158,7 +160,8 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; ++ LayoutNode *node; + } Client; + + typedef struct { +@@ -225,6 +228,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -267,6 +271,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 capturerequest(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -350,6 +355,10 @@ 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 swapsplit(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -357,6 +366,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglesplit(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -481,6 +491,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.h" + + #include "ime.h" + +@@ -724,6 +735,17 @@ buttonpress(struct wl_listener *listener, void *data) + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ /* btrtile */ ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ setmon(c, selmon, 0); ++ insert_client(selmon, xytoclient(cursor->x, cursor->y), c); ++ setfloating(c, 0); ++ apply_layout(selmon, selmon->root, selmon->w, 1); ++ } ++ } ++ /* Default behaviour */ + cursor_mode = CurNormal; + /* Update the cursor */ + wlr_seat_pointer_clear_focus(seat); +@@ -840,6 +862,8 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_scene_output_destroy(m->scene_output); + + closemon(m); ++ /* btrtile */ ++ destroy_tree(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); + } +@@ -1188,6 +1212,8 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ /* btrtile */ ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1431,6 +1457,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); ++ ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -2002,8 +2032,9 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { ++ int tiled = 0; + int nx, ny; +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL, *focusedl = NULL; + struct wlr_surface *surface = NULL; +@@ -2057,28 +2088,55 @@ 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)); + ++ /* Skip if internal call */ ++ if (time == 0) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- nx = (int)round(cursor->x) - grabcx; +- ny = (int)round(cursor->y) - grabcy; +- if (abs(selmon->w.x - nx) < (int)snap) +- nx = selmon->w.x; +- else if (abs((selmon->w.x + selmon->w.width) - (nx + grabc->geom.width)) < (int)snap) +- nx = selmon->w.x + selmon->w.width - grabc->geom.width; +- if (abs(selmon->w.y - ny) < (int)snap) +- ny = selmon->w.y; +- else if (abs((selmon->w.y + selmon->w.height) - (ny + grabc->geom.height)) < (int)snap) +- ny = selmon->w.y + selmon->w.height - grabc->geom.height; +- resize(grabc, (struct wlr_box){.x = nx, .y = ny, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ nx = (int)round(cursor->x) - grabcx; ++ ny = (int)round(cursor->y) - grabcy; ++ if (abs(selmon->w.x - nx) < (int)snap) ++ nx = selmon->w.x; ++ else if (abs((selmon->w.x + selmon->w.width) - (nx + grabc->geom.width)) < (int)snap) ++ nx = selmon->w.x + selmon->w.width - grabc->geom.width; ++ if (abs(selmon->w.y - ny) < (int)snap) ++ ny = selmon->w.y; ++ else if (abs((selmon->w.y + selmon->w.height) - (ny + grabc->geom.height)) < (int)snap) ++ ny = selmon->w.y + selmon->w.height - grabc->geom.height; ++ resize(grabc, (struct wlr_box){.x = nx, .y = ny, ++ .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; +- } ++ /* btrtile */ ++ if (tiled) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ if (fabs(dx_total) > fabs(dy_total)) ++ resize_split_px(1, dx_total); ++ else ++ resize_split_px(0, dy_total); ++ ++ 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. */ +@@ -2112,22 +2170,40 @@ 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, "all-scroll"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ 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, "all-scroll"); ++ 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; ++ } + } + } + +@@ -2998,6 +3074,9 @@ unmapnotify(struct wl_listener *listener, void *data) + focusclient(focustop(selmon), 1); + } + } else { ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); +-- +2.55.0 + diff --git a/patches/btrtile/btrtile-gaps-d16036e.patch b/patches/btrtile/btrtile-gaps-d16036e.patch new file mode 100644 index 0000000..b181aa3 --- /dev/null +++ b/patches/btrtile/btrtile-gaps-d16036e.patch @@ -0,0 +1,1244 @@ +From 6084ff30e1a85ab76b50e7827208337dbccc1926 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Sun, 20 Sep 2026 01:35:56 +0300 +Subject: [PATCH] btrtile: refactor and few features and code simplification + + - Refactor to current upstream main + - Added more user toggles to control the behaviour of btrtile into + config.h + - Simplified the btrtile gapps logic + - Simplified the changes to dwl.c + - Added two keybind functions to swap and toggle splits + - Minor code cleanups +--- + Makefile | 3 +- + btrtile.h | 884 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 21 +- + dwl.c | 149 +++++++-- + 4 files changed, 1020 insertions(+), 37 deletions(-) + create mode 100644 btrtile.h + +diff --git a/Makefile b/Makefile +index 8304245..063ba0a 100644 +--- a/Makefile ++++ b/Makefile +@@ -20,7 +20,8 @@ all: dwl + dwl: dwl.c client.h config.h ime.h util.h config.mk cursor-shape-v1-protocol.h \ + ext-image-copy-capture-v1-protocol.h \ + pointer-constraints-unstable-v1-protocol.h wlr-layer-shell-unstable-v1-protocol.h \ +- wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h ++ wlr-output-power-management-unstable-v1-protocol.h xdg-shell-protocol.h \ ++ btrtile.h + $(CC) dwl.c $(DWLCFLAGS) $(LDFLAGS) $(LDLIBS) -o $@ + + # wayland-scanner is a tool which generates C headers and rigging for Wayland +diff --git a/btrtile.h b/btrtile.h +new file mode 100644 +index 0000000..9f91be2 +--- /dev/null ++++ b/btrtile.h +@@ -0,0 +1,884 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.h @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/09/20 01:07:46 by julmajustus : : : : : : */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int is_split_vertically; ++ unsigned int keep_dir; ++ float split_ratio; ++ struct wlr_box box; ++ struct LayoutNode *left; /* left or top */ ++ struct LayoutNode *right; /* right or bottom */ ++ struct LayoutNode *split_node; /* parent */ ++ Client *client; ++} LayoutNode; ++ ++static void apply_boxes(Monitor *m, LayoutNode *node); ++static void apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void btrtile(Monitor *m); ++static int clamp_split(int total, int mid, int lmin, int rmin, int g); ++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_suitable_split(Monitor *m, LayoutNode *start, ++ unsigned int need_vertical, int focused_on_left); ++static int half_gap(Monitor *m); ++static int in_tree(Monitor *m, Client *c); ++static void init_tree(Monitor *m); ++static void insert_client(Monitor *m, Client *target, Client *new_client); ++static void layout_boxes(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void layout_node(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root); ++static void node_min_size(Monitor *m, LayoutNode *node, int *w, int *h); ++static int outer_gap(Monitor *m); ++static void remove_client(Monitor *m, Client *c); ++static void remove_leaf(Monitor *m, Client *c); ++static void resize_split(unsigned int need_vertical, double delta, int in_pixels); ++static void resize_split_px(unsigned int need_vertical, double pixels); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static Client *split_target(Monitor *m); ++static void swapclients(const Arg *arg); ++static void swapsplit(const Arg *arg); ++static void togglesplit(const Arg *arg); ++static LayoutNode *tree_root(LayoutNode *n); ++static void unlink_client(Client *c); ++static unsigned int visible_count(LayoutNode *node, Monitor *m); ++static LayoutNode *visible_parent(Monitor *m, LayoutNode *n); ++static int wants_vertical(int width, int height); ++static Client *xytoclient(double x, double y); ++ ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++static int dir_changed; ++ ++/* give every visible client the box of its leaf */ ++void ++apply_boxes(Monitor *m, LayoutNode *node) ++{ ++ Client *c; ++ ++ if (!node) { ++ return; ++ } ++ ++ if (node->is_client_node) { ++ c = node->client; ++ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ resize(c, node->box, 0); ++ } ++ ++ return; ++ } ++ ++ apply_boxes(m, node->left); ++ apply_boxes(m, node->right); ++} ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ layout_boxes(m, node, area, is_root); ++ apply_boxes(m, node); ++} ++ ++void ++btrtile(Monitor *m) ++{ ++ Client *c, *target; ++ int n = 0; ++ ++ if (!m) { ++ return; ++ } ++ ++ /* Remove floating and moved clients */ ++ wl_list_for_each(c, &clients, link) { ++ if (c->mon != m || c->isfloating) { ++ remove_client(m, c); ++ } ++ } ++ ++ layout_boxes(m, m->root, m->w, 1); ++ ++ /* Insert visible clients that are not part of the tree */ ++ target = split_target(m); ++ wl_list_for_each(c, &clients, link) { ++ if (!VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ continue; ++ } ++ ++ if (c->node && !in_tree(m, c)) { ++ unlink_client(c); ++ } ++ ++ if (!c->node) { ++ insert_client(m, target, c); ++ layout_boxes(m, m->root, m->w, 1); ++ target = c; ++ } ++ n++; ++ } ++ ++ if (n == 0) { ++ return; ++ } ++ ++ apply_boxes(m, m->root); ++} ++ ++/* Keep both sides of a split at least as large as min hint size */ ++int ++clamp_split(int total, int mid, int lmin, int rmin, int g) ++{ ++ int lo = lmin + g; ++ int hi = total - rmin - g; ++ ++ if (lo <= hi) { ++ return MAX(lo, MIN(mid, hi)); ++ } ++ ++ if (lmin + rmin <= 0) { ++ return mid; ++ } ++ ++ return (int)((double)total * lmin / (lmin + rmin)); ++} ++ ++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; ++ c->node = node; ++ ++ 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) { ++ if (node->client) { ++ node->client->node = NULL; ++ } ++ } ++ else { ++ 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_suitable_split(Monitor *m, LayoutNode *start_node, ++ unsigned int need_vertical, int focused_on_left) ++{ ++ LayoutNode *n = start_node, *child = NULL; ++ ++ if (!m) { ++ return NULL; ++ } ++ ++ if (n && n->is_client_node) { ++ child = n; ++ n = n->split_node; ++ } ++ ++ while (n) { ++ if (!n->is_client_node && n->is_split_vertically == need_vertical ++ && visible_count(n->left, m) > 0 ++ && visible_count(n->right, m) > 0) { ++ if ((focused_on_left && n->left == child) || ++ (!focused_on_left && n->right == child)) ++ return n; ++ } ++ child = n; ++ n = n->split_node; ++ } ++ ++ return NULL; ++} ++ ++#ifdef BTRTILE_GAPS ++int ++half_gap(Monitor *m) ++{ ++ return m->gaps ? (int)gappx / 2 : 0; ++} ++#else ++int ++half_gap(Monitor *m) ++{ ++ return 0; ++} ++#endif ++ ++int ++in_tree(Monitor *m, Client *c) ++{ ++ return m && m->root && c && c->node && tree_root(c->node) == m->root; ++} ++ ++void ++init_tree(Monitor *m) ++{ ++ if (m) { ++ m->root = NULL; ++ } ++} ++ ++ ++void ++insert_client(Monitor *m, Client *target, Client *new_client) ++{ ++ LayoutNode *leaf, *tnode, *split, *parent; ++ struct wlr_box b; ++ int vertical, new_first; ++ ++ if (new_client->node) { ++ unlink_client(new_client); ++ } ++ ++ if (!(leaf = create_client_node(new_client))) { ++ return; ++ } ++ ++ /* If no root, new client becomes the root. */ ++ if (!m->root) { ++ m->root = leaf; ++ return; ++ } ++ ++ if (!in_tree(m, target)) { ++ vertical = wants_vertical(m->w.width, m->w.height); ++ if (!(split = create_split_node(vertical, m->root, leaf))) { ++ goto fail; ++ } ++ ++ m->root = split; ++ return; ++ } ++ ++ tnode = target->node; ++ b = tnode->box; ++ vertical = wants_vertical(b.width, b.height); ++ ++ /* Pick the side of the new client */ ++ if (force_split == 1) { ++ new_first = 1; ++ } ++ else if (force_split == 2) { ++ new_first = 0; ++ } ++ else if (vertical) { ++ new_first = cursor->x <= b.x + b.width / 2.0; ++ } ++ else { ++ new_first = cursor->y <= b.y + b.height / 2.0; ++ } ++ ++ parent = tnode->split_node; ++ split = new_first ? create_split_node(vertical, leaf, tnode) ++ : create_split_node(vertical, tnode, leaf); ++ if (!split) { ++ goto fail; ++ } ++ ++ split->split_node = parent; ++ if (!parent) { ++ m->root = split; ++ } ++ else if (parent->left == tnode) { ++ parent->left = split; ++ } ++ else { ++ parent->right = split; ++ } ++ ++ return; ++ ++fail: ++ new_client->node = NULL; ++ free(leaf); ++} ++ ++/* compute the box of every node */ ++void ++layout_boxes(Monitor *m, LayoutNode *node, struct wlr_box area, unsigned int is_root) ++{ ++ int i; ++ ++ for (i = 0; i < 4; i++) { ++ dir_changed = 0; ++ layout_node(m, node, area, is_root); ++ ++ if (!dir_changed) { ++ break; ++ } ++ } ++} ++ ++void ++layout_node(Monitor *m, LayoutNode *node, struct wlr_box area, unsigned int is_root) ++{ ++ unsigned int left_count, right_count; ++ int mid, lw, lh, rw, rh, vertical, og, g = half_gap(m); ++ float ratio; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) { ++ return; ++ } ++ ++ if (is_root) { ++ og = outer_gap(m); ++ area.x += og; ++ area.y += og; ++ area.width -= 2 * og; ++ area.height -= 2 * og; ++ } ++ node->box = area; ++ ++ if (node->is_client_node) { ++ return; ++ } ++ ++ /* For a split node, see how many visible clients 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; ++ } ++ ++ if (right_count == 0) { ++ layout_node(m, node->left, area, 0); ++ return; ++ } ++ ++ if (left_count == 0) { ++ layout_node(m, node->right, area, 0); ++ return; ++ } ++ ++ /* Visible clients on both sides */ ++ if (!preserve_split && !node->keep_dir) { ++ vertical = wants_vertical(area.width, area.height); ++ if ((unsigned int)vertical != node->is_split_vertically) { ++ dir_changed = 1; ++ } ++ node->is_split_vertically = vertical; ++ } ++ ++ ratio = node->split_ratio; ++ if (ratio < 0.05f) { ++ ratio = 0.05f; ++ } ++ ++ if (ratio > 0.95f) { ++ ratio = 0.95f; ++ } ++ ++ node_min_size(m, node->left, &lw, &lh); ++ node_min_size(m, node->right, &rw, &rh); ++ left_area = right_area = area; ++ ++ if (node->is_split_vertically) { ++ mid = clamp_split(area.width, (int)(area.width * ratio), lw, rw, g); ++ left_area.width = mid - g; ++ right_area.x = area.x + mid + g; ++ right_area.width = area.width - mid - g; ++ } else { ++ mid = clamp_split(area.height, (int)(area.height * ratio), lh, rh, g); ++ left_area.height = mid - g; ++ right_area.y = area.y + mid + g; ++ right_area.height = area.height - mid - g; ++ } ++ ++ layout_node(m, node->left, left_area, 0); ++ layout_node(m, node->right, right_area, 0); ++} ++ ++/* Smallest size a subtree can be given without violating size hints */ ++void ++node_min_size(Monitor *m, LayoutNode *node, int *w, int *h) ++{ ++ Client *c; ++ struct wlr_box min = {0}, max = {0}; ++ int lw, lh, rw, rh, g; ++ ++ *w = *h = 0; ++ if (!node) { ++ return; ++ } ++ ++ if (node->is_client_node) { ++ c = node->client; ++ ++ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen) { ++ return; ++ } ++ ++ client_get_size_hints(c, &max, &min); ++ *w = MAX(min.width, 1) + 2 * (int)c->bw; ++ *h = MAX(min.height, 1) + 2 * (int)c->bw; ++ ++ return; ++ } ++ ++ node_min_size(m, node->left, &lw, &lh); ++ node_min_size(m, node->right, &rw, &rh); ++ if (!lw) { ++ *w = rw; ++ *h = rh; ++ return; ++ } ++ if (!rw) { ++ *w = lw; ++ *h = lh; ++ return; ++ } ++ ++ g = 2 * half_gap(m); ++ if (node->is_split_vertically) { ++ *w = lw + rw + g; ++ *h = MAX(lh, rh); ++ } ++ else { ++ *w = MAX(lw, rw); ++ *h = lh + rh + g; ++ } ++} ++ ++#ifdef BTRTILE_GAPS ++int ++outer_gap(Monitor *m) ++{ ++ return m->gaps ? (int)gappx : 0; ++} ++ ++#else ++int ++outer_gap(Monitor *m) ++{ ++ return 0; ++} ++#endif ++ ++/* Remove c if in the tree */ ++void ++remove_client(Monitor *m, Client *c) ++{ ++ if (in_tree(m, c)) { ++ remove_leaf(m, c); ++ } ++} ++ ++void ++remove_leaf(Monitor *m, Client *c) ++{ ++ LayoutNode *n = c->node, *p, *sib, *gp; ++ ++ if (!n) { ++ return; ++ } ++ ++ c->node = NULL; ++ ++ if (!(p = n->split_node)) { ++ m->root = NULL; ++ free(n); ++ return; ++ } ++ ++ sib = (p->left == n) ? p->right : p->left; ++ gp = p->split_node; ++ sib->split_node = gp; ++ if (!gp) { ++ m->root = sib; ++ } ++ else if (gp->left == p) { ++ gp->left = sib; ++ } ++ else { ++ gp->right = sib; ++ } ++ ++ free(n); ++ free(p); ++} ++ ++/* In pixel mode delta is a pointer movement, otherwise it is a change of the split ratio. */ ++void ++resize_split(unsigned int need_vertical, double delta, int in_pixels) ++{ ++ Client *sel; ++ LayoutNode *split; ++ double extent, ratio; ++ ++ if (!selmon || !selmon->lt[selmon->sellt]->arrange) { ++ return; ++ } ++ ++ sel = focustop(selmon); ++ if (!in_tree(selmon, sel)) { ++ return; ++ } ++ ++ if (in_pixels && delta == 0.0) { ++ return; ++ } ++ ++ split = find_suitable_split(selmon, sel->node, need_vertical, delta >= 0.0); ++ if (!split) { ++ split = find_suitable_split(selmon, sel->node, need_vertical, delta < 0.0); ++ } ++ ++ if (!split) { ++ return; ++ } ++ ++ if (in_pixels) { ++ extent = need_vertical ? split->box.width : split->box.height; ++ if (extent <= 0.0) { ++ return; ++ } ++ ++ delta /= extent; ++ } ++ ++ ratio = (delta != 0.0) ? split->split_ratio + delta : 0.5; ++ ratio = MAX(0.05, MIN(ratio, 0.95)); ++ split->split_ratio = (float)ratio; ++ ++ apply_layout(selmon, selmon->root, selmon->w, 1); ++} ++ ++void ++resize_split_px(unsigned int need_vertical, double pixels) ++{ ++ resize_split(need_vertical, pixels, 1); ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ resize_split(1, arg->f, 0); ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ resize_split(0, arg->f, 0); ++} ++ ++Client * ++split_target(Monitor *m) ++{ ++ Client *c; ++ ++ if (!use_active_for_splits && (c = xytoclient(cursor->x, cursor->y)) ++ && in_tree(m, c)) { ++ return c; ++ } ++ ++ wl_list_for_each(c, &fstack, flink) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && in_tree(m, c)) { ++ return c; ++ } ++ } ++ ++ return NULL; ++} ++ ++void ++swapclients(const Arg *arg) ++{ ++ Client *c, *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 || !in_tree(selmon, sel) ++ || !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' leaves in the layout tree */ ++ if (target && in_tree(selmon, target)) { ++ sel_node = sel->node; ++ target_node = target->node; ++ sel_node->client = target; ++ target_node->client = sel; ++ sel->node = target_node; ++ target->node = sel_node; ++ arrange(selmon); ++ } ++} ++ ++/* Mirror the split */ ++void ++swapsplit(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *p, *tmp; ++ ++ if (!in_tree(selmon, sel) || !(p = visible_parent(selmon, sel->node))) { ++ return; ++ } ++ ++ tmp = p->left; ++ p->left = p->right; ++ p->right = tmp; ++ p->split_ratio = 1.0f - p->split_ratio; ++ arrange(selmon); ++} ++ ++/* Flip the split direction of the split node */ ++void ++togglesplit(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *p; ++ ++ if (!in_tree(selmon, sel) || !(p = visible_parent(selmon, sel->node))) { ++ return; ++ } ++ ++ p->is_split_vertically = !p->is_split_vertically; ++ p->keep_dir = 1; ++ arrange(selmon); ++} ++ ++LayoutNode * ++tree_root(LayoutNode *n) ++{ ++ while (n && n->split_node) { ++ n = n->split_node; ++ } ++ ++ return n; ++} ++ ++/* Remove c */ ++void ++unlink_client(Client *c) ++{ ++ LayoutNode *root; ++ Monitor *m; ++ ++ if (!c || !c->node) { ++ return; ++ } ++ ++ root = tree_root(c->node); ++ wl_list_for_each(m, &mons, link) { ++ if (m->root == root) { ++ remove_leaf(m, c); ++ return; ++ } ++ } ++ ++ c->node->client = NULL; ++ c->node = NULL; ++} ++ ++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); ++} ++ ++/* get nearest ancestor of node that splits visible clients on both sides */ ++LayoutNode * ++visible_parent(Monitor *m, LayoutNode *n) ++{ ++ for (n = n->split_node; n; n = n->split_node) { ++ if (visible_count(n->left, m) > 0 && visible_count(n->right, m) > 0) { ++ return n; ++ } ++ } ++ ++ return NULL; ++} ++ ++int ++wants_vertical(int width, int height) ++{ ++ return (double)width >= (double)height * split_width_multiplier; ++} ++ ++/* get tiled client under the point, or the closest one */ ++Client * ++xytoclient(double x, double y) { ++ Monitor *m = xytomon(x, y); ++ Client *c, *closest = NULL; ++ double dist, mindist = INT_MAX, dx, dy; ++ ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->node && ++ 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, m) && !c->isfloating && !c->isfullscreen && c->node) { ++ 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 = dx * dx + dy * dy; ++ if (dist < mindist) { ++ mindist = dist; ++ closest = c; ++ } ++ } ++ } ++ ++ return closest; ++} +diff --git a/config.def.h b/config.def.h +index 13c5322..061e6e4 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -14,7 +14,15 @@ 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 */ +- ++/* btrtile */ ++#define BTRTILE_GAPS ++static const int use_active_for_splits = 1; /* 0: split the client under the cursor */ ++static const int force_split = 0; /* 0: new window on the cursor's side, 1: left/top, 2: right/bottom */ ++static const int preserve_split = 1; /* 1: splits keep the direction they were created with */ ++static const float split_width_multiplier = 1.0f; /* side by side when width >= height * multiplier */ ++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 +39,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 +153,16 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_comma, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_period, 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} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_t, togglesplit, {0} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_y, swapsplit, {0} }, + TAGKEYS( XKB_KEY_1, 0), + TAGKEYS( XKB_KEY_2, 1), + TAGKEYS( XKB_KEY_3, 2), +diff --git a/dwl.c b/dwl.c +index 4cfd0fd..8f59bef 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -113,6 +114,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct LayoutNode LayoutNode; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep this field first */ +@@ -158,7 +160,8 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; ++ LayoutNode *node; + } Client; + + typedef struct { +@@ -225,6 +228,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -267,6 +271,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 capturerequest(struct wl_listener *listener, void *data); + static void chvt(const Arg *arg); +@@ -350,6 +355,10 @@ 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 swapsplit(const Arg *arg); + static void spawn(const Arg *arg); + static void startdrag(struct wl_listener *listener, void *data); + static void tag(const Arg *arg); +@@ -357,6 +366,7 @@ static void tagmon(const Arg *arg); + static void tile(Monitor *m); + static void togglefloating(const Arg *arg); + static void togglefullscreen(const Arg *arg); ++static void togglesplit(const Arg *arg); + static void toggletag(const Arg *arg); + static void toggleview(const Arg *arg); + static void unlocksession(struct wl_listener *listener, void *data); +@@ -481,6 +491,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.h" + + #include "ime.h" + +@@ -724,6 +735,17 @@ buttonpress(struct wl_listener *listener, void *data) + case WL_POINTER_BUTTON_STATE_RELEASED: + /* If you released any buttons, we exit interactive move/resize mode. */ + if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) { ++ /* btrtile */ ++ c = grabc; ++ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) { ++ if (cursor_mode == CurMove && c->isfloating) { ++ setmon(c, selmon, 0); ++ insert_client(selmon, xytoclient(cursor->x, cursor->y), c); ++ setfloating(c, 0); ++ apply_layout(selmon, selmon->root, selmon->w, 1); ++ } ++ } ++ /* Default behaviour */ + cursor_mode = CurNormal; + /* Update the cursor */ + wlr_seat_pointer_clear_focus(seat); +@@ -840,6 +862,8 @@ cleanupmon(struct wl_listener *listener, void *data) + wlr_scene_output_destroy(m->scene_output); + + closemon(m); ++ /* btrtile */ ++ destroy_tree(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); + } +@@ -1188,6 +1212,8 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ /* btrtile */ ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1431,6 +1457,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); ++ ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -2002,8 +2032,9 @@ void + motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy, + double dx_unaccel, double dy_unaccel) + { ++ int tiled = 0; + int nx, ny; +- double sx = 0, sy = 0, sx_confined, sy_confined; ++ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total; + Client *c = NULL, *w = NULL; + LayerSurface *l = NULL, *focusedl = NULL; + struct wlr_surface *surface = NULL; +@@ -2057,28 +2088,55 @@ 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)); + ++ /* Skip if internal call */ ++ if (time == 0) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + /* If we are currently grabbing the mouse, handle and return */ + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- nx = (int)round(cursor->x) - grabcx; +- ny = (int)round(cursor->y) - grabcy; +- if (abs(selmon->w.x - nx) < (int)snap) +- nx = selmon->w.x; +- else if (abs((selmon->w.x + selmon->w.width) - (nx + grabc->geom.width)) < (int)snap) +- nx = selmon->w.x + selmon->w.width - grabc->geom.width; +- if (abs(selmon->w.y - ny) < (int)snap) +- ny = selmon->w.y; +- else if (abs((selmon->w.y + selmon->w.height) - (ny + grabc->geom.height)) < (int)snap) +- ny = selmon->w.y + selmon->w.height - grabc->geom.height; +- resize(grabc, (struct wlr_box){.x = nx, .y = ny, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ nx = (int)round(cursor->x) - grabcx; ++ ny = (int)round(cursor->y) - grabcy; ++ if (abs(selmon->w.x - nx) < (int)snap) ++ nx = selmon->w.x; ++ else if (abs((selmon->w.x + selmon->w.width) - (nx + grabc->geom.width)) < (int)snap) ++ nx = selmon->w.x + selmon->w.width - grabc->geom.width; ++ if (abs(selmon->w.y - ny) < (int)snap) ++ ny = selmon->w.y; ++ else if (abs((selmon->w.y + selmon->w.height) - (ny + grabc->geom.height)) < (int)snap) ++ ny = selmon->w.y + selmon->w.height - grabc->geom.height; ++ resize(grabc, (struct wlr_box){.x = nx, .y = ny, ++ .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; +- } ++ /* btrtile */ ++ if (tiled) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ if (fabs(dx_total) > fabs(dy_total)) ++ resize_split_px(1, dx_total); ++ else ++ resize_split_px(0, dy_total); ++ ++ 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. */ +@@ -2112,22 +2170,40 @@ 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, "all-scroll"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ 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, "all-scroll"); ++ 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; ++ } + } + } + +@@ -2998,6 +3074,9 @@ unmapnotify(struct wl_listener *listener, void *data) + focusclient(focustop(selmon), 1); + } + } else { ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ + wl_list_remove(&c->link); + setmon(c, NULL, 0); + wl_list_remove(&c->flink); +-- +2.55.0 + diff --git a/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch deleted file mode 100644 index 44c7b8e..0000000 --- a/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch +++ /dev/null @@ -1,922 +0,0 @@ -From efb796ce92e7212aa392141705c7322fedc45dba Mon Sep 17 00:00:00 2001 -From: julmajustus -Date: Mon, 22 Jun 2026 22:46:19 +0300 -Subject: [PATCH] btrtile: bug fixes and readability improvement - -- Removed client old_geom caching for simplicity fixes: https://codeberg.org/dwl/dwl-patches/issues/647 -- Improved readability of the client sanitization loop inside btrtile function -- Removed fullscreened client's from the sanitization loop to retain simple_scratchpad: Spring update scratchpad V2 - - - Added support for multiple scratchpads their geometry ---- - btrtile.c | 575 +++++++++++++++++++++++++++++++++++++++++++++++++++ - config.def.h | 12 ++ - dwl.c | 151 +++++++++++--- - 3 files changed, 711 insertions(+), 27 deletions(-) - create mode 100644 btrtile.c - -diff --git a/btrtile.c b/btrtile.c -new file mode 100644 -index 0000000..645b741 ---- /dev/null -+++ b/btrtile.c -@@ -0,0 +1,575 @@ -+/* ************************************************************************** */ -+/* @@@ @@@@@@@@ */ -+/* @@@ @@@@@@@@@@ */ -+/* @@! @@! @@@@ */ -+/* !@! !@! @!@!@ */ -+/* btrtile.c @!! @!@ @! !@! */ -+/* !!! !@!!! !!! */ -+/* By: julmajustus !!: !!:! !!! */ -+/* ::! :!: !:! */ -+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ -+/* Updated: 2026/06/22 22:34:11 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(Monitor *m, LayoutNode *start, -+ unsigned int need_vertical, int focused_on_left); -+static void init_tree(Monitor *m); -+static void insert_client(Monitor *m, Client *focused_client, Client *new_client); -+static LayoutNode *remove_client_node(LayoutNode *node, Client *c); -+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 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); -+ 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) -+ return; -+ -+ /* Remove floating and moved clients */ -+ wl_list_for_each(c, &clients, link) { -+ if (c->mon != m || c->isfloating) -+ 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(Monitor *m, LayoutNode *start_node, -+ unsigned int need_vertical, int focused_on_left) -+{ -+ LayoutNode *n = start_node, *child = NULL; -+ -+ if (!m) -+ return NULL; -+ -+ if (n && n->is_client_node) { -+ child = n; -+ n = n->split_node; -+ } -+ -+ while (n) { -+ if (!n->is_client_node && n->is_split_vertically == need_vertical -+ && visible_count(n->left, m) > 0 -+ && visible_count(n->right, m) > 0) { -+ if ((focused_on_left && n->left == child) || -+ (!focused_on_left && n->right == child)) -+ return n; -+ } -+ child = n; -+ n = n->split_node; -+ } -+ return NULL; -+} -+void -+init_tree(Monitor *m) -+{ -+ if (m) -+ 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); -+} -+ -+static void -+setratio(unsigned int need_vertical, const Arg *arg) -+{ -+ Client *sel; -+ LayoutNode *client_node, *split_node; -+ float new_ratio; -+ int focused_on_left; -+ -+ if (!selmon || !selmon->lt[selmon->sellt]->arrange) -+ return; -+ -+ sel = focustop(selmon); -+ if (!sel) -+ return; -+ -+ client_node = find_client_node(selmon->root, sel); -+ if (!client_node) -+ return; -+ -+ focused_on_left = (arg->f >= 0.0f); -+ -+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left); -+ -+ if (!split_node) -+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left); -+ if (!split_node) -+ return; -+ -+ 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; -+ -+ apply_layout(selmon, selmon->root, selmon->w, 1); -+} -+ -+void -+setratio_h(const Arg *arg) -+{ -+ setratio(1, arg); -+} -+ -+void -+setratio_v(const Arg *arg) -+{ -+ setratio(0, arg); -+} -+ -+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) { -+ Monitor *m = xytomon(x, y); -+ Client *c, *closest = NULL; -+ double dist, mindist = INT_MAX, dx, dy; -+ -+ wl_list_for_each_reverse(c, &clients, link) { -+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && -+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && -+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ -+ return c; -+ } -+ } -+ -+ /* If no client was found at cursor position fallback to closest. */ -+ wl_list_for_each_reverse(c, &clients, link) { -+ if (VISIBLEON(c, m) && !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 = 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..7abf390 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,7 +143,7 @@ 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 */ - } Client; - -@@ -209,6 +211,7 @@ struct Monitor { - int nmaster; - char ltsymbol[16]; - int asleep; -+ LayoutNode *root; - }; - - typedef struct { -@@ -251,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); -+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 +337,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 +465,7 @@ static struct wlr_xwayland *xwayland; - - /* attempt to encapsulate suck into one file */ - #include "client.h" -+#include "btrtile.c" - - /* function implementations */ - void -@@ -628,7 +636,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 +657,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 +667,21 @@ 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); -+ apply_layout(selmon, selmon->root, selmon->w, 1); -+ } -+ } -+ /* Default behaviour */ - wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); - cursor_mode = CurNormal; - /* Drop the window off on its new monitor */ -@@ -750,6 +773,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 +1118,7 @@ createmon(struct wl_listener *listener, void *data) - - wl_list_insert(&mons, &m->link); - printstatus(); -+ init_tree(m); - - /* The xdg-protocol specifies: - * -@@ -1332,10 +1357,18 @@ void - destroynotify(struct wl_listener *listener, void *data) - { - /* Called when the xdg_toplevel is destroyed. */ -+ Monitor *mon; - Client *c = wl_container_of(listener, c, destroy); - wl_list_remove(&c->destroy.link); - wl_list_remove(&c->set_title.link); - wl_list_remove(&c->fullscreen.link); -+ /* 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 */ -+ wl_list_for_each(mon, &mons, link) { -+ if (mon->root) { -+ remove_client(mon, c); -+ } -+ } - #ifdef XWAYLAND - if (c->type != XDGShell) { - wl_list_remove(&c->activate.link); -@@ -1866,7 +1899,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 +1954,55 @@ 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 */ -+ if (time == 0) -+ 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) { -+ 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); -+ } -+ -+ 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 +2036,40 @@ 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; -+ 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; -+ } - } - } - -@@ -2833,6 +2922,14 @@ unmapnotify(struct wl_listener *listener, void *data) - focusclient(focustop(selmon), 1); - } - } else { -+ /* btrtile remove clients for each monitor */ -+ Monitor *mon; -+ wl_list_for_each(mon, &mons, link) { -+ if (mon->root) { -+ remove_client(mon, c); -+ } -+ } -+ - wl_list_remove(&c->link); - setmon(c, NULL, 0); - wl_list_remove(&c->flink); --- -2.53.0 - diff --git a/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch b/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch deleted file mode 100644 index 1593fce..0000000 --- a/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch +++ /dev/null @@ -1,900 +0,0 @@ -From 3500e732fc34aa849cd3babeff7d2545a0230fa9 Mon Sep 17 00:00:00 2001 -From: julmajustus -Date: Mon, 22 Jun 2026 22:33:17 +0300 -Subject: [PATCH] btrtile: bug fixes and readability improvement - -- Removed client old_geom caching for simplicity fixes: https://codeberg.org/dwl/dwl-patches/issues/647 -- Improved readability of the client sanitization loop inside btrtile function -- Removed fullscreened client's from the sanitization loop to retain their geometry ---- - btrtile.c | 556 +++++++++++++++++++++++++++++++++++++++++++++++++++ - config.def.h | 12 ++ - dwl.c | 151 +++++++++++--- - 3 files changed, 692 insertions(+), 27 deletions(-) - create mode 100644 btrtile.c - -diff --git a/btrtile.c b/btrtile.c -new file mode 100644 -index 0000000..b4f6f43 ---- /dev/null -+++ b/btrtile.c -@@ -0,0 +1,556 @@ -+/* ************************************************************************** */ -+/* @@@ @@@@@@@@ */ -+/* @@@ @@@@@@@@@@ */ -+/* @@! @@! @@@@ */ -+/* !@! !@! @!@!@ */ -+/* btrtile.c @!! @!@ @! !@! */ -+/* !!! !@!!! !!! */ -+/* By: julmajustus !!: !!:! !!! */ -+/* ::! :!: !:! */ -+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ -+/* Updated: 2026/06/22 22:30:19 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(Monitor *m, LayoutNode *start, -+ unsigned int need_vertical, int focused_on_left); -+static void init_tree(Monitor *m); -+static void insert_client(Monitor *m, Client *focused_client, Client *new_client); -+static LayoutNode *remove_client_node(LayoutNode *node, Client *c); -+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 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); -+ 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) -+ return; -+ -+ /* Remove floating and moved clients */ -+ wl_list_for_each(c, &clients, link) { -+ if (c->mon != m || c->isfloating) -+ 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(Monitor *m, LayoutNode *start_node, -+ unsigned int need_vertical, int focused_on_left) -+{ -+ LayoutNode *n = start_node, *child = NULL; -+ -+ if (!m) -+ return NULL; -+ -+ if (n && n->is_client_node) { -+ child = n; -+ n = n->split_node; -+ } -+ -+ while (n) { -+ if (!n->is_client_node && n->is_split_vertically == need_vertical -+ && visible_count(n->left, m) > 0 -+ && visible_count(n->right, m) > 0) { -+ if ((focused_on_left && n->left == child) || -+ (!focused_on_left && n->right == child)) -+ return n; -+ } -+ child = n; -+ n = n->split_node; -+ } -+ return NULL; -+} -+void -+init_tree(Monitor *m) -+{ -+ if (m) -+ 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); -+} -+ -+static void -+setratio(unsigned int need_vertical, const Arg *arg) -+{ -+ Client *sel; -+ LayoutNode *client_node, *split_node; -+ float new_ratio; -+ int focused_on_left; -+ -+ if (!selmon || !selmon->lt[selmon->sellt]->arrange) -+ return; -+ -+ sel = focustop(selmon); -+ if (!sel) -+ return; -+ -+ client_node = find_client_node(selmon->root, sel); -+ if (!client_node) -+ return; -+ -+ focused_on_left = (arg->f >= 0.0f); -+ -+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left); -+ -+ if (!split_node) -+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left); -+ if (!split_node) -+ return; -+ -+ 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; -+ -+ apply_layout(selmon, selmon->root, selmon->w, 1); -+} -+ -+void -+setratio_h(const Arg *arg) -+{ -+ setratio(1, arg); -+} -+ -+void -+setratio_v(const Arg *arg) -+{ -+ setratio(0, arg); -+} -+ -+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) { -+ Monitor *m = xytomon(x, y); -+ Client *c, *closest = NULL; -+ double dist, mindist = INT_MAX, dx, dy; -+ -+ wl_list_for_each_reverse(c, &clients, link) { -+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && -+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) && -+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){ -+ return c; -+ } -+ } -+ -+ /* If no client was found at cursor position fallback to closest. */ -+ wl_list_for_each_reverse(c, &clients, link) { -+ if (VISIBLEON(c, m) && !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 = 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..31446ed 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,7 +143,7 @@ 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 */ - } Client; - -@@ -209,6 +211,7 @@ struct Monitor { - int nmaster; - char ltsymbol[16]; - int asleep; -+ LayoutNode *root; - }; - - typedef struct { -@@ -251,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); -+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 +337,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 +465,7 @@ static struct wlr_xwayland *xwayland; - - /* attempt to encapsulate suck into one file */ - #include "client.h" -+#include "btrtile.c" - - /* function implementations */ - void -@@ -628,7 +636,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 +657,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 +667,21 @@ 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); -+ apply_layout(selmon, selmon->root, selmon->w, 1); -+ } -+ } -+ /* Default behaviour */ - wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); - cursor_mode = CurNormal; - /* Drop the window off on its new monitor */ -@@ -750,6 +773,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 +1118,7 @@ createmon(struct wl_listener *listener, void *data) - - wl_list_insert(&mons, &m->link); - printstatus(); -+ init_tree(m); - - /* The xdg-protocol specifies: - * -@@ -1333,9 +1358,17 @@ destroynotify(struct wl_listener *listener, void *data) - { - /* Called when the xdg_toplevel is destroyed. */ - Client *c = wl_container_of(listener, c, destroy); -+ Monitor *mon; - 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 */ -+ wl_list_for_each(mon, &mons, link) { -+ if (mon->root) { -+ remove_client(mon, c); -+ } -+ } - #ifdef XWAYLAND - if (c->type != XDGShell) { - wl_list_remove(&c->activate.link); -@@ -1866,7 +1899,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 +1954,55 @@ 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 */ -+ if (time == 0) -+ 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) { -+ 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); -+ } -+ -+ 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 +2036,40 @@ 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; -+ 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; -+ } - } - } - -@@ -2833,6 +2922,14 @@ unmapnotify(struct wl_listener *listener, void *data) - focusclient(focustop(selmon), 1); - } - } else { -+ /* btrtile remove clients for each monitor */ -+ Monitor *mon; -+ wl_list_for_each(mon, &mons, link) { -+ if (mon->root) { -+ remove_client(mon, c); -+ } -+ } -+ - wl_list_remove(&c->link); - setmon(c, NULL, 0); - wl_list_remove(&c->flink); --- -2.53.0 -