Download raw body.
Adding Tilling to cwm
Hello tech@,
Currently cwm have window-htile and window-vtile, but these only work
for one time you press key, after that if you open new window or close
window, tiling is not keep automatically, you must press key again
every time. For my daily usage (many xterm + firefox open/close all
day) this is bit annoying, so I make patch to add automatic mode.
What diff does, briefly:
- new tilemode directive in cwmrc, can be "none", "vtile" or "htile".
This become default tile mode for group when group is created.
- each group remember own tile_last (none/vtile/htile), not only
global setting. So group 1 can be vtile and group 2 can be none,
independent.
- when new window is mapped, or a window is closed, if the group's
tile mode is not none, cwm will retile automatically, call same
client_htile()/client_vtile() as the manual bind. Master window
selection prefer whatever window currently has focus, so it dont
jump around randomly when you close something.
- new bind function window-tile-none, to turn off auto tiling for
current window's group.
- new bind function group-tile-toggle, cycle group between
none -> vtile -> htile -> none, and retile imediately. I bind this
to CM-t myself, very convenient, one key change whole layout.
- gap(top bottom left right) is now also using for spacing BETWEEN
tiled windows, not only edge of screen. left value used for
horizontal space between windows side by side, bottom value used
for vertical space (master to rest, and between stacked windows).
top/right not touched, because top usually big number for status
bar so dont want that much space between every window. If your
gap is "0 0 0 0" like default, nothing change, tiled windows still
touch each other same as before.
- fix small bug: calling window-htile/window-vtile on a window with
no group (nogroup, gc == NULL) would crash cwm, NULL deref. Now
just maximize the window instead, dont touch tile_last since there
is no group to remember it in.
How to configure, on ~/.cwmrc:
# make every new group start in vertical tile mode by default
tilemode vtile
# optional, percent of screen master window take (already exist
# option before my diff, just mention here because work together)
vtile 50
htile 50
# gap around screen edge AND between tiled windows now, see above
# top bottom left right
gap 35 5 5 5
# manual one-shot tile of current window, same as before my diff
bind-key M-h window-htile
bind-key M-v window-vtile
# stop auto tiling for the group of current window
bind-key M-n window-tile-none
# cycle WHOLE GROUP thru none -> vtile -> htile -> none, dont
# need window focus for this one, act on active group
bind-key CM-t group-tile-toggle
Tested on amd64, compile clean with no new warning.
Thanks.--
Secured with Tuta Mail:
https://tuta.com/free-email
--- calmwm.h
+++ calmwm.h
@@ -178,11 +178,18 @@
};
TAILQ_HEAD(client_q, client_ctx);
+enum cwm_tilemode {
+ TILE_NONE = 0,
+ TILE_VTILE = 1,
+ TILE_HTILE = 2
+};
+
struct group_ctx {
TAILQ_ENTRY(group_ctx) entry;
struct screen_ctx *sc;
char *name;
int num;
+ enum cwm_tilemode tile_last;
};
TAILQ_HEAD(group_q, group_ctx);
@@ -296,6 +303,7 @@
int snapdist;
int htile;
int vtile;
+ enum cwm_tilemode tilemode;
struct gap gap;
char *color[CWM_COLOR_NITEMS];
char *font;
@@ -421,10 +429,12 @@
void client_raise(struct client_ctx *);
void client_remove(struct client_ctx *);
void client_resize(struct client_ctx *, int);
+void client_retile_group(struct group_ctx *);
void client_set_active(struct client_ctx *);
void client_set_name(struct client_ctx *);
void client_show(struct client_ctx *);
int client_snapcalc(int, int, int, int, int);
+void client_tile_none(struct client_ctx *);
void client_toggle_hidden(struct client_ctx *);
void client_toggle_hmaximize(struct client_ctx *);
void client_toggle_fullscreen(struct client_ctx *);
@@ -451,6 +461,7 @@
void group_close(struct screen_ctx *, int);
int group_restore(struct client_ctx *);
void group_show(struct group_ctx *);
+void group_tile_toggle(struct screen_ctx *);
void group_toggle(struct screen_ctx *, int);
void group_toggle_all(struct screen_ctx *);
void group_toggle_membership(struct client_ctx *);
@@ -508,6 +519,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 *);
@@ -517,6 +529,7 @@
void kbfunc_group_close(void *, struct cargs *);
void kbfunc_group_cycle(void *, struct cargs *);
void kbfunc_group_toggle_all(void *, struct cargs *);
+void kbfunc_group_tile_toggle(void *, struct cargs *);
void kbfunc_menu_client(void *, struct cargs *);
void kbfunc_menu_cmd(void *, struct cargs *);
void kbfunc_menu_group(void *, struct cargs *);
--- client.c
+++ client.c
@@ -935,25 +935,29 @@
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) {
+ /* Alone in its group (or no group): just fill the area. */
+ cc->geom.h = area.h - (cc->bwidth * 2);
+ if (cc->gc != NULL)
+ cc->gc->tile_last = TILE_HTILE;
+ 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 = TILE_HTILE;
client_resize(cc, 1);
client_ptr_warp(cc);
- mh = cc->geom.h + (cc->bwidth * 2);
+ mh = cc->geom.h + (cc->bwidth * 2) + Conf.gap.bottom;
x = area.x;
- w = area.w / n;
+ w = (area.w - (n - 1) * Conf.gap.left) / n;
h = area.h - mh;
TAILQ_FOREACH(ci, &sc->clientq, entry) {
if (ci->gc != cc->gc)
@@ -973,7 +977,7 @@
if (i + 1 == n)
ci->geom.w = area.x + area.w -
ci->geom.x - (ci->bwidth * 2);
- x += w;
+ x += w + Conf.gap.left;
i++;
client_resize(ci, 1);
}
@@ -1004,25 +1008,29 @@
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) {
+ /* Alone in its group (or no group): just fill the area. */
+ cc->geom.w = area.w - (cc->bwidth * 2);
+ if (cc->gc != NULL)
+ cc->gc->tile_last = TILE_VTILE;
+ 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 = TILE_VTILE;
client_resize(cc, 1);
client_ptr_warp(cc);
- mw = cc->geom.w + (cc->bwidth * 2);
+ mw = cc->geom.w + (cc->bwidth * 2) + Conf.gap.left;
y = area.y;
- h = area.h / n;
+ h = (area.h - (n - 1) * Conf.gap.bottom) / n;
w = area.w - mw;
TAILQ_FOREACH(ci, &sc->clientq, entry) {
if (ci->gc != cc->gc)
@@ -1042,8 +1050,57 @@
if (i + 1 == n)
ci->geom.h = area.y + area.h -
ci->geom.y - (ci->bwidth * 2);
- y += h;
+ y += h + Conf.gap.bottom;
i++;
client_resize(ci, 1);
}
}
+
+/*
+ * Re-run the group's last tile action, e.g. after a window was opened
+ * or closed within it. The master is chosen as follows: prefer the
+ * client that currently has focus (CLIENT_ACTIVE) within the group,
+ * since that is the window the user is looking at; if none does
+ * (e.g. the client that just triggered the retile was the active one
+ * and got removed before we got here, or a brand-new window hasn't
+ * been focused yet), fall back to the first eligible client in
+ * clientq, which for a freshly mapped window means the pre-existing
+ * master keeps its place.
+ */
+void
+client_retile_group(struct group_ctx *gc)
+{
+ struct screen_ctx *sc = gc->sc;
+ struct client_ctx *cc, *first;
+
+ if (gc->tile_last == TILE_NONE)
+ return;
+
+ first = NULL;
+ TAILQ_FOREACH(cc, &sc->clientq, entry) {
+ if (cc->gc != gc)
+ continue;
+ if (cc->flags & (CLIENT_HIDDEN | CLIENT_IGNORE))
+ continue;
+ if (first == NULL)
+ first = cc;
+ if (cc->flags & CLIENT_ACTIVE)
+ break;
+ }
+ if (cc == NULL)
+ cc = first;
+ if (cc == NULL)
+ return;
+
+ if (gc->tile_last == TILE_VTILE)
+ client_vtile(cc);
+ else
+ client_htile(cc);
+}
+
+void
+client_tile_none(struct client_ctx *cc)
+{
+ if (cc->gc != NULL)
+ cc->gc->tile_last = TILE_NONE;
+}
--- group.c
+++ group.c
@@ -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)
@@ -255,6 +256,40 @@
sc->hideall = !sc->hideall;
}
+/*
+ * Cycle the active group's automatic tile mode: none -> vtile ->
+ * htile -> none. Unlike window-htile/window-vtile (which act on
+ * the currently focused client), this acts on the active group as
+ * a whole, so it works even when no client currently has focus.
+ * When the new mode isn't TILE_NONE, retile immediately so the
+ * change is visible right away instead of waiting for the next
+ * window open/close.
+ */
+void
+group_tile_toggle(struct screen_ctx *sc)
+{
+ struct group_ctx *gc = sc->group_active;
+
+ if (gc == NULL)
+ return;
+
+ switch (gc->tile_last) {
+ case TILE_NONE:
+ gc->tile_last = TILE_VTILE;
+ break;
+ case TILE_VTILE:
+ gc->tile_last = TILE_HTILE;
+ break;
+ case TILE_HTILE:
+ default:
+ gc->tile_last = TILE_NONE;
+ break;
+ }
+
+ if (gc->tile_last != TILE_NONE)
+ client_retile_group(gc);
+}
+
void
group_close(struct screen_ctx *sc, int idx)
{
--- xevents.c
+++ xevents.c
@@ -94,6 +94,9 @@
if ((cc != NULL) && (!(cc->flags & CLIENT_IGNORE)))
client_ptr_warp(cc);
+
+ if (cc != NULL && cc->gc != NULL)
+ 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
+++ conf.c
@@ -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) },
@@ -145,6 +146,7 @@
{ FUNC_SC(group-cycle, group_cycle, (CWM_CYCLE_FORWARD)) },
{ FUNC_SC(group-rcycle, group_cycle, (CWM_CYCLE_REVERSE)) },
{ FUNC_SC(group-last, group_last, 0) },
+ { FUNC_SC(group-tile-toggle, group_tile_toggle, 0) },
{ FUNC_SC(group-toggle-all, group_toggle_all, 0) },
{ FUNC_SC(group-toggle-1, group_toggle, 1) },
{ FUNC_SC(group-toggle-2, group_toggle, 2) },
@@ -291,6 +293,7 @@
c->mamount = 1;
c->htile = 50;
c->vtile = 50;
+ c->tilemode = TILE_NONE;
c->snapdist = 0;
c->ngroups = 0;
c->nameqlen = 5;
--- parse.y
+++ parse.y
@@ -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,22 @@
}
conf->vtile = $2;
}
+ | TILEMODE STRING {
+ if (strcmp($2, "none") == 0)
+ conf->tilemode = TILE_NONE;
+ else {
+ yyerror("invalid tilemode");
+ free($2);
+ YYERROR;
+ }
+ free($2);
+ }
+ | TILEMODE VTILE {
+ conf->tilemode = TILE_VTILE;
+ }
+ | TILEMODE HTILE {
+ conf->tilemode = TILE_HTILE;
+ }
| MOVEAMOUNT NUMBER {
if ($2 < 0 || $2 > INT_MAX) {
yyerror("invalid movemount");
@@ -351,6 +367,7 @@
{ "selfont", FONTSELCOLOR},
{ "snapdist", SNAPDIST},
{ "sticky", STICKY},
+ { "tilemode", TILEMODE},
{ "unbind-key", UNBINDKEY},
{ "unbind-mouse", UNBINDMOUSE},
{ "ungroupborder", UNGROUPBORDER},
--- kbfunc.c
+++ kbfunc.c
@@ -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;
@@ -517,6 +523,12 @@
}
void
+kbfunc_group_tile_toggle(void *ctx, struct cargs *cargs)
+{
+ group_tile_toggle(ctx);
+}
+
+void
kbfunc_group_close(void *ctx, struct cargs *cargs)
{
group_close(ctx, cargs->flag);
--- cwmrc.5
+++ cwmrc.5
@@ -183,6 +183,29 @@
can be used for applications such as
.Xr xclock 1 ,
where the user may wish to remain visible.
+.Pp
+When tiling
+.Po
+.Ic window-htile ,
+.Ic window-vtile ,
+or automatic
+.Ic tilemode
+.Pc ,
+.Ar left
+and
+.Ar bottom
+are reused as the spacing between tiled windows: horizontally-adjacent
+windows are separated by
+.Ar left ,
+and vertically-adjacent windows (including the gap between the master
+window and the rest) are separated by
+.Ar bottom .
+.Ar top
+and
+.Ar right
+are not used for inter-window spacing, since
+.Ar top
+is typically reserved for a status bar.
.It Ic htile Ar percent
Set the percentage of screen the master window should occupy
after calling
@@ -230,6 +253,32 @@
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 default automatic tile mode used by newly created 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 .
+Opening or closing a window causes the remaining windows in its
+group to reflow and fill the screen.
+The tile mode of the current window's group may also be changed
+at runtime using the
+.Ic window-vtile ,
+.Ic window-htile ,
+and
+.Ic window-tile-none
+bind functions;
+this only affects that group, and does not change the
+.Ic tilemode
+default applied to groups created afterwards.
+The default is
+.Ar none .
.It Ic wm Ar name path
Every
.Ar name
@@ -279,6 +328,21 @@
Close all windows in group n, where n is 1-9.
.It group-toggle-all
Toggle visibility of all groups.
+.It group-tile-toggle
+Cycle the active group's automatic tile mode, in order:
+.Ar none ,
+.Ar vtile ,
+.Ar htile ,
+back to
+.Ar none .
+Acts on the group as a whole, so unlike
+.Ic window-htile
+and
+.Ic window-vtile
+it works even when no window currently has focus.
+See
+.Ic tilemode
+for details on each mode.
.It window-group
Toggle group membership of current window.
.It window-movetogroup-[n]
@@ -327,12 +391,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
Adding Tilling to cwm