diff options
Diffstat (limited to 'layout-custom.c')
-rw-r--r-- | layout-custom.c | 368 |
1 files changed, 368 insertions, 0 deletions
diff --git a/layout-custom.c b/layout-custom.c new file mode 100644 index 0000000..932b30e --- /dev/null +++ b/layout-custom.c @@ -0,0 +1,368 @@ +/* $OpenBSD$ */ + +/* + * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott@gmail.com> + * + * 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 <sys/types.h> + +#include <ctype.h> +#include <string.h> + +#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); +} |