From 6a762dde8c3f0779108626ce1663f7ef50f49c1e Mon Sep 17 00:00:00 2001 From: Dane Jensen Date: Mon, 20 Jul 2026 17:59:09 -0700 Subject: [PATCH] Bug fixes and touchups. --- cmd-select-layout.c | 2 +- format.c | 2 +- layout-custom.c | 170 +++++++++++++++++++++++++++++--------------- tmux.h | 2 +- 4 files changed, 114 insertions(+), 62 deletions(-) diff --git a/cmd-select-layout.c b/cmd-select-layout.c index 08ca351a0..106b8ce7b 100644 --- a/cmd-select-layout.c +++ b/cmd-select-layout.c @@ -130,7 +130,7 @@ cmd_select_layout_exec(struct cmd *self, struct cmdq_item *item) } if (layoutname != NULL) { - if (layout_parse(w, layoutname, flags, &cause) == -1) { + if (layout_parse(w, layoutname, &cause) == -1) { cmdq_error(item, "%s: %s", cause, layoutname); free(cause); goto error; diff --git a/format.c b/format.c index 8848c65d3..0fbd4d10c 100644 --- a/format.c +++ b/format.c @@ -878,7 +878,7 @@ format_cb_window_layout(struct format_tree *ft) static void * format_cb_window_visible_layout(struct format_tree *ft) { - struct client *c = ft->c; + struct client *c = ft->client; struct window *w = ft->w; int flags = 0; diff --git a/layout-custom.c b/layout-custom.c index e3db6e264..f46417e03 100644 --- a/layout-custom.c +++ b/layout-custom.c @@ -20,6 +20,7 @@ #include #include +#include #include #include #include @@ -28,14 +29,17 @@ #include "tmux.h" /* - * Layouts can be represented as strings and currently in two formats: a JSON - * format (v2, *current*), and a legacy format (v1). The legacy format will be - * deprecated at some point in future and should no longer be used. + * Layouts can be represented as strings in a JSON format (v2). The legacy + * format (v1) will be deprecated in the future and should no longer be used. * - * The current (v2) format is JSON. The top level has two key: - * "V": version number, currently 2; - * "L": array of layout cells + * The current (v2) format is JSON. The top level has two keys: + * "V": version number, currently 2 + * "L": root layout cell * Each cell is an object with: + * "t": cell type: + * "h": horizontal + * "v": vertical + * "p": pane * "w": cell width * "h": cell height * "x": horizontal position @@ -163,15 +167,23 @@ static struct layout_cell *layout_find_bottomright(struct layout_cell *); static u_short layout_checksum(const char *); static int layout_append(struct layout_cell *, struct layout_string *, int); -static struct layout_cell *layout_construct(const char *, int, char **); +static struct layout_cell *layout_construct(const char *, char **); static void layout_assign(struct window_pane **, struct layout_cell *); -/* Return 1 when the string view compares as equal to the string. */ +/* Wrapper for string views with strcmp semantics. */ static int lsvcmp(const struct layout_string_view *lsv, const char *s) { - return strncmp(lsv->ptr, s, lsv->len); + int len; + + len = lsv->len - (int)strlen(s); + if (len < 0) + return (-1); + if (len > 0) + return (1); + + return (memcmp(lsv->ptr, s, lsv->len)); } /* Tokenize the layout string. */ @@ -221,9 +233,9 @@ layout_tokenize_input(const char *input) goto fail; input += scan - 1; } - if (layout_tokens_push(tokens, type, &lsv) != 0) { + if (layout_tokens_push(tokens, type, &lsv) != 0) goto fail; - } + lsv.ptr = NULL; lsv.len = 0; input++; @@ -239,7 +251,7 @@ fail: /* * Tokenize a value from the input string. Strings are terminated by a '"', and - * numbers/booleans are terminated by a ','. ']', or '}'. + * numbers/booleans are terminated by a ',', ']', or '}'. */ static int layout_tokenize_value(struct layout_tokens *tokens, const char *input, @@ -253,15 +265,13 @@ layout_tokenize_value(struct layout_tokens *tokens, const char *input, if (prev->type == TOK_QUOTE) { do { if (input[scan] == '\0') - return -1; - if (input[scan] == '"' && input[scan - 1] == '\\') - scan++; + return (-1); scan++; - } while (input[scan] != '"'); + } while (input[scan] != '"' || input[scan - 1] == '\\'); } else if (prev->type == TOK_COLON) { do { if (input[scan] == '\0') - return -1; + return (-1); scan++; } while (input[scan] != ']' && input[scan] != '}' && input[scan] != ','); @@ -448,9 +458,9 @@ layout_parse_key(struct layout_token **toks, struct layout_string_view *lsv) lsv->len = (*toks)->val.len; (*toks)++; - if ((*toks)->type != TOK_QUOTE) { + if ((*toks)->type != TOK_QUOTE) return (-1); - } + (*toks)++; return (0); @@ -465,7 +475,7 @@ layout_parse_object(struct layout_token **toks, struct layout_node *parent, struct layout_string_view fkey; if ((*toks)->type != TOK_OPENOBJECT) - goto fail; + return (NULL); (*toks)++; object = layout_node_create(parent, NODE_OBJECT, key, NULL); @@ -481,10 +491,14 @@ layout_parse_object(struct layout_token **toks, struct layout_node *parent, field = layout_parse_string(toks, object, &fkey); break; case TOK_VALUE: - if (isdigit((u_char)*(*toks)->val.ptr)) - field = layout_parse_number(toks, object, &fkey); - else - field = layout_parse_boolean(toks, object, &fkey); + if (lsvcmp(&(*toks)->val, "true") == 0 || + lsvcmp(&(*toks)->val, "false") == 0) { + field = layout_parse_boolean(toks, object, + &fkey); + } else { + field = layout_parse_number(toks, object, + &fkey); + } break; case TOK_OPENOBJECT: field = layout_parse_object(toks, object, &fkey); @@ -518,13 +532,14 @@ static struct layout_node * layout_parse_array(struct layout_token **toks, struct layout_node *parent, struct layout_string_view *key) { - struct layout_node *array = layout_node_create(parent, NODE_ARRAY, key, NULL); + struct layout_node *array; struct layout_node *member; if ((*toks)->type != TOK_OPENARRAY) - goto fail; + return (NULL); (*toks)++; + array = layout_node_create(parent, NODE_ARRAY, key, NULL); while ((*toks)->type != TOK_CLOSEARRAY) { switch ((*toks)->type) { case TOK_OPENOBJECT: @@ -583,11 +598,12 @@ layout_parse_number(struct layout_token **toks, struct layout_node *parent, struct layout_string_view *key) { const char *numstr = (*toks)->val.ptr; + char *endptr; int64_t val; errno = 0; - val = strtoll(numstr, NULL, 10); - if (errno != 0) + val = strtoll(numstr, &endptr, 10); + if (errno != 0 || endptr != numstr + (*toks)->val.len) return (NULL); (*toks)++; @@ -631,8 +647,9 @@ layout_val_is_eq(const struct layout_node *field, const void *val) return (field->val.num == *(int64_t *)val); case NODE_BOOLEAN: return (field->val.bool == *(int *)val); - default: - fatalx("node has no value"); + case NODE_OBJECT: + case NODE_ARRAY: + fatalx("unknown value comparison"); } return (0); } @@ -655,14 +672,18 @@ layout_evaluate_nodes(struct layout_node *root, int version) ver = field->val.num; break; case NODE_OBJECT: - if (layout_key_is_eq(field, "L")) + if (layout_key_is_eq(field, "L")) { + if (lcroot != NULL) + goto fail; lcroot = layout_evaluate_layout(field, NULL); + if (lcroot == NULL) + goto fail; + } break; default: break; } } - if (ver != version) goto fail; if (lcroot == NULL) @@ -709,7 +730,7 @@ layout_evaluate_layout(struct layout_node *node, struct layout_cell *lcparent) break; case NODE_BOOLEAN: if (layout_key_is_eq(field, "a")) - active = field->val.num; /* unused */ + active = field->val.bool; /* unused */ break; case NODE_STRING: if (layout_key_is_eq(field, "t")) { @@ -721,6 +742,7 @@ layout_evaluate_layout(struct layout_node *node, struct layout_cell *lcparent) type = LAYOUT_WINDOWPANE; else goto fail; + lc->type = type; } else if (layout_key_is_eq(field, "i")) { errno = 0; numstr = field->val.lsv.ptr; @@ -754,9 +776,15 @@ layout_evaluate_layout(struct layout_node *node, struct layout_cell *lcparent) yoff == INT_MAX) goto fail; layout_set_size(lc, sx, sy, xoff, yoff); - lc->type = type; return (lc); fail: + /* + * Ensure the children are freed if the child field is parsed and fails + * before the layout type is set. + */ + if (type == LAYOUT_WINDOWPANE && !TAILQ_EMPTY(&lc->cells)) + lc->type = LAYOUT_TOPBOTTOM; + layout_free_cell(lc, 0); return (NULL); } @@ -779,11 +807,11 @@ layout_string_write(struct layout_string *ls, const char *fmt, ...) va_start(ap, fmt); len = vsnprintf(ls->write, remaining, fmt, ap); - va_end(ap); + va_end(ap); - if (len < 0 || len >= remaining) - return (-1); - ls->write += len; + if (len < 0 || len >= remaining) + return (-1); + ls->write += len; return (0); } @@ -825,7 +853,8 @@ layout_dump(__unused struct window *w, struct layout_cell *root, int flags) return (NULL); if (flags & LAYOUT_CUSTOM_OLD_FORMAT) - xasprintf(&out, "%04hx,%s", layout_checksum(layout.dat), layout.dat); + xasprintf(&out, "%04hx,%s", layout_checksum(layout.dat), + layout.dat); else xasprintf(&out, "{\"V\":2,\"L\":%s}", layout.dat); return (out); @@ -839,7 +868,7 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls) struct window_pane *wp; enum layout_type type; char c; - u_int i; + u_int i, n; if (lc == NULL) return (0); @@ -860,29 +889,39 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls) if (type != LAYOUT_WINDOWPANE) { if (layout_string_write(ls, ",\"c\":[") != 0) return (-1); + n = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (layout_append_v2(lcchild, ls) != 0) return (-1); if (layout_string_write(ls, ",") != 0) return (-1); + n++; } + if (n == 0) + return (-1); *(--ls->write) = '\0'; /* removing trailing comma */ if (layout_string_write(ls, "]") != 0) return (-1); } else { wp = lc->wp; + if (wp == NULL) + return (-1); if (wp == wp->window->active) { if (layout_string_write(ls, ",\"a\":true") != 0) return (-1); } else { - window_pane_last_index(wp, &i); - if (layout_string_write(ls, ",\"l\":%u", i) != 0) - return (-1); + if (window_pane_last_index(wp, &i) != -1) { + if (layout_string_write(ls, ",\"l\":%u", i) + != 0) + return (-1); + } } if (lc->flags & LAYOUT_CELL_FLOATING) { - window_pane_zindex(wp, &i); - if (layout_string_write(ls, ",\"z\":%u", i) != 0) - return (-1); + if (window_pane_zindex(wp, &i) != -1) { + if (layout_string_write(ls, ",\"z\":%u", i) + != 0) + return (-1); + } } if (layout_string_write(ls, ",\"i\":\"%%%u\"", wp->id) != 0) return (-1); @@ -898,11 +937,13 @@ layout_append_v2(struct layout_cell *lc, struct layout_string *ls) static int layout_append_v1(struct layout_cell *lc, struct layout_string *ls) { - struct layout_cell *lcchild; - const char *brackets = "[]"; + struct layout_cell *lcchild; + const char *brackets = "[]"; + int n; - if (lc == NULL || lc->flags & LAYOUT_CELL_FLOATING) + if (lc == NULL) return (0); + if (lc->wp != NULL) { if (layout_string_write(ls, "%ux%u,%d,%d,%u", lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff, lc->wp->id) != 0) @@ -919,12 +960,20 @@ layout_append_v1(struct layout_cell *lc, struct layout_string *ls) case LAYOUT_TOPBOTTOM: if (layout_string_write(ls, "%c", brackets[0]) != 0) return (-1); + n = 0; TAILQ_FOREACH(lcchild, &lc->cells, entry) { + if (!layout_cell_is_tiled(lcchild) && + !layout_cell_has_tiled_child(lcchild)) + continue; if (layout_append_v1(lcchild, ls) != 0) return (-1); if (layout_string_write(ls, ",") != 0) return (-1); + n++; } + if (n == 0) + return (-1); + *(--ls->write) = '\0'; /* removing trailing comma */ if (layout_string_write(ls, "%c", brackets[1]) != 0) return (-1); @@ -957,7 +1006,7 @@ layout_check(struct layout_cell *lc) break; case LAYOUT_LEFTRIGHT: TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (!layout_cell_is_tiled(lcchild) || + if (!layout_cell_is_tiled(lcchild) && !layout_cell_has_tiled_child(lcchild)) continue; if (lcchild->g.sy != lc->g.sy) @@ -971,7 +1020,7 @@ layout_check(struct layout_cell *lc) break; case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) { - if (!layout_cell_is_tiled(lcchild) || + if (!layout_cell_is_tiled(lcchild) && !layout_cell_has_tiled_child(lcchild)) continue; if (lcchild->g.sx != lc->g.sx) @@ -989,17 +1038,17 @@ layout_check(struct layout_cell *lc) /* Parse a layout string and arrange window as layout. */ int -layout_parse(struct window *w, const char *layout, int flags, char **cause) +layout_parse(struct window *w, const char *layout, char **cause) { struct layout_cell *lcchild, *lc = NULL; struct window_pane *wp; u_int npanes, ncells, sx = 0, sy = 0; /* Build the layout. */ - lc = layout_construct(layout, flags, cause); - if (lc == NULL) { + lc = layout_construct(layout, cause); + if (lc == NULL) return (-1); - } + /* Check this window will fit into the layout. */ npanes = window_count_panes(w, 1); for (;;) { @@ -1216,15 +1265,18 @@ fail: /* Construct a layout root from a formatted string. */ static struct layout_cell * -layout_construct(const char *layout, int flags, char **cause) +layout_construct(const char *layout, char **cause) { struct layout_cell *lc; - struct layout_tokens *tokens = NULL; - struct layout_node *node = NULL; + struct layout_tokens *tokens = NULL; + struct layout_node *node = NULL; u_short csum; int n; - if (flags & LAYOUT_CUSTOM_OLD_FORMAT) { + while (isspace((u_char) *layout)) + layout++; + + if (*layout != '{') { /* sniffing version */ if (sscanf(layout, "%hx,%n", &csum, &n) != 1 || n != 5) { *cause = xstrdup("malformed layout header"); return (NULL); diff --git a/tmux.h b/tmux.h index 3ffbaa9dd..e89af9ff5 100644 --- a/tmux.h +++ b/tmux.h @@ -3895,7 +3895,7 @@ int layout_insert_tile(struct window *, struct layout_cell *); /* layout-custom.c */ #define LAYOUT_CUSTOM_OLD_FORMAT 0x1 char *layout_dump(struct window *, struct layout_cell *, int); -int layout_parse(struct window *, const char *, int, char **); +int layout_parse(struct window *, const char *, char **); /* layout-set.c */ int layout_set_lookup(const char *);