1 1.1.1.5 christos /* $OpenBSD$ */ 2 1.1 jmmv 3 1.1 jmmv /* 4 1.1.1.6 christos * Copyright (c) 2010 Nicholas Marriott <nicholas.marriott (at) gmail.com> 5 1.1 jmmv * 6 1.1 jmmv * Permission to use, copy, modify, and distribute this software for any 7 1.1 jmmv * purpose with or without fee is hereby granted, provided that the above 8 1.1 jmmv * copyright notice and this permission notice appear in all copies. 9 1.1 jmmv * 10 1.1 jmmv * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 1.1 jmmv * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 1.1 jmmv * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 1.1 jmmv * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 1.1 jmmv * WHATSOEVER RESULTING FROM LOSS OF MIND, USE, DATA OR PROFITS, WHETHER 15 1.1 jmmv * IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING 16 1.1 jmmv * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 1.1 jmmv */ 18 1.1 jmmv 19 1.1 jmmv #include <sys/types.h> 20 1.1 jmmv 21 1.1 jmmv #include <ctype.h> 22 1.1 jmmv #include <string.h> 23 1.1 jmmv 24 1.1 jmmv #include "tmux.h" 25 1.1 jmmv 26 1.1.1.7 christos static struct layout_cell *layout_find_bottomright(struct layout_cell *); 27 1.1.1.7 christos static u_short layout_checksum(const char *); 28 1.1.1.7 christos static int layout_append(struct layout_cell *, char *, 29 1.1.1.7 christos size_t); 30 1.1.1.7 christos static struct layout_cell *layout_construct(struct layout_cell *, 31 1.1.1.7 christos const char **); 32 1.1.1.7 christos static void layout_assign(struct window_pane **, 33 1.1.1.7 christos struct layout_cell *); 34 1.1 jmmv 35 1.1.1.2 jmmv /* Find the bottom-right cell. */ 36 1.1.1.7 christos static struct layout_cell * 37 1.1.1.2 jmmv layout_find_bottomright(struct layout_cell *lc) 38 1.1.1.2 jmmv { 39 1.1.1.2 jmmv if (lc->type == LAYOUT_WINDOWPANE) 40 1.1.1.2 jmmv return (lc); 41 1.1.1.2 jmmv lc = TAILQ_LAST(&lc->cells, layout_cells); 42 1.1.1.2 jmmv return (layout_find_bottomright(lc)); 43 1.1.1.2 jmmv } 44 1.1.1.2 jmmv 45 1.1 jmmv /* Calculate layout checksum. */ 46 1.1.1.7 christos static u_short 47 1.1 jmmv layout_checksum(const char *layout) 48 1.1 jmmv { 49 1.1 jmmv u_short csum; 50 1.1 jmmv 51 1.1 jmmv csum = 0; 52 1.1 jmmv for (; *layout != '\0'; layout++) { 53 1.1 jmmv csum = (csum >> 1) + ((csum & 1) << 15); 54 1.1 jmmv csum += *layout; 55 1.1 jmmv } 56 1.1 jmmv return (csum); 57 1.1 jmmv } 58 1.1 jmmv 59 1.1 jmmv /* Dump layout as a string. */ 60 1.1 jmmv char * 61 1.1.1.5 christos layout_dump(struct layout_cell *root) 62 1.1 jmmv { 63 1.1.1.9 christos char layout[8192], *out; 64 1.1 jmmv 65 1.1 jmmv *layout = '\0'; 66 1.1.1.5 christos if (layout_append(root, layout, sizeof layout) != 0) 67 1.1 jmmv return (NULL); 68 1.1 jmmv 69 1.1.1.7 christos xasprintf(&out, "%04hx,%s", layout_checksum(layout), layout); 70 1.1 jmmv return (out); 71 1.1 jmmv } 72 1.1 jmmv 73 1.1 jmmv /* Append information for a single cell. */ 74 1.1.1.7 christos static int 75 1.1 jmmv layout_append(struct layout_cell *lc, char *buf, size_t len) 76 1.1 jmmv { 77 1.1 jmmv struct layout_cell *lcchild; 78 1.1 jmmv char tmp[64]; 79 1.1 jmmv size_t tmplen; 80 1.1 jmmv const char *brackets = "]["; 81 1.1 jmmv 82 1.1 jmmv if (len == 0) 83 1.1 jmmv return (-1); 84 1.1 jmmv 85 1.1.1.3 christos if (lc->wp != NULL) { 86 1.1.1.3 christos tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u,%u", 87 1.1.1.3 christos lc->sx, lc->sy, lc->xoff, lc->yoff, lc->wp->id); 88 1.1.1.3 christos } else { 89 1.1.1.3 christos tmplen = xsnprintf(tmp, sizeof tmp, "%ux%u,%u,%u", 90 1.1.1.3 christos lc->sx, lc->sy, lc->xoff, lc->yoff); 91 1.1.1.3 christos } 92 1.1 jmmv if (tmplen > (sizeof tmp) - 1) 93 1.1 jmmv return (-1); 94 1.1 jmmv if (strlcat(buf, tmp, len) >= len) 95 1.1 jmmv return (-1); 96 1.1 jmmv 97 1.1 jmmv switch (lc->type) { 98 1.1 jmmv case LAYOUT_LEFTRIGHT: 99 1.1 jmmv brackets = "}{"; 100 1.1 jmmv /* FALLTHROUGH */ 101 1.1 jmmv case LAYOUT_TOPBOTTOM: 102 1.1 jmmv if (strlcat(buf, &brackets[1], len) >= len) 103 1.1 jmmv return (-1); 104 1.1 jmmv TAILQ_FOREACH(lcchild, &lc->cells, entry) { 105 1.1 jmmv if (layout_append(lcchild, buf, len) != 0) 106 1.1 jmmv return (-1); 107 1.1 jmmv if (strlcat(buf, ",", len) >= len) 108 1.1 jmmv return (-1); 109 1.1 jmmv } 110 1.1 jmmv buf[strlen(buf) - 1] = brackets[0]; 111 1.1 jmmv break; 112 1.1 jmmv case LAYOUT_WINDOWPANE: 113 1.1 jmmv break; 114 1.1 jmmv } 115 1.1 jmmv 116 1.1 jmmv return (0); 117 1.1 jmmv } 118 1.1 jmmv 119 1.1.1.9 christos /* Check layout sizes fit. */ 120 1.1.1.9 christos static int 121 1.1.1.9 christos layout_check(struct layout_cell *lc) 122 1.1.1.9 christos { 123 1.1.1.9 christos struct layout_cell *lcchild; 124 1.1.1.9 christos u_int n = 0; 125 1.1.1.9 christos 126 1.1.1.9 christos switch (lc->type) { 127 1.1.1.9 christos case LAYOUT_WINDOWPANE: 128 1.1.1.9 christos break; 129 1.1.1.9 christos case LAYOUT_LEFTRIGHT: 130 1.1.1.9 christos TAILQ_FOREACH(lcchild, &lc->cells, entry) { 131 1.1.1.9 christos if (lcchild->sy != lc->sy) 132 1.1.1.9 christos return (0); 133 1.1.1.9 christos if (!layout_check(lcchild)) 134 1.1.1.9 christos return (0); 135 1.1.1.9 christos n += lcchild->sx + 1; 136 1.1.1.9 christos } 137 1.1.1.9 christos if (n - 1 != lc->sx) 138 1.1.1.9 christos return (0); 139 1.1.1.9 christos break; 140 1.1.1.9 christos case LAYOUT_TOPBOTTOM: 141 1.1.1.9 christos TAILQ_FOREACH(lcchild, &lc->cells, entry) { 142 1.1.1.9 christos if (lcchild->sx != lc->sx) 143 1.1.1.9 christos return (0); 144 1.1.1.9 christos if (!layout_check(lcchild)) 145 1.1.1.9 christos return (0); 146 1.1.1.9 christos n += lcchild->sy + 1; 147 1.1.1.9 christos } 148 1.1.1.9 christos if (n - 1 != lc->sy) 149 1.1.1.9 christos return (0); 150 1.1.1.9 christos break; 151 1.1.1.9 christos } 152 1.1.1.9 christos return (1); 153 1.1.1.9 christos } 154 1.1.1.9 christos 155 1.1 jmmv /* Parse a layout string and arrange window as layout. */ 156 1.1 jmmv int 157 1.1.1.12 wiz layout_parse(struct window *w, const char *layout, char **cause) 158 1.1 jmmv { 159 1.1 jmmv struct layout_cell *lc, *lcchild; 160 1.1 jmmv struct window_pane *wp; 161 1.1.1.9 christos u_int npanes, ncells, sx = 0, sy = 0; 162 1.1 jmmv u_short csum; 163 1.1 jmmv 164 1.1 jmmv /* Check validity. */ 165 1.1.1.13 wiz if (sscanf(layout, "%hx,", &csum) != 1) { 166 1.1.1.13 wiz *cause = xstrdup("invalid layout"); 167 1.1 jmmv return (-1); 168 1.1.1.13 wiz } 169 1.1 jmmv layout += 5; 170 1.1.1.12 wiz if (csum != layout_checksum(layout)) { 171 1.1.1.12 wiz *cause = xstrdup("invalid layout"); 172 1.1 jmmv return (-1); 173 1.1.1.12 wiz } 174 1.1 jmmv 175 1.1 jmmv /* Build the layout. */ 176 1.1 jmmv lc = layout_construct(NULL, &layout); 177 1.1.1.12 wiz if (lc == NULL) { 178 1.1.1.12 wiz *cause = xstrdup("invalid layout"); 179 1.1 jmmv return (-1); 180 1.1.1.12 wiz } 181 1.1.1.12 wiz if (*layout != '\0') { 182 1.1.1.12 wiz *cause = xstrdup("invalid layout"); 183 1.1 jmmv goto fail; 184 1.1.1.12 wiz } 185 1.1 jmmv 186 1.1 jmmv /* Check this window will fit into the layout. */ 187 1.1 jmmv for (;;) { 188 1.1 jmmv npanes = window_count_panes(w); 189 1.1 jmmv ncells = layout_count_cells(lc); 190 1.1.1.12 wiz if (npanes > ncells) { 191 1.1.1.12 wiz xasprintf(cause, "have %u panes but need %u", npanes, 192 1.1.1.12 wiz ncells); 193 1.1 jmmv goto fail; 194 1.1.1.12 wiz } 195 1.1 jmmv if (npanes == ncells) 196 1.1 jmmv break; 197 1.1 jmmv 198 1.1 jmmv /* Fewer panes than cells - close the bottom right. */ 199 1.1 jmmv lcchild = layout_find_bottomright(lc); 200 1.1.1.7 christos layout_destroy_cell(w, lcchild, &lc); 201 1.1 jmmv } 202 1.1 jmmv 203 1.1.1.9 christos /* 204 1.1.1.9 christos * It appears older versions of tmux were able to generate layouts with 205 1.1.1.9 christos * an incorrect top cell size - if it is larger than the top child then 206 1.1.1.9 christos * correct that (if this is still wrong the check code will catch it). 207 1.1.1.9 christos */ 208 1.1.1.9 christos switch (lc->type) { 209 1.1.1.9 christos case LAYOUT_WINDOWPANE: 210 1.1.1.9 christos break; 211 1.1.1.9 christos case LAYOUT_LEFTRIGHT: 212 1.1.1.9 christos TAILQ_FOREACH(lcchild, &lc->cells, entry) { 213 1.1.1.9 christos sy = lcchild->sy + 1; 214 1.1.1.9 christos sx += lcchild->sx + 1; 215 1.1.1.9 christos } 216 1.1.1.9 christos break; 217 1.1.1.9 christos case LAYOUT_TOPBOTTOM: 218 1.1.1.9 christos TAILQ_FOREACH(lcchild, &lc->cells, entry) { 219 1.1.1.9 christos sx = lcchild->sx + 1; 220 1.1.1.9 christos sy += lcchild->sy + 1; 221 1.1.1.9 christos } 222 1.1.1.9 christos break; 223 1.1.1.9 christos } 224 1.1.1.9 christos if (lc->type != LAYOUT_WINDOWPANE && (lc->sx != sx || lc->sy != sy)) { 225 1.1.1.9 christos log_debug("fix layout %u,%u to %u,%u", lc->sx, lc->sy, sx,sy); 226 1.1.1.9 christos layout_print_cell(lc, __func__, 0); 227 1.1.1.9 christos lc->sx = sx - 1; lc->sy = sy - 1; 228 1.1.1.9 christos } 229 1.1.1.9 christos 230 1.1.1.9 christos /* Check the new layout. */ 231 1.1.1.12 wiz if (!layout_check(lc)) { 232 1.1.1.12 wiz *cause = xstrdup("size mismatch after applying layout"); 233 1.1.1.14 wiz goto fail; 234 1.1.1.12 wiz } 235 1.1.1.9 christos 236 1.1.1.9 christos /* Resize to the layout size. */ 237 1.1.1.10 christos window_resize(w, lc->sx, lc->sy, -1, -1); 238 1.1 jmmv 239 1.1 jmmv /* Destroy the old layout and swap to the new. */ 240 1.1 jmmv layout_free_cell(w->layout_root); 241 1.1 jmmv w->layout_root = lc; 242 1.1 jmmv 243 1.1 jmmv /* Assign the panes into the cells. */ 244 1.1 jmmv wp = TAILQ_FIRST(&w->panes); 245 1.1 jmmv layout_assign(&wp, lc); 246 1.1 jmmv 247 1.1 jmmv /* Update pane offsets and sizes. */ 248 1.1.1.9 christos layout_fix_offsets(w); 249 1.1.1.11 christos layout_fix_panes(w, NULL); 250 1.1.1.9 christos recalculate_sizes(); 251 1.1 jmmv 252 1.1 jmmv layout_print_cell(lc, __func__, 0); 253 1.1 jmmv 254 1.1.1.7 christos notify_window("window-layout-changed", w); 255 1.1.1.3 christos 256 1.1 jmmv return (0); 257 1.1 jmmv 258 1.1 jmmv fail: 259 1.1 jmmv layout_free_cell(lc); 260 1.1 jmmv return (-1); 261 1.1 jmmv } 262 1.1 jmmv 263 1.1 jmmv /* Assign panes into cells. */ 264 1.1.1.7 christos static void 265 1.1 jmmv layout_assign(struct window_pane **wp, struct layout_cell *lc) 266 1.1 jmmv { 267 1.1 jmmv struct layout_cell *lcchild; 268 1.1 jmmv 269 1.1 jmmv switch (lc->type) { 270 1.1 jmmv case LAYOUT_WINDOWPANE: 271 1.1 jmmv layout_make_leaf(lc, *wp); 272 1.1 jmmv *wp = TAILQ_NEXT(*wp, entry); 273 1.1 jmmv return; 274 1.1 jmmv case LAYOUT_LEFTRIGHT: 275 1.1 jmmv case LAYOUT_TOPBOTTOM: 276 1.1 jmmv TAILQ_FOREACH(lcchild, &lc->cells, entry) 277 1.1 jmmv layout_assign(wp, lcchild); 278 1.1 jmmv return; 279 1.1 jmmv } 280 1.1 jmmv } 281 1.1 jmmv 282 1.1 jmmv /* Construct a cell from all or part of a layout tree. */ 283 1.1.1.7 christos static struct layout_cell * 284 1.1 jmmv layout_construct(struct layout_cell *lcparent, const char **layout) 285 1.1 jmmv { 286 1.1 jmmv struct layout_cell *lc, *lcchild; 287 1.1 jmmv u_int sx, sy, xoff, yoff; 288 1.1.1.3 christos const char *saved; 289 1.1 jmmv 290 1.1 jmmv if (!isdigit((u_char) **layout)) 291 1.1 jmmv return (NULL); 292 1.1 jmmv if (sscanf(*layout, "%ux%u,%u,%u", &sx, &sy, &xoff, &yoff) != 4) 293 1.1 jmmv return (NULL); 294 1.1 jmmv 295 1.1 jmmv while (isdigit((u_char) **layout)) 296 1.1 jmmv (*layout)++; 297 1.1 jmmv if (**layout != 'x') 298 1.1 jmmv return (NULL); 299 1.1 jmmv (*layout)++; 300 1.1 jmmv while (isdigit((u_char) **layout)) 301 1.1 jmmv (*layout)++; 302 1.1 jmmv if (**layout != ',') 303 1.1 jmmv return (NULL); 304 1.1 jmmv (*layout)++; 305 1.1 jmmv while (isdigit((u_char) **layout)) 306 1.1 jmmv (*layout)++; 307 1.1 jmmv if (**layout != ',') 308 1.1 jmmv return (NULL); 309 1.1 jmmv (*layout)++; 310 1.1 jmmv while (isdigit((u_char) **layout)) 311 1.1 jmmv (*layout)++; 312 1.1.1.3 christos if (**layout == ',') { 313 1.1.1.3 christos saved = *layout; 314 1.1.1.3 christos (*layout)++; 315 1.1.1.3 christos while (isdigit((u_char) **layout)) 316 1.1.1.3 christos (*layout)++; 317 1.1.1.3 christos if (**layout == 'x') 318 1.1.1.3 christos *layout = saved; 319 1.1.1.3 christos } 320 1.1 jmmv 321 1.1 jmmv lc = layout_create_cell(lcparent); 322 1.1 jmmv lc->sx = sx; 323 1.1 jmmv lc->sy = sy; 324 1.1 jmmv lc->xoff = xoff; 325 1.1 jmmv lc->yoff = yoff; 326 1.1 jmmv 327 1.1 jmmv switch (**layout) { 328 1.1 jmmv case ',': 329 1.1 jmmv case '}': 330 1.1 jmmv case ']': 331 1.1 jmmv case '\0': 332 1.1 jmmv return (lc); 333 1.1 jmmv case '{': 334 1.1 jmmv lc->type = LAYOUT_LEFTRIGHT; 335 1.1 jmmv break; 336 1.1 jmmv case '[': 337 1.1 jmmv lc->type = LAYOUT_TOPBOTTOM; 338 1.1 jmmv break; 339 1.1 jmmv default: 340 1.1 jmmv goto fail; 341 1.1 jmmv } 342 1.1 jmmv 343 1.1 jmmv do { 344 1.1 jmmv (*layout)++; 345 1.1 jmmv lcchild = layout_construct(lc, layout); 346 1.1 jmmv if (lcchild == NULL) 347 1.1 jmmv goto fail; 348 1.1 jmmv TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry); 349 1.1 jmmv } while (**layout == ','); 350 1.1 jmmv 351 1.1 jmmv switch (lc->type) { 352 1.1 jmmv case LAYOUT_LEFTRIGHT: 353 1.1 jmmv if (**layout != '}') 354 1.1 jmmv goto fail; 355 1.1 jmmv break; 356 1.1 jmmv case LAYOUT_TOPBOTTOM: 357 1.1 jmmv if (**layout != ']') 358 1.1 jmmv goto fail; 359 1.1 jmmv break; 360 1.1 jmmv default: 361 1.1 jmmv goto fail; 362 1.1 jmmv } 363 1.1 jmmv (*layout)++; 364 1.1 jmmv 365 1.1 jmmv return (lc); 366 1.1 jmmv 367 1.1 jmmv fail: 368 1.1 jmmv layout_free_cell(lc); 369 1.1 jmmv return (NULL); 370 1.1 jmmv } 371