Patch refactor, updated readme.

This commit is contained in:
julmajustus 2025-01-01 21:11:11 +02:00
parent 15ed11dd2e
commit fa433ca6e3
4 changed files with 521 additions and 454 deletions

View File

@ -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 dwls 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 clients 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) youre interacting with.
2. **Split Creation**
- If theres 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 Doesnt 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)

View File

@ -1,21 +1,21 @@
From cdeedfa24d7f2fbc4a916d953e7a389fd24a281c Mon Sep 17 00:00:00 2001
From 6d60b3cd6eb4d08f8566e80141071bd33d231c59 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
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 <jmakkone@student.hive.fi> +#+ +:+ +#+ */
+/* +#+#+#+#+#+ +#+ */
+/* 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, &current_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

View File

@ -1,21 +1,21 @@
From 2a2d5dd776834879005f1dbcb5c2cc8aaab90125 Mon Sep 17 00:00:00 2001
From 2b7e152bd39432cd82738a29a8815cf9264f02d8 Mon Sep 17 00:00:00 2001
From: julmajustus <julmajustus@tutanota.com>
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 <jmakkone@student.hive.fi> +#+ +:+ +#+ */
+/* +#+#+#+#+#+ +#+ */
+/* 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, &current_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

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 MiB