--- 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