/* $OpenBSD: layout-custom.c,v 1.21 2022/05/30 12:52:02 nicm Exp $ */ /* * Copyright (c) 2010 Nicholas Marriott * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include "tmux.h" static struct layout_cell *layout_find_bottomright(struct layout_cell *); static u_short layout_checksum(const char *); static int layout_append(struct layout_cell *, char *, size_t); static struct layout_cell *layout_construct(struct layout_cell *, const char **); static void layout_assign(struct window_pane **, struct layout_cell *); /* Find the bottom-right cell. */ static struct layout_cell * layout_find_bottomright(struct layout_cell *lc) { if (lc->type == LAYOUT_WINDOWPANE) return (lc); lc = TAILQ_LAST(&lc->cells, layout_cells); return (layout_find_bottomright(lc)); } /* Calculate layout checksum. */ static u_short layout_checksum(const char *layout) { u_short csum; csum = 0; for (; *layout != '\0'; layout++) { csum = (csum >> 1) + ((csum & 1) << 15); csum += *layout; } return (csum); } /* Dump layout as a string. */ char * layout_dump(struct layout_cell *root) { char layout[8192], *out; *layout = '\0'; if (layout_append(root, layout, sizeof layout) != 0) return (NULL); xasprintf(&out, "%04hx,%s", layout_checksum(layout), layout); return (out); } /* Append information for a single cell. */ static int layout_append(struct layout_cell *lc, char *buf, size_t len) { struct layout_cell *lcchild; char tmp[64]; size_t tmplen; const char *brackets = "]["; if (len == 0) return (-1); if (lc->wp != NULL) { tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u", lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id); } else { tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u", lc->sx, lc->sy, lc->xoff, lc->yoff); } if (tmplen > (sizeof tmp) - 1) return (-1); if (strlcat(buf, tmp, len) >= len) return (-1); switch (lc->type) { case LAYOUT_LEFTRIGHT: brackets = "}{"; /* FALLTHROUGH */ case LAYOUT_TOPBOTTOM: if (strlcat(buf, &brackets[1], len) >= len) return (-1); TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (layout_append(lcchild, buf, len) != 0) return (-1); if (strlcat(buf, ",", len) >= len) return (-1); } buf[strlen(buf) - 1] = brackets[0]; break; case LAYOUT_WINDOWPANE: break; } return (0); } /* Check layout sizes fit. */ static int layout_check(struct layout_cell *lc) { struct layout_cell *lcchild; u_int n = 0; switch (lc->type) { case LAYOUT_WINDOWPANE: break; case LAYOUT_LEFTRIGHT: TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->sy != lc->sy) return (0); if (!layout_check(lcchild)) return (0); n += lcchild->sx + 1; } if (n - 1 != lc->sx) return (0); break; case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) { if (lcchild->sx != lc->sx) return (0); if (!layout_check(lcchild)) return (0); n += lcchild->sy + 1; } if (n - 1 != lc->sy) return (0); break; } return (1); } /* Parse a layout string and arrange window as layout. */ int layout_parse(struct window *w, const char *layout, char **cause) { struct layout_cell *lc, *lcchild; struct window_pane *wp; u_int npanes, ncells, sx = 0, sy = 0; u_short csum; /* Check validity. */ if (sscanf(layout, "%hx,", &csum) != 1) return (-1); layout += 5; if (csum != layout_checksum(layout)) { *cause = xstrdup("invalid layout"); return (-1); } /* Build the layout. */ lc = layout_construct(NULL, &layout); if (lc == NULL) { *cause = xstrdup("invalid layout"); return (-1); } if (*layout != '\0') { *cause = xstrdup("invalid layout"); goto fail; } /* Check this window will fit into the layout. */ for (;;) { npanes = window_count_panes(w); ncells = layout_count_cells(lc); if (npanes > ncells) { xasprintf(cause, "have %u panes but need %u", npanes, ncells); goto fail; } if (npanes == ncells) break; /* Fewer panes than cells - close the bottom right. */ lcchild = layout_find_bottomright(lc); layout_destroy_cell(w, lcchild, &lc); } /* * It appears older versions of tmux were able to generate layouts with * an incorrect top cell size - if it is larger than the top child then * correct that (if this is still wrong the check code will catch it). */ switch (lc->type) { case LAYOUT_WINDOWPANE: break; case LAYOUT_LEFTRIGHT: TAILQ_FOREACH(lcchild, &lc->cells, entry) { sy = lcchild->sy + 1; sx += lcchild->sx + 1; } break; case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) { sx = lcchild->sx + 1; sy += lcchild->sy + 1; } break; } if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) { log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy); layout_print_cell(lc, __func__, 0); lc->sx = sx - 1; lc->sy = sy - 1; } /* Check the new layout. */ if (!layout_check(lc)) { *cause = xstrdup("size mismatch after applying layout"); return (-1); } /* Resize to the layout size. */ window_resize(w, lc->sx, lc->sy, -1, -1); /* Destroy the old layout and swap to the new. */ layout_free_cell(w->layout_root); w->layout_root = lc; /* Assign the panes into the cells. */ wp = TAILQ_FIRST(&w->panes); layout_assign(&wp, lc); /* Update pane offsets and sizes. */ layout_fix_offsets(w); layout_fix_panes(w, NULL); recalculate_sizes(); layout_print_cell(lc, __func__, 0); notify_window("window-layout-changed", w); return (0); fail: layout_free_cell(lc); return (-1); } /* Assign panes into cells. */ static void layout_assign(struct window_pane **wp, struct layout_cell *lc) { struct layout_cell *lcchild; switch (lc->type) { case LAYOUT_WINDOWPANE: layout_make_leaf(lc, *wp); *wp = TAILQ_NEXT(*wp, entry); return; case LAYOUT_LEFTRIGHT: case LAYOUT_TOPBOTTOM: TAILQ_FOREACH(lcchild, &lc->cells, entry) layout_assign(wp, lcchild); return; } } /* Construct a cell from all or part of a layout tree. */ static struct layout_cell * layout_construct(struct layout_cell *lcparent, const char **layout) { struct layout_cell *lc, *lcchild; u_int sx, sy, xoff, yoff; const char *saved; if (!isdigit((u_char) **layout)) return (NULL); if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4) return (NULL); while (isdigit((u_char) **layout)) (*layout)++; if (**layout != 'x') return (NULL); (*layout)++; while (isdigit((u_char) **layout)) (*layout)++; if (**layout != ',') return (NULL); (*layout)++; while (isdigit((u_char) **layout)) (*layout)++; if (**layout != ',') return (NULL); (*layout)++; while (isdigit((u_char) **layout)) (*layout)++; if (**layout == ',') { saved = *layout; (*layout)++; while (isdigit((u_char) **layout)) (*layout)++; if (**layout == 'x') *layout = saved; } lc = layout_create_cell(lcparent); lc->sx = sx; lc->sy = sy; lc->xoff = xoff; lc->yoff = yoff; switch (**layout) { case ',': case '}': case ']': case '\0': return (lc); case '{': lc->type = LAYOUT_LEFTRIGHT; break; case '[': lc->type = LAYOUT_TOPBOTTOM; break; default: goto fail; } do { (*layout)++; lcchild = layout_construct(lc, layout); if (lcchild == NULL) goto fail; TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); } while (**layout == ','); switch (lc->type) { case LAYOUT_LEFTRIGHT: if (**layout != '}') goto fail; break; case LAYOUT_TOPBOTTOM: if (**layout != ']') goto fail; break; default: goto fail; } (*layout)++; return (lc); fail: layout_free_cell(lc); return (NULL); }