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