btrtile with multi-tag support
This commit is contained in:
parent
71dd023220
commit
758d89c031
3 changed files with 698 additions and 29 deletions
563
btrtile.c
Normal file
563
btrtile.c
Normal file
|
@ -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;
|
||||||
|
}
|
12
config.def.h
12
config.def.h
|
@ -13,7 +13,10 @@ static const float focuscolor[] = COLOR(0x005577ff);
|
||||||
static const float urgentcolor[] = COLOR(0xff0000ff);
|
static const float urgentcolor[] = COLOR(0xff0000ff);
|
||||||
/* This conforms to the xdg-protocol. Set the alpha to zero to restore the old behavior */
|
/* 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 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 */
|
/* tagging - TAGCOUNT must be no greater than 31 */
|
||||||
#define TAGCOUNT (9)
|
#define TAGCOUNT (9)
|
||||||
|
|
||||||
|
@ -31,6 +34,7 @@ static const Rule rules[] = {
|
||||||
/* layout(s) */
|
/* layout(s) */
|
||||||
static const Layout layouts[] = {
|
static const Layout layouts[] = {
|
||||||
/* symbol arrange function */
|
/* symbol arrange function */
|
||||||
|
{ "|w|", btrtile },
|
||||||
{ "[]=", tile },
|
{ "[]=", tile },
|
||||||
{ "><>", NULL }, /* no layout function means floating behavior */
|
{ "><>", NULL }, /* no layout function means floating behavior */
|
||||||
{ "[M]", monocle },
|
{ "[M]", monocle },
|
||||||
|
@ -148,6 +152,14 @@ static const Key keys[] = {
|
||||||
{ MODKEY, XKB_KEY_period, focusmon, {.i = WLR_DIRECTION_RIGHT} },
|
{ 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_less, tagmon, {.i = WLR_DIRECTION_LEFT} },
|
||||||
{ MODKEY|WLR_MODIFIER_SHIFT, XKB_KEY_greater, tagmon, {.i = WLR_DIRECTION_RIGHT} },
|
{ 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_1, XKB_KEY_exclam, 0),
|
||||||
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
|
TAGKEYS( XKB_KEY_2, XKB_KEY_at, 1),
|
||||||
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
|
TAGKEYS( XKB_KEY_3, XKB_KEY_numbersign, 2),
|
||||||
|
|
152
dwl.c
152
dwl.c
|
@ -1,6 +1,7 @@
|
||||||
/*
|
/*
|
||||||
* See LICENSE file for copyright and license details.
|
* See LICENSE file for copyright and license details.
|
||||||
*/
|
*/
|
||||||
|
#include <limits.h>
|
||||||
#include <getopt.h>
|
#include <getopt.h>
|
||||||
#include <libinput.h>
|
#include <libinput.h>
|
||||||
#include <linux/input-event-codes.h>
|
#include <linux/input-event-codes.h>
|
||||||
|
@ -103,6 +104,7 @@ typedef struct {
|
||||||
const Arg arg;
|
const Arg arg;
|
||||||
} Button;
|
} Button;
|
||||||
|
|
||||||
|
typedef struct LayoutNode LayoutNode;
|
||||||
typedef struct Monitor Monitor;
|
typedef struct Monitor Monitor;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
/* Must keep these three elements in this order */
|
/* Must keep these three elements in this order */
|
||||||
|
@ -139,8 +141,9 @@ typedef struct {
|
||||||
#endif
|
#endif
|
||||||
unsigned int bw;
|
unsigned int bw;
|
||||||
uint32_t tags;
|
uint32_t tags;
|
||||||
int isfloating, isurgent, isfullscreen;
|
int isfloating, isurgent, isfullscreen, was_tiled;
|
||||||
uint32_t resize; /* configure serial of a pending resize */
|
uint32_t resize; /* configure serial of a pending resize */
|
||||||
|
struct wlr_box old_geom;
|
||||||
} Client;
|
} Client;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -208,6 +211,7 @@ struct Monitor {
|
||||||
int nmaster;
|
int nmaster;
|
||||||
char ltsymbol[16];
|
char ltsymbol[16];
|
||||||
int asleep;
|
int asleep;
|
||||||
|
LayoutNode *root;
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
@ -250,6 +254,7 @@ static void arrangelayer(Monitor *m, struct wl_list *list,
|
||||||
struct wlr_box *usable_area, int exclusive);
|
struct wlr_box *usable_area, int exclusive);
|
||||||
static void arrangelayers(Monitor *m);
|
static void arrangelayers(Monitor *m);
|
||||||
static void axisnotify(struct wl_listener *listener, void *data);
|
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 buttonpress(struct wl_listener *listener, void *data);
|
||||||
static void chvt(const Arg *arg);
|
static void chvt(const Arg *arg);
|
||||||
static void checkidleinhibitor(struct wlr_surface *exclude);
|
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 setpsel(struct wl_listener *listener, void *data);
|
||||||
static void setsel(struct wl_listener *listener, void *data);
|
static void setsel(struct wl_listener *listener, void *data);
|
||||||
static void setup(void);
|
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 spawn(const Arg *arg);
|
||||||
static void startdrag(struct wl_listener *listener, void *data);
|
static void startdrag(struct wl_listener *listener, void *data);
|
||||||
static void tag(const Arg *arg);
|
static void tag(const Arg *arg);
|
||||||
|
@ -431,6 +439,7 @@ static xcb_atom_t netatom[NetLast];
|
||||||
|
|
||||||
/* attempt to encapsulate suck into one file */
|
/* attempt to encapsulate suck into one file */
|
||||||
#include "client.h"
|
#include "client.h"
|
||||||
|
#include "btrtile.c"
|
||||||
|
|
||||||
/* function implementations */
|
/* function implementations */
|
||||||
void
|
void
|
||||||
|
@ -601,7 +610,7 @@ buttonpress(struct wl_listener *listener, void *data)
|
||||||
struct wlr_pointer_button_event *event = data;
|
struct wlr_pointer_button_event *event = data;
|
||||||
struct wlr_keyboard *keyboard;
|
struct wlr_keyboard *keyboard;
|
||||||
uint32_t mods;
|
uint32_t mods;
|
||||||
Client *c;
|
Client *c, *target = NULL;
|
||||||
const Button *b;
|
const Button *b;
|
||||||
|
|
||||||
wlr_idle_notifier_v1_notify_activity(idle_notifier, seat);
|
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;
|
mods = keyboard ? wlr_keyboard_get_modifiers(keyboard) : 0;
|
||||||
for (b = buttons; b < END(buttons); b++) {
|
for (b = buttons; b < END(buttons); b++) {
|
||||||
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
|
if (CLEANMASK(mods) == CLEANMASK(b->mod) &&
|
||||||
event->button == b->button && b->func) {
|
event->button == b->button && b->func) {
|
||||||
b->func(&b->arg);
|
b->func(&b->arg);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -632,15 +641,36 @@ buttonpress(struct wl_listener *listener, void *data)
|
||||||
/* If you released any buttons, we exit interactive move/resize mode. */
|
/* If you released any buttons, we exit interactive move/resize mode. */
|
||||||
/* TODO should reset to the pointer focus's current setcursor */
|
/* TODO should reset to the pointer focus's current setcursor */
|
||||||
if (!locked && cursor_mode != CurNormal && cursor_mode != CurPressed) {
|
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");
|
wlr_cursor_set_xcursor(cursor, cursor_mgr, "default");
|
||||||
cursor_mode = CurNormal;
|
cursor_mode = CurNormal;
|
||||||
/* Drop the window off on its new monitor */
|
/* Drop the window off on its new monitor */
|
||||||
selmon = xytomon(cursor->x, cursor->y);
|
selmon = xytomon(cursor->x, cursor->y);
|
||||||
setmon(grabc, selmon, 0);
|
setmon(grabc, selmon, 0);
|
||||||
|
grabc = NULL;
|
||||||
return;
|
return;
|
||||||
} else {
|
|
||||||
cursor_mode = CurNormal;
|
|
||||||
}
|
}
|
||||||
|
cursor_mode = CurNormal;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
/* If the event wasn't handled by the compositor, notify the client with
|
/* 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_output_layout_remove(output_layout, m->wlr_output);
|
||||||
wlr_scene_output_destroy(m->scene_output);
|
wlr_scene_output_destroy(m->scene_output);
|
||||||
|
|
||||||
|
destroy_tree(m);
|
||||||
closemon(m);
|
closemon(m);
|
||||||
wlr_scene_node_destroy(&m->fullscreen_bg->node);
|
wlr_scene_node_destroy(&m->fullscreen_bg->node);
|
||||||
free(m);
|
free(m);
|
||||||
|
@ -1024,6 +1055,7 @@ createmon(struct wl_listener *listener, void *data)
|
||||||
|
|
||||||
wl_list_insert(&mons, &m->link);
|
wl_list_insert(&mons, &m->link);
|
||||||
printstatus();
|
printstatus();
|
||||||
|
init_tree(m);
|
||||||
|
|
||||||
/* The xdg-protocol specifies:
|
/* 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->destroy.link);
|
||||||
wl_list_remove(&c->set_title.link);
|
wl_list_remove(&c->set_title.link);
|
||||||
wl_list_remove(&c->fullscreen.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
|
#ifdef XWAYLAND
|
||||||
if (c->type != XDGShell) {
|
if (c->type != XDGShell) {
|
||||||
wl_list_remove(&c->activate.link);
|
wl_list_remove(&c->activate.link);
|
||||||
|
@ -1809,7 +1845,8 @@ void
|
||||||
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
|
motionnotify(uint32_t time, struct wlr_input_device *device, double dx, double dy,
|
||||||
double dx_unaccel, double dy_unaccel)
|
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;
|
Client *c = NULL, *w = NULL;
|
||||||
LayerSurface *l = NULL;
|
LayerSurface *l = NULL;
|
||||||
struct wlr_surface *surface = 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 */
|
/* Update drag icon's position */
|
||||||
wlr_scene_node_set_position(&drag_icon->node, (int)round(cursor->x), (int)round(cursor->y));
|
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) {
|
if (cursor_mode == CurMove) {
|
||||||
/* Move the grabbed client to the new position. */
|
/* 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,
|
if (grabc && grabc->isfloating) {
|
||||||
.width = grabc->geom.width, .height = grabc->geom.height}, 1);
|
resize(grabc, (struct wlr_box){
|
||||||
return;
|
.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) {
|
} else if (cursor_mode == CurResize) {
|
||||||
resize(grabc, (struct wlr_box){.x = grabc->geom.x, .y = grabc->geom.y,
|
if (tiled && resizing_from_mouse) {
|
||||||
.width = (int)round(cursor->x) - grabc->geom.x, .height = (int)round(cursor->y) - grabc->geom.y}, 1);
|
dx_total = cursor->x - resize_last_update_x;
|
||||||
return;
|
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
|
/* 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
|
* default. This is what makes the cursor image appear when you move it
|
||||||
* off of a client or over its border. */
|
* off of a client or over its border. */
|
||||||
|
@ -1908,22 +1983,41 @@ moveresize(const Arg *arg)
|
||||||
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
|
if (!grabc || client_is_unmanaged(grabc) || grabc->isfullscreen)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
/* Float the window and tell motionnotify to grab it */
|
cursor_mode = arg->ui;
|
||||||
setfloating(grabc, 1);
|
grabc->was_tiled = (!grabc->isfloating && !grabc->isfullscreen);
|
||||||
switch (cursor_mode = arg->ui) {
|
|
||||||
case CurMove:
|
if (grabc->was_tiled) {
|
||||||
grabcx = (int)round(cursor->x) - grabc->geom.x;
|
switch (cursor_mode) {
|
||||||
grabcy = (int)round(cursor->y) - grabc->geom.y;
|
case CurMove:
|
||||||
wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
|
setfloating(grabc, 1);
|
||||||
break;
|
grabcx = (int)round(cursor->x) - grabc->geom.x;
|
||||||
case CurResize:
|
grabcy = (int)round(cursor->y) - grabc->geom.y;
|
||||||
/* Doesn't work for X11 output - the next absolute motion event
|
wlr_cursor_set_xcursor(cursor, cursor_mgr, "fleur");
|
||||||
* returns the cursor to where it started */
|
break;
|
||||||
wlr_cursor_warp_closest(cursor, NULL,
|
case CurResize:
|
||||||
grabc->geom.x + grabc->geom.width,
|
wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
||||||
grabc->geom.y + grabc->geom.height);
|
resize_last_update_x = cursor->x;
|
||||||
wlr_cursor_set_xcursor(cursor, cursor_mgr, "se-resize");
|
resize_last_update_y = cursor->y;
|
||||||
break;
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue