From fd756a150b43d319d08ac4117f34edef9e0438c4 Mon Sep 17 00:00:00 2001 From: nicm Date: Fri, 27 Aug 2021 17:15:57 +0000 Subject: [PATCH] Allow control mode clients to set a hard limit on the window width and height, GitHub issue 2594. --- cmd-refresh-client.c | 72 ++++++++++++++++++++++++++-------- cmd-resize-window.c | 4 +- resize.c | 93 ++++++++++++++++++++++++++++++++++++-------- server-client.c | 24 ++++++++---- tmux.1 | 14 ++++++- tmux.h | 9 +++++ window.c | 2 + 7 files changed, 174 insertions(+), 44 deletions(-) diff --git a/cmd-refresh-client.c b/cmd-refresh-client.c index 24a49dcb..821558ae 100644 --- a/cmd-refresh-client.c +++ b/cmd-refresh-client.c @@ -77,6 +77,58 @@ out: free(copy); } +static enum cmd_retval +cmd_refresh_client_control_client_size(struct cmd *self, struct cmdq_item *item) +{ + struct args *args = cmd_get_args(self); + struct client *tc = cmdq_get_target_client(item); + const char *size = args_get(args, 'C'); + u_int w, x, y; + struct client_window *cw; + + if (sscanf(size, "@%u:%ux%u", &w, &x, &y) == 3) { + if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || + y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { + cmdq_error(item, "size too small or too big"); + return (CMD_RETURN_ERROR); + } + log_debug("%s: client %s window @%u: size %ux%u", __func__, + tc->name, w, x, y); + cw = server_client_add_client_window(tc, w); + cw->sx = x; + cw->sy = y; + tc->flags |= CLIENT_WINDOWSIZECHANGED; + recalculate_sizes_now(1); + return (CMD_RETURN_NORMAL); + } + if (sscanf(size, "@%u:", &w) == 1) { + cw = server_client_get_client_window(tc, w); + if (cw != NULL) { + log_debug("%s: client %s window @%u: no size", __func__, + tc->name, w); + cw->sx = 0; + cw->sy = 0; + recalculate_sizes_now(1); + } + return (CMD_RETURN_NORMAL); + } + + if (sscanf(size, "%u,%u", &x, &y) != 2 && + sscanf(size, "%ux%u", &x, &y) != 2) { + cmdq_error(item, "bad size argument"); + return (CMD_RETURN_ERROR); + } + if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || + y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { + cmdq_error(item, "size too small or too big"); + return (CMD_RETURN_ERROR); + } + tty_set_size(&tc->tty, x, y, 0, 0); + tc->flags |= CLIENT_SIZECHANGED; + recalculate_sizes_now(1); + return (CMD_RETURN_NORMAL); +} + static void cmd_refresh_client_update_offset(struct client *tc, const char *value) { @@ -117,8 +169,8 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) struct client *tc = cmdq_get_target_client(item); struct tty *tty = &tc->tty; struct window *w; - const char *size, *errstr; - u_int x, y, adjust; + const char *errstr; + u_int adjust; struct args_value *av; if (args_has(args, 'c') || @@ -205,21 +257,7 @@ cmd_refresh_client_exec(struct cmd *self, struct cmdq_item *item) if (args_has(args, 'C')) { if (~tc->flags & CLIENT_CONTROL) goto not_control_client; - size = args_get(args, 'C'); - if (sscanf(size, "%u,%u", &x, &y) != 2 && - sscanf(size, "%ux%u", &x, &y) != 2) { - cmdq_error(item, "bad size argument"); - return (CMD_RETURN_ERROR); - } - if (x < WINDOW_MINIMUM || x > WINDOW_MAXIMUM || - y < WINDOW_MINIMUM || y > WINDOW_MAXIMUM) { - cmdq_error(item, "size too small or too big"); - return (CMD_RETURN_ERROR); - } - tty_set_size(&tc->tty, x, y, 0, 0); - tc->flags |= CLIENT_SIZECHANGED; - recalculate_sizes_now(1); - return (CMD_RETURN_NORMAL); + return (cmd_refresh_client_control_client_size(self, item)); } if (args_has(args, 'S')) { diff --git a/cmd-resize-window.c b/cmd-resize-window.c index 24d73c87..ad739165 100644 --- a/cmd-resize-window.c +++ b/cmd-resize-window.c @@ -108,7 +108,9 @@ cmd_resize_window_exec(struct cmd *self, struct cmdq_item *item) } options_set_number(w->options, "window-size", WINDOW_SIZE_MANUAL); - resize_window(w, sx, sy, xpixel, ypixel); + w->manual_sx = sx; + w->manual_sy = sy; + recalculate_size(w, 1); return (CMD_RETURN_NORMAL); } diff --git a/resize.c b/resize.c index f321e7d2..8416ad6a 100644 --- a/resize.c +++ b/resize.c @@ -87,7 +87,9 @@ ignore_client_size(struct client *c) return (1); } } - if ((c->flags & CLIENT_CONTROL) && (~c->flags & CLIENT_SIZECHANGED)) + if ((c->flags & CLIENT_CONTROL) && + (~c->flags & CLIENT_SIZECHANGED) && + (~c->flags & CLIENT_WINDOWSIZECHANGED)) return (1); return (0); } @@ -113,23 +115,25 @@ clients_calculate_size(int type, int current, struct client *c, int, int, struct session *, struct window *), u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel) { - struct client *loop; - u_int cx, cy, n = 0; - - /* Manual windows do not have their size changed based on a client. */ - if (type == WINDOW_SIZE_MANUAL) { - log_debug("%s: type is manual", __func__); - return (0); - } + struct client *loop; + struct client_window *cw; + u_int cx, cy, n = 0; /* * Start comparing with 0 for largest and UINT_MAX for smallest or * latest. */ - if (type == WINDOW_SIZE_LARGEST) - *sx = *sy = 0; - else - *sx = *sy = UINT_MAX; + if (type == WINDOW_SIZE_LARGEST) { + *sx = 0; + *sy = 0; + } else if (type == WINDOW_SIZE_MANUAL) { + *sx = w->manual_sx; + *sy = w->manual_sy; + log_debug("%s: manual size %ux%u", __func__, *sx, *sy); + } else { + *sx = UINT_MAX; + *sy = UINT_MAX; + } *xpixel = *ypixel = 0; /* @@ -139,14 +143,18 @@ clients_calculate_size(int type, int current, struct client *c, if (type == WINDOW_SIZE_LATEST && w != NULL) n = clients_with_window(w); + /* Skip setting the size if manual */ + if (type == WINDOW_SIZE_MANUAL) + goto skip; + /* Loop over the clients and work out the size. */ TAILQ_FOREACH(loop, &clients, entry) { if (loop != c && ignore_client_size(loop)) { - log_debug("%s: ignoring %s", __func__, loop->name); + log_debug("%s: ignoring %s (1)", __func__, loop->name); continue; } if (loop != c && skip_client(loop, type, current, s, w)) { - log_debug("%s: skipping %s", __func__, loop->name); + log_debug("%s: skipping %s (1)", __func__, loop->name); continue; } @@ -160,9 +168,23 @@ clients_calculate_size(int type, int current, struct client *c, continue; } + /* + * If the client has a per-window size, use this instead if it is + * smaller. + */ + if (w != NULL) + cw = server_client_get_client_window(loop, w->id); + else + cw = NULL; + /* Work out this client's size. */ - cx = loop->tty.sx; - cy = loop->tty.sy - status_line_size(loop); + if (cw != NULL) { + cx = cw->sx; + cy = cw->sy; + } else { + cx = loop->tty.sx; + cy = loop->tty.sy - status_line_size(loop); + } /* * If it is larger or smaller than the best so far, update the @@ -191,7 +213,44 @@ clients_calculate_size(int type, int current, struct client *c, else log_debug("%s: no calculated size", __func__); +skip: + /* + * Do not allow any size to be larger than the per-client window size + * if one exists. + */ + if (w != NULL) { + TAILQ_FOREACH(loop, &clients, entry) { + if (loop != c && ignore_client_size(loop)) + continue; + if (loop != c && skip_client(loop, type, current, s, w)) + continue; + + /* Look up per-window size if any. */ + if (~loop->flags & CLIENT_WINDOWSIZECHANGED) + continue; + cw = server_client_get_client_window(loop, w->id); + if (cw == NULL) + continue; + + /* Clamp the size. */ + log_debug("%s: %s size for @%u is %ux%u", __func__, + loop->name, w->id, cw->sx, cw->sy); + if (cw->sx != 0 && *sx > cw->sx) + *sx = cw->sx; + if (cw->sy != 0 && *sy > cw->sy) + *sy = cw->sy; + } + } + if (*sx != UINT_MAX && *sy != UINT_MAX) + log_debug("%s: calculated size %ux%u", __func__, *sx, *sy); + else + log_debug("%s: no calculated size", __func__); + /* Return whether a suitable size was found. */ + if (type == WINDOW_SIZE_MANUAL) { + log_debug("%s: type is manual", __func__); + return (1); + } if (type == WINDOW_SIZE_LARGEST) { log_debug("%s: type is largest", __func__); return (*sx != 0 && *sy != 0); diff --git a/server-client.c b/server-client.c index ac9a7475..062e72d4 100644 --- a/server-client.c +++ b/server-client.c @@ -2443,7 +2443,7 @@ server_client_get_flags(struct client *c) } /* Get client window. */ -static struct client_window * +struct client_window * server_client_get_client_window(struct client *c, u_int id) { struct client_window cw = { .window = id }; @@ -2451,6 +2451,21 @@ server_client_get_client_window(struct client *c, u_int id) return (RB_FIND(client_windows, &c->windows, &cw)); } +/* Add client window. */ +struct client_window * +server_client_add_client_window(struct client *c, u_int id) +{ + struct client_window *cw; + + cw = server_client_get_client_window(c, id); + if (cw == NULL) { + cw = xcalloc(1, sizeof *cw); + cw->window = id; + RB_INSERT(client_windows, &c->windows, cw); + } + return cw; +} + /* Get client active pane. */ struct window_pane * server_client_get_pane(struct client *c) @@ -2479,12 +2494,7 @@ server_client_set_pane(struct client *c, struct window_pane *wp) if (s == NULL) return; - cw = server_client_get_client_window(c, s->curw->window->id); - if (cw == NULL) { - cw = xcalloc(1, sizeof *cw); - cw->window = s->curw->window->id; - RB_INSERT(client_windows, &c->windows, cw); - } + cw = server_client_add_client_window(c, s->curw->window->id); cw->pane = wp; log_debug("%s pane now %%%u", c->name, wp->id); } diff --git a/tmux.1 b/tmux.1 index 28c54905..d48415fe 100644 --- a/tmux.1 +++ b/tmux.1 @@ -1331,7 +1331,7 @@ specified multiple times. .Op Fl cDlLRSU .Op Fl A Ar pane:state .Op Fl B Ar name:what:format -.Op Fl C Ar XxY +.Op Fl C Ar size .Op Fl f Ar flags .Op Fl t Ar target-client .Op Ar adjustment @@ -1375,7 +1375,17 @@ window, changing the current window in the attached session will reset it. .Pp .Fl C -sets the width and height of a control mode client. +sets the width and height of a control mode client or of a window for a +control mode client, +.Ar size +must be one of +.Ql widthxheight +or +.Ql window ID:widthxheight , +for example +.Ql 80x24 +or +.Ql @0:80x24 . .Fl A allows a control mode client to trigger actions on a pane. The argument is a pane ID (with leading diff --git a/tmux.h b/tmux.h index 96baaf4d..98c3b9a3 100644 --- a/tmux.h +++ b/tmux.h @@ -998,6 +998,8 @@ struct window { u_int sx; u_int sy; + u_int manual_sx; + u_int manual_sy; u_int xpixel; u_int ypixel; @@ -1555,6 +1557,10 @@ RB_HEAD(client_files, client_file); struct client_window { u_int window; struct window_pane *pane; + + u_int sx; + u_int sy; + RB_ENTRY(client_window) entry; }; RB_HEAD(client_windows, client_window); @@ -1650,6 +1656,7 @@ struct client { #define CLIENT_ACTIVEPANE 0x80000000ULL #define CLIENT_CONTROL_PAUSEAFTER 0x100000000ULL #define CLIENT_CONTROL_WAITEXIT 0x200000000ULL +#define CLIENT_WINDOWSIZECHANGED 0x400000000ULL #define CLIENT_ALLREDRAWFLAGS \ (CLIENT_REDRAWWINDOW| \ CLIENT_REDRAWSTATUS| \ @@ -2465,6 +2472,8 @@ void server_client_push_stderr(struct client *); const char *server_client_get_cwd(struct client *, struct session *); void server_client_set_flags(struct client *, const char *); const char *server_client_get_flags(struct client *); +struct client_window *server_client_get_client_window(struct client *, u_int); +struct client_window *server_client_add_client_window(struct client *, u_int); struct window_pane *server_client_get_pane(struct client *); void server_client_set_pane(struct client *, struct window_pane *); void server_client_remove_pane(struct window_pane *); diff --git a/window.c b/window.c index 584dbf9c..64d75d43 100644 --- a/window.c +++ b/window.c @@ -318,6 +318,8 @@ window_create(u_int sx, u_int sy, u_int xpixel, u_int ypixel) w->sx = sx; w->sy = sy; + w->manual_sx = sx; + w->manual_sy = sy; w->xpixel = xpixel; w->ypixel = ypixel;