From 15ed11dd2e02ae205062b1d73e7a36e5714dbe90 Mon Sep 17 00:00:00 2001 From: julmajustus Date: Mon, 30 Dec 2024 02:25:57 +0200 Subject: [PATCH] Add btrtile --- patches/btrtile/README.md | 10 + patches/btrtile/btrtile-v0.7-gapps.patch | 1055 ++++++++++++++++++++++ patches/btrtile/btrtile-v0.7.patch | 1035 +++++++++++++++++++++ 3 files changed, 2100 insertions(+) create mode 100644 patches/btrtile/README.md create mode 100644 patches/btrtile/btrtile-v0.7-gapps.patch create mode 100644 patches/btrtile/btrtile-v0.7.patch diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md new file mode 100644 index 0000000..0d804ce --- /dev/null +++ b/patches/btrtile/README.md @@ -0,0 +1,10 @@ +### Description +Dynamic tiling layout desingned for ultrawide monitors. It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized. It combines both the layout and client management under one patch. + +More detailed description on my [github](https://github.com/julmajustus/dwl-patches). + +### Download +- [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.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..d790216 --- /dev/null +++ b/patches/btrtile/btrtile-v0.7-gapps.patch @@ -0,0 +1,1055 @@ +From cdeedfa24d7f2fbc4a916d953e7a389fd24a281c Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Mon, 23 Dec 2024 20:27:50 +0200 +Subject: [PATCH] Btrtile layout with gapps + +--- + btrtile.c | 669 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 + + dwl.c | 206 +++++++++++++--- + 3 files changed, 849 insertions(+), 38 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..12ccd20 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,669 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2024/12/23 20:26:09 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_init_x, resize_init_y; ++static double resize_last_update_x, resize_last_update_y; ++static unsigned int arrange_counter = 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. ++ * Aka 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 (*root == NULL) { ++ *root = create_client_node(new_client); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ return; ++ } ++ if (!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); ++ return; ++ } ++ ++ client_node = find_client_node(*root, focused); ++ if (!client_node) { ++ /*Focused client not found in the tree, insert as root*/ ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(new_client)); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ return; ++ } ++ 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->is_client_node = 0; ++ 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->is_client_node = 0; ++ 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; ++ } ++ } ++ 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 child node is NULL after removal and the other is not, ++ * we "lift" the other child 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 resize if done 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 resize if done 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..c7924cb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,9 +11,12 @@ static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); ++static const unsigned int ARRANGE_RATE = 3; /* Call rate limiter for client rearrange when resizing with mouse */ ++static const unsigned int resize_threshold = 5; /* Pixel threshold for mouse resizing, smaller threshold for smoother updates */ + /* 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 */ + ++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 }, +@@ -127,6 +131,14 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { 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, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..e9cb17b 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); +@@ -432,6 +442,7 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "btrtile.c" + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -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) { +@@ -631,18 +649,52 @@ buttonpress(struct wl_listener *listener, void *data) + case WL_POINTER_BUTTON_STATE_RELEASED: + /* 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) { +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); +- cursor_mode = CurNormal; ++ 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); +- return; +- } else { +- cursor_mode = CurNormal; +- } +- break; +- } ++ selmon = xytomon(cursor->x, cursor->y); ++ setmon(grabc, selmon, 0); ++ grabc = NULL; ++ return; ++ } ++ cursor_mode = CurNormal; ++ break; ++ } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, +@@ -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); +@@ -1025,6 +1080,7 @@ createmon(struct wl_listener *listener, void *data) + wl_list_insert(&mons, &m->link); + printstatus(); + ++ init_tree_layout(m); + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +@@ -1263,6 +1319,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); +@@ -1809,7 +1874,9 @@ 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; ++ float factor; ++ 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; +@@ -1863,18 +1930,60 @@ 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 or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ + /* If we are currently grabbing the mouse, handle and return */ ++ 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 (fabs(dx_total) > resize_threshold || fabs(dy_total) > resize_threshold) { ++ Arg a = {0}; ++ factor = 0.0002f; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * factor); ++ setratio_v(&a); ++ } ++ /* Limit arrange rate to reduce recursion calls */ ++ if (++arrange_counter >= ARRANGE_RATE) { ++ arrange(selmon); ++ arrange_counter = 0; ++ } ++ 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. */ +@@ -1908,23 +2017,44 @@ 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, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; +- } ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_init_x = cursor->x; ++ resize_init_y = cursor->y; ++ resize_last_update_x = resize_init_x; ++ resize_last_update_y = resize_init_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; ++ } ++ } + } + + void +-- +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..0c3b2a2 --- /dev/null +++ b/patches/btrtile/btrtile-v0.7.patch @@ -0,0 +1,1035 @@ +From 2a2d5dd776834879005f1dbcb5c2cc8aaab90125 Mon Sep 17 00:00:00 2001 +From: julmajustus +Date: Mon, 23 Dec 2024 20:24:21 +0200 +Subject: [PATCH] Btrtile layout + +--- + btrtile.c | 649 +++++++++++++++++++++++++++++++++++++++++++++++++++ + config.def.h | 12 + + dwl.c | 206 +++++++++++++--- + 3 files changed, 829 insertions(+), 38 deletions(-) + create mode 100644 btrtile.c + +diff --git a/btrtile.c b/btrtile.c +new file mode 100644 +index 0000000..955b374 +--- /dev/null ++++ b/btrtile.c +@@ -0,0 +1,649 @@ ++/* ************************************************************************** */ ++/* */ ++/* ::: :::::::: */ ++/* btrtile.c :+: :+: :+: */ ++/* +:+ +:+ +:+ */ ++/* By: jmakkone +#+ +:+ +#+ */ ++/* +#+#+#+#+#+ +#+ */ ++/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ ++/* Updated: 2024/12/23 20:23:05 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_init_x, resize_init_y; ++static double resize_last_update_x, resize_last_update_y; ++static unsigned int arrange_counter = 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. ++ * Aka 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 (*root == NULL) { ++ *root = create_client_node(new_client); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ return; ++ } ++ if (!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); ++ return; ++ } ++ ++ client_node = find_client_node(*root, focused); ++ if (!client_node) { ++ /*Focused client not found in the tree, insert as root*/ ++ old_root = *root; ++ *root = create_split_node(1, old_root, create_client_node(new_client)); ++ add_client_to_tiled_list(new_client, tiled_clients); ++ return; ++ } ++ 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->is_client_node = 0; ++ 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->is_client_node = 0; ++ 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; ++ } ++ } ++ 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 child node is NULL after removal and the other is not, ++ * we "lift" the other child 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 resize if done 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 resize if done 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..c7924cb 100644 +--- a/config.def.h ++++ b/config.def.h +@@ -11,9 +11,12 @@ static const float rootcolor[] = COLOR(0x222222ff); + static const float bordercolor[] = COLOR(0x444444ff); + static const float focuscolor[] = COLOR(0x005577ff); + static const float urgentcolor[] = COLOR(0xff0000ff); ++static const unsigned int ARRANGE_RATE = 3; /* Call rate limiter for client rearrange when resizing with mouse */ ++static const unsigned int resize_threshold = 5; /* Pixel threshold for mouse resizing, smaller threshold for smoother updates */ + /* 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 */ + ++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 }, +@@ -127,6 +131,14 @@ static const Key keys[] = { + /* modifier key function argument */ + { MODKEY, XKB_KEY_p, spawn, {.v = menucmd} }, + { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Return, spawn, {.v = termcmd} }, ++ { 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, XKB_KEY_j, focusstack, {.i = +1} }, + { MODKEY, XKB_KEY_k, focusstack, {.i = -1} }, + { MODKEY, XKB_KEY_i, incnmaster, {.i = +1} }, +diff --git a/dwl.c b/dwl.c +index a2711f6..e9cb17b 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); +@@ -432,6 +442,7 @@ static xcb_atom_t netatom[NetLast]; + /* attempt to encapsulate suck into one file */ + #include "client.h" + ++#include "btrtile.c" + /* function implementations */ + void + applybounds(Client *c, struct wlr_box *bbox) +@@ -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) { +@@ -631,18 +649,52 @@ buttonpress(struct wl_listener *listener, void *data) + case WL_POINTER_BUTTON_STATE_RELEASED: + /* 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) { +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "default"); +- cursor_mode = CurNormal; ++ 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); +- return; +- } else { +- cursor_mode = CurNormal; +- } +- break; +- } ++ selmon = xytomon(cursor->x, cursor->y); ++ setmon(grabc, selmon, 0); ++ grabc = NULL; ++ return; ++ } ++ cursor_mode = CurNormal; ++ break; ++ } + /* If the event wasn't handled by the compositor, notify the client with + * pointer focus that a button press has occurred */ + wlr_seat_pointer_notify_button(seat, +@@ -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); +@@ -1025,6 +1080,7 @@ createmon(struct wl_listener *listener, void *data) + wl_list_insert(&mons, &m->link); + printstatus(); + ++ init_tree_layout(m); + /* The xdg-protocol specifies: + * + * If the fullscreened surface is not opaque, the compositor must make +@@ -1263,6 +1319,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); +@@ -1809,7 +1874,9 @@ 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; ++ float factor; ++ 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; +@@ -1863,18 +1930,60 @@ 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 or already resizing */ ++ if (time == 0 && resizing_from_mouse) ++ goto focus; ++ + /* If we are currently grabbing the mouse, handle and return */ ++ 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 (fabs(dx_total) > resize_threshold || fabs(dy_total) > resize_threshold) { ++ Arg a = {0}; ++ factor = 0.0002f; ++ if (fabs(dx_total) > fabs(dy_total)) { ++ a.f = (float)(dx_total * factor); ++ setratio_h(&a); ++ } else { ++ a.f = (float)(dy_total * factor); ++ setratio_v(&a); ++ } ++ /* Limit arrange rate to reduce recursion calls */ ++ if (++arrange_counter >= ARRANGE_RATE) { ++ arrange(selmon); ++ arrange_counter = 0; ++ } ++ 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. */ +@@ -1908,23 +2017,44 @@ 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, +- grabc->geom.x + grabc->geom.width, +- grabc->geom.y + grabc->geom.height); +- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); +- break; +- } ++ cursor_mode = arg->ui; ++ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen); ++ ++ if (grabc->was_tiled) { ++ switch (cursor_mode) { ++ case CurMove: ++ setfloating(grabc, 1); ++ grabcx = (int)round(cursor->x) - grabc->geom.x; ++ grabcy = (int)round(cursor->y) - grabc->geom.y; ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur"); ++ break; ++ case CurResize: ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ resize_init_x = cursor->x; ++ resize_init_y = cursor->y; ++ resize_last_update_x = resize_init_x; ++ resize_last_update_y = resize_init_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; ++ } ++ } + } + + void +-- +2.45.2 +