mirror of
				https://codeberg.org/dwl/dwl-patches.git
				synced 2025-10-31 03:54:19 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			904 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
			
		
		
	
	
			904 lines
		
	
	
		
			27 KiB
		
	
	
	
		
			Diff
		
	
	
	
	
	
| From b9789420f166c20579f29ecd171a8956c681848d Mon Sep 17 00:00:00 2001
 | ||
| From: julmajustus <julmajustus@tutanota.com>
 | ||
| Date: Thu, 13 Feb 2025 23:23:40 +0200
 | ||
| Subject: [PATCH] btrtile with multi-tag support
 | ||
| 
 | ||
| ---
 | ||
|  btrtile.c    | 563 +++++++++++++++++++++++++++++++++++++++++++++++++++
 | ||
|  config.def.h |  12 ++
 | ||
|  dwl.c        | 152 +++++++++++---
 | ||
|  3 files changed, 698 insertions(+), 29 deletions(-)
 | ||
|  create mode 100644 btrtile.c
 | ||
| 
 | ||
| diff --git a/btrtile.c b/btrtile.c
 | ||
| new file mode 100644
 | ||
| index 0000000..03f4680
 | ||
| --- /dev/null
 | ||
| +++ b/btrtile.c
 | ||
| @@ -0,0 +1,563 @@
 | ||
| +/* ************************************************************************** */
 | ||
| +/*                                                                            */
 | ||
| +/*                                                        :::      ::::::::   */
 | ||
| +/*   btrtile.c                                          :+:      :+:    :+:   */
 | ||
| +/*                                                    +:+ +:+         +:+     */
 | ||
| +/*   By: jmakkone <jmakkone@student.hive.fi>        +#+  +:+       +#+        */
 | ||
| +/*                                                +#+#+#+#+#+   +#+           */
 | ||
| +/*   Created: 2024/12/15 00:26:07 by jmakkone          #+#    #+#             */
 | ||
| +/*   Updated: 2025/02/13 23:22:33 by jmakkone         ###   ########.fr       */
 | ||
| +/*                                                                            */
 | ||
| +/* ************************************************************************** */
 | ||
| +
 | ||
| +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(LayoutNode *start, unsigned int need_vert);
 | ||
| +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 int resizing_from_mouse = 0;
 | ||
| +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);
 | ||
| +		c->old_geom = area;
 | ||
| +		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 || !m->root)
 | ||
| +		return;
 | ||
| +
 | ||
| +	/* Remove non tiled clients from tree. */
 | ||
| +	wl_list_for_each(c, &clients, link) {
 | ||
| +		if (c->mon == m && !c->isfloating && !c->isfullscreen) {
 | ||
| +		} else {
 | ||
| +			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(LayoutNode *start_node, unsigned int need_vertical)
 | ||
| +{
 | ||
| +	LayoutNode *n = start_node;
 | ||
| +	/* if we started from a client node, jump to its parent: */
 | ||
| +	if (n && n->is_client_node)
 | ||
| +		n = n->split_node;
 | ||
| +
 | ||
| +	while (n) {
 | ||
| +		if (!n->is_client_node && n->is_split_vertically == need_vertical &&
 | ||
| +			visible_count(n->left, selmon) > 0 && visible_count(n->right, selmon) > 0)
 | ||
| +			return n;
 | ||
| +		n = n->split_node;
 | ||
| +	}
 | ||
| +	return NULL;
 | ||
| +}
 | ||
| +
 | ||
| +void
 | ||
| +init_tree(Monitor *m)
 | ||
| +{
 | ||
| +	if (!m)
 | ||
| +		return;
 | ||
| +	m->root = calloc(1, sizeof(LayoutNode));
 | ||
| +	if (!m->root)
 | ||
| +		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);
 | ||
| +}
 | ||
| +
 | ||
| +void
 | ||
| +setratio_h(const Arg *arg)
 | ||
| +{
 | ||
| +	Client *sel = focustop(selmon);
 | ||
| +	LayoutNode *client_node, *split_node;
 | ||
| +	float new_ratio;
 | ||
| +
 | ||
| +	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
 | ||
| +		return;
 | ||
| +
 | ||
| +	client_node = find_client_node(selmon->root, sel);
 | ||
| +	if (!client_node)
 | ||
| +		return;
 | ||
| +
 | ||
| +	split_node = find_suitable_split(client_node, 1);
 | ||
| +	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;
 | ||
| +
 | ||
| +	/* Skip the arrange if done resizing by mouse,
 | ||
| +	 * we call arrange from motionotify */
 | ||
| +	if (!resizing_from_mouse) {
 | ||
| +		arrange(selmon);
 | ||
| +	}
 | ||
| +}
 | ||
| +
 | ||
| +void
 | ||
| +setratio_v(const Arg *arg)
 | ||
| +{
 | ||
| +	Client *sel = focustop(selmon);
 | ||
| +	LayoutNode *client_node, *split_node;
 | ||
| +	float new_ratio;
 | ||
| +
 | ||
| +	if (!sel || !selmon || !selmon->lt[selmon->sellt]->arrange)
 | ||
| +		return;
 | ||
| +
 | ||
| +	client_node = find_client_node(selmon->root, sel);
 | ||
| +	if (!client_node)
 | ||
| +		return;
 | ||
| +
 | ||
| +	split_node = find_suitable_split(client_node, 0);
 | ||
| +	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;
 | ||
| +
 | ||
| +	/* Skip the arrange if done resizing by mouse,
 | ||
| +	 * we call arrange from motionotify */
 | ||
| +	if (!resizing_from_mouse) {
 | ||
| +		arrange(selmon);
 | ||
| +	}
 | ||
| +}
 | ||
| +
 | ||
| +void swapclients(const Arg *arg) {
 | ||
| +    Client  *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) {
 | ||
| +	Client *c, *closest = NULL;
 | ||
| +	double dist, mindist = INT_MAX, dx, dy;
 | ||
| +
 | ||
| +	wl_list_for_each_reverse(c, &clients, link) {
 | ||
| +		if (VISIBLEON(c, selmon) && !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, selmon) && !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 = sqrt(dx * dx + dy * dy);
 | ||
| +			if (dist < mindist) {
 | ||
| +				mindist = dist;
 | ||
| +				closest = c;
 | ||
| +			}
 | ||
| +		}
 | ||
| +	}
 | ||
| +	return closest;
 | ||
| +}
 | ||
| diff --git a/config.def.h b/config.def.h
 | ||
| index 22d2171..92f3ad6 100644
 | ||
| --- a/config.def.h
 | ||
| +++ b/config.def.h
 | ||
| @@ -13,7 +13,10 @@ static const float focuscolor[]            = COLOR(0x005577ff);
 | ||
|  static const float urgentcolor[]           = COLOR(0xff0000ff);
 | ||
|  /* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
 | ||
|  static const float fullscreen_bg[]         = {0.1f, 0.1f, 0.1f, 1.0f}; /* You can also use glsl colors */
 | ||
| +static const float resize_factor           = 0.0002f; /* Resize multiplier for mouse resizing, depends on mouse sensivity. */
 | ||
| +static const uint32_t resize_interval_ms   = 16; /* Resize interval depends on framerate and screen refresh rate. */
 | ||
|  
 | ||
| +enum Direction { DIR_LEFT, DIR_RIGHT, DIR_UP, DIR_DOWN };
 | ||
|  /* tagging - TAGCOUNT must be no greater than 31 */
 | ||
|  #define TAGCOUNT (9)
 | ||
|  
 | ||
| @@ -31,6 +34,7 @@ static const Rule rules[] = {
 | ||
|  /* layout(s) */
 | ||
|  static const Layout layouts[] = {
 | ||
|  	/* symbol     arrange function */
 | ||
| +	{ "|w|",      btrtile },
 | ||
|  	{ "[]=",      tile },
 | ||
|  	{ "><>",      NULL },    /* no layout function means floating behavior */
 | ||
|  	{ "[M]",      monocle },
 | ||
| @@ -148,6 +152,14 @@ static const Key keys[] = {
 | ||
|  	{ MODKEY,                    XKB_KEY_period,     focusmon,       {.i = WLR_DIRECTION_RIGHT} },
 | ||
|  	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_less,       tagmon,         {.i = WLR_DIRECTION_LEFT} },
 | ||
|  	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater,    tagmon,         {.i = WLR_DIRECTION_RIGHT} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Up,         swapclients,    {.i = DIR_UP} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Down,       swapclients,    {.i = DIR_DOWN} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Right,      swapclients,    {.i = DIR_RIGHT} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_Left,       swapclients,    {.i = DIR_LEFT} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Right,      setratio_h,     {.f = +0.025f} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Left,       setratio_h,     {.f = -0.025f} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Up,         setratio_v,     {.f = -0.025f} },
 | ||
| +	{ MODKEY|WLR_MODIFIER_CTRL,  XKB_KEY_Down,       setratio_v,     {.f = +0.025f} },
 | ||
|  	TAGKEYS(          XKB_KEY_1, XKB_KEY_exclam,                     0),
 | ||
|  	TAGKEYS(          XKB_KEY_2, XKB_KEY_at,                         1),
 | ||
|  	TAGKEYS(          XKB_KEY_3, XKB_KEY_numbersign,                 2),
 | ||
| diff --git a/dwl.c b/dwl.c
 | ||
| index a2711f6..e49a061 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>
 | ||
| @@ -103,6 +104,7 @@ typedef struct {
 | ||
|  	const Arg arg;
 | ||
|  } Button;
 | ||
|  
 | ||
| +typedef struct LayoutNode LayoutNode;
 | ||
|  typedef struct Monitor Monitor;
 | ||
|  typedef struct {
 | ||
|  	/* Must keep these three elements in this order */
 | ||
| @@ -139,8 +141,9 @@ typedef struct {
 | ||
|  #endif
 | ||
|  	unsigned int bw;
 | ||
|  	uint32_t tags;
 | ||
| -	int isfloating, isurgent, isfullscreen;
 | ||
| +	int isfloating, isurgent, isfullscreen, was_tiled;
 | ||
|  	uint32_t resize; /* configure serial of a pending resize */
 | ||
| +	struct wlr_box old_geom;
 | ||
|  } Client;
 | ||
|  
 | ||
|  typedef struct {
 | ||
| @@ -208,6 +211,7 @@ struct Monitor {
 | ||
|  	int nmaster;
 | ||
|  	char ltsymbol[16];
 | ||
|  	int asleep;
 | ||
| +	LayoutNode *root;
 | ||
|  };
 | ||
|  
 | ||
|  typedef struct {
 | ||
| @@ -250,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 +338,9 @@ static void setmon(Client *c, Monitor *m, uint32_t newtags);
 | ||
|  static void setpsel(struct wl_listener *listener, void *data);
 | ||
|  static void setsel(struct wl_listener *listener, void *data);
 | ||
|  static void setup(void);
 | ||
| +static void setratio_h(const Arg *arg);
 | ||
| +static void setratio_v(const Arg *arg);
 | ||
| +static void swapclients(const Arg *arg);
 | ||
|  static void spawn(const Arg *arg);
 | ||
|  static void startdrag(struct wl_listener *listener, void *data);
 | ||
|  static void tag(const Arg *arg);
 | ||
| @@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
 | ||
|  
 | ||
|  /* attempt to encapsulate suck into one file */
 | ||
|  #include "client.h"
 | ||
| +#include "btrtile.c"
 | ||
|  
 | ||
|  /* function implementations */
 | ||
|  void
 | ||
| @@ -601,7 +610,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);
 | ||
| @@ -622,7 +631,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;
 | ||
|  			}
 | ||
| @@ -632,15 +641,36 @@ 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);
 | ||
| +					arrange(selmon);
 | ||
| +
 | ||
| +				} else if (cursor_mode == CurResize && !c->isfloating) {
 | ||
| +					resizing_from_mouse = 0;
 | ||
| +				}
 | ||
| +			} else {
 | ||
| +				if (cursor_mode == CurResize && resizing_from_mouse)
 | ||
| +					resizing_from_mouse = 0;
 | ||
| +			}
 | ||
| +			/* Default behaviour */
 | ||
|  			wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
 | ||
|  			cursor_mode = CurNormal;
 | ||
|  			/* Drop the window off on its new monitor */
 | ||
|  			selmon = xytomon(cursor->x, cursor->y);
 | ||
|  			setmon(grabc, selmon, 0);
 | ||
| +			grabc = NULL;
 | ||
|  			return;
 | ||
| -		} else {
 | ||
| -			cursor_mode = CurNormal;
 | ||
|  		}
 | ||
| +		cursor_mode = CurNormal;
 | ||
|  		break;
 | ||
|  	}
 | ||
|  	/* If the event wasn't handled by the compositor, notify the client with
 | ||
| @@ -720,6 +750,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);
 | ||
| @@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
 | ||
|  
 | ||
|  	wl_list_insert(&mons, &m->link);
 | ||
|  	printstatus();
 | ||
| +	init_tree(m);
 | ||
|  
 | ||
|  	/* The xdg-protocol specifies:
 | ||
|  	 *
 | ||
| @@ -1263,6 +1295,10 @@ destroynotify(struct wl_listener *listener, void *data)
 | ||
|  	wl_list_remove(&c->destroy.link);
 | ||
|  	wl_list_remove(&c->set_title.link);
 | ||
|  	wl_list_remove(&c->fullscreen.link);
 | ||
| +	/* We check if the destroyed client was part of any tiled_list, to catch
 | ||
| +	 * client removals even if they would not be currently managed by btrtile */
 | ||
| +	if (selmon && selmon->root)
 | ||
| +		remove_client(selmon, c);
 | ||
|  #ifdef XWAYLAND
 | ||
|  	if (c->type != XDGShell) {
 | ||
|  		wl_list_remove(&c->activate.link);
 | ||
| @@ -1809,7 +1845,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;
 | ||
| @@ -1863,18 +1900,56 @@ motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double d
 | ||
|  	/* Update drag icon's position */
 | ||
|  	wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
 | ||
|  
 | ||
| -	/* If we are currently grabbing the mouse, handle and return */
 | ||
| +	/* Skip if internal call or already resizing */
 | ||
| +	if (time == 0 && resizing_from_mouse)
 | ||
| +		goto focus;
 | ||
| +
 | ||
| +	tiled = grabc && !grabc->isfloating && !grabc->isfullscreen;
 | ||
|  	if (cursor_mode == CurMove) {
 | ||
|  		/* Move the grabbed client to the new position. */
 | ||
| -		resize(grabc, (struct wlr_box){.x = (int)round(cursor->x) - grabcx, .y = (int)round(cursor->y) - grabcy,
 | ||
| -			.width = grabc->geom.width, .height = grabc->geom.height}, 1);
 | ||
| -		return;
 | ||
| +		if (grabc && grabc->isfloating) {
 | ||
| +			resize(grabc, (struct wlr_box){
 | ||
| +				.x = (int)round(cursor->x) - grabcx,
 | ||
| +				.y = (int)round(cursor->y) - grabcy,
 | ||
| +				.width = grabc->geom.width,
 | ||
| +				.height = grabc->geom.height
 | ||
| +			}, 1);
 | ||
| +			return;
 | ||
| +		}
 | ||
|  	} else if (cursor_mode == CurResize) {
 | ||
| -		resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
 | ||
| -			.width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
 | ||
| -		return;
 | ||
| +		if (tiled && resizing_from_mouse) {
 | ||
| +			dx_total = cursor->x - resize_last_update_x;
 | ||
| +			dy_total = cursor->y - resize_last_update_y;
 | ||
| +
 | ||
| +			if (time - last_resize_time >= resize_interval_ms) {
 | ||
| +				Arg a = {0};
 | ||
| +				if (fabs(dx_total) > fabs(dy_total)) {
 | ||
| +					a.f = (float)(dx_total * resize_factor);
 | ||
| +					setratio_h(&a);
 | ||
| +				} else {
 | ||
| +					a.f = (float)(dy_total * resize_factor);
 | ||
| +					setratio_v(&a);
 | ||
| +				}
 | ||
| +				arrange(selmon);
 | ||
| +
 | ||
| +				last_resize_time = time;
 | ||
| +				resize_last_update_x = cursor->x;
 | ||
| +				resize_last_update_y = cursor->y;
 | ||
| +			}
 | ||
| +
 | ||
| +		} else if (grabc && grabc->isfloating) {
 | ||
| +			/* Floating resize as original */
 | ||
| +			resize(grabc, (struct wlr_box){
 | ||
| +				.x = grabc->geom.x,
 | ||
| +				.y = grabc->geom.y,
 | ||
| +				.width = (int)round(cursor->x) - grabc->geom.x,
 | ||
| +				.height = (int)round(cursor->y) - grabc->geom.y
 | ||
| +			}, 1);
 | ||
| +			return;
 | ||
| +		}
 | ||
|  	}
 | ||
|  
 | ||
| +focus:
 | ||
|  	/* If there's no client surface under the cursor, set the cursor image to a
 | ||
|  	 * default. This is what makes the cursor image appear when you move it
 | ||
|  	 * off of a client or over its border. */
 | ||
| @@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
 | ||
|  	if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
 | ||
|  		return;
 | ||
|  
 | ||
| -	/* Float the window and tell motionnotify to grab it */
 | ||
| -	setfloating(grabc, 1);
 | ||
| -	switch (cursor_mode = arg->ui) {
 | ||
| -	case CurMove:
 | ||
| -		grabcx = (int)round(cursor->x) - grabc->geom.x;
 | ||
| -		grabcy = (int)round(cursor->y) - grabc->geom.y;
 | ||
| -		wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
 | ||
| -		break;
 | ||
| -	case CurResize:
 | ||
| -		/* Doesn't work for X11 output - the next absolute motion event
 | ||
| -		 * returns the cursor to where it started */
 | ||
| -		wlr_cursor_warp_closest(cursor, NULL,
 | ||
| -				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;
 | ||
| +			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;
 | ||
| +		}
 | ||
|  	}
 | ||
|  }
 | ||
|  
 | ||
| -- 
 | ||
| 2.45.3
 | ||
| 
 | 
