diff --git a/patches/btrtile/README.md b/patches/btrtile/README.md index 0d804ce..32832d7 100644 --- a/patches/btrtile/README.md +++ b/patches/btrtile/README.md @@ -1,7 +1,102 @@ ### 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). +# btrtile — A Focus-Driven Tiling Layout + +It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized. + +![btrtile in action](./demos/btrtiledemo.gif) + +--- + +# Why btrtile + +While dwl’s patches folder is full of different layouts, I couldn't find suitable layout that would work well with my workflow and single ultrawide monitor setup. btrtile aims to solve that by introducing a layout strategy that splits clients according to user focus and pointer position. + +--- + +# Features + +- **Combined Tiling and Management** + Combines tiling layout and management of clients under one patchset. + +- **Focus-Driven Splits** + When you add a new client, btrtile checks where your pointer is relative to the focused client’s geometry. + - If the pointer is on the left half (for a horizontally large client), the new client spawns on the left side, and vice versa. + - By default, new splits are 50/50. + +- **Adaptive Splitting** + - If the area to be split is wider than its height, btrtile does a horizontal split. + - Otherwise, it does a vertical split. + +- **Keyboard and Mouse Driven** + - Supports keyboard-based commands for quick ratio adjustments and client swapping. + - Mouse-based resizing and moving are integrated for more intuitive manipulation. + +--- + +# How It Works + +btrtile organizes clients using a binary tree data structure that represents splits either vertically or horizontally. + +When a new client appears: +1. **Focused Client Detection** + btrtile checks your pointer location to find which client (if any) you’re interacting with. +2. **Split Creation** + - If there’s a focused client, btrtile creates a split node around it, placing the new client on the side where your pointer is. + +3. **Ratio Management** + Each split node has a `split_ratio` (defaulting to 0.5). This ratio defines how much space is allocated to each child node. You can adjust this ratio using keyboard or mouse actions. + +--- + +# What It Doesn’t Handle + +- **Multi-Tag client Management** + btrtile intentionally reverts clients to a single tag if they span multiple tags. This prevents potential inconsistencies or duplicate entries in its per-tag client tree. + - If you attempt to place a single client on multiple tags while using the btrtile layout, btrtile will enforce a single-tag assignment to maintain stability. + +- **Suckless philosophy** + - Yea, it's a bloat. I tried to hide the suck inside a single file as much I could. While this approach is not ideal, it's how it's at least for now. + +--- + +# Configuring btrtile + +btrtile adds couple variables to config.h to fine tune the mouse resizing of tiled clients. + +1. **resize_factor** + - A multiplier to transfer pointer movement to client weight ratio. Depends heavily on mouse sensivity. + Defaults to 0.0002f. + +2. **resize_interval_ms** + - A time based resize call limiter. Depends on framerate and screen refresh rate. + Defaults to 16ms. (~60 resize updates per second) + +Fine tune these values to find the best values for your setup, smoother resizing can significally increase cpu overhead. +If mouse resizing feels sluggish, you can try compiling dwl with more aggressive optimization flags like -O2/-O3. + +--- + +# Patch recommendations + +1. **Patches that I use with my btrtile** + + - [focusdir](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/focusdir) + Great patch to move focus between clients. + + - [rotatetags](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/rotatetags) + Good patch to rotate the view or shift clients between tags. + + - [warpcursor](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/warpcursor) + Moves cursor location to focused client. + + - [pertag](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/pertag) + Allows each tag to have individual layout setups. + + - [gaps](https://codeberg.org/dwl/dwl-patches/src/branch/main/patches/gaps) + Add gaps between clients. + +--- ### Download - [0.7](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.7.patch) diff --git a/patches/btrtile/btrtile-v0.7-gapps.patch b/patches/btrtile/btrtile-v0.7-gapps.patch index d790216..1e8073a 100644 --- a/patches/btrtile/btrtile-v0.7-gapps.patch +++ b/patches/btrtile/btrtile-v0.7-gapps.patch @@ -1,21 +1,21 @@ -From cdeedfa24d7f2fbc4a916d953e7a389fd24a281c Mon Sep 17 00:00:00 2001 +From 6d60b3cd6eb4d08f8566e80141071bd33d231c59 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Mon, 23 Dec 2024 20:27:50 +0200 -Subject: [PATCH] Btrtile layout with gapps +Date: Wed, 1 Jan 2025 19:08:45 +0200 +Subject: [PATCH] btrtile-gapps init --- - btrtile.c | 669 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 679 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 + - dwl.c | 206 +++++++++++++--- - 3 files changed, 849 insertions(+), 38 deletions(-) + dwl.c | 177 ++++++++++++-- + 3 files changed, 841 insertions(+), 27 deletions(-) create mode 100644 btrtile.c diff --git a/btrtile.c b/btrtile.c new file mode 100644 -index 0000000..12ccd20 +index 0000000..dea8a8c --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,669 @@ +@@ -0,0 +1,679 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ @@ -24,7 +24,7 @@ index 0000000..12ccd20 +/* By: jmakkone +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2024/12/23 20:26:09 by jmakkone ### ########.fr */ ++/* Updated: 2025/01/01 17:26:52 by jmakkone ### ########.fr */ +/* */ +/* ************************************************************************** */ + @@ -50,35 +50,37 @@ index 0000000..12ccd20 + +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); ++ 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 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 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); ++ 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 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; ++static uint32_t last_resize_time = 0; + +void +add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients) @@ -89,7 +91,7 @@ index 0000000..12ccd20 + +void +apply_layout(Monitor *m, LayoutNode *node, -+ struct wlr_box area, unsigned int is_root) ++ struct wlr_box area, unsigned int is_root) +{ + float ratio; + int mid; @@ -122,9 +124,9 @@ index 0000000..12ccd20 + + if (node->split_vertically) { + mid = (int)(area.width * ratio); -+ left_area = (struct wlr_box){ area.x, area.y, mid, area.height }; ++ 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 }; ++ area.width - mid, area.height}; + + if (e) { + left_area.width -= gappx / 2; @@ -134,7 +136,7 @@ index 0000000..12ccd20 + } 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, ++ right_area = (struct wlr_box){ area.x, area.y + mid, + area.width, area.height - mid }; + + if (e) { @@ -167,9 +169,9 @@ index 0000000..12ccd20 + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { + n++; + if (!focused_client && -+ cursor->x >= c->old_geom.x ++ 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 + && cursor->y < c->old_geom.y + c->old_geom.height) { + focused_client = c; + } @@ -200,10 +202,11 @@ index 0000000..12ccd20 + 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 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) { @@ -219,10 +222,11 @@ index 0000000..12ccd20 + } + } + -+ /* 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 */ ++ /* 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) { @@ -260,8 +264,8 @@ index 0000000..12ccd20 +} + +LayoutNode * -+create_split_node(unsigned int split_vertically, -+ LayoutNode *left, LayoutNode *right) ++create_split_node(unsigned int split_vertically, ++ LayoutNode *left, LayoutNode *right) +{ + LayoutNode *node = calloc(1, sizeof(LayoutNode)); + @@ -283,7 +287,8 @@ index 0000000..12ccd20 +{ + Client *cc; + wl_list_for_each(cc, tiled_clients, link_tiled) { -+ if (cc == c) return 1; ++ if (cc == c) ++ return 1; + } + return 0; +} @@ -345,14 +350,15 @@ index 0000000..12ccd20 + 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 : ++ 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 ++ /* 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 : ++ 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); + } + @@ -360,7 +366,9 @@ index 0000000..12ccd20 +} + +LayoutNode * -+find_closest_client_node(LayoutNode *split_node, enum Direction dir, int current_x, int current_y, LayoutNode **closest, int *closest_dist) ++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) @@ -408,8 +416,10 @@ index 0000000..12ccd20 + } + + /* 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); ++ 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; +} @@ -443,7 +453,7 @@ index 0000000..12ccd20 + if (active & (1u << i)) + return i; + } -+ return 0; ++ return 0; +} + +void @@ -466,64 +476,59 @@ index 0000000..12ccd20 + LayoutNode *old_root, *client_node, *old_client_node, *new_client_node; + unsigned int wider; + -+ if (*root == NULL) { ++ /* 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); -+ return; -+ } -+ if (!focused) { ++ } 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); -+ 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; ++ /* 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 { -+ client_node->left = old_client_node; -+ client_node->right = new_client_node; ++ /* 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); + } -+ 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) { @@ -539,8 +544,8 @@ index 0000000..12ccd20 + 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 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; + @@ -569,7 +574,8 @@ index 0000000..12ccd20 +} + +void -+remove_client(Monitor *m, Client *c, LayoutNode **root, struct wl_list *tiled_clients) ++remove_client(Monitor *m, Client *c, LayoutNode **root, ++ struct wl_list *tiled_clients) +{ + Client *cc, *tmp; + @@ -608,7 +614,8 @@ index 0000000..12ccd20 + new_ratio = 0.95f; + + node->split_ratio = new_ratio; -+ /* Skip the resize if done by mouse, we call arrange from motionotify */ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ + if (!resizing_from_mouse) { + arrange(selmon); + } @@ -638,7 +645,8 @@ index 0000000..12ccd20 + 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 */ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ + if (!resizing_from_mouse) { + arrange(selmon); + } @@ -650,7 +658,7 @@ index 0000000..12ccd20 + 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); ++ unsigned int current_x, current_y, curtag = get_current_tag(selmon); + int closest_dist = INT_MAX; + + if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) @@ -663,8 +671,8 @@ index 0000000..12ccd20 + 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 */ ++ /* 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) { @@ -674,8 +682,10 @@ index 0000000..12ccd20 + 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); ++ /* 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; @@ -686,17 +696,15 @@ index 0000000..12ccd20 + } +} diff --git a/config.def.h b/config.def.h -index 22d2171..c7924cb 100644 +index 22d2171..92f3ad6 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); +@@ -13,7 +13,10 @@ 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 */ ++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 */ @@ -710,10 +718,10 @@ index 22d2171..c7924cb 100644 { "[]=", 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} }, +@@ -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} }, @@ -722,11 +730,11 @@ index 22d2171..c7924cb 100644 + { 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} }, + 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 a2711f6..e9cb17b 100644 +index def2562..604a9a4 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -784,14 +792,14 @@ index a2711f6..e9cb17b 100644 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]; +@@ -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 - 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; @@ -812,70 +820,58 @@ index a2711f6..e9cb17b 100644 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: +@@ -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) { -- 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|")) { + 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 (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); -+ } -+ } ++ 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); ++ 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; -+ } ++ } 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; + 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; + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; - } else { - cursor_mode = CurNormal; -- } -- break; -- } -+ selmon = xytomon(cursor->x, cursor->y); -+ setmon(grabc, selmon, 0); -+ grabc = NULL; -+ return; -+ } -+ cursor_mode = CurNormal; -+ break; -+ } + } ++ 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); @@ -886,15 +882,15 @@ index a2711f6..e9cb17b 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1025,6 +1080,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -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: * - * If the fullscreened surface is not opaque, the compositor must make -@@ -1263,6 +1319,15 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -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); @@ -910,26 +906,25 @@ index a2711f6..e9cb17b 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1874,9 @@ void +@@ -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; -+ 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 +@@ -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; + - /* 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. */ @@ -953,21 +948,18 @@ index a2711f6..e9cb17b 100644 + 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) { ++ if (time - last_resize_time >= resize_interval_ms) { + Arg a = {0}; -+ factor = 0.0002f; + if (fabs(dx_total) > fabs(dy_total)) { -+ a.f = (float)(dx_total * factor); ++ a.f = (float)(dx_total * resize_factor); + setratio_h(&a); + } else { -+ a.f = (float)(dy_total * factor); ++ a.f = (float)(dy_total * resize_factor); + setratio_v(&a); + } -+ /* Limit arrange rate to reduce recursion calls */ -+ if (++arrange_counter >= ARRANGE_RATE) { -+ arrange(selmon); -+ arrange_counter = 0; -+ } ++ arrange(selmon); ++ ++ last_resize_time = time; + resize_last_update_x = cursor->x; + resize_last_update_y = cursor->y; + } @@ -988,7 +980,7 @@ index a2711f6..e9cb17b 100644 /* 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) +@@ -1910,22 +2014,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -1004,52 +996,46 @@ index a2711f6..e9cb17b 100644 - /* 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); ++ 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; -- } -+ 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; -+ } -+ } ++ 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 index 0c3b2a2..be4fbee 100644 --- a/patches/btrtile/btrtile-v0.7.patch +++ b/patches/btrtile/btrtile-v0.7.patch @@ -1,21 +1,21 @@ -From 2a2d5dd776834879005f1dbcb5c2cc8aaab90125 Mon Sep 17 00:00:00 2001 +From 2b7e152bd39432cd82738a29a8815cf9264f02d8 Mon Sep 17 00:00:00 2001 From: julmajustus -Date: Mon, 23 Dec 2024 20:24:21 +0200 -Subject: [PATCH] Btrtile layout +Date: Wed, 1 Jan 2025 19:06:03 +0200 +Subject: [PATCH] btrtile init --- - btrtile.c | 649 +++++++++++++++++++++++++++++++++++++++++++++++++++ + btrtile.c | 659 +++++++++++++++++++++++++++++++++++++++++++++++++++ config.def.h | 12 + - dwl.c | 206 +++++++++++++--- - 3 files changed, 829 insertions(+), 38 deletions(-) + 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..955b374 +index 0000000..7fb0716 --- /dev/null +++ b/btrtile.c -@@ -0,0 +1,649 @@ +@@ -0,0 +1,659 @@ +/* ************************************************************************** */ +/* */ +/* ::: :::::::: */ @@ -24,7 +24,7 @@ index 0000000..955b374 +/* By: jmakkone +#+ +:+ +#+ */ +/* +#+#+#+#+#+ +#+ */ +/* Created: 2024/12/15 00:26:07 by jmakkone #+# #+# */ -+/* Updated: 2024/12/23 20:23:05 by jmakkone ### ########.fr */ ++/* Updated: 2025/01/01 18:40:14 by jmakkone ### ########.fr */ +/* */ +/* ************************************************************************** */ + @@ -50,35 +50,37 @@ index 0000000..955b374 + +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); ++ 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 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 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); ++ 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 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; ++static uint32_t last_resize_time = 0; + +void +add_client_to_tiled_list(Client *c, struct wl_list *tiled_clients) @@ -89,7 +91,7 @@ index 0000000..955b374 + +void +apply_layout(Monitor *m, LayoutNode *node, -+ struct wlr_box area, unsigned int is_root) ++ struct wlr_box area, unsigned int is_root) +{ + float ratio; + int mid; @@ -114,13 +116,13 @@ index 0000000..955b374 + + if (node->split_vertically) { + mid = (int)(area.width * ratio); -+ left_area = (struct wlr_box){ area.x, area.y, mid, area.height }; ++ 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 }; ++ 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, ++ right_area = (struct wlr_box){ area.x, area.y + mid, + area.width, area.height - mid }; + } + @@ -147,9 +149,9 @@ index 0000000..955b374 + if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) { + n++; + if (!focused_client && -+ cursor->x >= c->old_geom.x ++ 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 + && cursor->y < c->old_geom.y + c->old_geom.height) { + focused_client = c; + } @@ -180,10 +182,11 @@ index 0000000..955b374 + 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 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) { @@ -199,10 +202,11 @@ index 0000000..955b374 + } + } + -+ /* 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 */ ++ /* 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) { @@ -240,8 +244,8 @@ index 0000000..955b374 +} + +LayoutNode * -+create_split_node(unsigned int split_vertically, -+ LayoutNode *left, LayoutNode *right) ++create_split_node(unsigned int split_vertically, ++ LayoutNode *left, LayoutNode *right) +{ + LayoutNode *node = calloc(1, sizeof(LayoutNode)); + @@ -263,7 +267,8 @@ index 0000000..955b374 +{ + Client *cc; + wl_list_for_each(cc, tiled_clients, link_tiled) { -+ if (cc == c) return 1; ++ if (cc == c) ++ return 1; + } + return 0; +} @@ -325,14 +330,15 @@ index 0000000..955b374 + 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 : ++ 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 ++ /* 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 : ++ 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); + } + @@ -340,7 +346,9 @@ index 0000000..955b374 +} + +LayoutNode * -+find_closest_client_node(LayoutNode *split_node, enum Direction dir, int current_x, int current_y, LayoutNode **closest, int *closest_dist) ++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) @@ -388,8 +396,10 @@ index 0000000..955b374 + } + + /* 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); ++ 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; +} @@ -423,7 +433,7 @@ index 0000000..955b374 + if (active & (1u << i)) + return i; + } -+ return 0; ++ return 0; +} + +void @@ -446,64 +456,59 @@ index 0000000..955b374 + LayoutNode *old_root, *client_node, *old_client_node, *new_client_node; + unsigned int wider; + -+ if (*root == NULL) { ++ /* 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); -+ return; -+ } -+ if (!focused) { ++ } 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); -+ 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; ++ /* 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 { -+ client_node->left = old_client_node; -+ client_node->right = new_client_node; ++ /* 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); + } -+ 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) { @@ -519,8 +524,8 @@ index 0000000..955b374 + 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 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; + @@ -549,7 +554,8 @@ index 0000000..955b374 +} + +void -+remove_client(Monitor *m, Client *c, LayoutNode **root, struct wl_list *tiled_clients) ++remove_client(Monitor *m, Client *c, LayoutNode **root, ++ struct wl_list *tiled_clients) +{ + Client *cc, *tmp; + @@ -588,7 +594,8 @@ index 0000000..955b374 + new_ratio = 0.95f; + + node->split_ratio = new_ratio; -+ /* Skip the resize if done by mouse, we call arrange from motionotify */ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ + if (!resizing_from_mouse) { + arrange(selmon); + } @@ -618,7 +625,8 @@ index 0000000..955b374 + 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 */ ++ /* Skip the arrange if done resizing by mouse, ++ * we call arrange from motionotify */ + if (!resizing_from_mouse) { + arrange(selmon); + } @@ -630,7 +638,7 @@ index 0000000..955b374 + 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); ++ unsigned int current_x, current_y, curtag = get_current_tag(selmon); + int closest_dist = INT_MAX; + + if (!arg || !sel || !selmon->lt[selmon->sellt]->arrange) @@ -643,8 +651,8 @@ index 0000000..955b374 + 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 */ ++ /* 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) { @@ -654,8 +662,10 @@ index 0000000..955b374 + 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); ++ /* 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; @@ -666,17 +676,15 @@ index 0000000..955b374 + } +} diff --git a/config.def.h b/config.def.h -index 22d2171..c7924cb 100644 +index 22d2171..92f3ad6 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); +@@ -13,7 +13,10 @@ 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 */ ++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 */ @@ -690,10 +698,10 @@ index 22d2171..c7924cb 100644 { "[]=", 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} }, +@@ -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} }, @@ -702,11 +710,11 @@ index 22d2171..c7924cb 100644 + { 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} }, + 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 a2711f6..e9cb17b 100644 +index def2562..604a9a4 100644 --- a/dwl.c +++ b/dwl.c @@ -1,6 +1,7 @@ @@ -764,14 +772,14 @@ index a2711f6..e9cb17b 100644 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]; +@@ -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 - 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; @@ -792,70 +800,58 @@ index a2711f6..e9cb17b 100644 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: +@@ -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) { -- 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|")) { + 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 (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); -+ } -+ } ++ 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); ++ 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; -+ } ++ } 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; + 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; + selmon = xytomon(cursor->x, cursor->y); + setmon(grabc, selmon, 0); ++ grabc = NULL; + return; - } else { - cursor_mode = CurNormal; -- } -- break; -- } -+ selmon = xytomon(cursor->x, cursor->y); -+ setmon(grabc, selmon, 0); -+ grabc = NULL; -+ return; -+ } -+ cursor_mode = CurNormal; -+ break; -+ } + } ++ 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); @@ -866,15 +862,15 @@ index a2711f6..e9cb17b 100644 closemon(m); wlr_scene_node_destroy(&m->fullscreen_bg->node); free(m); -@@ -1025,6 +1080,7 @@ createmon(struct wl_listener *listener, void *data) +@@ -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: * - * If the fullscreened surface is not opaque, the compositor must make -@@ -1263,6 +1319,15 @@ destroynotify(struct wl_listener *listener, void *data) +@@ -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); @@ -890,26 +886,25 @@ index a2711f6..e9cb17b 100644 #ifdef XWAYLAND if (c->type != XDGShell) { wl_list_remove(&c->activate.link); -@@ -1809,7 +1874,9 @@ void +@@ -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; -+ 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 +@@ -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; + - /* 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. */ @@ -933,21 +928,18 @@ index a2711f6..e9cb17b 100644 + 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) { ++ if (time - last_resize_time >= resize_interval_ms) { + Arg a = {0}; -+ factor = 0.0002f; + if (fabs(dx_total) > fabs(dy_total)) { -+ a.f = (float)(dx_total * factor); ++ a.f = (float)(dx_total * resize_factor); + setratio_h(&a); + } else { -+ a.f = (float)(dy_total * factor); ++ a.f = (float)(dy_total * resize_factor); + setratio_v(&a); + } -+ /* Limit arrange rate to reduce recursion calls */ -+ if (++arrange_counter >= ARRANGE_RATE) { -+ arrange(selmon); -+ arrange_counter = 0; -+ } ++ arrange(selmon); ++ ++ last_resize_time = time; + resize_last_update_x = cursor->x; + resize_last_update_y = cursor->y; + } @@ -968,7 +960,7 @@ index a2711f6..e9cb17b 100644 /* 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) +@@ -1910,22 +2014,41 @@ moveresize(const Arg *arg) if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen) return; @@ -984,52 +976,46 @@ index a2711f6..e9cb17b 100644 - /* 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); ++ 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; -- } -+ 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; -+ } -+ } ++ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize"); ++ break; ++ } + } } - void -- 2.45.2 diff --git a/patches/btrtile/demos/btrtiledemo.gif b/patches/btrtile/demos/btrtiledemo.gif new file mode 100644 index 0000000..8f896bf Binary files /dev/null and b/patches/btrtile/demos/btrtiledemo.gif differ