aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar BanceDev 2026-02-17 22:27:02 -0500
committerGravatar BanceDev 2026-02-17 22:27:02 -0500
commit45b04aac6eb76a93afdd7b1691e7c08bf141f01b (patch)
treef65232464de4dae62f2ee498063696c36ef2b2fa
parentreadme (diff)
monad tiling
-rw-r--r--README.md2
-rw-r--r--src/config.h16
-rw-r--r--src/defs.h13
-rw-r--r--src/tilite.c441
4 files changed, 155 insertions, 317 deletions
diff --git a/README.md b/README.md
index 56811b9..475dedb 100644
--- a/README.md
+++ b/README.md
@@ -10,7 +10,7 @@
# Tilite
-Tilite is a ultra-light minimal dynamic window manager with just over 2k lines SLOC. This project seeks to cover the exact minimum number of features I need to have the desktop experience I want. This window manager is not designed to be general purpose but if it fits your use case I hope you find it as useful as I do.
+Tilite is a ultra-light minimal dynamic window manager with just under 2k SLOC. This project seeks to cover the exact minimum number of features I need to have the desktop experience I want. This window manager is not designed to be general purpose but if it fits your use case I hope you find it as useful as I do.
---
diff --git a/src/config.h b/src/config.h
index 67e4cf4..0d2befd 100644
--- a/src/config.h
+++ b/src/config.h
@@ -11,9 +11,6 @@
#define CFG_GAPS 5
#define CFG_BORDER_WIDTH 3
-#define CFG_MASTER_WIDTH 0.70f
-#define CFG_RESIZE_MASTER_AMT 1
-#define CFG_RESIZE_STACK_AMT 20
#define CFG_MOVE_WINDOW_AMT 50
#define CFG_RESIZE_WINDOW_AMT 50
#define CFG_SNAP_DISTANCE 5
@@ -21,7 +18,6 @@
#define CFG_NEW_WIN_FOCUS True
#define CFG_WARP_CURSOR True
#define CFG_FLOATING_ON_TOP True
-#define CFG_NEW_WIN_MASTER False
#define CFG_BINDS \
/* Application launchers */ \
@@ -38,15 +34,9 @@
/* Focus */ \
{ MODKEY, XK_j, 0, { .fn = focus_next }, TYPE_FUNC }, \
{ MODKEY, XK_k, 0, { .fn = focus_prev }, TYPE_FUNC }, \
- /* Master/stack movement */ \
- { MODKEY|ShiftMask, XK_j, 0, { .fn = move_master_next }, TYPE_FUNC }, \
- { MODKEY|ShiftMask, XK_k, 0, { .fn = move_master_prev }, TYPE_FUNC }, \
- /* Master resize */ \
- { MODKEY, XK_l, 0, { .fn = resize_master_add }, TYPE_FUNC }, \
- { MODKEY, XK_h, 0, { .fn = resize_master_sub }, TYPE_FUNC }, \
- /* Stack resize */ \
- { MODKEY|ControlMask, XK_l, 0, { .fn = resize_stack_add }, TYPE_FUNC }, \
- { MODKEY|ControlMask, XK_h, 0, { .fn = resize_stack_sub }, TYPE_FUNC }, \
+ /* Movement */ \
+ { MODKEY|ShiftMask, XK_j, 0, { .fn = move_focused_next }, TYPE_FUNC }, \
+ { MODKEY|ShiftMask, XK_k, 0, { .fn = move_focused_prev }, TYPE_FUNC }, \
/* Keyboard window movement */ \
{ MODKEY, XK_Up, 0, { .fn = move_win_up }, TYPE_FUNC }, \
{ MODKEY, XK_Down, 0, { .fn = move_win_down }, TYPE_FUNC }, \
diff --git a/src/defs.h b/src/defs.h
index 95c3ea1..142dcac 100644
--- a/src/defs.h
+++ b/src/defs.h
@@ -67,7 +67,6 @@ typedef struct client_t {
Window win;
int x, y, w, h;
int orig_x, orig_y, orig_w, orig_h;
- int custom_stack_height;
int ws;
Bool fixed;
Bool floating;
@@ -84,10 +83,7 @@ typedef struct {
long border_foc_col;
long border_ufoc_col;
long border_swap_col;
- float master_width;
int motion_throttle;
- int resize_master_amt;
- int resize_stack_amt;
int snap_distance;
int n_binds;
int move_window_amt;
@@ -95,7 +91,6 @@ typedef struct {
Bool new_win_focus;
Bool warp_cursor;
Bool floating_on_top;
- Bool new_win_master;
binding_t binds[MAX_ITEMS];
char *to_run[MAX_ITEMS];
} config_t;
@@ -173,8 +168,8 @@ void hdl_property_ntf(XEvent *xev);
void hdl_unmap_ntf(XEvent *xev);
void init_defaults(void);
Bool is_child_proc(pid_t pid1, pid_t pid2);
-void move_master_next(void);
-void move_master_prev(void);
+void move_focused_next(void);
+void move_focused_prev(void);
void move_to_workspace(int ws);
void move_win_down(void);
void move_win_left(void);
@@ -184,10 +179,6 @@ void other_wm(void);
int other_wm_err(Display *d, XErrorEvent *ee);
long parse_col(const char *hex);
void quit(void);
-void resize_master_add(void);
-void resize_master_sub(void);
-void resize_stack_add(void);
-void resize_stack_sub(void);
void resize_win_down(void);
void resize_win_left(void);
void resize_win_right(void);
diff --git a/src/tilite.c b/src/tilite.c
index 46f3112..733cb18 100644
--- a/src/tilite.c
+++ b/src/tilite.c
@@ -158,9 +158,6 @@ static void load_config(void) {
user_config.modkey = MODKEY;
user_config.gaps = CFG_GAPS;
user_config.border_width = CFG_BORDER_WIDTH;
- user_config.master_width = CFG_MASTER_WIDTH;
- user_config.resize_master_amt = CFG_RESIZE_MASTER_AMT;
- user_config.resize_stack_amt = CFG_RESIZE_STACK_AMT;
user_config.move_window_amt = CFG_MOVE_WINDOW_AMT;
user_config.resize_window_amt = CFG_RESIZE_WINDOW_AMT;
user_config.snap_distance = CFG_SNAP_DISTANCE;
@@ -168,7 +165,6 @@ static void load_config(void) {
user_config.new_win_focus = CFG_NEW_WIN_FOCUS;
user_config.warp_cursor = CFG_WARP_CURSOR;
user_config.floating_on_top = CFG_FLOATING_ON_TOP;
- user_config.new_win_master = CFG_NEW_WIN_MASTER;
/* colours — parse_col needs dpy to be open, so call after XOpenDisplay */
user_config.border_foc_col = parse_col(CFG_FOCUSED_BORDER_COL);
@@ -196,16 +192,16 @@ client_t *add_client(Window w, int ws) {
if (!workspaces[ws]) {
workspaces[ws] = c;
+ } else if (focused && focused->ws == ws) {
+ /* Insert after focused so the new window splits the focused slot */
+ c->next = focused->next;
+ focused->next = c;
} else {
- if (user_config.new_win_master) {
- c->next = workspaces[ws];
- workspaces[ws] = c;
- } else {
- client_t *tail = workspaces[ws];
- while (tail->next)
- tail = tail->next;
- tail->next = c;
- }
+ /* Fallback: append to tail */
+ client_t *tail = workspaces[ws];
+ while (tail->next)
+ tail = tail->next;
+ tail->next = c;
}
open_windows++;
@@ -236,7 +232,6 @@ client_t *add_client(Window w, int ws) {
c->floating = False;
c->fullscreen = False;
c->mapped = True;
- c->custom_stack_height = 0;
if (global_floating)
c->floating = True;
@@ -1159,16 +1154,11 @@ void init_defaults(void) {
user_config.move_window_amt = 10;
user_config.resize_window_amt = 10;
- user_config.master_width = 50 / 100.0f;
-
user_config.motion_throttle = 60;
- user_config.resize_master_amt = 5;
- user_config.resize_stack_amt = 20;
user_config.snap_distance = 5;
user_config.n_binds = 0;
user_config.new_win_focus = True;
user_config.warp_cursor = True;
- user_config.new_win_master = False;
user_config.floating_on_top = True;
}
@@ -1210,58 +1200,66 @@ Bool is_child_proc(pid_t parent_pid, pid_t child_pid) {
return False;
}
-void move_master_next(void) {
- if (!workspaces[current_ws] || !workspaces[current_ws]->next)
+void move_focused_next(void) {
+ if (!focused || !workspaces[current_ws])
return;
- client_t *first = workspaces[current_ws];
- client_t *old_focused = focused;
-
- workspaces[current_ws] = first->next;
- first->next = NULL;
+ client_t *prev = NULL;
+ client_t *c = workspaces[current_ws];
+ while (c && c != focused) {
+ prev = c;
+ c = c->next;
+ }
+ if (!c || !c->next)
+ return;
- client_t *tail = workspaces[current_ws];
- while (tail->next)
- tail = tail->next;
+ client_t *next = c->next;
- tail->next = first;
+ c->next = next->next;
+ next->next = c;
+ if (prev)
+ prev->next = next;
+ else
+ workspaces[current_ws] = next;
tile();
-
- if (user_config.warp_cursor && old_focused)
- warp_cursor(old_focused);
-
- if (old_focused)
- send_wm_take_focus(old_focused->win);
-
+ if (user_config.warp_cursor)
+ warp_cursor(focused);
+ send_wm_take_focus(focused->win);
update_borders();
}
-void move_master_prev(void) {
- if (!workspaces[current_ws] || !workspaces[current_ws]->next)
+void move_focused_prev(void) {
+ if (!focused || !workspaces[current_ws])
return;
client_t *prev = NULL;
- client_t *cur = workspaces[current_ws];
- client_t *old_focused = focused;
-
- while (cur->next) {
- prev = cur;
- cur = cur->next;
+ client_t *c = workspaces[current_ws];
+ while (c && c != focused) {
+ prev = c;
+ c = c->next;
}
+ if (!c || !prev)
+ return;
- if (prev)
- prev->next = NULL;
+ client_t *prev_prev = NULL;
+ client_t *p = workspaces[current_ws];
+ while (p && p != prev) {
+ prev_prev = p;
+ p = p->next;
+ }
- cur->next = workspaces[current_ws];
- workspaces[current_ws] = cur;
+ prev->next = c->next;
+ c->next = prev;
+ if (prev_prev)
+ prev_prev->next = c;
+ else
+ workspaces[current_ws] = c;
tile();
- if (user_config.warp_cursor && old_focused)
- warp_cursor(old_focused);
- if (old_focused)
- send_wm_take_focus(old_focused->win);
-
+ if (user_config.warp_cursor)
+ warp_cursor(focused);
+ send_wm_take_focus(focused->win);
update_borders();
}
@@ -1393,82 +1391,6 @@ void quit(void) {
running = False;
}
-void resize_master_add(void) {
- float *mw = &user_config.master_width;
-
- if (*mw < MF_MAX - 0.001f)
- *mw += ((float)user_config.resize_master_amt / 100);
-
- tile();
- update_borders();
-}
-
-void resize_master_sub(void) {
- float *mw = &user_config.master_width;
-
- if (*mw > MF_MIN + 0.001f)
- *mw -= ((float)user_config.resize_master_amt / 100);
-
- tile();
- update_borders();
-}
-
-void resize_stack_add(void) {
- if (!focused || focused->floating || focused == workspaces[current_ws])
- return;
-
- int bw2 = 2 * user_config.border_width;
- int raw_cur = (focused->custom_stack_height > 0)
- ? focused->custom_stack_height
- : (focused->h + bw2);
-
- int raw_new = raw_cur + user_config.resize_stack_amt;
-
- /* Calculate maximum allowed height to prevent extending off-screen */
- int gaps = user_config.gaps;
- int tile_height = MAX(1, scr_height - 2 * gaps);
-
- /* Count stack windows (excluding master) */
- int n_stack = 0;
- for (client_t *c = workspaces[current_ws]; c; c = c->next) {
- if (c->mapped && !c->floating && !c->fullscreen &&
- c != workspaces[current_ws])
- n_stack++;
- }
-
- /* Maximum height: tile_height minus space for other stack windows
- * (min_height + gap each) */
- int min_stack_height = bw2 + 1;
- int other_stack_space =
- (n_stack > 1) ? (n_stack - 1) * (min_stack_height + gaps) : 0;
- int max_raw = tile_height - other_stack_space;
-
- if (raw_new > max_raw)
- raw_new = max_raw;
-
- focused->custom_stack_height = raw_new;
- tile();
-}
-
-void resize_stack_sub(void) {
- if (!focused || focused->floating || focused == workspaces[current_ws])
- return;
-
- int bw2 = 2 * user_config.border_width;
- int raw_cur = (focused->custom_stack_height > 0)
- ? focused->custom_stack_height
- : (focused->h + bw2);
-
- int raw_new = raw_cur - user_config.resize_stack_amt;
- int min_raw = bw2 + 1;
-
- if (raw_new < min_raw)
- raw_new = min_raw;
-
- focused->custom_stack_height = raw_new;
- tile();
-}
-
void resize_win_down(void) {
if (!focused || !focused->floating)
return;
@@ -1904,195 +1826,130 @@ void swap_clients(client_t *a, client_t *b) {
ta->next = tb_next == ta ? tb : tb_next;
}
-void tile(void) {
- update_struts();
- client_t *head = workspaces[current_ws];
- int total = 0;
-
- for (client_t *c = head; c; c = c->next) {
- if (c->mapped && !c->floating && !c->fullscreen)
- total++;
- }
-
- if (total == 0)
+static void tile_recursive(client_t **clients, int start, int end, int x, int y,
+ int w, int h) {
+ if (start >= end)
return;
-
- if (monocle) {
- for (client_t *c = head; c; c = c->next) {
- if (!c->mapped || c->floating || c->fullscreen)
- continue;
-
- int border_width = user_config.border_width;
- int gaps = user_config.gaps;
-
- int x = reserve_left + gaps;
- int y = reserve_top + gaps;
- int w = scr_width - reserve_left - reserve_right - 2 * gaps;
- int h = scr_height - reserve_top - reserve_bottom - 2 * gaps;
-
- XWindowChanges wc = {.x = x,
- .y = y,
- .width = MAX(1, w - 2 * border_width),
- .height = MAX(1, h - 2 * border_width),
- .border_width = border_width};
+ if (start == end - 1) {
+ /* Only one client: give it the full rectangle */
+ client_t *c = clients[start];
+ int bw = user_config.border_width;
+ XWindowChanges wc = {.x = x,
+ .y = y,
+ .width = MAX(1, w - 2 * bw),
+ .height = MAX(1, h - 2 * bw),
+ .border_width = bw};
+ if (c->x != wc.x || c->y != wc.y || c->w != wc.width ||
+ c->h != wc.height)
XConfigureWindow(dpy, c->win,
CWX | CWY | CWWidth | CWHeight | CWBorderWidth,
&wc);
-
- c->x = wc.x;
- c->y = wc.y;
- c->w = wc.width;
- c->h = wc.height;
- }
-
- if (focused && focused->mapped && !focused->floating &&
- !focused->fullscreen)
- XRaiseWindow(dpy, focused->win);
-
- update_borders();
+ c->x = wc.x;
+ c->y = wc.y;
+ c->w = wc.width;
+ c->h = wc.height;
return;
}
- int mon_x = reserve_left;
- int mon_y = reserve_top;
- int mon_width = MAX(1, scr_width - reserve_left - reserve_right);
- int mon_height = MAX(1, scr_height - reserve_top - reserve_bottom);
-
- client_t *tileable[MAX_CLIENTS] = {0};
- int n_tileable = 0;
- for (client_t *c = head; c && n_tileable < MAX_CLIENTS; c = c->next) {
- if (c->mapped && !c->floating && !c->fullscreen)
- tileable[n_tileable++] = c;
- }
-
- if (n_tileable == 0)
- return;
-
int gaps = user_config.gaps;
- int tile_x = mon_x + gaps;
- int tile_y = mon_y + gaps;
- int tile_width = MAX(1, mon_width - 2 * gaps);
- int tile_height = MAX(1, mon_height - 2 * gaps);
- float master_frac = CLAMP(user_config.master_width, MF_MIN, MF_MAX);
- int master_width =
- (n_tileable > 1) ? (int)(tile_width * master_frac) : tile_width;
- int stack_width = (n_tileable > 1) ? (tile_width - master_width - gaps) : 0;
-
- {
- client_t *c = tileable[0];
- int border_width = 2 * user_config.border_width;
- XWindowChanges wc = {.x = tile_x,
- .y = tile_y,
- .width = MAX(1, master_width - border_width),
- .height = MAX(1, tile_height - border_width),
- .border_width = user_config.border_width};
-
- Bool geom_differ = c->x != wc.x || c->y != wc.y || c->w != wc.width ||
- c->h != wc.height;
- if (geom_differ)
+ int bw = user_config.border_width;
+
+ /* Place first client in one half, recurse into the other */
+ if (w >= h) {
+ /* Split horizontally: left | right */
+ int left_w = (w - gaps) / 2;
+ int right_w = w - left_w - gaps;
+ int right_x = x + left_w + gaps;
+
+ client_t *c = clients[start];
+ XWindowChanges wc = {.x = x,
+ .y = y,
+ .width = MAX(1, left_w - 2 * bw),
+ .height = MAX(1, h - 2 * bw),
+ .border_width = bw};
+ if (c->x != wc.x || c->y != wc.y || c->w != wc.width ||
+ c->h != wc.height)
XConfigureWindow(dpy, c->win,
CWX | CWY | CWWidth | CWHeight | CWBorderWidth,
&wc);
-
c->x = wc.x;
c->y = wc.y;
c->w = wc.width;
c->h = wc.height;
- }
- if (n_tileable == 1) {
- update_borders();
- return;
- }
-
- int border_width = 2 * user_config.border_width;
- int n_stack = n_tileable - 1;
- int min_stack_height = border_width + 1;
- int total_fixed_heights = 0;
- int n_auto = 0; /* automatically take up leftover space */
- int heights_final[MAX_CLIENTS] = {0};
+ tile_recursive(clients, start + 1, end, right_x, y, right_w, h);
+ } else {
+ /* Split vertically: top / bottom */
+ int top_h = (h - gaps) / 2;
+ int bottom_h = h - top_h - gaps;
+ int bottom_y = y + top_h + gaps;
+
+ client_t *c = clients[start];
+ XWindowChanges wc = {.x = x,
+ .y = y,
+ .width = MAX(1, w - 2 * bw),
+ .height = MAX(1, top_h - 2 * bw),
+ .border_width = bw};
+ if (c->x != wc.x || c->y != wc.y || c->w != wc.width ||
+ c->h != wc.height)
+ XConfigureWindow(dpy, c->win,
+ CWX | CWY | CWWidth | CWHeight | CWBorderWidth,
+ &wc);
+ c->x = wc.x;
+ c->y = wc.y;
+ c->w = wc.width;
+ c->h = wc.height;
- for (int i = 1; i < n_tileable; i++) { /* i=1 - we are excluding master */
- if (tileable[i]->custom_stack_height > 0)
- total_fixed_heights += tileable[i]->custom_stack_height;
- else
- n_auto++;
+ tile_recursive(clients, start + 1, end, x, bottom_y, w, bottom_h);
}
+}
- int total_vgaps = (n_stack - 1) * gaps;
- int remaining = tile_height - total_fixed_heights - total_vgaps;
-
- if (n_auto > 0 && remaining >= n_auto * min_stack_height) {
- int used = 0;
- int count = 0;
- int auto_height = remaining / n_auto;
+void tile(void) {
+ update_struts();
+ client_t *head = workspaces[current_ws];
- for (int i = 1; i < n_tileable; i++) {
- if (tileable[i]->custom_stack_height > 0) {
- heights_final[i] = tileable[i]->custom_stack_height;
- } else {
- count++;
- heights_final[i] =
- (count < n_auto) ? auto_height : remaining - used;
- used += auto_height;
- }
- }
- } else {
- for (int i = 1; i < n_tileable; i++) {
- if (tileable[i]->custom_stack_height > 0)
- heights_final[i] = tileable[i]->custom_stack_height;
- else
- heights_final[i] = min_stack_height;
- }
- }
+ /* Collect tileable clients */
+ client_t *tileable[MAX_CLIENTS] = {0};
+ int n_tileable = 0;
+ for (client_t *c = head; c && n_tileable < MAX_CLIENTS; c = c->next)
+ if (c->mapped && !c->floating && !c->fullscreen)
+ tileable[n_tileable++] = c;
- int total_height = total_vgaps;
- for (int i = 1; i < n_tileable; i++)
- total_height += heights_final[i];
+ if (n_tileable == 0)
+ return;
- int overfill = total_height - tile_height;
- if (overfill > 0) {
- /* shrink from top down, excluding bottom */
- for (int i = 1; i < n_tileable - 1 && overfill > 0; i++) {
- int shrink = MIN(overfill, heights_final[i] - min_stack_height);
- heights_final[i] -= shrink;
- overfill -= shrink;
- }
- }
+ int gaps = user_config.gaps;
+ int x = reserve_left + gaps;
+ int y = reserve_top + gaps;
+ int w = MAX(1, scr_width - reserve_left - reserve_right - 2 * gaps);
+ int h = MAX(1, scr_height - reserve_top - reserve_bottom - 2 * gaps);
- /* if its not perfectly filled stretch bottom to absorb remainder */
- int actual_height = total_vgaps;
- for (int i = 1; i < n_tileable; i++)
- actual_height += heights_final[i];
-
- int shortfall = tile_height - actual_height;
- if (shortfall > 0)
- heights_final[n_tileable - 1] += shortfall;
-
- int stack_y = tile_y;
- for (int i = 1; i < n_tileable; i++) {
- client_t *c = tileable[i];
- XWindowChanges wc = {
- .x = tile_x + master_width + gaps,
- .y = stack_y,
- .width = MAX(1, stack_width - (2 * user_config.border_width)),
- .height = MAX(1, heights_final[i] - (2 * user_config.border_width)),
- .border_width = user_config.border_width};
-
- Bool geom_differ = c->x != wc.x || c->y != wc.y || c->w != wc.width ||
- c->h != wc.height;
- if (geom_differ)
+ if (monocle) {
+ /* Monocle: all clients get the full area, focused on top */
+ for (int i = 0; i < n_tileable; i++) {
+ client_t *c = tileable[i];
+ int bw = user_config.border_width;
+ XWindowChanges wc = {.x = x,
+ .y = y,
+ .width = MAX(1, w - 2 * bw),
+ .height = MAX(1, h - 2 * bw),
+ .border_width = bw};
XConfigureWindow(dpy, c->win,
CWX | CWY | CWWidth | CWHeight | CWBorderWidth,
&wc);
-
- c->x = wc.x;
- c->y = wc.y;
- c->w = wc.width;
- c->h = wc.height;
-
- stack_y += heights_final[i] + gaps;
+ c->x = wc.x;
+ c->y = wc.y;
+ c->w = wc.width;
+ c->h = wc.height;
+ }
+ if (focused && focused->mapped && !focused->floating &&
+ !focused->fullscreen)
+ XRaiseWindow(dpy, focused->win);
+ update_borders();
+ return;
}
+
+ tile_recursive(tileable, 0, n_tileable, x, y, w, h);
update_borders();
}