mirror of
https://codeberg.org/dwl/dwl-patches.git
synced 2026-09-20 14:22:59 +00:00
btrtile: update and minor changes
- Updated to current main - Added more user toggles to control the behaviour of btrtile into config.h - Simplified the btrtile gapps logic - Simplified the changes to dwl.c - Added two keybind functions to swap and toggle splits - Minor code cleanups - Added functionality to respect the client size hints
This commit is contained in:
+34
-55
@@ -1,6 +1,6 @@
|
|||||||
### Description
|
### Description
|
||||||
|
|
||||||
# btrtile — A Focus-Driven Tiling Layout
|
# 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.
|
It provides a focus-driven, mouse- and keyboard-friendly tiling layout that grants you granular control over how clients are placed and resized.
|
||||||
|
|
||||||
@@ -14,39 +14,35 @@ While dwl’s patches folder is full of different layouts, I couldn't find suita
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# Features
|
# How it works
|
||||||
|
|
||||||
- **Combined Tiling and Management**
|
Clients live in a binary tree, every split has a ratio (50/50 by default).
|
||||||
Combines tiling layout and management of clients under one patchset.
|
|
||||||
|
When a new client opens, it splits the client you last focused. The pointer decides which side it lands on: left half of the client puts it on the left, and so on. If the area is wider than it is tall the split is vertical, otherwise horizontal. When several clients open at once, each one splits the previous one, so you get a nice spiral instead of a pile in one corner.
|
||||||
- **Focus-Driven Splits**
|
|
||||||
When you add a new client, btrtile checks where your pointer is relative to the focused client’s geometry.
|
- **Mouse**
|
||||||
- If the pointer is on the left half (for a horizontally large client), the new client spawns on the left side, and vice versa.
|
Drag a tiled client with `MODKEY` + left button to float it, drop it on another tiled client to place it next to it.
|
||||||
- By default, new splits are 50/50.
|
`MODKEY` + right button resizes the divider next to the client you grabbed, it follows the pointer 1:1.
|
||||||
|
- **Keyboard**
|
||||||
- **Adaptive Splitting**
|
`setratio_h` / `setratio_v` move a divider, `swapclients` swaps a client with its neighbour in a direction,
|
||||||
- If the area to be split is wider than its height, btrtile does a vertical split.
|
`togglesplit` flips the direction of the split you're in (and keeps it that way),
|
||||||
- Otherwise, it does a horizontal split.
|
`swapsplit` mirrors the 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.
|
|
||||||
|
|
||||||
|
**config.h:**
|
||||||
|
```c
|
||||||
|
use_active_for_splits = 1; /* 1: split the last focused client, 0: the one under the pointer */
|
||||||
|
force_split = 0; /* 0: new client on the pointer's side, 1: always left/top, 2: always right/bottom */
|
||||||
|
preserve_split = 0; /* 1: splits keep their direction, 0: direction follows the shape of the area */
|
||||||
|
split_width_multiplier = 1.0f; /* split side by side when width >= height * this */
|
||||||
|
resize_interval_ms = 16; /* limits mouse resize updates, 16ms is ~60 per second */
|
||||||
|
```
|
||||||
|
|
||||||
|
`force_split = 2` gives you a predictable spiral if you don't want the pointer to matter.
|
||||||
|
`split_width_multiplier` is handy on wide monitors, where windows tend to stay wider than tall for a long time.
|
||||||
|
|
||||||
|
Smoother mouse resizing costs some cpu, so tune `resize_interval_ms` to your refresh rate.
|
||||||
|
If resizing feels sluggish, try compiling dwl with -O2/-O3.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# What It Doesn’t Handle
|
# What It Doesn’t Handle
|
||||||
@@ -56,23 +52,6 @@ When a new client appears:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
# 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 sensitivity.
|
|
||||||
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
|
# Patch recommendations
|
||||||
|
|
||||||
1. **Patches that I use with my btrtile**
|
1. **Patches that I use with my btrtile**
|
||||||
@@ -95,11 +74,11 @@ If mouse resizing feels sluggish, you can try compiling dwl with more aggressive
|
|||||||
---
|
---
|
||||||
|
|
||||||
### Download
|
### Download
|
||||||
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-dev)
|
- [git branch](https://codeberg.org/julmajustus/dwl/src/branch/btrtile-0.9)
|
||||||
- [0.8](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8.patch)
|
- [0.8](./btrtile-v0.8.patch)
|
||||||
- [0.8 WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-v0.8-gaps.patch)
|
- [0.8 WITH gaps](./btrtile-v0.8-gaps.patch)
|
||||||
- [wlroots-next](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7.patch)
|
- [main-d16036e](./btrtile-d16036e.patch)
|
||||||
- [wlroots-next WITH gaps](https://codeberg.org/dwl/dwl-patches/raw/branch/main/patches/btrtile/btrtile-wlroots-next-d41ecb7-gaps.patch)
|
- [main-d16036e WITH gaps](./btrtile-gaps-d16036e.patch)
|
||||||
|
|
||||||
### Authors
|
### Authors
|
||||||
- [julmajustus](https://codeberg.org/julmajustus)
|
- [julmajustus](https://codeberg.org/julmajustus)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,922 +0,0 @@
|
|||||||
From efb796ce92e7212aa392141705c7322fedc45dba Mon Sep 17 00:00:00 2001
|
|
||||||
From: julmajustus <julmajustus@tutanota.com>
|
|
||||||
Date: Mon, 22 Jun 2026 22:46:19 +0300
|
|
||||||
Subject: [PATCH] btrtile: bug fixes and readability improvement
|
|
||||||
|
|
||||||
- Removed client old_geom caching for simplicity fixes: https://codeberg.org/dwl/dwl-patches/issues/647
|
|
||||||
- Improved readability of the client sanitization loop inside btrtile function
|
|
||||||
- Removed fullscreened client's from the sanitization loop to retain simple_scratchpad: Spring update scratchpad V2
|
|
||||||
|
|
||||||
- Added support for multiple scratchpads their geometry
|
|
||||||
---
|
|
||||||
btrtile.c | 575 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
config.def.h | 12 ++
|
|
||||||
dwl.c | 151 +++++++++++---
|
|
||||||
3 files changed, 711 insertions(+), 27 deletions(-)
|
|
||||||
create mode 100644 btrtile.c
|
|
||||||
|
|
||||||
diff --git a/btrtile.c b/btrtile.c
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..645b741
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/btrtile.c
|
|
||||||
@@ -0,0 +1,575 @@
|
|
||||||
+/* ************************************************************************** */
|
|
||||||
+/* @@@ @@@@@@@@ */
|
|
||||||
+/* @@@ @@@@@@@@@@ */
|
|
||||||
+/* @@! @@! @@@@ */
|
|
||||||
+/* !@! !@! @!@!@ */
|
|
||||||
+/* btrtile.c @!! @!@ @! !@! */
|
|
||||||
+/* !!! !@!!! !!! */
|
|
||||||
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
|
|
||||||
+/* ::! :!: !:! */
|
|
||||||
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
|
|
||||||
+/* Updated: 2026/06/22 22:34:11 by julmajustus : : : : : : */
|
|
||||||
+/* */
|
|
||||||
+/* ************************************************************************** */
|
|
||||||
+
|
|
||||||
+typedef struct LayoutNode {
|
|
||||||
+ unsigned int is_client_node;
|
|
||||||
+ unsigned int is_split_vertically;
|
|
||||||
+ float split_ratio;
|
|
||||||
+ struct LayoutNode *left;
|
|
||||||
+ struct LayoutNode *right;
|
|
||||||
+ struct LayoutNode *split_node;
|
|
||||||
+ Client *client;
|
|
||||||
+} LayoutNode;
|
|
||||||
+
|
|
||||||
+static void apply_layout(Monitor *m, LayoutNode *node,
|
|
||||||
+ struct wlr_box area, unsigned int is_root);
|
|
||||||
+static void btrtile(Monitor *m);
|
|
||||||
+static LayoutNode *create_client_node(Client *c);
|
|
||||||
+static LayoutNode *create_split_node(unsigned int is_split_vertically,
|
|
||||||
+ LayoutNode *left, LayoutNode *right);
|
|
||||||
+static void destroy_node(LayoutNode *node);
|
|
||||||
+static void destroy_tree(Monitor *m);
|
|
||||||
+static LayoutNode *find_client_node(LayoutNode *node, Client *c);
|
|
||||||
+static LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
|
|
||||||
+ unsigned int need_vertical, int focused_on_left);
|
|
||||||
+static void init_tree(Monitor *m);
|
|
||||||
+static void insert_client(Monitor *m, Client *focused_client, Client *new_client);
|
|
||||||
+static LayoutNode *remove_client_node(LayoutNode *node, Client *c);
|
|
||||||
+static void remove_client(Monitor *m, Client *c);
|
|
||||||
+static void setratio_h(const Arg *arg);
|
|
||||||
+static void setratio_v(const Arg *arg);
|
|
||||||
+static void swapclients(const Arg *arg);
|
|
||||||
+static unsigned int visible_count(LayoutNode *node, Monitor *m);
|
|
||||||
+static Client *xytoclient(double x, double y);
|
|
||||||
+
|
|
||||||
+static double resize_last_update_x, resize_last_update_y;
|
|
||||||
+static uint32_t last_resize_time = 0;
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+apply_layout(Monitor *m, LayoutNode *node,
|
|
||||||
+ struct wlr_box area, unsigned int is_root)
|
|
||||||
+{
|
|
||||||
+ Client *c;
|
|
||||||
+ float ratio;
|
|
||||||
+ unsigned int left_count, right_count, mid, e = m->gaps;
|
|
||||||
+ struct wlr_box left_area, right_area;
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ if (is_root && e) {
|
|
||||||
+ area.x += gappx;
|
|
||||||
+ area.y += gappx;
|
|
||||||
+ area.width -= 2 * gappx;
|
|
||||||
+ area.height -= 2 * gappx;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If this node is a client node, check if it is visible. */
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ c = node->client;
|
|
||||||
+ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
|
|
||||||
+ return;
|
|
||||||
+ resize(c, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* For a split node, we see how many visible children are on each side: */
|
|
||||||
+ left_count = visible_count(node->left, m);
|
|
||||||
+ right_count = visible_count(node->right, m);
|
|
||||||
+
|
|
||||||
+ if (left_count == 0 && right_count == 0) {
|
|
||||||
+ return;
|
|
||||||
+ } else if (left_count > 0 && right_count == 0) {
|
|
||||||
+ apply_layout(m, node->left, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ } else if (left_count == 0 && right_count > 0) {
|
|
||||||
+ apply_layout(m, node->right, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If we’re here, we have visible clients in both subtrees. */
|
|
||||||
+ ratio = node->split_ratio;
|
|
||||||
+ if (ratio < 0.05f)
|
|
||||||
+ ratio = 0.05f;
|
|
||||||
+ if (ratio > 0.95f)
|
|
||||||
+ ratio = 0.95f;
|
|
||||||
+
|
|
||||||
+ memset(&left_area, 0, sizeof(left_area));
|
|
||||||
+ memset(&right_area, 0, sizeof(right_area));
|
|
||||||
+
|
|
||||||
+ if (node->is_split_vertically) {
|
|
||||||
+ mid = (unsigned int)(area.width * ratio);
|
|
||||||
+ left_area.x = area.x;
|
|
||||||
+ left_area.y = area.y;
|
|
||||||
+ left_area.width = mid;
|
|
||||||
+ left_area.height = area.height;
|
|
||||||
+
|
|
||||||
+ right_area.x = area.x + mid;
|
|
||||||
+ right_area.y = area.y;
|
|
||||||
+ right_area.width = area.width - mid;
|
|
||||||
+ right_area.height = area.height;
|
|
||||||
+
|
|
||||||
+ if (e) {
|
|
||||||
+ left_area.width -= gappx / 2;
|
|
||||||
+ right_area.x += gappx / 2;
|
|
||||||
+ right_area.width -= gappx / 2;
|
|
||||||
+ }
|
|
||||||
+ } else {
|
|
||||||
+ /* horizontal split */
|
|
||||||
+ mid = (unsigned int)(area.height * ratio);
|
|
||||||
+ left_area.x = area.x;
|
|
||||||
+ left_area.y = area.y;
|
|
||||||
+ left_area.width = area.width;
|
|
||||||
+ left_area.height = mid;
|
|
||||||
+
|
|
||||||
+ right_area.x = area.x;
|
|
||||||
+ right_area.y = area.y + mid;
|
|
||||||
+ right_area.width = area.width;
|
|
||||||
+ right_area.height= area.height - mid;
|
|
||||||
+
|
|
||||||
+ if (e) {
|
|
||||||
+ left_area.height -= gappx / 2;
|
|
||||||
+ right_area.y += gappx / 2;
|
|
||||||
+ right_area.height -= gappx / 2;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ apply_layout(m, node->left, left_area, 0);
|
|
||||||
+ apply_layout(m, node->right, right_area, 0);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+btrtile(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ Client *c, *focused = NULL;
|
|
||||||
+ int n = 0;
|
|
||||||
+ LayoutNode *found;
|
|
||||||
+ struct wlr_box full_area;
|
|
||||||
+
|
|
||||||
+ if (!m)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ /* Remove floating and moved clients */
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (c->mon != m || c->isfloating)
|
|
||||||
+ remove_client(m, c);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If no client is found under cursor, fallback to focustop(m) */
|
|
||||||
+ if (!(focused = xytoclient(cursor->x, cursor->y)))
|
|
||||||
+ focused = focustop(m);
|
|
||||||
+
|
|
||||||
+ /* Insert visible clients that are not part of the tree. */
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) {
|
|
||||||
+ found = find_client_node(m->root, c);
|
|
||||||
+ if (!found) {
|
|
||||||
+ insert_client(m, focused, c);
|
|
||||||
+ }
|
|
||||||
+ n++;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (n == 0)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ full_area = m->w;
|
|
||||||
+ apply_layout(m, m->root, full_area, 1);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+create_client_node(Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ node->is_client_node = 1;
|
|
||||||
+ node->split_ratio = 0.5f;
|
|
||||||
+ node->client = c;
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+create_split_node(unsigned int is_split_vertically,
|
|
||||||
+ LayoutNode *left, LayoutNode *right)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ node->is_client_node = 0;
|
|
||||||
+ node->split_ratio = 0.5f;
|
|
||||||
+ node->is_split_vertically = is_split_vertically;
|
|
||||||
+ node->left = left;
|
|
||||||
+ node->right = right;
|
|
||||||
+ if (left)
|
|
||||||
+ left->split_node = node;
|
|
||||||
+ if (right)
|
|
||||||
+ right->split_node = node;
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+destroy_node(LayoutNode *node)
|
|
||||||
+{
|
|
||||||
+ if (!node)
|
|
||||||
+ return;
|
|
||||||
+ if (!node->is_client_node) {
|
|
||||||
+ destroy_node(node->left);
|
|
||||||
+ destroy_node(node->right);
|
|
||||||
+ }
|
|
||||||
+ free(node);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+destroy_tree(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ if (!m || !m->root)
|
|
||||||
+ return;
|
|
||||||
+ destroy_node(m->root);
|
|
||||||
+ m->root = NULL;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+find_client_node(LayoutNode *node, Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *res;
|
|
||||||
+
|
|
||||||
+ if (!node || !c)
|
|
||||||
+ return NULL;
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ return (node->client == c) ? node : NULL;
|
|
||||||
+ }
|
|
||||||
+ res = find_client_node(node->left, c);
|
|
||||||
+ return res ? res : find_client_node(node->right, c);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+find_suitable_split(Monitor *m, LayoutNode *start_node,
|
|
||||||
+ unsigned int need_vertical, int focused_on_left)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *n = start_node, *child = NULL;
|
|
||||||
+
|
|
||||||
+ if (!m)
|
|
||||||
+ return NULL;
|
|
||||||
+
|
|
||||||
+ if (n && n->is_client_node) {
|
|
||||||
+ child = n;
|
|
||||||
+ n = n->split_node;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ while (n) {
|
|
||||||
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
|
|
||||||
+ && visible_count(n->left, m) > 0
|
|
||||||
+ && visible_count(n->right, m) > 0) {
|
|
||||||
+ if ((focused_on_left && n->left == child) ||
|
|
||||||
+ (!focused_on_left && n->right == child))
|
|
||||||
+ return n;
|
|
||||||
+ }
|
|
||||||
+ child = n;
|
|
||||||
+ n = n->split_node;
|
|
||||||
+ }
|
|
||||||
+ return NULL;
|
|
||||||
+}
|
|
||||||
+void
|
|
||||||
+init_tree(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ if (m)
|
|
||||||
+ m->root = NULL;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+insert_client(Monitor *m, Client *focused_client, Client *new_client)
|
|
||||||
+{
|
|
||||||
+ Client *old_client;
|
|
||||||
+ LayoutNode **root = &m->root, *old_root,
|
|
||||||
+ *focused_node, *new_client_node, *old_client_node;
|
|
||||||
+ unsigned int wider, mid_x, mid_y;
|
|
||||||
+
|
|
||||||
+ /* If no root , new client becomes the root. */
|
|
||||||
+ if (!*root) {
|
|
||||||
+ *root = create_client_node(new_client);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Find the focused_client node,
|
|
||||||
+ * if not found split the root. */
|
|
||||||
+ focused_node = focused_client ?
|
|
||||||
+ find_client_node(*root, focused_client) : NULL;
|
|
||||||
+ if (!focused_node) {
|
|
||||||
+ old_root = *root;
|
|
||||||
+ new_client_node = create_client_node(new_client);
|
|
||||||
+ *root = create_split_node(1, old_root, new_client_node);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Turn focused node from a client node into a split node,
|
|
||||||
+ * and attach old_client + new_client. */
|
|
||||||
+ old_client = focused_node->client;
|
|
||||||
+ old_client_node = create_client_node(old_client);
|
|
||||||
+ new_client_node = create_client_node(new_client);
|
|
||||||
+
|
|
||||||
+ /* Decide split direction. */
|
|
||||||
+ wider = (focused_client->geom.width >= focused_client->geom.height);
|
|
||||||
+ focused_node->is_client_node = 0;
|
|
||||||
+ focused_node->client = NULL;
|
|
||||||
+ focused_node->is_split_vertically = (wider ? 1 : 0);
|
|
||||||
+
|
|
||||||
+ /* Pick new_client side depending on the cursor position. */
|
|
||||||
+ mid_x = focused_client->geom.x + focused_client->geom.width / 2;
|
|
||||||
+ mid_y = focused_client->geom.y + focused_client->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ if (wider) {
|
|
||||||
+ /* vertical split => left vs right */
|
|
||||||
+ if (cursor->x <= mid_x) {
|
|
||||||
+ focused_node->left = new_client_node;
|
|
||||||
+ focused_node->right = old_client_node;
|
|
||||||
+ } else {
|
|
||||||
+ focused_node->left = old_client_node;
|
|
||||||
+ focused_node->right = new_client_node;
|
|
||||||
+ }
|
|
||||||
+ } else {
|
|
||||||
+ /* horizontal split => top vs bottom */
|
|
||||||
+ if (cursor->y <= mid_y) {
|
|
||||||
+ focused_node->left = new_client_node;
|
|
||||||
+ focused_node->right = old_client_node;
|
|
||||||
+ } else {
|
|
||||||
+ focused_node->left = old_client_node;
|
|
||||||
+ focused_node->right = new_client_node;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ old_client_node->split_node = focused_node;
|
|
||||||
+ new_client_node->split_node = focused_node;
|
|
||||||
+ focused_node->split_ratio = 0.5f;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+remove_client_node(LayoutNode *node, Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *tmp;
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ /* If this client_node is the client we're removing,
|
|
||||||
+ * return NULL to remove it */
|
|
||||||
+ if (node->client == c) {
|
|
||||||
+ free(node);
|
|
||||||
+ return NULL;
|
|
||||||
+ }
|
|
||||||
+ return node;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ node->left = remove_client_node(node->left, c);
|
|
||||||
+ node->right = remove_client_node(node->right, c);
|
|
||||||
+
|
|
||||||
+ /* If one of the client node is NULL after removal and the other is not,
|
|
||||||
+ * we "lift" the other client node up to replace this split node. */
|
|
||||||
+ if (!node->left && node->right) {
|
|
||||||
+ tmp = node->right;
|
|
||||||
+
|
|
||||||
+ /* Save pointer to split node */
|
|
||||||
+ if (tmp)
|
|
||||||
+ tmp->split_node = node->split_node;
|
|
||||||
+
|
|
||||||
+ free(node);
|
|
||||||
+ return tmp;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (!node->right && node->left) {
|
|
||||||
+ tmp = node->left;
|
|
||||||
+
|
|
||||||
+ /* Save pointer to split node */
|
|
||||||
+ if (tmp)
|
|
||||||
+ tmp->split_node = node->split_node;
|
|
||||||
+
|
|
||||||
+ free(node);
|
|
||||||
+ return tmp;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If both children exist or both are NULL (empty tree),
|
|
||||||
+ * return node as is. */
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+remove_client(Monitor *m, Client *c)
|
|
||||||
+{
|
|
||||||
+ if (!m->root || !c)
|
|
||||||
+ return;
|
|
||||||
+ m->root = remove_client_node(m->root, c);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+static void
|
|
||||||
+setratio(unsigned int need_vertical, const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ Client *sel;
|
|
||||||
+ LayoutNode *client_node, *split_node;
|
|
||||||
+ float new_ratio;
|
|
||||||
+ int focused_on_left;
|
|
||||||
+
|
|
||||||
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ sel = focustop(selmon);
|
|
||||||
+ if (!sel)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ client_node = find_client_node(selmon->root, sel);
|
|
||||||
+ if (!client_node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ focused_on_left = (arg->f >= 0.0f);
|
|
||||||
+
|
|
||||||
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
|
|
||||||
+
|
|
||||||
+ if (!split_node)
|
|
||||||
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
|
|
||||||
+ if (!split_node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
|
|
||||||
+ if (new_ratio < 0.05f)
|
|
||||||
+ new_ratio = 0.05f;
|
|
||||||
+ if (new_ratio > 0.95f)
|
|
||||||
+ new_ratio = 0.95f;
|
|
||||||
+ split_node->split_ratio = new_ratio;
|
|
||||||
+
|
|
||||||
+ apply_layout(selmon, selmon->root, selmon->w, 1);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+setratio_h(const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ setratio(1, arg);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+setratio_v(const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ setratio(0, arg);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void swapclients(const Arg *arg) {
|
|
||||||
+ Client *c, *tmp, *target = NULL, *sel = focustop(selmon);
|
|
||||||
+ LayoutNode *sel_node, *target_node;
|
|
||||||
+ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y,
|
|
||||||
+ cand_center_x, cand_center_y;
|
|
||||||
+
|
|
||||||
+ if (!sel || sel->isfullscreen ||
|
|
||||||
+ !selmon->root || !selmon->lt[selmon->sellt]->arrange)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+ /* Get the center coordinates of the selected client */
|
|
||||||
+ sel_center_x = sel->geom.x + sel->geom.width / 2;
|
|
||||||
+ sel_center_y = sel->geom.y + sel->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel)
|
|
||||||
+ continue;
|
|
||||||
+
|
|
||||||
+ /* Get the center of candidate client */
|
|
||||||
+ cand_center_x = c->geom.x + c->geom.width / 2;
|
|
||||||
+ cand_center_y = c->geom.y + c->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ /* Check that the candidate lies in the requested direction. */
|
|
||||||
+ switch (arg->ui) {
|
|
||||||
+ case 0:
|
|
||||||
+ if (cand_center_x >= sel_center_x)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 1:
|
|
||||||
+ if (cand_center_x <= sel_center_x)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 2:
|
|
||||||
+ if (cand_center_y >= sel_center_y)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 3:
|
|
||||||
+ if (cand_center_y <= sel_center_y)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ default:
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Get distance between the centers */
|
|
||||||
+ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y);
|
|
||||||
+ if (dist < closest_dist) {
|
|
||||||
+ closest_dist = dist;
|
|
||||||
+ target = c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If target is found, swap the two clients’ positions in the layout tree */
|
|
||||||
+ if (target) {
|
|
||||||
+ sel_node = find_client_node(selmon->root, sel);
|
|
||||||
+ target_node = find_client_node(selmon->root, target);
|
|
||||||
+ if (sel_node && target_node) {
|
|
||||||
+ tmp = sel_node->client;
|
|
||||||
+ sel_node->client = target_node->client;
|
|
||||||
+ target_node->client = tmp;
|
|
||||||
+ arrange(selmon);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+unsigned int
|
|
||||||
+visible_count(LayoutNode *node, Monitor *m)
|
|
||||||
+{
|
|
||||||
+ Client *c;
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return 0;
|
|
||||||
+ /* Check if this client is visible. */
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ c = node->client;
|
|
||||||
+ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
|
|
||||||
+ return 1;
|
|
||||||
+ return 0;
|
|
||||||
+ }
|
|
||||||
+ /* Else it’s a split node. */
|
|
||||||
+ return visible_count(node->left, m) + visible_count(node->right, m);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+Client *
|
|
||||||
+xytoclient(double x, double y) {
|
|
||||||
+ Monitor *m = xytomon(x, y);
|
|
||||||
+ Client *c, *closest = NULL;
|
|
||||||
+ double dist, mindist = INT_MAX, dx, dy;
|
|
||||||
+
|
|
||||||
+ wl_list_for_each_reverse(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen &&
|
|
||||||
+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) &&
|
|
||||||
+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){
|
|
||||||
+ return c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If no client was found at cursor position fallback to closest. */
|
|
||||||
+ wl_list_for_each_reverse(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
|
|
||||||
+ dx = 0, dy = 0;
|
|
||||||
+
|
|
||||||
+ if (x < c->geom.x)
|
|
||||||
+ dx = c->geom.x - x;
|
|
||||||
+ else if (x > (c->geom.x + c->geom.width))
|
|
||||||
+ dx = x - (c->geom.x + c->geom.width);
|
|
||||||
+
|
|
||||||
+ if (y < c->geom.y)
|
|
||||||
+ dy = c->geom.y - y;
|
|
||||||
+ else if (y > (c->geom.y + c->geom.height))
|
|
||||||
+ dy = y - (c->geom.y + c->geom.height);
|
|
||||||
+
|
|
||||||
+ dist = dx * dx + dy * dy;
|
|
||||||
+ if (dist < mindist) {
|
|
||||||
+ mindist = dist;
|
|
||||||
+ closest = c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ return closest;
|
|
||||||
+}
|
|
||||||
diff --git a/config.def.h b/config.def.h
|
|
||||||
index 8a6eda0..bc04e3f 100644
|
|
||||||
--- a/config.def.h
|
|
||||||
+++ b/config.def.h
|
|
||||||
@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff);
|
|
||||||
static const float urgentcolor[] = COLOR(0xff0000ff);
|
|
||||||
/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
|
|
||||||
static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
|
|
||||||
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
|
|
||||||
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
|
|
||||||
|
|
||||||
+enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
|
|
||||||
/* tagging - TAGCOUNT must be no greater than 31 */
|
|
||||||
#define TAGCOUNT (9)
|
|
||||||
|
|
||||||
@@ -30,6 +33,7 @@ static const Rule rules[] = {
|
|
||||||
/* layout(s) */
|
|
||||||
static const Layout layouts[] = {
|
|
||||||
/* symbol arrange function */
|
|
||||||
+ { "|w|", btrtile },
|
|
||||||
{ "[]=", tile },
|
|
||||||
{ "><>", NULL }, /* no layout function means floating behavior */
|
|
||||||
{ "[M]", monocle },
|
|
||||||
@@ -144,6 +148,14 @@ static const Key keys[] = {
|
|
||||||
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
|
|
||||||
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
|
|
||||||
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
|
|
||||||
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
|
|
||||||
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
|
|
||||||
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
|
|
||||||
diff --git a/dwl.c b/dwl.c
|
|
||||||
index 8101ffa..7abf390 100644
|
|
||||||
--- a/dwl.c
|
|
||||||
+++ b/dwl.c
|
|
||||||
@@ -1,6 +1,7 @@
|
|
||||||
/*
|
|
||||||
* See LICENSE file for copyright and license details.
|
|
||||||
*/
|
|
||||||
+#include <limits.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <libinput.h>
|
|
||||||
#include <linux/input-event-codes.h>
|
|
||||||
@@ -104,6 +105,7 @@ typedef struct {
|
|
||||||
const Arg arg;
|
|
||||||
} Button;
|
|
||||||
|
|
||||||
+typedef struct LayoutNode LayoutNode;
|
|
||||||
typedef struct Monitor Monitor;
|
|
||||||
typedef struct {
|
|
||||||
/* Must keep this field first */
|
|
||||||
@@ -141,7 +143,7 @@ typedef struct {
|
|
||||||
#endif
|
|
||||||
unsigned int bw;
|
|
||||||
uint32_t tags;
|
|
||||||
- int isfloating, isurgent, isfullscreen;
|
|
||||||
+ int isfloating, isurgent, isfullscreen, was_tiled;
|
|
||||||
uint32_t resize; /* configure serial of a pending resize */
|
|
||||||
} Client;
|
|
||||||
|
|
||||||
@@ -209,6 +211,7 @@ struct Monitor {
|
|
||||||
int nmaster;
|
|
||||||
char ltsymbol[16];
|
|
||||||
int asleep;
|
|
||||||
+ LayoutNode *root;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
@@ -251,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
|
|
||||||
struct wlr_box *usable_area, int exclusive);
|
|
||||||
static void arrangelayers(Monitor *m);
|
|
||||||
static void axisnotify(struct wl_listener *listener, void *data);
|
|
||||||
+static void btrtile(Monitor *m);
|
|
||||||
static void buttonpress(struct wl_listener *listener, void *data);
|
|
||||||
static void chvt(const Arg *arg);
|
|
||||||
static void checkidleinhibitor(struct wlr_surface *exclude);
|
|
||||||
@@ -333,6 +337,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
|
|
||||||
static void setpsel(struct wl_listener *listener, void *data);
|
|
||||||
static void setsel(struct wl_listener *listener, void *data);
|
|
||||||
static void setup(void);
|
|
||||||
+static void setratio_h(const Arg *arg);
|
|
||||||
+static void setratio_v(const Arg *arg);
|
|
||||||
+static void swapclients(const Arg *arg);
|
|
||||||
static void spawn(const Arg *arg);
|
|
||||||
static void startdrag(struct wl_listener *listener, void *data);
|
|
||||||
static void tag(const Arg *arg);
|
|
||||||
@@ -458,6 +465,7 @@ static struct wlr_xwayland *xwayland;
|
|
||||||
|
|
||||||
/* attempt to encapsulate suck into one file */
|
|
||||||
#include "client.h"
|
|
||||||
+#include "btrtile.c"
|
|
||||||
|
|
||||||
/* function implementations */
|
|
||||||
void
|
|
||||||
@@ -628,7 +636,7 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
struct wlr_pointer_button_event *event = data;
|
|
||||||
struct wlr_keyboard *keyboard;
|
|
||||||
uint32_t mods;
|
|
||||||
- Client *c;
|
|
||||||
+ Client *c, *target = NULL;
|
|
||||||
const Button *b;
|
|
||||||
|
|
||||||
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
|
|
||||||
@@ -649,7 +657,7 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
|
|
||||||
for (b = buttons; b < END(buttons); b++) {
|
|
||||||
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
|
|
||||||
- event->button == b->button && b->func) {
|
|
||||||
+ event->button == b->button && b->func) {
|
|
||||||
b->func(&b->arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@@ -659,6 +667,21 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
/* If you released any buttons, we exit interactive move/resize mode. */
|
|
||||||
/* TODO: should reset to the pointer focus's current setcursor */
|
|
||||||
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
|
|
||||||
+ c = grabc;
|
|
||||||
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
|
|
||||||
+ if (cursor_mode == CurMove && c->isfloating) {
|
|
||||||
+ target = xytoclient(cursor->x, cursor->y);
|
|
||||||
+
|
|
||||||
+ if (target && !target->isfloating && !target->isfullscreen)
|
|
||||||
+ insert_client(selmon, target, c);
|
|
||||||
+ else
|
|
||||||
+ selmon->root = create_client_node(c);
|
|
||||||
+
|
|
||||||
+ setfloating(c, 0);
|
|
||||||
+ apply_layout(selmon, selmon->root, selmon->w, 1);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ /* Default behaviour */
|
|
||||||
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
|
|
||||||
cursor_mode = CurNormal;
|
|
||||||
/* Drop the window off on its new monitor */
|
|
||||||
@@ -750,6 +773,7 @@ cleanupmon(struct wl_listener *listener, void *data)
|
|
||||||
wlr_output_layout_remove(output_layout, m->wlr_output);
|
|
||||||
wlr_scene_output_destroy(m->scene_output);
|
|
||||||
|
|
||||||
+ destroy_tree(m);
|
|
||||||
closemon(m);
|
|
||||||
wlr_scene_node_destroy(&m->fullscreen_bg->node);
|
|
||||||
free(m);
|
|
||||||
@@ -1094,6 +1118,7 @@ createmon(struct wl_listener *listener, void *data)
|
|
||||||
|
|
||||||
wl_list_insert(&mons, &m->link);
|
|
||||||
printstatus();
|
|
||||||
+ init_tree(m);
|
|
||||||
|
|
||||||
/* The xdg-protocol specifies:
|
|
||||||
*
|
|
||||||
@@ -1332,10 +1357,18 @@ void
|
|
||||||
destroynotify(struct wl_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
/* Called when the xdg_toplevel is destroyed. */
|
|
||||||
+ Monitor *mon;
|
|
||||||
Client *c = wl_container_of(listener, c, destroy);
|
|
||||||
wl_list_remove(&c->destroy.link);
|
|
||||||
wl_list_remove(&c->set_title.link);
|
|
||||||
wl_list_remove(&c->fullscreen.link);
|
|
||||||
+ /* We check if the destroyed client was part of any tiled_list, to catch
|
|
||||||
+ * client removals even if they would not be currently managed by btrtile */
|
|
||||||
+ wl_list_for_each(mon, &mons, link) {
|
|
||||||
+ if (mon->root) {
|
|
||||||
+ remove_client(mon, c);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
#ifdef XWAYLAND
|
|
||||||
if (c->type != XDGShell) {
|
|
||||||
wl_list_remove(&c->activate.link);
|
|
||||||
@@ -1866,7 +1899,8 @@ void
|
|
||||||
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
|
|
||||||
double dx_unaccel, double dy_unaccel)
|
|
||||||
{
|
|
||||||
- double sx = 0, sy = 0, sx_confined, sy_confined;
|
|
||||||
+ int tiled = 0;
|
|
||||||
+ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total;
|
|
||||||
Client *c = NULL, *w = NULL;
|
|
||||||
LayerSurface *l = NULL;
|
|
||||||
struct wlr_surface *surface = NULL;
|
|
||||||
@@ -1920,18 +1954,55 @@ 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 */
|
|
||||||
+ if (time == 0)
|
|
||||||
+ goto focus;
|
|
||||||
+
|
|
||||||
+ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
|
|
||||||
if (cursor_mode == CurMove) {
|
|
||||||
/* Move the grabbed client to the new position. */
|
|
||||||
- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
|
|
||||||
- .width = grabc->geom.width, .height = grabc->geom.height}, 1);
|
|
||||||
- return;
|
|
||||||
+ if (grabc && grabc->isfloating) {
|
|
||||||
+ resize(grabc, (struct wlr_box){
|
|
||||||
+ .x = (int)round(cursor->x) - grabcx,
|
|
||||||
+ .y = (int)round(cursor->y) - grabcy,
|
|
||||||
+ .width = grabc->geom.width,
|
|
||||||
+ .height = grabc->geom.height
|
|
||||||
+ }, 1);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
} else if (cursor_mode == CurResize) {
|
|
||||||
- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
|
|
||||||
- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
|
|
||||||
- return;
|
|
||||||
+ if (tiled) {
|
|
||||||
+ dx_total = cursor->x - resize_last_update_x;
|
|
||||||
+ dy_total = cursor->y - resize_last_update_y;
|
|
||||||
+
|
|
||||||
+ if (time - last_resize_time >= resize_interval_ms) {
|
|
||||||
+ Arg a = {0};
|
|
||||||
+ if (fabs(dx_total) > fabs(dy_total)) {
|
|
||||||
+ a.f = (float)(dx_total * resize_factor);
|
|
||||||
+ setratio_h(&a);
|
|
||||||
+ } else {
|
|
||||||
+ a.f = (float)(dy_total * resize_factor);
|
|
||||||
+ setratio_v(&a);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ last_resize_time = time;
|
|
||||||
+ resize_last_update_x = cursor->x;
|
|
||||||
+ resize_last_update_y = cursor->y;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ } else if (grabc && grabc->isfloating) {
|
|
||||||
+ /* Floating resize as original */
|
|
||||||
+ resize(grabc, (struct wlr_box){
|
|
||||||
+ .x = grabc->geom.x,
|
|
||||||
+ .y = grabc->geom.y,
|
|
||||||
+ .width = (int)round(cursor->x) - grabc->geom.x,
|
|
||||||
+ .height = (int)round(cursor->y) - grabc->geom.y
|
|
||||||
+ }, 1);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
+focus:
|
|
||||||
/* If there's no client surface under the cursor, set the cursor image to a
|
|
||||||
* default. This is what makes the cursor image appear when you move it
|
|
||||||
* off of a client or over its border. */
|
|
||||||
@@ -1965,22 +2036,40 @@ moveresize(const Arg *arg)
|
|
||||||
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
- /* Float the window and tell motionnotify to grab it */
|
|
||||||
- setfloating(grabc, 1);
|
|
||||||
- switch (cursor_mode = arg->ui) {
|
|
||||||
- case CurMove:
|
|
||||||
- grabcx = (int)round(cursor->x) - grabc->geom.x;
|
|
||||||
- grabcy = (int)round(cursor->y) - grabc->geom.y;
|
|
||||||
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
|
|
||||||
- break;
|
|
||||||
- case CurResize:
|
|
||||||
- /* Doesn't work for X11 output - the next absolute motion event
|
|
||||||
- * returns the cursor to where it started */
|
|
||||||
- wlr_cursor_warp_closest(cursor, NULL,
|
|
||||||
- grabc->geom.x + grabc->geom.width,
|
|
||||||
- grabc->geom.y + grabc->geom.height);
|
|
||||||
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
|
||||||
- break;
|
|
||||||
+ cursor_mode = arg->ui;
|
|
||||||
+ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
|
|
||||||
+
|
|
||||||
+ if (grabc->was_tiled) {
|
|
||||||
+ switch (cursor_mode) {
|
|
||||||
+ case CurMove:
|
|
||||||
+ setfloating(grabc, 1);
|
|
||||||
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
|
|
||||||
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
|
|
||||||
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
|
|
||||||
+ break;
|
|
||||||
+ case CurResize:
|
|
||||||
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
|
||||||
+ resize_last_update_x = cursor->x;
|
|
||||||
+ resize_last_update_y = cursor->y;
|
|
||||||
+ 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;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -2833,6 +2922,14 @@ unmapnotify(struct wl_listener *listener, void *data)
|
|
||||||
focusclient(focustop(selmon), 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
+ /* btrtile remove clients for each monitor */
|
|
||||||
+ Monitor *mon;
|
|
||||||
+ wl_list_for_each(mon, &mons, link) {
|
|
||||||
+ if (mon->root) {
|
|
||||||
+ remove_client(mon, c);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
wl_list_remove(&c->link);
|
|
||||||
setmon(c, NULL, 0);
|
|
||||||
wl_list_remove(&c->flink);
|
|
||||||
--
|
|
||||||
2.53.0
|
|
||||||
|
|
||||||
@@ -1,900 +0,0 @@
|
|||||||
From 3500e732fc34aa849cd3babeff7d2545a0230fa9 Mon Sep 17 00:00:00 2001
|
|
||||||
From: julmajustus <julmajustus@tutanota.com>
|
|
||||||
Date: Mon, 22 Jun 2026 22:33:17 +0300
|
|
||||||
Subject: [PATCH] btrtile: bug fixes and readability improvement
|
|
||||||
|
|
||||||
- Removed client old_geom caching for simplicity fixes: https://codeberg.org/dwl/dwl-patches/issues/647
|
|
||||||
- Improved readability of the client sanitization loop inside btrtile function
|
|
||||||
- Removed fullscreened client's from the sanitization loop to retain their geometry
|
|
||||||
---
|
|
||||||
btrtile.c | 556 +++++++++++++++++++++++++++++++++++++++++++++++++++
|
|
||||||
config.def.h | 12 ++
|
|
||||||
dwl.c | 151 +++++++++++---
|
|
||||||
3 files changed, 692 insertions(+), 27 deletions(-)
|
|
||||||
create mode 100644 btrtile.c
|
|
||||||
|
|
||||||
diff --git a/btrtile.c b/btrtile.c
|
|
||||||
new file mode 100644
|
|
||||||
index 0000000..b4f6f43
|
|
||||||
--- /dev/null
|
|
||||||
+++ b/btrtile.c
|
|
||||||
@@ -0,0 +1,556 @@
|
|
||||||
+/* ************************************************************************** */
|
|
||||||
+/* @@@ @@@@@@@@ */
|
|
||||||
+/* @@@ @@@@@@@@@@ */
|
|
||||||
+/* @@! @@! @@@@ */
|
|
||||||
+/* !@! !@! @!@!@ */
|
|
||||||
+/* btrtile.c @!! @!@ @! !@! */
|
|
||||||
+/* !!! !@!!! !!! */
|
|
||||||
+/* By: julmajustus <julmajustus@tutanota.com> !!: !!:! !!! */
|
|
||||||
+/* ::! :!: !:! */
|
|
||||||
+/* Created: 2024/12/15 00:26:07 by julmajustus :: ::::::: :: */
|
|
||||||
+/* Updated: 2026/06/22 22:30:19 by julmajustus : : : : : : */
|
|
||||||
+/* */
|
|
||||||
+/* ************************************************************************** */
|
|
||||||
+
|
|
||||||
+typedef struct LayoutNode {
|
|
||||||
+ unsigned int is_client_node;
|
|
||||||
+ unsigned int is_split_vertically;
|
|
||||||
+ float split_ratio;
|
|
||||||
+ struct LayoutNode *left;
|
|
||||||
+ struct LayoutNode *right;
|
|
||||||
+ struct LayoutNode *split_node;
|
|
||||||
+ Client *client;
|
|
||||||
+} LayoutNode;
|
|
||||||
+
|
|
||||||
+static void apply_layout(Monitor *m, LayoutNode *node,
|
|
||||||
+ struct wlr_box area, unsigned int is_root);
|
|
||||||
+static void btrtile(Monitor *m);
|
|
||||||
+static LayoutNode *create_client_node(Client *c);
|
|
||||||
+static LayoutNode *create_split_node(unsigned int is_split_vertically,
|
|
||||||
+ LayoutNode *left, LayoutNode *right);
|
|
||||||
+static void destroy_node(LayoutNode *node);
|
|
||||||
+static void destroy_tree(Monitor *m);
|
|
||||||
+static LayoutNode *find_client_node(LayoutNode *node, Client *c);
|
|
||||||
+static LayoutNode *find_suitable_split(Monitor *m, LayoutNode *start,
|
|
||||||
+ unsigned int need_vertical, int focused_on_left);
|
|
||||||
+static void init_tree(Monitor *m);
|
|
||||||
+static void insert_client(Monitor *m, Client *focused_client, Client *new_client);
|
|
||||||
+static LayoutNode *remove_client_node(LayoutNode *node, Client *c);
|
|
||||||
+static void remove_client(Monitor *m, Client *c);
|
|
||||||
+static void setratio_h(const Arg *arg);
|
|
||||||
+static void setratio_v(const Arg *arg);
|
|
||||||
+static void swapclients(const Arg *arg);
|
|
||||||
+static unsigned int visible_count(LayoutNode *node, Monitor *m);
|
|
||||||
+static Client *xytoclient(double x, double y);
|
|
||||||
+
|
|
||||||
+static double resize_last_update_x, resize_last_update_y;
|
|
||||||
+static uint32_t last_resize_time = 0;
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+apply_layout(Monitor *m, LayoutNode *node,
|
|
||||||
+ struct wlr_box area, unsigned int is_root)
|
|
||||||
+{
|
|
||||||
+ Client *c;
|
|
||||||
+ float ratio;
|
|
||||||
+ unsigned int left_count, right_count, mid;
|
|
||||||
+ struct wlr_box left_area, right_area;
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ /* If this node is a client node, check if it is visible. */
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ c = node->client;
|
|
||||||
+ if (!c || !VISIBLEON(c, m) || c->isfloating || c->isfullscreen)
|
|
||||||
+ return;
|
|
||||||
+ resize(c, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* For a split node, we see how many visible children are on each side: */
|
|
||||||
+ left_count = visible_count(node->left, m);
|
|
||||||
+ right_count = visible_count(node->right, m);
|
|
||||||
+
|
|
||||||
+ if (left_count == 0 && right_count == 0) {
|
|
||||||
+ return;
|
|
||||||
+ } else if (left_count > 0 && right_count == 0) {
|
|
||||||
+ apply_layout(m, node->left, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ } else if (left_count == 0 && right_count > 0) {
|
|
||||||
+ apply_layout(m, node->right, area, 0);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If we’re here, we have visible clients in both subtrees. */
|
|
||||||
+ ratio = node->split_ratio;
|
|
||||||
+ if (ratio < 0.05f)
|
|
||||||
+ ratio = 0.05f;
|
|
||||||
+ if (ratio > 0.95f)
|
|
||||||
+ ratio = 0.95f;
|
|
||||||
+
|
|
||||||
+ memset(&left_area, 0, sizeof(left_area));
|
|
||||||
+ memset(&right_area, 0, sizeof(right_area));
|
|
||||||
+
|
|
||||||
+ if (node->is_split_vertically) {
|
|
||||||
+ mid = (unsigned int)(area.width * ratio);
|
|
||||||
+ left_area.x = area.x;
|
|
||||||
+ left_area.y = area.y;
|
|
||||||
+ left_area.width = mid;
|
|
||||||
+ left_area.height = area.height;
|
|
||||||
+
|
|
||||||
+ right_area.x = area.x + mid;
|
|
||||||
+ right_area.y = area.y;
|
|
||||||
+ right_area.width = area.width - mid;
|
|
||||||
+ right_area.height = area.height;
|
|
||||||
+ } else {
|
|
||||||
+ /* horizontal split */
|
|
||||||
+ mid = (unsigned int)(area.height * ratio);
|
|
||||||
+ left_area.x = area.x;
|
|
||||||
+ left_area.y = area.y;
|
|
||||||
+ left_area.width = area.width;
|
|
||||||
+ left_area.height = mid;
|
|
||||||
+
|
|
||||||
+ right_area.x = area.x;
|
|
||||||
+ right_area.y = area.y + mid;
|
|
||||||
+ right_area.width = area.width;
|
|
||||||
+ right_area.height= area.height - mid;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ apply_layout(m, node->left, left_area, 0);
|
|
||||||
+ apply_layout(m, node->right, right_area, 0);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+btrtile(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ Client *c, *focused = NULL;
|
|
||||||
+ int n = 0;
|
|
||||||
+ LayoutNode *found;
|
|
||||||
+ struct wlr_box full_area;
|
|
||||||
+
|
|
||||||
+ if (!m)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ /* Remove floating and moved clients */
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (c->mon != m || c->isfloating)
|
|
||||||
+ remove_client(m, c);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If no client is found under cursor, fallback to focustop(m) */
|
|
||||||
+ if (!(focused = xytoclient(cursor->x, cursor->y)))
|
|
||||||
+ focused = focustop(m);
|
|
||||||
+
|
|
||||||
+ /* Insert visible clients that are not part of the tree. */
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen && c->mon == m) {
|
|
||||||
+ found = find_client_node(m->root, c);
|
|
||||||
+ if (!found) {
|
|
||||||
+ insert_client(m, focused, c);
|
|
||||||
+ }
|
|
||||||
+ n++;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (n == 0)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ full_area = m->w;
|
|
||||||
+ apply_layout(m, m->root, full_area, 1);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+create_client_node(Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ node->is_client_node = 1;
|
|
||||||
+ node->split_ratio = 0.5f;
|
|
||||||
+ node->client = c;
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+create_split_node(unsigned int is_split_vertically,
|
|
||||||
+ LayoutNode *left, LayoutNode *right)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *node = calloc(1, sizeof(LayoutNode));
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ node->is_client_node = 0;
|
|
||||||
+ node->split_ratio = 0.5f;
|
|
||||||
+ node->is_split_vertically = is_split_vertically;
|
|
||||||
+ node->left = left;
|
|
||||||
+ node->right = right;
|
|
||||||
+ if (left)
|
|
||||||
+ left->split_node = node;
|
|
||||||
+ if (right)
|
|
||||||
+ right->split_node = node;
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+destroy_node(LayoutNode *node)
|
|
||||||
+{
|
|
||||||
+ if (!node)
|
|
||||||
+ return;
|
|
||||||
+ if (!node->is_client_node) {
|
|
||||||
+ destroy_node(node->left);
|
|
||||||
+ destroy_node(node->right);
|
|
||||||
+ }
|
|
||||||
+ free(node);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+destroy_tree(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ if (!m || !m->root)
|
|
||||||
+ return;
|
|
||||||
+ destroy_node(m->root);
|
|
||||||
+ m->root = NULL;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+find_client_node(LayoutNode *node, Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *res;
|
|
||||||
+
|
|
||||||
+ if (!node || !c)
|
|
||||||
+ return NULL;
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ return (node->client == c) ? node : NULL;
|
|
||||||
+ }
|
|
||||||
+ res = find_client_node(node->left, c);
|
|
||||||
+ return res ? res : find_client_node(node->right, c);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+find_suitable_split(Monitor *m, LayoutNode *start_node,
|
|
||||||
+ unsigned int need_vertical, int focused_on_left)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *n = start_node, *child = NULL;
|
|
||||||
+
|
|
||||||
+ if (!m)
|
|
||||||
+ return NULL;
|
|
||||||
+
|
|
||||||
+ if (n && n->is_client_node) {
|
|
||||||
+ child = n;
|
|
||||||
+ n = n->split_node;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ while (n) {
|
|
||||||
+ if (!n->is_client_node && n->is_split_vertically == need_vertical
|
|
||||||
+ && visible_count(n->left, m) > 0
|
|
||||||
+ && visible_count(n->right, m) > 0) {
|
|
||||||
+ if ((focused_on_left && n->left == child) ||
|
|
||||||
+ (!focused_on_left && n->right == child))
|
|
||||||
+ return n;
|
|
||||||
+ }
|
|
||||||
+ child = n;
|
|
||||||
+ n = n->split_node;
|
|
||||||
+ }
|
|
||||||
+ return NULL;
|
|
||||||
+}
|
|
||||||
+void
|
|
||||||
+init_tree(Monitor *m)
|
|
||||||
+{
|
|
||||||
+ if (m)
|
|
||||||
+ m->root = NULL;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+insert_client(Monitor *m, Client *focused_client, Client *new_client)
|
|
||||||
+{
|
|
||||||
+ Client *old_client;
|
|
||||||
+ LayoutNode **root = &m->root, *old_root,
|
|
||||||
+ *focused_node, *new_client_node, *old_client_node;
|
|
||||||
+ unsigned int wider, mid_x, mid_y;
|
|
||||||
+
|
|
||||||
+ /* If no root , new client becomes the root. */
|
|
||||||
+ if (!*root) {
|
|
||||||
+ *root = create_client_node(new_client);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Find the focused_client node,
|
|
||||||
+ * if not found split the root. */
|
|
||||||
+ focused_node = focused_client ?
|
|
||||||
+ find_client_node(*root, focused_client) : NULL;
|
|
||||||
+ if (!focused_node) {
|
|
||||||
+ old_root = *root;
|
|
||||||
+ new_client_node = create_client_node(new_client);
|
|
||||||
+ *root = create_split_node(1, old_root, new_client_node);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Turn focused node from a client node into a split node,
|
|
||||||
+ * and attach old_client + new_client. */
|
|
||||||
+ old_client = focused_node->client;
|
|
||||||
+ old_client_node = create_client_node(old_client);
|
|
||||||
+ new_client_node = create_client_node(new_client);
|
|
||||||
+
|
|
||||||
+ /* Decide split direction. */
|
|
||||||
+ wider = (focused_client->geom.width >= focused_client->geom.height);
|
|
||||||
+ focused_node->is_client_node = 0;
|
|
||||||
+ focused_node->client = NULL;
|
|
||||||
+ focused_node->is_split_vertically = (wider ? 1 : 0);
|
|
||||||
+
|
|
||||||
+ /* Pick new_client side depending on the cursor position. */
|
|
||||||
+ mid_x = focused_client->geom.x + focused_client->geom.width / 2;
|
|
||||||
+ mid_y = focused_client->geom.y + focused_client->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ if (wider) {
|
|
||||||
+ /* vertical split => left vs right */
|
|
||||||
+ if (cursor->x <= mid_x) {
|
|
||||||
+ focused_node->left = new_client_node;
|
|
||||||
+ focused_node->right = old_client_node;
|
|
||||||
+ } else {
|
|
||||||
+ focused_node->left = old_client_node;
|
|
||||||
+ focused_node->right = new_client_node;
|
|
||||||
+ }
|
|
||||||
+ } else {
|
|
||||||
+ /* horizontal split => top vs bottom */
|
|
||||||
+ if (cursor->y <= mid_y) {
|
|
||||||
+ focused_node->left = new_client_node;
|
|
||||||
+ focused_node->right = old_client_node;
|
|
||||||
+ } else {
|
|
||||||
+ focused_node->left = old_client_node;
|
|
||||||
+ focused_node->right = new_client_node;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ old_client_node->split_node = focused_node;
|
|
||||||
+ new_client_node->split_node = focused_node;
|
|
||||||
+ focused_node->split_ratio = 0.5f;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+LayoutNode *
|
|
||||||
+remove_client_node(LayoutNode *node, Client *c)
|
|
||||||
+{
|
|
||||||
+ LayoutNode *tmp;
|
|
||||||
+ if (!node)
|
|
||||||
+ return NULL;
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ /* If this client_node is the client we're removing,
|
|
||||||
+ * return NULL to remove it */
|
|
||||||
+ if (node->client == c) {
|
|
||||||
+ free(node);
|
|
||||||
+ return NULL;
|
|
||||||
+ }
|
|
||||||
+ return node;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ node->left = remove_client_node(node->left, c);
|
|
||||||
+ node->right = remove_client_node(node->right, c);
|
|
||||||
+
|
|
||||||
+ /* If one of the client node is NULL after removal and the other is not,
|
|
||||||
+ * we "lift" the other client node up to replace this split node. */
|
|
||||||
+ if (!node->left && node->right) {
|
|
||||||
+ tmp = node->right;
|
|
||||||
+
|
|
||||||
+ /* Save pointer to split node */
|
|
||||||
+ if (tmp)
|
|
||||||
+ tmp->split_node = node->split_node;
|
|
||||||
+
|
|
||||||
+ free(node);
|
|
||||||
+ return tmp;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ if (!node->right && node->left) {
|
|
||||||
+ tmp = node->left;
|
|
||||||
+
|
|
||||||
+ /* Save pointer to split node */
|
|
||||||
+ if (tmp)
|
|
||||||
+ tmp->split_node = node->split_node;
|
|
||||||
+
|
|
||||||
+ free(node);
|
|
||||||
+ return tmp;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If both children exist or both are NULL (empty tree),
|
|
||||||
+ * return node as is. */
|
|
||||||
+ return node;
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+remove_client(Monitor *m, Client *c)
|
|
||||||
+{
|
|
||||||
+ if (!m->root || !c)
|
|
||||||
+ return;
|
|
||||||
+ m->root = remove_client_node(m->root, c);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+static void
|
|
||||||
+setratio(unsigned int need_vertical, const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ Client *sel;
|
|
||||||
+ LayoutNode *client_node, *split_node;
|
|
||||||
+ float new_ratio;
|
|
||||||
+ int focused_on_left;
|
|
||||||
+
|
|
||||||
+ if (!selmon || !selmon->lt[selmon->sellt]->arrange)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ sel = focustop(selmon);
|
|
||||||
+ if (!sel)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ client_node = find_client_node(selmon->root, sel);
|
|
||||||
+ if (!client_node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ focused_on_left = (arg->f >= 0.0f);
|
|
||||||
+
|
|
||||||
+ split_node = find_suitable_split(selmon, client_node, need_vertical, focused_on_left);
|
|
||||||
+
|
|
||||||
+ if (!split_node)
|
|
||||||
+ split_node = find_suitable_split(selmon, client_node, need_vertical, !focused_on_left);
|
|
||||||
+ if (!split_node)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+ new_ratio = (arg->f != 0.0f) ? (split_node->split_ratio + arg->f) : 0.5f;
|
|
||||||
+ if (new_ratio < 0.05f)
|
|
||||||
+ new_ratio = 0.05f;
|
|
||||||
+ if (new_ratio > 0.95f)
|
|
||||||
+ new_ratio = 0.95f;
|
|
||||||
+ split_node->split_ratio = new_ratio;
|
|
||||||
+
|
|
||||||
+ apply_layout(selmon, selmon->root, selmon->w, 1);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+setratio_h(const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ setratio(1, arg);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void
|
|
||||||
+setratio_v(const Arg *arg)
|
|
||||||
+{
|
|
||||||
+ setratio(0, arg);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+void swapclients(const Arg *arg) {
|
|
||||||
+ Client *c, *tmp, *target = NULL, *sel = focustop(selmon);
|
|
||||||
+ LayoutNode *sel_node, *target_node;
|
|
||||||
+ int closest_dist = INT_MAX, dist, sel_center_x, sel_center_y,
|
|
||||||
+ cand_center_x, cand_center_y;
|
|
||||||
+
|
|
||||||
+ if (!sel || sel->isfullscreen ||
|
|
||||||
+ !selmon->root || !selmon->lt[selmon->sellt]->arrange)
|
|
||||||
+ return;
|
|
||||||
+
|
|
||||||
+
|
|
||||||
+ /* Get the center coordinates of the selected client */
|
|
||||||
+ sel_center_x = sel->geom.x + sel->geom.width / 2;
|
|
||||||
+ sel_center_y = sel->geom.y + sel->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ wl_list_for_each(c, &clients, link) {
|
|
||||||
+ if (!VISIBLEON(c, selmon) || c->isfloating || c->isfullscreen || c == sel)
|
|
||||||
+ continue;
|
|
||||||
+
|
|
||||||
+ /* Get the center of candidate client */
|
|
||||||
+ cand_center_x = c->geom.x + c->geom.width / 2;
|
|
||||||
+ cand_center_y = c->geom.y + c->geom.height / 2;
|
|
||||||
+
|
|
||||||
+ /* Check that the candidate lies in the requested direction. */
|
|
||||||
+ switch (arg->ui) {
|
|
||||||
+ case 0:
|
|
||||||
+ if (cand_center_x >= sel_center_x)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 1:
|
|
||||||
+ if (cand_center_x <= sel_center_x)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 2:
|
|
||||||
+ if (cand_center_y >= sel_center_y)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ case 3:
|
|
||||||
+ if (cand_center_y <= sel_center_y)
|
|
||||||
+ continue;
|
|
||||||
+ break;
|
|
||||||
+ default:
|
|
||||||
+ continue;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* Get distance between the centers */
|
|
||||||
+ dist = abs(sel_center_x - cand_center_x) + abs(sel_center_y - cand_center_y);
|
|
||||||
+ if (dist < closest_dist) {
|
|
||||||
+ closest_dist = dist;
|
|
||||||
+ target = c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If target is found, swap the two clients’ positions in the layout tree */
|
|
||||||
+ if (target) {
|
|
||||||
+ sel_node = find_client_node(selmon->root, sel);
|
|
||||||
+ target_node = find_client_node(selmon->root, target);
|
|
||||||
+ if (sel_node && target_node) {
|
|
||||||
+ tmp = sel_node->client;
|
|
||||||
+ sel_node->client = target_node->client;
|
|
||||||
+ target_node->client = tmp;
|
|
||||||
+ arrange(selmon);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+unsigned int
|
|
||||||
+visible_count(LayoutNode *node, Monitor *m)
|
|
||||||
+{
|
|
||||||
+ Client *c;
|
|
||||||
+
|
|
||||||
+ if (!node)
|
|
||||||
+ return 0;
|
|
||||||
+ /* Check if this client is visible. */
|
|
||||||
+ if (node->is_client_node) {
|
|
||||||
+ c = node->client;
|
|
||||||
+ if (c && VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen)
|
|
||||||
+ return 1;
|
|
||||||
+ return 0;
|
|
||||||
+ }
|
|
||||||
+ /* Else it’s a split node. */
|
|
||||||
+ return visible_count(node->left, m) + visible_count(node->right, m);
|
|
||||||
+}
|
|
||||||
+
|
|
||||||
+Client *
|
|
||||||
+xytoclient(double x, double y) {
|
|
||||||
+ Monitor *m = xytomon(x, y);
|
|
||||||
+ Client *c, *closest = NULL;
|
|
||||||
+ double dist, mindist = INT_MAX, dx, dy;
|
|
||||||
+
|
|
||||||
+ wl_list_for_each_reverse(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen &&
|
|
||||||
+ x >= c->geom.x && x <= (c->geom.x + c->geom.width) &&
|
|
||||||
+ y >= c->geom.y && y <= (c->geom.y + c->geom.height)){
|
|
||||||
+ return c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ /* If no client was found at cursor position fallback to closest. */
|
|
||||||
+ wl_list_for_each_reverse(c, &clients, link) {
|
|
||||||
+ if (VISIBLEON(c, m) && !c->isfloating && !c->isfullscreen) {
|
|
||||||
+ dx = 0, dy = 0;
|
|
||||||
+
|
|
||||||
+ if (x < c->geom.x)
|
|
||||||
+ dx = c->geom.x - x;
|
|
||||||
+ else if (x > (c->geom.x + c->geom.width))
|
|
||||||
+ dx = x - (c->geom.x + c->geom.width);
|
|
||||||
+
|
|
||||||
+ if (y < c->geom.y)
|
|
||||||
+ dy = c->geom.y - y;
|
|
||||||
+ else if (y > (c->geom.y + c->geom.height))
|
|
||||||
+ dy = y - (c->geom.y + c->geom.height);
|
|
||||||
+
|
|
||||||
+ dist = dx * dx + dy * dy;
|
|
||||||
+ if (dist < mindist) {
|
|
||||||
+ mindist = dist;
|
|
||||||
+ closest = c;
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ return closest;
|
|
||||||
+}
|
|
||||||
diff --git a/config.def.h b/config.def.h
|
|
||||||
index 8a6eda0..bc04e3f 100644
|
|
||||||
--- a/config.def.h
|
|
||||||
+++ b/config.def.h
|
|
||||||
@@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff);
|
|
||||||
static const float urgentcolor[] = COLOR(0xff0000ff);
|
|
||||||
/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
|
|
||||||
static const float fullscreen_bg[] = {0.0f, 0.0f, 0.0f, 1.0f}; /* You can also use glsl colors */
|
|
||||||
+static const float resize_factor = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
|
|
||||||
+static const uint32_t resize_interval_ms = 16; /* Resize interval depends on framerate and screen refresh rate. */
|
|
||||||
|
|
||||||
+enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
|
|
||||||
/* tagging - TAGCOUNT must be no greater than 31 */
|
|
||||||
#define TAGCOUNT (9)
|
|
||||||
|
|
||||||
@@ -30,6 +33,7 @@ static const Rule rules[] = {
|
|
||||||
/* layout(s) */
|
|
||||||
static const Layout layouts[] = {
|
|
||||||
/* symbol arrange function */
|
|
||||||
+ { "|w|", btrtile },
|
|
||||||
{ "[]=", tile },
|
|
||||||
{ "><>", NULL }, /* no layout function means floating behavior */
|
|
||||||
{ "[M]", monocle },
|
|
||||||
@@ -144,6 +148,14 @@ static const Key keys[] = {
|
|
||||||
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
|
|
||||||
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
|
|
||||||
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up, swapclients, {.i = DIR_UP} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down, swapclients, {.i = DIR_DOWN} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right, swapclients, {.i = DIR_RIGHT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left, swapclients, {.i = DIR_LEFT} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Right, setratio_h, {.f = +0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Left, setratio_h, {.f = -0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Up, setratio_v, {.f = -0.025f} },
|
|
||||||
+ { MODKEY|WLR_MODIFIER_CTRL, XKB_KEY_Down, setratio_v, {.f = +0.025f} },
|
|
||||||
TAGKEYS( XKB_KEY_1, XKB_KEY_exclam, 0),
|
|
||||||
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
|
|
||||||
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
|
|
||||||
diff --git a/dwl.c b/dwl.c
|
|
||||||
index 8101ffa..31446ed 100644
|
|
||||||
--- a/dwl.c
|
|
||||||
+++ b/dwl.c
|
|
||||||
@@ -1,6 +1,7 @@
|
|
||||||
/*
|
|
||||||
* See LICENSE file for copyright and license details.
|
|
||||||
*/
|
|
||||||
+#include <limits.h>
|
|
||||||
#include <getopt.h>
|
|
||||||
#include <libinput.h>
|
|
||||||
#include <linux/input-event-codes.h>
|
|
||||||
@@ -104,6 +105,7 @@ typedef struct {
|
|
||||||
const Arg arg;
|
|
||||||
} Button;
|
|
||||||
|
|
||||||
+typedef struct LayoutNode LayoutNode;
|
|
||||||
typedef struct Monitor Monitor;
|
|
||||||
typedef struct {
|
|
||||||
/* Must keep this field first */
|
|
||||||
@@ -141,7 +143,7 @@ typedef struct {
|
|
||||||
#endif
|
|
||||||
unsigned int bw;
|
|
||||||
uint32_t tags;
|
|
||||||
- int isfloating, isurgent, isfullscreen;
|
|
||||||
+ int isfloating, isurgent, isfullscreen, was_tiled;
|
|
||||||
uint32_t resize; /* configure serial of a pending resize */
|
|
||||||
} Client;
|
|
||||||
|
|
||||||
@@ -209,6 +211,7 @@ struct Monitor {
|
|
||||||
int nmaster;
|
|
||||||
char ltsymbol[16];
|
|
||||||
int asleep;
|
|
||||||
+ LayoutNode *root;
|
|
||||||
};
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
@@ -251,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
|
|
||||||
struct wlr_box *usable_area, int exclusive);
|
|
||||||
static void arrangelayers(Monitor *m);
|
|
||||||
static void axisnotify(struct wl_listener *listener, void *data);
|
|
||||||
+static void btrtile(Monitor *m);
|
|
||||||
static void buttonpress(struct wl_listener *listener, void *data);
|
|
||||||
static void chvt(const Arg *arg);
|
|
||||||
static void checkidleinhibitor(struct wlr_surface *exclude);
|
|
||||||
@@ -333,6 +337,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
|
|
||||||
static void setpsel(struct wl_listener *listener, void *data);
|
|
||||||
static void setsel(struct wl_listener *listener, void *data);
|
|
||||||
static void setup(void);
|
|
||||||
+static void setratio_h(const Arg *arg);
|
|
||||||
+static void setratio_v(const Arg *arg);
|
|
||||||
+static void swapclients(const Arg *arg);
|
|
||||||
static void spawn(const Arg *arg);
|
|
||||||
static void startdrag(struct wl_listener *listener, void *data);
|
|
||||||
static void tag(const Arg *arg);
|
|
||||||
@@ -458,6 +465,7 @@ static struct wlr_xwayland *xwayland;
|
|
||||||
|
|
||||||
/* attempt to encapsulate suck into one file */
|
|
||||||
#include "client.h"
|
|
||||||
+#include "btrtile.c"
|
|
||||||
|
|
||||||
/* function implementations */
|
|
||||||
void
|
|
||||||
@@ -628,7 +636,7 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
struct wlr_pointer_button_event *event = data;
|
|
||||||
struct wlr_keyboard *keyboard;
|
|
||||||
uint32_t mods;
|
|
||||||
- Client *c;
|
|
||||||
+ Client *c, *target = NULL;
|
|
||||||
const Button *b;
|
|
||||||
|
|
||||||
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
|
|
||||||
@@ -649,7 +657,7 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
|
|
||||||
for (b = buttons; b < END(buttons); b++) {
|
|
||||||
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
|
|
||||||
- event->button == b->button && b->func) {
|
|
||||||
+ event->button == b->button && b->func) {
|
|
||||||
b->func(&b->arg);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@@ -659,6 +667,21 @@ buttonpress(struct wl_listener *listener, void *data)
|
|
||||||
/* If you released any buttons, we exit interactive move/resize mode. */
|
|
||||||
/* TODO: should reset to the pointer focus's current setcursor */
|
|
||||||
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
|
|
||||||
+ c = grabc;
|
|
||||||
+ if (c && c->was_tiled && !strcmp(selmon->ltsymbol, "|w|")) {
|
|
||||||
+ if (cursor_mode == CurMove && c->isfloating) {
|
|
||||||
+ target = xytoclient(cursor->x, cursor->y);
|
|
||||||
+
|
|
||||||
+ if (target && !target->isfloating && !target->isfullscreen)
|
|
||||||
+ insert_client(selmon, target, c);
|
|
||||||
+ else
|
|
||||||
+ selmon->root = create_client_node(c);
|
|
||||||
+
|
|
||||||
+ setfloating(c, 0);
|
|
||||||
+ apply_layout(selmon, selmon->root, selmon->w, 1);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+ /* Default behaviour */
|
|
||||||
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
|
|
||||||
cursor_mode = CurNormal;
|
|
||||||
/* Drop the window off on its new monitor */
|
|
||||||
@@ -750,6 +773,7 @@ cleanupmon(struct wl_listener *listener, void *data)
|
|
||||||
wlr_output_layout_remove(output_layout, m->wlr_output);
|
|
||||||
wlr_scene_output_destroy(m->scene_output);
|
|
||||||
|
|
||||||
+ destroy_tree(m);
|
|
||||||
closemon(m);
|
|
||||||
wlr_scene_node_destroy(&m->fullscreen_bg->node);
|
|
||||||
free(m);
|
|
||||||
@@ -1094,6 +1118,7 @@ createmon(struct wl_listener *listener, void *data)
|
|
||||||
|
|
||||||
wl_list_insert(&mons, &m->link);
|
|
||||||
printstatus();
|
|
||||||
+ init_tree(m);
|
|
||||||
|
|
||||||
/* The xdg-protocol specifies:
|
|
||||||
*
|
|
||||||
@@ -1333,9 +1358,17 @@ destroynotify(struct wl_listener *listener, void *data)
|
|
||||||
{
|
|
||||||
/* Called when the xdg_toplevel is destroyed. */
|
|
||||||
Client *c = wl_container_of(listener, c, destroy);
|
|
||||||
+ Monitor *mon;
|
|
||||||
wl_list_remove(&c->destroy.link);
|
|
||||||
wl_list_remove(&c->set_title.link);
|
|
||||||
wl_list_remove(&c->fullscreen.link);
|
|
||||||
+ /* We check if the destroyed client was part of any tiled_list, to catch
|
|
||||||
+ * client removals even if they would not be currently managed by btrtile */
|
|
||||||
+ wl_list_for_each(mon, &mons, link) {
|
|
||||||
+ if (mon->root) {
|
|
||||||
+ remove_client(mon, c);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
#ifdef XWAYLAND
|
|
||||||
if (c->type != XDGShell) {
|
|
||||||
wl_list_remove(&c->activate.link);
|
|
||||||
@@ -1866,7 +1899,8 @@ void
|
|
||||||
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
|
|
||||||
double dx_unaccel, double dy_unaccel)
|
|
||||||
{
|
|
||||||
- double sx = 0, sy = 0, sx_confined, sy_confined;
|
|
||||||
+ int tiled = 0;
|
|
||||||
+ double sx = 0, sy = 0, sx_confined, sy_confined, dx_total, dy_total;
|
|
||||||
Client *c = NULL, *w = NULL;
|
|
||||||
LayerSurface *l = NULL;
|
|
||||||
struct wlr_surface *surface = NULL;
|
|
||||||
@@ -1920,18 +1954,55 @@ 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 */
|
|
||||||
+ if (time == 0)
|
|
||||||
+ goto focus;
|
|
||||||
+
|
|
||||||
+ tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
|
|
||||||
if (cursor_mode == CurMove) {
|
|
||||||
/* Move the grabbed client to the new position. */
|
|
||||||
- resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
|
|
||||||
- .width = grabc->geom.width, .height = grabc->geom.height}, 1);
|
|
||||||
- return;
|
|
||||||
+ if (grabc && grabc->isfloating) {
|
|
||||||
+ resize(grabc, (struct wlr_box){
|
|
||||||
+ .x = (int)round(cursor->x) - grabcx,
|
|
||||||
+ .y = (int)round(cursor->y) - grabcy,
|
|
||||||
+ .width = grabc->geom.width,
|
|
||||||
+ .height = grabc->geom.height
|
|
||||||
+ }, 1);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
} else if (cursor_mode == CurResize) {
|
|
||||||
- resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
|
|
||||||
- .width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
|
|
||||||
- return;
|
|
||||||
+ if (tiled) {
|
|
||||||
+ dx_total = cursor->x - resize_last_update_x;
|
|
||||||
+ dy_total = cursor->y - resize_last_update_y;
|
|
||||||
+
|
|
||||||
+ if (time - last_resize_time >= resize_interval_ms) {
|
|
||||||
+ Arg a = {0};
|
|
||||||
+ if (fabs(dx_total) > fabs(dy_total)) {
|
|
||||||
+ a.f = (float)(dx_total * resize_factor);
|
|
||||||
+ setratio_h(&a);
|
|
||||||
+ } else {
|
|
||||||
+ a.f = (float)(dy_total * resize_factor);
|
|
||||||
+ setratio_v(&a);
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ last_resize_time = time;
|
|
||||||
+ resize_last_update_x = cursor->x;
|
|
||||||
+ resize_last_update_y = cursor->y;
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
+ } else if (grabc && grabc->isfloating) {
|
|
||||||
+ /* Floating resize as original */
|
|
||||||
+ resize(grabc, (struct wlr_box){
|
|
||||||
+ .x = grabc->geom.x,
|
|
||||||
+ .y = grabc->geom.y,
|
|
||||||
+ .width = (int)round(cursor->x) - grabc->geom.x,
|
|
||||||
+ .height = (int)round(cursor->y) - grabc->geom.y
|
|
||||||
+ }, 1);
|
|
||||||
+ return;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
|
|
||||||
+focus:
|
|
||||||
/* If there's no client surface under the cursor, set the cursor image to a
|
|
||||||
* default. This is what makes the cursor image appear when you move it
|
|
||||||
* off of a client or over its border. */
|
|
||||||
@@ -1965,22 +2036,40 @@ moveresize(const Arg *arg)
|
|
||||||
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
|
|
||||||
return;
|
|
||||||
|
|
||||||
- /* Float the window and tell motionnotify to grab it */
|
|
||||||
- setfloating(grabc, 1);
|
|
||||||
- switch (cursor_mode = arg->ui) {
|
|
||||||
- case CurMove:
|
|
||||||
- grabcx = (int)round(cursor->x) - grabc->geom.x;
|
|
||||||
- grabcy = (int)round(cursor->y) - grabc->geom.y;
|
|
||||||
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "all-scroll");
|
|
||||||
- break;
|
|
||||||
- case CurResize:
|
|
||||||
- /* Doesn't work for X11 output - the next absolute motion event
|
|
||||||
- * returns the cursor to where it started */
|
|
||||||
- wlr_cursor_warp_closest(cursor, NULL,
|
|
||||||
- grabc->geom.x + grabc->geom.width,
|
|
||||||
- grabc->geom.y + grabc->geom.height);
|
|
||||||
- wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
|
||||||
- break;
|
|
||||||
+ cursor_mode = arg->ui;
|
|
||||||
+ grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
|
|
||||||
+
|
|
||||||
+ if (grabc->was_tiled) {
|
|
||||||
+ switch (cursor_mode) {
|
|
||||||
+ case CurMove:
|
|
||||||
+ setfloating(grabc, 1);
|
|
||||||
+ grabcx = (int)round(cursor->x) - grabc->geom.x;
|
|
||||||
+ grabcy = (int)round(cursor->y) - grabc->geom.y;
|
|
||||||
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
|
|
||||||
+ break;
|
|
||||||
+ case CurResize:
|
|
||||||
+ wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
|
||||||
+ resize_last_update_x = cursor->x;
|
|
||||||
+ resize_last_update_y = cursor->y;
|
|
||||||
+ 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;
|
|
||||||
+ }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@@ -2833,6 +2922,14 @@ unmapnotify(struct wl_listener *listener, void *data)
|
|
||||||
focusclient(focustop(selmon), 1);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
+ /* btrtile remove clients for each monitor */
|
|
||||||
+ Monitor *mon;
|
|
||||||
+ wl_list_for_each(mon, &mons, link) {
|
|
||||||
+ if (mon->root) {
|
|
||||||
+ remove_client(mon, c);
|
|
||||||
+ }
|
|
||||||
+ }
|
|
||||||
+
|
|
||||||
wl_list_remove(&c->link);
|
|
||||||
setmon(c, NULL, 0);
|
|
||||||
wl_list_remove(&c->flink);
|
|
||||||
--
|
|
||||||
2.53.0
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user