From 3360d3acf71c82b6529fdef13a63f37a3040af38 Mon Sep 17 00:00:00 2001 From: julmajustus Date: Thu, 29 Oct 2026 20:11:47 +0200 Subject: [PATCH] btrtile: update patches for main - Refactored the resizing logic to work with the changes in main branch. - Replaced the fixed resize_interval_ms throttle with a pending flag set from motionnotify() and applied once per output frame from rendermon(), so the resize update rate follows the display refresh rate. --- .../btrtile/btrtile-main-12a5856-gaps.patch | 1296 +++++++++++++++++ patches/btrtile/btrtile-main-12a5856.patch | 1294 ++++++++++++++++ 2 files changed, 2590 insertions(+) create mode 100644 patches/btrtile/btrtile-main-12a5856-gaps.patch create mode 100644 patches/btrtile/btrtile-main-12a5856.patch diff --git a/patches/btrtile/btrtile-main-12a5856-gaps.patch b/patches/btrtile/btrtile-main-12a5856-gaps.patch new file mode 100644 index 0000000..acca93e --- /dev/null +++ b/patches/btrtile/btrtile-main-12a5856-gaps.patch @@ -0,0 +1,1296 @@ +From 769c61f2b6a71be6d1eb7fd5338184c775eeda04 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 29 Oct 2026 19:52:53 +0200 +Subject: [PATCH] btrtile: update + + - Refactored the resizing logic to work with the changes in main + branch. + - Replaced the fixed resize_interval_ms throttle with a pending flag + set from motionnotify() and applied once per output frame from + rendermon(), so the resize update rate follows the display refresh rate. +--- + Makefile | 3 +- + btrtile.h | 914 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 20 +- + dwl.c | 150 +++++++-- + 4 files changed, 1049 insertions(+), 38 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..5e31ab0 +--- /dev/null ++++ b/btrtile.h +@@ -0,0 +1,914 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.h @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/10/29 19:52:12 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 apply_pending_resize(Monitor *m); ++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 int resize_pending; ++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); ++} ++ ++void ++apply_pending_resize(Monitor *m) ++{ ++ double dx, dy; ++ ++ if (!resize_pending || m != selmon) { ++ return; ++ } ++ ++ resize_pending = 0; ++ if (cursor_mode != CurResize || !grabc ++ || grabc->isfloating || grabc->isfullscreen) { ++ return; ++ } ++ ++ dx = cursor->x - resize_last_update_x; ++ dy = cursor->y - resize_last_update_y; ++ if (fabs(dx) > fabs(dy)) { ++ resize_split_px(1, dx); ++ } ++ else { ++ resize_split_px(0, dy); ++ } ++ ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++} ++ ++/* 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 4901e7a..029da0e 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,14 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + 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 */ ++ ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +37,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 }, +@@ -143,6 +151,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 89e8caf..fb619af 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 */ +@@ -161,7 +163,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 { +@@ -229,6 +232,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -272,6 +276,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); +@@ -358,6 +363,10 @@ static void setmonoff(struct wlr_output *wlr_output, int off); + 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 struct wlr_scene_node *snapshot(struct wlr_scene_node *node, + struct wlr_scene_tree *parent, void *data); + static void spawn(const Arg *arg); +@@ -367,6 +376,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 transactionapply(void); +@@ -506,6 +516,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.h" + + #include "ime.h" + +@@ -763,6 +774,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 && c->mon->lt[c->mon->sellt]->arrange == btrtile) { ++ 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); +@@ -889,6 +911,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); + } +@@ -1254,6 +1278,8 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ /* btrtile */ ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1503,6 +1529,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); +@@ -2093,6 +2123,7 @@ 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; + Client *c = NULL, *w = NULL; +@@ -2148,28 +2179,45 @@ 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) { ++ resize_pending = 1; ++ if (selmon) { ++ wlr_output_schedule_frame(selmon->wlr_output); ++ } ++ } 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. */ +@@ -2203,22 +2251,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; ++ } + } + } + +@@ -2418,6 +2484,9 @@ rendermon(struct wl_listener *listener, void *data) + Monitor *m = wl_container_of(listener, m, frame); + struct timespec now; + ++ /* btrtile */ ++ apply_pending_resize(m); ++ + if (!wlr_scene_output_needs_frame(m->scene_output)) { + return; + } +@@ -2461,10 +2530,16 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + uint32_t serial; ++ int immediate; + + if (!c->mon || !client_surface(c)->mapped) + return; + ++ /* btrtile */ ++ immediate = interact ++ || (cursor_mode == CurResize && grabc && grabc->was_tiled ++ && c->mon->lt[c->mon->sellt]->arrange == btrtile); ++ + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); +@@ -2475,7 +2550,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom.height - 2 * c->bw))) + c->resize_serial = serial; + +- if (interact || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) { ++ if (immediate || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) { + c->resize_serial = 0; + applygeom(c); + return; +@@ -3255,7 +3330,10 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } else if (client_wants_focus(c)) { + focusclient(focustop(selmon), NULL, 1); +- } ++ } else { ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ } + + if (c->foreign_toplevel_handle) { + wlr_ext_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel_handle); +-- +2.55.0 + diff --git a/patches/btrtile/btrtile-main-12a5856.patch b/patches/btrtile/btrtile-main-12a5856.patch new file mode 100644 index 0000000..9370afb --- /dev/null +++ b/patches/btrtile/btrtile-main-12a5856.patch @@ -0,0 +1,1294 @@ +From bfa75201bae21707755300b3a3cffd9a227f3f5c Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Thu, 29 Oct 2026 19:53:42 +0200 +Subject: [PATCH] btrtile: update + + - Refactored the resizing logic to work with the changes in main + branch. + - Replaced the fixed resize_interval_ms throttle with a pending flag + set from motionnotify() and applied once per output frame from + rendermon(), so the resize update rate follows the display refresh rate. +--- + Makefile | 3 +- + btrtile.h | 914 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 17 + + dwl.c | 150 +++++++-- + 4 files changed, 1047 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..6585171 +--- /dev/null ++++ b/btrtile.h +@@ -0,0 +1,914 @@ ++/* ************************************************************************** */ ++/* @@@ @@@@@@@@ */ ++/* @@@ @@@@@@@@@@ */ ++/* @@! @@! @@@@ */ ++/* !@! !@! @!@!@ */ ++/* btrtile.h @!! @!@ @! !@! */ ++/* !!! !@!!! !!! */ ++/* By: julmajustus !!: !!:! !!! */ ++/* ::! :!: !:! */ ++/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */ ++/* Updated: 2026/10/29 19:53:35 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 apply_pending_resize(Monitor *m); ++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 int resize_pending; ++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); ++} ++ ++void ++apply_pending_resize(Monitor *m) ++{ ++ double dx, dy; ++ ++ if (!resize_pending || m != selmon) { ++ return; ++ } ++ ++ resize_pending = 0; ++ if (cursor_mode != CurResize || !grabc ++ || grabc->isfloating || grabc->isfullscreen) { ++ return; ++ } ++ ++ dx = cursor->x - resize_last_update_x; ++ dy = cursor->y - resize_last_update_y; ++ if (fabs(dx) > fabs(dy)) { ++ resize_split_px(1, dx); ++ } ++ else { ++ resize_split_px(0, dy); ++ } ++ ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++} ++ ++/* 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 4901e7a..e3a9c66 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,13 @@ static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + 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 */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -30,6 +36,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 }, +@@ -143,6 +150,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 89e8caf..fb619af 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 */ +@@ -161,7 +163,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 { +@@ -229,6 +232,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ LayoutNode *root; + }; + + typedef struct { +@@ -272,6 +276,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); +@@ -358,6 +363,10 @@ static void setmonoff(struct wlr_output *wlr_output, int off); + 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 struct wlr_scene_node *snapshot(struct wlr_scene_node *node, + struct wlr_scene_tree *parent, void *data); + static void spawn(const Arg *arg); +@@ -367,6 +376,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 transactionapply(void); +@@ -506,6 +516,7 @@ static struct wlr_xwayland *xwayland; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.h" + + #include "ime.h" + +@@ -763,6 +774,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 && c->mon->lt[c->mon->sellt]->arrange == btrtile) { ++ 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); +@@ -889,6 +911,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); + } +@@ -1254,6 +1278,8 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ /* btrtile */ ++ init_tree(m); + + /* The xdg-protocol specifies: + * +@@ -1503,6 +1529,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); +@@ -2093,6 +2123,7 @@ 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; + Client *c = NULL, *w = NULL; +@@ -2148,28 +2179,45 @@ 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) { ++ resize_pending = 1; ++ if (selmon) { ++ wlr_output_schedule_frame(selmon->wlr_output); ++ } ++ } 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. */ +@@ -2203,22 +2251,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; ++ } + } + } + +@@ -2418,6 +2484,9 @@ rendermon(struct wl_listener *listener, void *data) + Monitor *m = wl_container_of(listener, m, frame); + struct timespec now; + ++ /* btrtile */ ++ apply_pending_resize(m); ++ + if (!wlr_scene_output_needs_frame(m->scene_output)) { + return; + } +@@ -2461,10 +2530,16 @@ resize(Client *c, struct wlr_box geo, int interact) + { + struct wlr_box *bbox; + uint32_t serial; ++ int immediate; + + if (!c->mon || !client_surface(c)->mapped) + return; + ++ /* btrtile */ ++ immediate = interact ++ || (cursor_mode == CurResize && grabc && grabc->was_tiled ++ && c->mon->lt[c->mon->sellt]->arrange == btrtile); ++ + bbox = interact ? &sgeom : &c->mon->w; + + client_set_bounds(c, geo.width, geo.height); +@@ -2475,7 +2550,7 @@ resize(Client *c, struct wlr_box geo, int interact) + c->geom.height - 2 * c->bw))) + c->resize_serial = serial; + +- if (interact || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) { ++ if (immediate || (!c->resize_serial && wlr_box_equal(&c->geom, &c->current))) { + c->resize_serial = 0; + applygeom(c); + return; +@@ -3255,7 +3330,10 @@ unmapnotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->flink); + } else if (client_wants_focus(c)) { + focusclient(focustop(selmon), NULL, 1); +- } ++ } else { ++ // btrtile remove clients for each monitor ++ unlink_client(c); ++ } + + if (c->foreign_toplevel_handle) { + wlr_ext_foreign_toplevel_handle_v1_destroy(c->foreign_toplevel_handle); +-- +2.55.0 +