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