diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md new file mode 100644 index 0000000..562ca92 --- /dev/null +++ b/patches/btrtile/README.md @@ -0,0 +1,106 @@ +### Description + +# 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. + +![btrtile in action](./demos/btrtiledemo.gif) + +--- + +# Why btrtile + +While dwl’s patches folder is full of different layouts, I couldn't find suitable layout that would work well with my workflow and single ultrawide monitor setup. btrtile aims to solve that by introducing a layout strategy that splits clients according to user focus and pointer position. + +--- + +# 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 horizontal split. + - Otherwise, it does a vertical 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. + +--- + +# What It Doesn’t Handle + +- **Multi-Tag client Management** + btrtile intentionally reverts clients to a single tag if they span multiple tags. This prevents potential inconsistencies or duplicate entries in its per-tag client tree. + - If you attempt to place a single client on multiple tags while using the btrtile layout, btrtile will enforce a single-tag assignment to maintain stability. + +- **Suckless philosophy** + - Yea, it's a bloat. I tried to hide the suck inside a single file as much I could. While this approach is not ideal, it's how it's at least for now. + +--- + +# 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 sensivity. + 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** + + - [focusdir](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/focusdir) + Great patch to move focus between clients. + + - [rotatetags](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/rotatetags) + Good patch to rotate the view or shift clients between tags. + + - [warpcursor](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/warpcursor) + Moves cursor location to focused client. + + - [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag) + Allows each tag to have individual layout setups. + + - [gaps](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) + Add gaps between clients. + +--- + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) +- [0.7 WITH gapps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7-gapps.patch) + +### Authors +- [julmajustus](https://codeberg.org/julmajustus) diff --git a/patches/btrtile/btrtile-v0.7-gapps.patch b/patches/btrtile/btrtile-v0.7-gapps.patch new file mode 100644 index 0000000..1e8073a --- /dev/null +++ b/patches/btrtile/btrtile-v0.7-gapps.patch @@ -0,0 +1,1041 @@ +From 6d60b3cd6eb4d08f8566e80141071bd33d231c59 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Wed, 1 Jan 2025 19:08:45 +0200 +Subject: [PATCH] btrtile-gapps init + +--- + btrtile.c | 679 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 + + dwl.c | 177 ++++++++++++-- + 3 files changed, 841 insertions(+), 27 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..dea8a8c +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,679 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/01/01 17:26:52 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef enum { ++ COORD_X, ++ COORD_Y ++} CoordType; ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++struct TreeLayout { ++ LayoutNode *root[TAGCOUNT + 1]; ++ struct wl_list tiled_clients[TAGCOUNT + 1]; ++}; ++ ++static void add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients); ++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 split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node_tree(LayoutNode *node); ++static void destroy_tree_layout(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_split_node(LayoutNode *root, LayoutNode *child); ++static LayoutNode *find_suitable_split_node(LayoutNode *client_node, ++ unsigned int need_vertical); ++static LayoutNode *find_closest_client_node(LayoutNode *split_node, ++ enum Direction dir, int current_x, ++ int current_y, LayoutNode **closest, ++ int *closest_dist); ++static unsigned int get_client_center(LayoutNode *node, CoordType type); ++static unsigned int get_current_tag(Monitor *m); ++static void init_tree_layout(Monitor *m); ++static void insert_client(Monitor *m, Client *focused, Client *new_client, ++ LayoutNode **root, struct wl_list *tiled_clients); ++static unsigned int is_client_tiled(Client *c, struct wl_list *tiled_clients); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c, ++ LayoutNode **root, struct wl_list *tiled_clients); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients) ++{ ++ if (!is_client_tiled(c, tiled_clients)) ++ wl_list_insert(tiled_clients, &c->link_tiled); ++} ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ float ratio; ++ int mid; ++ unsigned int 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 (node->is_client_node) { ++ resize(node->client, area, 0); ++ node->client->old_geom = area; ++ return; ++ } ++ ++ ratio = node->split_ratio; ++ if (ratio == 0.0f) ++ ratio = 0.5f; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ if (node->split_vertically) { ++ mid = (int)(area.width * ratio); ++ left_area = (struct wlr_box){ area.x, area.y, mid, area.height}; ++ right_area = (struct wlr_box){ area.x + mid, area.y, ++ area.width - mid, area.height}; ++ ++ if (e) { ++ left_area.width -= gappx / 2; ++ right_area.x += gappx / 2; ++ right_area.width -= gappx / 2; ++ } ++ } else { ++ mid = (int)(area.height * ratio); ++ left_area = (struct wlr_box){ area.x, area.y, area.width, mid }; ++ right_area = (struct wlr_box){ area.x, area.y + mid, ++ area.width, 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) ++{ ++ uint32_t active_tags = m->tagset[m->seltags]; ++ unsigned int n = 0, found = 0, curtag; ++ Client *c, *cc, *tmp, *cur, *focused_client = NULL; ++ LayoutNode **root_ptr; ++ struct wl_list current_clients, *tiled_clients; ++ struct wlr_box full_area = m->w; ++ ++ /* We skip handling clients in btrtile if multiple tags are selected */ ++ if (!m->tree_layout || (active_tags && (active_tags & (active_tags - 1)))) ++ return; ++ curtag = get_current_tag(m); ++ root_ptr = &m->tree_layout->root[curtag]; ++ tiled_clients = &m->tree_layout->tiled_clients[curtag]; ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ n++; ++ if (!focused_client && ++ cursor->x >= c->old_geom.x ++ && cursor->x < c->old_geom.x + c->old_geom.width ++ && cursor->y >= c->old_geom.y ++ && cursor->y < c->old_geom.y + c->old_geom.height) { ++ focused_client = c; ++ } ++ } ++ } ++ ++ /* If no visible clients, clear the node tree and tiled_clients list. */ ++ if (n == 0) { ++ destroy_node_tree(*root_ptr); ++ *root_ptr = NULL; ++ wl_list_init(tiled_clients); ++ return; ++ } ++ ++ /* If no focused client found, pick the first visible */ ++ if (!focused_client) { ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ focused_client = c; ++ break; ++ } ++ } ++ } ++ ++ /* Build a temporary list of currently visible tiled clients. ++ * If new clients are found add them to tree.*/ ++ wl_list_init(¤t_clients); ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ found = 0; ++ /* If client has multiple tags set, we might create infinite ++ * point to self loops by adding the same client to multiple ++ * tiled_clients lists, so hacky way to prevent that is to revert ++ * clients active tags to current active tag if multiple ++ * tags are selected. */ ++ if (c->tags != 1u << curtag) ++ c->tags = 1u << curtag; ++ wl_list_for_each(cc, tiled_clients, link_tiled) { ++ if (cc == c) { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) { ++ insert_client(m, focused_client, c, root_ptr, tiled_clients); ++ } ++ wl_list_insert(¤t_clients, &c->link_temp); ++ } ++ } ++ ++ /* Compare the current list of clients to the previous one and remove ++ * clients that no longer exist on the current tag. ++ * This handles cases where user moves clients to other tags. ++ * When client is closed or killed we manage the list and tree clean up in ++ * destroynotify */ ++ wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) { ++ found = 0; ++ wl_list_for_each(cur, ¤t_clients, link_temp) { ++ if (cur == cc) { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) { ++ remove_client(m, cc, root_ptr, tiled_clients); ++ } ++ } ++ ++ /* Rebuild the updated client list */ ++ wl_list_init(tiled_clients); ++ wl_list_for_each(cur, ¤t_clients, link_temp) { ++ wl_list_insert(tiled_clients, &cur->link_tiled); ++ } ++ ++ /* Tile visible clients. */ ++ apply_layout(m, *root_ptr, 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 split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_vertically = split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++unsigned int ++is_client_tiled(Client *c, struct wl_list *tiled_clients) ++{ ++ Client *cc; ++ wl_list_for_each(cc, tiled_clients, link_tiled) { ++ if (cc == c) ++ return 1; ++ } ++ return 0; ++} ++ ++void ++destroy_node_tree(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node_tree(node->left); ++ destroy_node_tree(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree_layout(Monitor *m) ++{ ++ if (!m) ++ return; ++ for (int i = 0; i <= TAGCOUNT; i++) { ++ destroy_node_tree(m->tree_layout->root[i]); ++ } ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node) ++ 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_split_node(LayoutNode *root, LayoutNode *child) ++{ ++ LayoutNode *res; ++ ++ if (!root || root->is_client_node) ++ return NULL; ++ if (root->left == child || root->right == child) ++ return root; ++ res = find_split_node(root->left, child); ++ return res ? res : find_split_node(root->right, child); ++} ++ ++LayoutNode * ++find_suitable_split_node(LayoutNode *client_node, unsigned int need_vertical) ++{ ++ LayoutNode *node = client_node; ++ unsigned int curtag; ++ ++ curtag = get_current_tag(selmon); ++ /* If we're starting from a client_node, go up one level first */ ++ if (node->is_client_node) { ++ node = node->split_node ? node->split_node : ++ find_split_node(selmon->tree_layout->root[curtag], node); ++ } ++ ++ /* Climb the tree until we find a node that is not client_node and ++ * match needed orientation. */ ++ while (node && (node->is_client_node || ++ node->split_vertically != need_vertical)) { ++ node = node->split_node ? node->split_node : ++ find_split_node(selmon->tree_layout->root[curtag], node); ++ } ++ ++ return node; ++} ++ ++LayoutNode * ++find_closest_client_node(LayoutNode *split_node, enum Direction dir, ++ int current_x, int current_y, LayoutNode **closest, ++ int *closest_dist) ++{ ++ int client_center_x, client_center_y, dist, is_candidate; ++ if (!split_node) ++ return NULL; ++ ++ if (split_node->is_client_node && split_node->client) { ++ client_center_x = get_client_center(split_node, COORD_X); ++ client_center_y = get_client_center(split_node, COORD_Y); ++ dist = 0; ++ is_candidate = 0; ++ ++ switch (dir) { ++ case DIR_LEFT: ++ if (client_center_x < current_x) { ++ dist = current_x - client_center_x; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_RIGHT: ++ if (client_center_x > current_x) { ++ dist = client_center_x - current_x; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_UP: ++ if (client_center_y < current_y) { ++ dist = current_y - client_center_y; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_DOWN: ++ if (client_center_y > current_y) { ++ dist = client_center_y - current_y; ++ is_candidate = 1; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (is_candidate && dist < *closest_dist) { ++ *closest_dist = dist; ++ *closest = split_node; ++ } ++ } ++ ++ /* Recursively search in left and right split_nodes */ ++ find_closest_client_node(split_node->left, dir, current_x, current_y, ++ closest, closest_dist); ++ find_closest_client_node(split_node->right, dir, current_x, current_y, ++ closest, closest_dist); ++ ++ return *closest; ++} ++ ++unsigned int ++get_client_center(LayoutNode *node, CoordType type) ++{ ++ if (!node || !node->is_client_node || !node->client) ++ return 0; ++ ++ switch (type) { ++ case COORD_X: ++ return node->client->old_geom.x + node->client->old_geom.width / 2; ++ case COORD_Y: ++ return node->client->old_geom.y + node->client->old_geom.height / 2; ++ default: ++ return 0; ++ } ++} ++ ++unsigned int ++get_current_tag(Monitor *m) ++{ ++ uint32_t active; ++ ++ if (!m) ++ return 0; ++ ++ active = m->tagset[m->seltags]; ++ for (int i = 0; i < TAGCOUNT; i++) { ++ if (active & (1u << i)) ++ return i; ++ } ++ return 0; ++} ++ ++void ++init_tree_layout(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->tree_layout = calloc(1, sizeof(TreeLayout)); ++ for (int i = 0; i <= TAGCOUNT; i++) { ++ m->tree_layout->root[i] = NULL; ++ wl_list_init(&m->tree_layout->tiled_clients[i]); ++ } ++} ++ ++void ++insert_client(Monitor *m, Client *focused, Client *new_client, ++ LayoutNode **root, struct wl_list *tiled_clients) ++{ ++ int mid_x, mid_y; ++ LayoutNode *old_root, *client_node, *old_client_node, *new_client_node; ++ unsigned int wider; ++ ++ /* If there is no root node, inserted client must be the first one. ++ * If there's no focused client or client node cannot be found for the ++ * focused client we treat them as root node.*/ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } else if (!focused || !(client_node = find_client_node(*root, focused))) { ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(new_client)); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } else { ++ /* We check the cursor location on splittable area and choose ++ * the new client's position. On horizontal splits left node represent ++ * the upper node and vice versa.*/ ++ mid_x = focused->old_geom.x + focused->old_geom.width / 2; ++ mid_y = focused->old_geom.y + focused->old_geom.height / 2; ++ old_client_node = create_client_node(client_node->client); ++ new_client_node = create_client_node(new_client); ++ ++ wider = focused->old_geom.width >= focused->old_geom.height; ++ if (wider) { ++ /* Vertical split */ ++ client_node->split_vertically = 1; ++ if (cursor->x < mid_x) { ++ client_node->left = new_client_node; ++ client_node->right = old_client_node; ++ } else { ++ client_node->left = old_client_node; ++ client_node->right = new_client_node; ++ } ++ } else { ++ /* Horizontal split */ ++ client_node->split_vertically = 0; ++ if (cursor->y < mid_y) { ++ client_node->left = new_client_node; ++ client_node->right = old_client_node; ++ } else { ++ client_node->left = old_client_node; ++ client_node->right = new_client_node; ++ } ++ } ++ /* The old client node becomes the splitnode for the old and new client ++ * nodes.*/ ++ client_node->is_client_node = 0; ++ client_node->client = NULL; ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } ++} ++ ++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, LayoutNode **root, ++ struct wl_list *tiled_clients) ++{ ++ Client *cc, *tmp; ++ ++ *root = remove_client_node(*root, c); ++ wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) { ++ if (cc == c) { ++ wl_list_remove(&cc->link_tiled); ++ break; ++ } ++ } ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *node; ++ float new_ratio; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel); ++ if (!client_node) ++ return; ++ ++ /* Find a suitable vertical node */ ++ node = find_suitable_split_node(client_node, 1); ++ if (!node) ++ return; ++ ++ new_ratio = arg->f ? (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; ++ ++ node->split_ratio = new_ratio; ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ float new_ratio; ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *node; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel); ++ if (!client_node) ++ return; ++ ++ /* Find a suitable horizontal node */ ++ node = find_suitable_split_node(client_node, 0); ++ if (!node) ++ return; ++ ++ new_ratio = arg->f ? (node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) new_ratio = 0.5f; ++ if (new_ratio > 0.95f) new_ratio = 0.95f; ++ ++ node->split_ratio = new_ratio; ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++swapclients(const Arg *arg) ++{ ++ Client *tmp, *sel = focustop(selmon); ++ enum Direction dir = (enum Direction)arg->i; ++ LayoutNode *client_node, *target = NULL, *split_node = NULL; ++ unsigned int current_x, current_y, curtag = get_current_tag(selmon); ++ int closest_dist = INT_MAX; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[curtag], sel); ++ if (!client_node) ++ return; ++ ++ current_x = get_client_center(client_node, COORD_X); ++ current_y = get_client_center(client_node, COORD_Y); ++ ++ /* For up/down swaps, restrict search within the current horizontal split ++ * node if no suitable horizontal split node is found, default to vertical */ ++ if (dir == DIR_UP || dir == DIR_DOWN) { ++ split_node = find_suitable_split_node(client_node, 0); ++ if (!split_node) { ++ return; ++ } ++ } else { ++ split_node = selmon->tree_layout->root[curtag]; ++ } ++ ++ /* Find the closest client node in the specified direction and swap ++ * the clients */ ++ find_closest_client_node(split_node, dir, current_x, current_y, ++ &target, &closest_dist); ++ ++ if (target && target->is_client_node && target->client) { ++ tmp = client_node->client; ++ client_node->client = target->client; ++ target->client = tmp; ++ ++ arrange(selmon); ++ } ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index def2562..604a9a4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct TreeLayout TreeLayout; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,11 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; ++ struct wl_list link_tiled; ++ struct wl_list link_temp; + } Client; + + typedef struct { +@@ -208,6 +213,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ TreeLayout *tree_layout; + }; + + typedef struct { +@@ -250,6 +256,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 +340,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); +@@ -431,6 +441,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -600,10 +611,17 @@ buttonpress(struct wl_listener *listener, void *data) + { + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; +- uint32_t mods; +- Client *c; ++ struct wl_list *tiled_clients; ++ struct wlr_surface *surface; ++ double sx, sy; ++ LayoutNode **root, *old_root; ++ uint32_t mods, curtag, active_tags = selmon->tagset[selmon->seltags]; ++ Client *c, *target; + const Button *b; + ++ curtag = get_current_tag(selmon); ++ root = &selmon->tree_layout->root[curtag]; ++ tiled_clients = &selmon->tree_layout->tiled_clients[curtag]; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + switch (event->state) { +@@ -632,15 +650,49 @@ 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|")) { ++ /* Check if more than one tag is active, if so we escape */ ++ if (active_tags && (active_tags & (active_tags - 1))) ++ break; ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = NULL; ++ surface = NULL; ++ xytonode(cursor->x, cursor->y, &surface, &target, NULL, &sx, &sy); ++ ++ if (target && !target->isfloating && !target->isfullscreen) { ++ insert_client(selmon, target, c, root, tiled_clients); ++ } else { ++ if (!root) { ++ *root = create_client_node(c); ++ add_client_to_tiled_list(c, tiled_clients); ++ } else { ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(c)); ++ add_client_to_tiled_list(c, tiled_clients); ++ } ++ } ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +772,9 @@ 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_layout(m); ++ free(m->tree_layout); ++ m->tree_layout = NULL; + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1026,6 +1081,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree_layout(m); + + /* The xdg-protocol specifies: + * +@@ -1265,6 +1321,15 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->tree_layout) { ++ for (int i = 0; i < TAGCOUNT; i++) { ++ remove_client(selmon, c, ++ &selmon->tree_layout->root[i], ++ &selmon->tree_layout->tiled_clients[i]); ++ } ++ } + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1811,7 +1876,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; +@@ -1865,18 +1931,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +@@ -1910,22 +2014,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- 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, ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.2 + diff --git a/patches/btrtile/btrtile-v0.7.patch b/patches/btrtile/btrtile-v0.7.patch new file mode 100644 index 0000000..be4fbee --- /dev/null +++ b/patches/btrtile/btrtile-v0.7.patch @@ -0,0 +1,1021 @@ +From 2b7e152bd39432cd82738a29a8815cf9264f02d8 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Wed, 1 Jan 2025 19:06:03 +0200 +Subject: [PATCH] btrtile init + +--- + btrtile.c | 659 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 + + dwl.c | 177 +++++++++++--- + 3 files changed, 821 insertions(+), 27 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..7fb0716 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,659 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2025/01/01 18:40:14 by jmakkone ### ########.fr */ ++/* */ ++/* ************************************************************************** */ ++ ++typedef enum { ++ COORD_X, ++ COORD_Y ++} CoordType; ++ ++typedef struct LayoutNode { ++ unsigned int is_client_node; ++ unsigned int split_vertically; ++ float split_ratio; ++ struct LayoutNode *left; ++ struct LayoutNode *right; ++ struct LayoutNode *split_node; ++ Client *client; ++} LayoutNode; ++ ++struct TreeLayout { ++ LayoutNode *root[TAGCOUNT + 1]; ++ struct wl_list tiled_clients[TAGCOUNT + 1]; ++}; ++ ++static void add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients); ++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 split_vertically, ++ LayoutNode *left, LayoutNode *right); ++static void destroy_node_tree(LayoutNode *node); ++static void destroy_tree_layout(Monitor *m); ++static LayoutNode *find_client_node(LayoutNode *node, Client *c); ++static LayoutNode *find_split_node(LayoutNode *root, LayoutNode *child); ++static LayoutNode *find_suitable_split_node(LayoutNode *client_node, ++ unsigned int need_vertical); ++static LayoutNode *find_closest_client_node(LayoutNode *split_node, ++ enum Direction dir, int current_x, ++ int current_y, LayoutNode **closest, ++ int *closest_dist); ++static unsigned int get_client_center(LayoutNode *node, CoordType type); ++static unsigned int get_current_tag(Monitor *m); ++static void init_tree_layout(Monitor *m); ++static void insert_client(Monitor *m, Client *focused, Client *new_client, ++ LayoutNode **root, struct wl_list *tiled_clients); ++static unsigned int is_client_tiled(Client *c, struct wl_list *tiled_clients); ++static LayoutNode *remove_client_node(LayoutNode *node, Client *c); ++static void remove_client(Monitor *m, Client *c, ++ LayoutNode **root, struct wl_list *tiled_clients); ++static void setratio_h(const Arg *arg); ++static void setratio_v(const Arg *arg); ++static void swapclients(const Arg *arg); ++ ++static int resizing_from_mouse = 0; ++static double resize_last_update_x, resize_last_update_y; ++static uint32_t last_resize_time = 0; ++ ++void ++add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients) ++{ ++ if (!is_client_tiled(c, tiled_clients)) ++ wl_list_insert(tiled_clients, &c->link_tiled); ++} ++ ++void ++apply_layout(Monitor *m, LayoutNode *node, ++ struct wlr_box area, unsigned int is_root) ++{ ++ float ratio; ++ int mid; ++ struct wlr_box left_area, right_area; ++ ++ if (!node) ++ return; ++ ++ if (node->is_client_node) { ++ resize(node->client, area, 0); ++ node->client->old_geom = area; ++ return; ++ } ++ ++ ratio = node->split_ratio; ++ if (ratio == 0.0f) ++ ratio = 0.5f; ++ if (ratio < 0.05f) ++ ratio = 0.05f; ++ if (ratio > 0.95f) ++ ratio = 0.95f; ++ ++ if (node->split_vertically) { ++ mid = (int)(area.width * ratio); ++ left_area = (struct wlr_box){ area.x, area.y, mid, area.height}; ++ right_area = (struct wlr_box){ area.x + mid, area.y, ++ area.width - mid, area.height}; ++ } else { ++ mid = (int)(area.height * ratio); ++ left_area = (struct wlr_box){ area.x, area.y, area.width, mid }; ++ right_area = (struct wlr_box){ area.x, area.y + mid, ++ area.width, area.height - mid }; ++ } ++ ++ apply_layout(m, node->left, left_area, 0); ++ apply_layout(m, node->right, right_area, 0); ++} ++ ++void btrtile(Monitor *m) ++{ ++ uint32_t active_tags = m->tagset[m->seltags]; ++ unsigned int n = 0, found = 0, curtag; ++ Client *c, *cc, *tmp, *cur, *focused_client = NULL; ++ LayoutNode **root_ptr; ++ struct wl_list current_clients, *tiled_clients; ++ struct wlr_box full_area = m->w; ++ ++ /* We skip handling clients in btrtile if multiple tags are selected */ ++ if (!m->tree_layout || (active_tags && (active_tags & (active_tags - 1)))) ++ return; ++ curtag = get_current_tag(m); ++ root_ptr = &m->tree_layout->root[curtag]; ++ tiled_clients = &m->tree_layout->tiled_clients[curtag]; ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ n++; ++ if (!focused_client && ++ cursor->x >= c->old_geom.x ++ && cursor->x < c->old_geom.x + c->old_geom.width ++ && cursor->y >= c->old_geom.y ++ && cursor->y < c->old_geom.y + c->old_geom.height) { ++ focused_client = c; ++ } ++ } ++ } ++ ++ /* If no visible clients, clear the node tree and tiled_clients list. */ ++ if (n == 0) { ++ destroy_node_tree(*root_ptr); ++ *root_ptr = NULL; ++ wl_list_init(tiled_clients); ++ return; ++ } ++ ++ /* If no focused client found, pick the first visible */ ++ if (!focused_client) { ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ focused_client = c; ++ break; ++ } ++ } ++ } ++ ++ /* Build a temporary list of currently visible tiled clients. ++ * If new clients are found add them to tree.*/ ++ wl_list_init(¤t_clients); ++ wl_list_for_each_reverse(c, &clients, link) { ++ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { ++ found = 0; ++ /* If client has multiple tags set, we might create infinite ++ * point to self loops by adding the same client to multiple ++ * tiled_clients lists, so hacky way to prevent that is to revert ++ * clients active tags to current active tag if multiple ++ * tags are selected. */ ++ if (c->tags != 1u << curtag) ++ c->tags = 1u << curtag; ++ wl_list_for_each(cc, tiled_clients, link_tiled) { ++ if (cc == c) { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) { ++ insert_client(m, focused_client, c, root_ptr, tiled_clients); ++ } ++ wl_list_insert(¤t_clients, &c->link_temp); ++ } ++ } ++ ++ /* Compare the current list of clients to the previous one and remove ++ * clients that no longer exist on the current tag. ++ * This handles cases where user moves clients to other tags. ++ * When client is closed or killed we manage the list and tree clean up in ++ * destroynotify */ ++ wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) { ++ found = 0; ++ wl_list_for_each(cur, ¤t_clients, link_temp) { ++ if (cur == cc) { ++ found = 1; ++ break; ++ } ++ } ++ if (!found) { ++ remove_client(m, cc, root_ptr, tiled_clients); ++ } ++ } ++ ++ /* Rebuild the updated client list */ ++ wl_list_init(tiled_clients); ++ wl_list_for_each(cur, ¤t_clients, link_temp) { ++ wl_list_insert(tiled_clients, &cur->link_tiled); ++ } ++ ++ /* Tile visible clients. */ ++ apply_layout(m, *root_ptr, 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 split_vertically, ++ LayoutNode *left, LayoutNode *right) ++{ ++ LayoutNode *node = calloc(1, sizeof(LayoutNode)); ++ ++ if (!node) ++ return NULL; ++ node->is_client_node = 0; ++ node->split_vertically = split_vertically; ++ node->left = left; ++ node->right = right; ++ if (left) ++ left->split_node = node; ++ if (right) ++ right->split_node = node; ++ return node; ++} ++ ++unsigned int ++is_client_tiled(Client *c, struct wl_list *tiled_clients) ++{ ++ Client *cc; ++ wl_list_for_each(cc, tiled_clients, link_tiled) { ++ if (cc == c) ++ return 1; ++ } ++ return 0; ++} ++ ++void ++destroy_node_tree(LayoutNode *node) ++{ ++ if (!node) ++ return; ++ if (!node->is_client_node) { ++ destroy_node_tree(node->left); ++ destroy_node_tree(node->right); ++ } ++ free(node); ++} ++ ++void ++destroy_tree_layout(Monitor *m) ++{ ++ if (!m) ++ return; ++ for (int i = 0; i <= TAGCOUNT; i++) { ++ destroy_node_tree(m->tree_layout->root[i]); ++ } ++} ++ ++LayoutNode * ++find_client_node(LayoutNode *node, Client *c) ++{ ++ LayoutNode *res; ++ ++ if (!node) ++ 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_split_node(LayoutNode *root, LayoutNode *child) ++{ ++ LayoutNode *res; ++ ++ if (!root || root->is_client_node) ++ return NULL; ++ if (root->left == child || root->right == child) ++ return root; ++ res = find_split_node(root->left, child); ++ return res ? res : find_split_node(root->right, child); ++} ++ ++LayoutNode * ++find_suitable_split_node(LayoutNode *client_node, unsigned int need_vertical) ++{ ++ LayoutNode *node = client_node; ++ unsigned int curtag; ++ ++ curtag = get_current_tag(selmon); ++ /* If we're starting from a client_node, go up one level first */ ++ if (node->is_client_node) { ++ node = node->split_node ? node->split_node : ++ find_split_node(selmon->tree_layout->root[curtag], node); ++ } ++ ++ /* Climb the tree until we find a node that is not client_node and ++ * match needed orientation. */ ++ while (node && (node->is_client_node || ++ node->split_vertically != need_vertical)) { ++ node = node->split_node ? node->split_node : ++ find_split_node(selmon->tree_layout->root[curtag], node); ++ } ++ ++ return node; ++} ++ ++LayoutNode * ++find_closest_client_node(LayoutNode *split_node, enum Direction dir, ++ int current_x, int current_y, LayoutNode **closest, ++ int *closest_dist) ++{ ++ int client_center_x, client_center_y, dist, is_candidate; ++ if (!split_node) ++ return NULL; ++ ++ if (split_node->is_client_node && split_node->client) { ++ client_center_x = get_client_center(split_node, COORD_X); ++ client_center_y = get_client_center(split_node, COORD_Y); ++ dist = 0; ++ is_candidate = 0; ++ ++ switch (dir) { ++ case DIR_LEFT: ++ if (client_center_x < current_x) { ++ dist = current_x - client_center_x; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_RIGHT: ++ if (client_center_x > current_x) { ++ dist = client_center_x - current_x; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_UP: ++ if (client_center_y < current_y) { ++ dist = current_y - client_center_y; ++ is_candidate = 1; ++ } ++ break; ++ case DIR_DOWN: ++ if (client_center_y > current_y) { ++ dist = client_center_y - current_y; ++ is_candidate = 1; ++ } ++ break; ++ default: ++ break; ++ } ++ ++ if (is_candidate && dist < *closest_dist) { ++ *closest_dist = dist; ++ *closest = split_node; ++ } ++ } ++ ++ /* Recursively search in left and right split_nodes */ ++ find_closest_client_node(split_node->left, dir, current_x, current_y, ++ closest, closest_dist); ++ find_closest_client_node(split_node->right, dir, current_x, current_y, ++ closest, closest_dist); ++ ++ return *closest; ++} ++ ++unsigned int ++get_client_center(LayoutNode *node, CoordType type) ++{ ++ if (!node || !node->is_client_node || !node->client) ++ return 0; ++ ++ switch (type) { ++ case COORD_X: ++ return node->client->old_geom.x + node->client->old_geom.width / 2; ++ case COORD_Y: ++ return node->client->old_geom.y + node->client->old_geom.height / 2; ++ default: ++ return 0; ++ } ++} ++ ++unsigned int ++get_current_tag(Monitor *m) ++{ ++ uint32_t active; ++ ++ if (!m) ++ return 0; ++ ++ active = m->tagset[m->seltags]; ++ for (int i = 0; i < TAGCOUNT; i++) { ++ if (active & (1u << i)) ++ return i; ++ } ++ return 0; ++} ++ ++void ++init_tree_layout(Monitor *m) ++{ ++ if (!m) ++ return; ++ m->tree_layout = calloc(1, sizeof(TreeLayout)); ++ for (int i = 0; i <= TAGCOUNT; i++) { ++ m->tree_layout->root[i] = NULL; ++ wl_list_init(&m->tree_layout->tiled_clients[i]); ++ } ++} ++ ++void ++insert_client(Monitor *m, Client *focused, Client *new_client, ++ LayoutNode **root, struct wl_list *tiled_clients) ++{ ++ int mid_x, mid_y; ++ LayoutNode *old_root, *client_node, *old_client_node, *new_client_node; ++ unsigned int wider; ++ ++ /* If there is no root node, inserted client must be the first one. ++ * If there's no focused client or client node cannot be found for the ++ * focused client we treat them as root node.*/ ++ if (!*root) { ++ *root = create_client_node(new_client); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } else if (!focused || !(client_node = find_client_node(*root, focused))) { ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(new_client)); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } else { ++ /* We check the cursor location on splittable area and choose ++ * the new client's position. On horizontal splits left node represent ++ * the upper node and vice versa.*/ ++ mid_x = focused->old_geom.x + focused->old_geom.width / 2; ++ mid_y = focused->old_geom.y + focused->old_geom.height / 2; ++ old_client_node = create_client_node(client_node->client); ++ new_client_node = create_client_node(new_client); ++ ++ wider = focused->old_geom.width >= focused->old_geom.height; ++ if (wider) { ++ /* Vertical split */ ++ client_node->split_vertically = 1; ++ if (cursor->x < mid_x) { ++ client_node->left = new_client_node; ++ client_node->right = old_client_node; ++ } else { ++ client_node->left = old_client_node; ++ client_node->right = new_client_node; ++ } ++ } else { ++ /* Horizontal split */ ++ client_node->split_vertically = 0; ++ if (cursor->y < mid_y) { ++ client_node->left = new_client_node; ++ client_node->right = old_client_node; ++ } else { ++ client_node->left = old_client_node; ++ client_node->right = new_client_node; ++ } ++ } ++ /* The old client node becomes the splitnode for the old and new client ++ * nodes.*/ ++ client_node->is_client_node = 0; ++ client_node->client = NULL; ++ add_client_to_tiled_list(new_client, tiled_clients); ++ } ++} ++ ++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, LayoutNode **root, ++ struct wl_list *tiled_clients) ++{ ++ Client *cc, *tmp; ++ ++ *root = remove_client_node(*root, c); ++ wl_list_for_each_safe(cc, tmp, tiled_clients, link_tiled) { ++ if (cc == c) { ++ wl_list_remove(&cc->link_tiled); ++ break; ++ } ++ } ++} ++ ++void ++setratio_h(const Arg *arg) ++{ ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *node; ++ float new_ratio; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel); ++ if (!client_node) ++ return; ++ ++ /* Find a suitable vertical node */ ++ node = find_suitable_split_node(client_node, 1); ++ if (!node) ++ return; ++ ++ new_ratio = arg->f ? (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; ++ ++ node->split_ratio = new_ratio; ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++setratio_v(const Arg *arg) ++{ ++ float new_ratio; ++ Client *sel = focustop(selmon); ++ LayoutNode *client_node, *node; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[get_current_tag(selmon)], sel); ++ if (!client_node) ++ return; ++ ++ /* Find a suitable horizontal node */ ++ node = find_suitable_split_node(client_node, 0); ++ if (!node) ++ return; ++ ++ new_ratio = arg->f ? (node->split_ratio + arg->f) : 0.5f; ++ if (new_ratio < 0.05f) new_ratio = 0.5f; ++ if (new_ratio > 0.95f) new_ratio = 0.95f; ++ ++ node->split_ratio = new_ratio; ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ ++ if (!resizing_from_mouse) { ++ arrange(selmon); ++ } ++} ++ ++void ++swapclients(const Arg *arg) ++{ ++ Client *tmp, *sel = focustop(selmon); ++ enum Direction dir = (enum Direction)arg->i; ++ LayoutNode *client_node, *target = NULL, *split_node = NULL; ++ unsigned int current_x, current_y, curtag = get_current_tag(selmon); ++ int closest_dist = INT_MAX; ++ ++ if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) ++ return; ++ ++ client_node = find_client_node(selmon->tree_layout->root[curtag], sel); ++ if (!client_node) ++ return; ++ ++ current_x = get_client_center(client_node, COORD_X); ++ current_y = get_client_center(client_node, COORD_Y); ++ ++ /* For up/down swaps, restrict search within the current horizontal split ++ * node if no suitable horizontal split node is found, default to vertical */ ++ if (dir == DIR_UP || dir == DIR_DOWN) { ++ split_node = find_suitable_split_node(client_node, 0); ++ if (!split_node) { ++ return; ++ } ++ } else { ++ split_node = selmon->tree_layout->root[curtag]; ++ } ++ ++ /* Find the closest client node in the specified direction and swap ++ * the clients */ ++ find_closest_client_node(split_node, dir, current_x, current_y, ++ &target, &closest_dist); ++ ++ if (target && target->is_client_node && target->client) { ++ tmp = client_node->client; ++ client_node->client = target->client; ++ target->client = tmp; ++ ++ arrange(selmon); ++ } ++} +diff --git a/config.def.h b/config.def.h +index 22d2171..92f3ad6 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); + /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */ + static const float fullscreen_bg[] = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */ ++static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */ ++static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */ + ++enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN }; + /* tagging - TAGCOUNT must be no greater than 31 */ + #define TAGCOUNT (9) + +@@ -31,6 +34,7 @@ static const Rule rules[] = { + /* layout(s) */ + static const Layout layouts[] = { + /* symbol arrange function */ ++ { "|w|", btrtile }, + { "[]=", tile }, + { "><>", NULL }, /* no layout function means floating behavior */ + { "[M]", monocle }, +@@ -148,6 +152,14 @@ static const Key keys[] = { + { MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} }, ++ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} }, ++ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} }, + TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0), + TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1), + TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2), +diff --git a/dwl.c b/dwl.c +index def2562..604a9a4 100644 +--- a/dwl.c ++++ b/dwl.c +@@ -1,6 +1,7 @@ + /* + * See LICENSE file for copyright and license details. + */ ++#include + #include + #include + #include +@@ -103,6 +104,7 @@ typedef struct { + const Arg arg; + } Button; + ++typedef struct TreeLayout TreeLayout; + typedef struct Monitor Monitor; + typedef struct { + /* Must keep these three elements in this order */ +@@ -139,8 +141,11 @@ typedef struct { + #endif + unsigned int bw; + uint32_t tags; +- int isfloating, isurgent, isfullscreen; ++ int isfloating, isurgent, isfullscreen, was_tiled; + uint32_t resize; /* configure serial of a pending resize */ ++ struct wlr_box old_geom; ++ struct wl_list link_tiled; ++ struct wl_list link_temp; + } Client; + + typedef struct { +@@ -208,6 +213,7 @@ struct Monitor { + int nmaster; + char ltsymbol[16]; + int asleep; ++ TreeLayout *tree_layout; + }; + + typedef struct { +@@ -250,6 +256,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 +340,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); +@@ -431,6 +441,7 @@ static xcb_atom_t netatom[NetLast]; + + /* attempt to encapsulate suck into one file */ + #include "client.h" ++#include "btrtile.c" + + /* function implementations */ + void +@@ -600,10 +611,17 @@ buttonpress(struct wl_listener *listener, void *data) + { + struct wlr_pointer_button_event *event = data; + struct wlr_keyboard *keyboard; +- uint32_t mods; +- Client *c; ++ struct wl_list *tiled_clients; ++ struct wlr_surface *surface; ++ double sx, sy; ++ LayoutNode **root, *old_root; ++ uint32_t mods, curtag, active_tags = selmon->tagset[selmon->seltags]; ++ Client *c, *target; + const Button *b; + ++ curtag = get_current_tag(selmon); ++ root = &selmon->tree_layout->root[curtag]; ++ tiled_clients = &selmon->tree_layout->tiled_clients[curtag]; + wlr_idle_notifier_v1_notify_activity(idle_notifier, seat); + + switch (event->state) { +@@ -632,15 +650,49 @@ 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|")) { ++ /* Check if more than one tag is active, if so we escape */ ++ if (active_tags && (active_tags & (active_tags - 1))) ++ break; ++ if (cursor_mode == CurMove && c->isfloating) { ++ target = NULL; ++ surface = NULL; ++ xytonode(cursor->x, cursor->y, &surface, &target, NULL, &sx, &sy); ++ ++ if (target && !target->isfloating && !target->isfullscreen) { ++ insert_client(selmon, target, c, root, tiled_clients); ++ } else { ++ if (!root) { ++ *root = create_client_node(c); ++ add_client_to_tiled_list(c, tiled_clients); ++ } else { ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(c)); ++ add_client_to_tiled_list(c, tiled_clients); ++ } ++ } ++ ++ setfloating(c, 0); ++ arrange(selmon); ++ ++ } else if (cursor_mode == CurResize && !c->isfloating) { ++ resizing_from_mouse = 0; ++ } ++ } else { ++ if (cursor_mode == CurResize && resizing_from_mouse) ++ resizing_from_mouse = 0; ++ } ++ /* Default behaviour */ + wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); + cursor_mode = CurNormal; + /* Drop the window off on its new monitor */ + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; +- } else { +- cursor_mode = CurNormal; + } ++ cursor_mode = CurNormal; + break; + } + /* If the event wasn't handled by the compositor, notify the client with +@@ -720,6 +772,9 @@ 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_layout(m); ++ free(m->tree_layout); ++ m->tree_layout = NULL; + closemon(m); + wlr_scene_node_destroy(&m->fullscreen_bg->node); + free(m); +@@ -1026,6 +1081,7 @@ createmon(struct wl_listener *listener, void *data) + + wl_list_insert(&mons, &m->link); + printstatus(); ++ init_tree_layout(m); + + /* The xdg-protocol specifies: + * +@@ -1265,6 +1321,15 @@ destroynotify(struct wl_listener *listener, void *data) + wl_list_remove(&c->destroy.link); + wl_list_remove(&c->set_title.link); + wl_list_remove(&c->fullscreen.link); ++ /* We check if the destroyed client was part of any tiled_list, to catch ++ * client removals even if they would not be currently managed by btrtile */ ++ if (selmon && selmon->tree_layout) { ++ for (int i = 0; i < TAGCOUNT; i++) { ++ remove_client(selmon, c, ++ &selmon->tree_layout->root[i], ++ &selmon->tree_layout->tiled_clients[i]); ++ } ++ } + #ifdef XWAYLAND + if (c->type != XDGShell) { + wl_list_remove(&c->activate.link); +@@ -1811,7 +1876,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; +@@ -1865,18 +1931,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d + /* Update drag icon's position */ + wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y)); + +- /* If we are currently grabbing the mouse, handle and return */ ++ /* Skip if internal call or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ ++ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen; + if (cursor_mode == CurMove) { + /* Move the grabbed client to the new position. */ +- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy, +- .width = grabc->geom.width, .height = grabc->geom.height}, 1); +- return; ++ if (grabc && grabc->isfloating) { ++ resize(grabc, (struct wlr_box){ ++ .x = (int)round(cursor->x) - grabcx, ++ .y = (int)round(cursor->y) - grabcy, ++ .width = grabc->geom.width, ++ .height = grabc->geom.height ++ }, 1); ++ return; ++ } + } else if (cursor_mode == CurResize) { +- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y, +- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1); +- return; ++ if (tiled && resizing_from_mouse) { ++ dx_total = cursor->x - resize_last_update_x; ++ dy_total = cursor->y - resize_last_update_y; ++ ++ if (time - last_resize_time >= resize_interval_ms) { ++ Arg a = {0}; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * resize_factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * resize_factor); ++ setratio_v(&a); ++ } ++ arrange(selmon); ++ ++ last_resize_time = time; ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ } ++ ++ } else if (grabc && grabc->isfloating) { ++ /* Floating resize as original */ ++ resize(grabc, (struct wlr_box){ ++ .x = grabc->geom.x, ++ .y = grabc->geom.y, ++ .width = (int)round(cursor->x) - grabc->geom.x, ++ .height = (int)round(cursor->y) - grabc->geom.y ++ }, 1); ++ return; ++ } + } + ++focus: + /* If there's no client surface under the cursor, set the cursor image to a + * default. This is what makes the cursor image appear when you move it + * off of a client or over its border. */ +@@ -1910,22 +2014,41 @@ moveresize(const Arg *arg) + if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) + return; + +- /* Float the window and tell motionnotify to grab it */ +- setfloating(grabc, 1); +- switch (cursor_mode = arg->ui) { +- case CurMove: +- grabcx = (int)round(cursor->x) - grabc->geom.x; +- grabcy = (int)round(cursor->y) - grabc->geom.y; +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); +- 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, ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_last_update_x = cursor->x; ++ resize_last_update_y = cursor->y; ++ resizing_from_mouse = 1; ++ break; ++ } ++ } else { ++ /* Default floating logic */ ++ /* Float the window and tell motionnotify to grab it */ ++ setfloating(grabc, 1); ++ switch (cursor_mode) { ++ case CurMove: ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_warp_closest(cursor, NULL, + grabc->geom.x + grabc->geom.width, + grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } + } + +-- +2.45.2 + diff --git a/patches/btrtile/demos/btrtiledemo.gif b/patches/btrtile/demos/btrtiledemo.gif new file mode 100644 index 0000000..8f896bf Binary files /dev/null and b/patches/btrtile/demos/btrtiledemo.gif differ