From: akira.sato@keemail.me Subject: cwm tilling attempt To: Tech Cc: Okan Date: Sat, 4 Apr 2026 22:09:29 +0200 Hello, This is my attempt to bring a closer tilling experience to cwm, since it has some hybrid of it now, the diff attached adds persistent tiling mode with automatic reflow, my main "issue" with it is that the existing window-htile and window-vtile actions arrange windows once when invoked but do not maintain any layout state. Opening a new window or closing one leaves the remaining windows where they are. This change makes tiling persistent in two ways. First, a new tilemode directive in cwmrc(5) accepts vtile, htile, or none. When set, every group starts up with that tile mode active and new windows are automatically retiled into their group the moment they map. The default is none, so existing configurations are unaffected. example .cwmrc vtile 50 htile 50 tilemode vtile Second, the window-htile and window-vtile keybind actions now record the tile direction on the group. A new window-tile-none action turns auto-tiling back off. Closing a tiled window causes the survivors to reflow immediately and fill the available space. If the last window in a group is closed the preceding window expands to fill the full screen. --- calmwm.h 2026-04-04 14:03:42.992832109 +0000 +++ calmwm.h 2026-04-04 14:04:20.437783559 +0000 @@ -183,6 +183,7 @@ struct screen_ctx *sc; char *name; int num; + int tile_last; }; TAILQ_HEAD(group_q, group_ctx); @@ -296,6 +297,7 @@ int snapdist; int htile; int vtile; + int tilemode; struct gap gap; char *color[CWM_COLOR_NITEMS]; char *font; @@ -437,6 +439,7 @@ void client_transient(struct client_ctx *); void client_urgency(struct client_ctx *); void client_vtile(struct client_ctx *); +void client_retile_group(struct group_ctx *); void client_wm_hints(struct client_ctx *); void group_assign(struct group_ctx *, struct client_ctx *); @@ -508,6 +511,7 @@ void kbfunc_client_toggle_vmaximize(void *, struct cargs *); void kbfunc_client_htile(void *, struct cargs *); void kbfunc_client_vtile(void *, struct cargs *); +void kbfunc_client_tile_none(void *, struct cargs *); void kbfunc_client_cycle(void *, struct cargs *); void kbfunc_client_toggle_group(void *, struct cargs *); void kbfunc_client_movetogroup(void *, struct cargs *); --- client.c 2026-04-04 14:03:42.992832109 +0000 +++ client.c 2026-04-04 14:04:20.438783559 +0000 @@ -935,19 +935,23 @@ continue; n++; } - if (n == 0) - return; - - if (cc->flags & CLIENT_VMAXIMIZED || - cc->geom.h + (cc->bwidth * 2) >= area.h) - return; - - cc->flags &= ~CLIENT_HMAXIMIZED; + cc->flags &= ~(CLIENT_HMAXIMIZED | CLIENT_VMAXIMIZED); cc->geom.x = area.x; cc->geom.y = area.y; cc->geom.w = area.w - (cc->bwidth * 2); + if (n == 0) { + cc->geom.h = area.h - (cc->bwidth * 2); + cc->gc->tile_last = 2; + Conf.tilemode = 2; + client_resize(cc, 1); + client_ptr_warp(cc); + return; + } if (Conf.htile > 0) cc->geom.h = ((area.h - (cc->bwidth * 2)) * Conf.htile) / 100; + if (cc->gc != NULL) + cc->gc->tile_last = 2; + Conf.tilemode = 2; client_resize(cc, 1); client_ptr_warp(cc); @@ -1004,19 +1008,23 @@ continue; n++; } - if (n == 0) - return; - - if (cc->flags & CLIENT_HMAXIMIZED || - cc->geom.w + (cc->bwidth * 2) >= area.w) - return; - - cc->flags &= ~CLIENT_VMAXIMIZED; + cc->flags &= ~(CLIENT_VMAXIMIZED | CLIENT_HMAXIMIZED); cc->geom.x = area.x; cc->geom.y = area.y; + cc->geom.h = area.h - (cc->bwidth * 2); + if (n == 0) { + cc->geom.w = area.w - (cc->bwidth * 2); + cc->gc->tile_last = 1; + Conf.tilemode = 1; + client_resize(cc, 1); + client_ptr_warp(cc); + return; + } if (Conf.vtile > 0) cc->geom.w = ((area.w - (cc->bwidth * 2)) * Conf.vtile) / 100; - cc->geom.h = area.h - (cc->bwidth * 2); + if (cc->gc != NULL) + cc->gc->tile_last = 1; + Conf.tilemode = 1; client_resize(cc, 1); client_ptr_warp(cc); @@ -1047,3 +1055,36 @@ client_resize(ci, 1); } } + +void +client_retile_group(struct group_ctx *gc) +{ + struct screen_ctx *sc = gc->sc; + struct client_ctx *cc; + + if (gc->tile_last == 0) + return; + + TAILQ_FOREACH(cc, &sc->clientq, entry) { + if (cc->gc != gc) + continue; + if (cc->flags & (CLIENT_HIDDEN | CLIENT_IGNORE)) + continue; + break; + } + if (cc == NULL) + return; + + if (gc->tile_last == 1) + client_vtile(cc); + else + client_htile(cc); +} + +void +client_tile_none(struct client_ctx *cc) +{ + Conf.tilemode = 0; + if (cc->gc != NULL) + cc->gc->tile_last = 0; +} --- group.c 2026-04-04 14:03:42.992832109 +0000 +++ group.c 2026-04-04 14:04:10.951783577 +0000 @@ -131,6 +131,7 @@ gc->sc = sc; gc->name = xstrdup(name); gc->num = num; + gc->tile_last = Conf.tilemode; TAILQ_INSERT_TAIL(&sc->groupq, gc, entry); if (num == 1) --- xevents.c 2026-04-04 14:03:42.992832109 +0000 +++ xevents.c 2026-04-04 14:04:10.955783577 +0000 @@ -94,6 +94,9 @@ if ((cc != NULL) && (!(cc->flags & CLIENT_IGNORE))) client_ptr_warp(cc); + + if (cc != NULL && cc->gc != NULL && cc->gc->tile_last != 0) + client_retile_group(cc->gc); } static void @@ -101,6 +104,7 @@ { XUnmapEvent *e = &ee->xunmap; struct client_ctx *cc; + struct group_ctx *gc; LOG_DEBUG3("window: 0x%lx", e->window); @@ -108,8 +112,12 @@ if (e->send_event) { xu_set_wm_state(cc->win, WithdrawnState); } else { - if (!(cc->flags & CLIENT_HIDDEN)) + if (!(cc->flags & CLIENT_HIDDEN)) { + gc = cc->gc; client_remove(cc); + if (gc != NULL) + client_retile_group(gc); + } } } } @@ -119,11 +127,16 @@ { XDestroyWindowEvent *e = &ee->xdestroywindow; struct client_ctx *cc; + struct group_ctx *gc; LOG_DEBUG3("window: 0x%lx", e->window); - if ((cc = client_find(e->window)) != NULL) + if ((cc = client_find(e->window)) != NULL) { + gc = cc->gc; client_remove(cc); + if (gc != NULL) + client_retile_group(gc); + } } static void --- conf.c 2026-04-04 14:03:42.992832109 +0000 +++ conf.c 2026-04-04 14:04:10.960783577 +0000 @@ -86,6 +86,7 @@ { FUNC_CC(window-delete, client_close, 0) }, { FUNC_CC(window-htile, client_htile, 0) }, { FUNC_CC(window-vtile, client_vtile, 0) }, + { FUNC_CC(window-tile-none, client_tile_none, 0) }, { FUNC_CC(window-stick, client_toggle_sticky, 0) }, { FUNC_CC(window-fullscreen, client_toggle_fullscreen, 0) }, { FUNC_CC(window-maximize, client_toggle_maximize, 0) }, @@ -291,6 +292,7 @@ c->mamount = 1; c->htile = 50; c->vtile = 50; + c->tilemode = 0; c->snapdist = 0; c->ngroups = 0; c->nameqlen = 5; --- parse.y 2026-04-04 14:03:42.992832109 +0000 +++ parse.y 2026-04-04 14:04:10.965783577 +0000 @@ -71,7 +71,7 @@ %token BINDKEY UNBINDKEY BINDMOUSE UNBINDMOUSE %token FONTNAME STICKY GAP %token AUTOGROUP COMMAND IGNORE WM -%token YES NO BORDERWIDTH MOVEAMOUNT HTILE VTILE +%token YES NO BORDERWIDTH MOVEAMOUNT HTILE VTILE TILEMODE %token COLOR SNAPDIST %token ACTIVEBORDER INACTIVEBORDER URGENCYBORDER %token GROUPBORDER UNGROUPBORDER @@ -147,6 +147,20 @@ } conf->vtile = $2; } + | TILEMODE STRING { + if (strcmp($2, "vtile") == 0) + conf->tilemode = 1; + else if (strcmp($2, "htile") == 0) + conf->tilemode = 2; + else if (strcmp($2, "none") == 0) + conf->tilemode = 0; + else { + yyerror("invalid tilemode"); + free($2); + YYERROR; + } + free($2); + } | MOVEAMOUNT NUMBER { if ($2 < 0 || $2 > INT_MAX) { yyerror("invalid movemount"); @@ -351,6 +365,7 @@ { "selfont", FONTSELCOLOR}, { "snapdist", SNAPDIST}, { "sticky", STICKY}, + { "tilemode", TILEMODE}, { "unbind-key", UNBINDKEY}, { "unbind-mouse", UNBINDMOUSE}, { "ungroupborder", UNGROUPBORDER}, --- kbfunc.c 2026-04-04 14:03:42.992832109 +0000 +++ kbfunc.c 2026-04-04 14:04:10.969783577 +0000 @@ -406,6 +406,12 @@ } void +kbfunc_client_tile_none(void *ctx, struct cargs *cargs) +{ + client_tile_none(ctx); +} + +void kbfunc_client_cycle(void *ctx, struct cargs *cargs) { struct screen_ctx *sc = ctx; --- cwmrc.5 2026-04-04 14:03:42.992832109 +0000 +++ cwmrc.5 2026-04-04 14:05:01.350783483 +0000 @@ -230,6 +230,28 @@ If set to 0, the vertical size of the master window will remain unchanged. The default is 50. +.It Ic tilemode Ar vtile No | Ar htile No | Ar none +Set the automatic tile mode for all groups. +When set to +.Ar vtile , +each new window is placed on the left of the screen and existing +windows share the remaining space, as with +.Ic window-vtile . +When set to +.Ar htile , +each new window is placed at the top of the screen and existing +windows share the remaining space, as with +.Ic window-htile . +Closing a window causes the remaining windows in the group to +reflow and fill the screen. +The tile mode may also be changed at runtime using the +.Ic window-vtile , +.Ic window-htile , +and +.Ic window-tile-none +bind functions. +The default is +.Ar none . .It Ic wm Ar name path Every .Ar name @@ -327,12 +349,16 @@ .Ar htile (default half) of the vertical screen space. Other windows in its group share remaining screen space. +Enables automatic tiling for the current group. .It window-vtile Current window is placed on the left of the screen, maximized vertically and resized to .Ar vtile (default half) of the horizontal screen space. Other windows in its group share remaining screen space. +Enables automatic tiling for the current group. +.It window-tile-none +Disable automatic tiling for the current group. .It window-move Move current window. .It window-resize