Coverage Report

Created: 2026-09-13 06:02

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/layout-custom.c
Line
Count
Source
1
/* $OpenBSD: layout-custom.c,v 1.41 2026/09/09 09:01:19 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 <errno.h>
23
#include <limits.h>
24
#include <stdarg.h>
25
#include <stdlib.h>
26
#include <string.h>
27
28
#include "tmux.h"
29
30
/*
31
 * Layouts can be represented as strings in a JSON format (v2). The version 1
32
 * format will be removed in the future and should no longer be used.
33
 *
34
 * The current (v2) format is JSON. The top level has two keys:
35
 *    "V": version number, currently 2
36
 *    "L": root layout cell
37
 *
38
 * Each cell is an object with:
39
 *    "t": cell type:
40
 *        "h": horizontal
41
 *        "v": vertical
42
 *        "p": pane
43
 *    "w": cell width
44
 *    "h": cell height
45
 *    "x": horizontal position
46
 *    "y": vertical position
47
 *
48
 * If the cell is a node cell (with child cells), it additionally has:
49
 *    "c": array of child cells
50
 *
51
 * If the cell is a leaf cell (that is, containing a pane and no child cells),
52
 * it additionally has:
53
 *    "I": pane ID as %n (currently ignored on parse)
54
 *    "l": index into last panes list if visited and not the active pane
55
 *    "a": true if the active pane
56
 *    "i": pane index
57
 *    "z": z-index, if a floating pane
58
 */
59
60
/* Maximum nesting depth for version 1 layouts. */
61
0
#define LAYOUT_V1_MAX_DEPTH 1000
62
63
/* Layout string. */
64
struct layout_string {
65
  char  *dat;
66
  size_t   size;    /* length written, not including terminator */
67
  size_t   capacity;  /* bytes allocated */
68
};
69
70
/* Layout parse cell context. */
71
struct layout_parse_cell_ctx {
72
  struct layout_cell  *lc;
73
  int      active;
74
  int      last;
75
  int      index;
76
  int      zindex;
77
};
78
79
/* Layout parse context. */
80
struct layout_parse_ctx {
81
  int64_t         version;
82
  int         num_active;
83
  struct layout_cell     *root;
84
  char        **cause;
85
86
  int         size;   /* number used */
87
  int         capacity; /* number allocated */
88
  struct layout_parse_cell_ctx   *cctxs;
89
};
90
91
static struct layout_cell *layout_find_bottomright(struct layout_cell *);
92
static u_short       layout_checksum(const char *);
93
static int       layout_append(struct layout_cell *,
94
             struct layout_string *, int);
95
static int       layout_construct(const char *,
96
             struct layout_parse_ctx *);
97
static void      layout_assign(struct window *,
98
             struct layout_parse_ctx *);
99
static void      layout_parse_apply_ctx(struct window *,
100
             struct layout_parse_ctx *);
101
static struct layout_cell *layout_parse_json_layout(struct json_node *,
102
             struct layout_cell *,
103
             struct layout_parse_ctx *);
104
static int       layout_parse_ctx_check_indexes(
105
             struct layout_parse_ctx *);
106
107
/* Compare cell contexts in ascending order of index. */
108
static int
109
layout_parse_index_cmp(const void *a, const void *b)
110
0
{
111
0
  const struct layout_parse_cell_ctx  *cca = a;
112
0
  const struct layout_parse_cell_ctx  *ccb = b;
113
0
  int          retval = 0;
114
115
0
  if (cca->index < ccb->index)
116
0
    retval = -1;
117
0
  if (cca->index > ccb->index)
118
0
    retval = 1;
119
0
  return (retval);
120
0
}
121
122
/* Compare cell contexts in descending order of z-index. */
123
static int
124
layout_parse_zindex_cmp(const void *a, const void *b)
125
0
{
126
0
  const struct layout_parse_cell_ctx  *cca = a;
127
0
  const struct layout_parse_cell_ctx  *ccb = b;
128
0
  int          retval = 0;
129
130
0
  if (cca->zindex > ccb->zindex)
131
0
    retval = -1;
132
0
  if (cca->zindex < ccb->zindex)
133
0
    retval = 1;
134
0
  return (retval);
135
0
}
136
137
/* Compare cell contexts in descending order of last. */
138
static int
139
layout_parse_last_cmp(const void *a, const void *b)
140
0
{
141
0
  const struct layout_parse_cell_ctx  *cca = a;
142
0
  const struct layout_parse_cell_ctx  *ccb = b;
143
0
  int          retval = 0;
144
145
0
  if (cca->last > ccb->last)
146
0
    retval = -1;
147
0
  if (cca->last < ccb->last)
148
0
    retval = 1;
149
0
  return (retval);
150
0
}
151
152
/* Initialize a layout string. */
153
static void
154
layout_string_init(struct layout_string *ls)
155
0
{
156
0
  ls->capacity = 1024;
157
0
  ls->dat = xmalloc(ls->capacity);
158
0
  ls->dat[0] = '\0';
159
0
  ls->size = 0;
160
0
}
161
162
/* Free a layout string. */
163
static void
164
layout_string_free(struct layout_string *ls)
165
0
{
166
0
  free(ls->dat);
167
0
  ls->dat = NULL;
168
0
  ls->size = 0;
169
0
  ls->capacity = 0;
170
0
}
171
172
/* Write an optionally formatted string to the end of the layout string. */
173
static void printflike(2, 3)
174
layout_string_write(struct layout_string *ls, const char *fmt, ...)
175
0
{
176
0
  va_list  ap;
177
0
  char  *s;
178
0
  int  slen;
179
180
0
  va_start(ap, fmt);
181
0
  slen = xvasprintf(&s, fmt, ap);
182
0
  va_end(ap);
183
184
0
  while (ls->size + slen + 1 > ls->capacity) {
185
0
    ls->dat = xreallocarray(ls->dat, 2, ls->capacity);
186
0
    ls->capacity *= 2;
187
0
  }
188
0
  memcpy(ls->dat + ls->size, s, slen);
189
0
  ls->size += slen;
190
0
  ls->dat[ls->size] = '\0';
191
192
0
  free(s);
193
0
}
194
195
/* Initialize a parse context. */
196
static void
197
layout_parse_init_ctx(struct layout_parse_ctx *pctx, char **cause)
198
0
{
199
0
  pctx->version = -1;
200
0
  pctx->num_active = 0;
201
0
  pctx->root = NULL;
202
0
  pctx->cause = cause;
203
0
  pctx->size = 0;
204
0
  pctx->capacity = 64;
205
0
  pctx->cctxs = xcalloc(pctx->capacity, sizeof *pctx->cctxs);
206
0
}
207
208
/* Free a parse context. */
209
static void
210
layout_parse_free_ctx(struct layout_parse_ctx *pctx)
211
0
{
212
0
  layout_free_cell(pctx->root, 0);
213
0
  pctx->root = NULL;
214
0
  free(pctx->cctxs);
215
0
  pctx->cctxs = NULL;
216
0
  pctx->size = 0;
217
0
  pctx->capacity = 0;
218
0
}
219
220
/* Add a cell context to the parse context. */
221
static void
222
layout_parse_add_cctx(struct layout_parse_ctx *pctx, struct layout_cell *lc,
223
    int active, int last, int index, int zindex)
224
0
{
225
0
  struct layout_parse_cell_ctx  *cctx;
226
227
0
  if (pctx->size >= pctx->capacity) {
228
0
    pctx->capacity *= 2;
229
0
    pctx->cctxs = xreallocarray(pctx->cctxs, pctx->capacity,
230
0
        sizeof *pctx->cctxs);
231
0
  }
232
0
  cctx = &pctx->cctxs[pctx->size++];
233
234
0
  cctx->lc = lc;
235
0
  cctx->active = active;
236
0
  cctx->last = last;
237
0
  cctx->index = index;
238
0
  cctx->zindex = zindex;
239
0
}
240
241
/* Remove a cell context from the parse context. Does not preserve ordering. */
242
static int
243
layout_parse_remove_cctx(struct layout_parse_ctx *pctx, struct layout_cell *lc)
244
0
{
245
0
  struct layout_parse_cell_ctx  *cctx;
246
0
  int        i;
247
248
0
  for (i = 0; i < pctx->size; i++) {
249
0
    if (lc == pctx->cctxs[i].lc) {
250
0
      cctx = &pctx->cctxs[--pctx->size];
251
0
      memmove(&pctx->cctxs[i], cctx, sizeof *cctx);
252
0
      return (0);
253
0
    }
254
0
  }
255
0
  return (-1);
256
0
}
257
258
/* Find the bottom-right cell. */
259
static struct layout_cell *
260
layout_find_bottomright(struct layout_cell *lc)
261
0
{
262
0
  if (lc->type == LAYOUT_WINDOWPANE)
263
0
    return (lc);
264
0
  lc = TAILQ_LAST(&lc->cells, layout_cells);
265
0
  return (layout_find_bottomright(lc));
266
0
}
267
268
/* Calculate layout checksum. */
269
static u_short
270
layout_checksum(const char *layout)
271
0
{
272
0
  u_short csum;
273
274
0
  csum = 0;
275
0
  for (; *layout != '\0'; layout++) {
276
0
    csum = (csum >> 1) + ((csum & 1) << 15);
277
0
    csum += *layout;
278
0
  }
279
0
  return (csum);
280
0
}
281
282
/* Dump layout as a string. */
283
char *
284
layout_dump(__unused struct window *w, struct layout_cell *lcroot, int flags)
285
0
{
286
0
  struct layout_string   layout_string;
287
0
  char      *out = NULL;
288
289
0
  if (lcroot == NULL)
290
0
    return NULL;
291
292
0
  layout_string_init(&layout_string);
293
294
0
  if (layout_append(lcroot, &layout_string, flags) == 0) {
295
0
    if (flags & LAYOUT_CUSTOM_OLD_FORMAT)
296
0
      xasprintf(&out, "%04hx,%s",
297
0
          layout_checksum(layout_string.dat),
298
0
          layout_string.dat);
299
0
    else
300
0
      xasprintf(&out, "{\"V\":2,\"L\":%s}",
301
0
          layout_string.dat);
302
0
  }
303
0
  layout_string_free(&layout_string);
304
305
0
  return (out);
306
0
}
307
308
/* Append information for a single cell in a JSON (v2) format. */
309
static int
310
layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
311
0
{
312
0
  struct layout_cell  *lcchild;
313
0
  struct window_pane  *wp;
314
0
  enum layout_type   type;
315
0
  char       c;
316
0
  u_int      i, n;
317
318
0
  if (lc == NULL)
319
0
    return (-1);
320
321
0
  type = lc->type;
322
0
  if (type == LAYOUT_TOPBOTTOM)
323
0
    c = 'v';
324
0
  else if (type == LAYOUT_LEFTRIGHT)
325
0
    c = 'h';
326
0
  else if (type == LAYOUT_WINDOWPANE)
327
0
    c = 'p';
328
0
  else
329
0
    return (-1);
330
331
0
  layout_string_write(ls, "{\"t\":\"%c\",\"w\":%u,\"h\":%u,\"x\":%d"
332
0
      ",\"y\":%d", c, lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff);
333
0
  if (type != LAYOUT_WINDOWPANE) {
334
0
    layout_string_write(ls, ",\"c\":[");
335
0
    n = 0;
336
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
337
0
      if (layout_append_v2(lcchild, ls) != 0)
338
0
        return (-1);
339
0
      layout_string_write(ls, ",");
340
0
      n++;
341
0
    }
342
0
    if (n == 0)
343
0
      return (-1);
344
0
    ls->dat[--ls->size] = '\0'; /* removing trailing comma */
345
0
    layout_string_write(ls, "]");
346
0
  } else {
347
0
    wp = lc->wp;
348
0
    if (wp == NULL)
349
0
      return (-1);
350
0
    if (wp == wp->window->active)
351
0
      layout_string_write(ls, ",\"a\":true");
352
0
    else if (window_pane_last_index(wp, &i) == 0)
353
0
      layout_string_write(ls, ",\"l\":%u", i);
354
0
    if (window_pane_index(wp, &i) != 0)
355
0
      return (-1);
356
0
    layout_string_write(ls, ",\"i\":%u", i);
357
0
    if ((lc->flags & LAYOUT_CELL_FLOATING) &&
358
0
        window_pane_zindex(wp, &i) == 0)
359
0
      layout_string_write(ls, ",\"z\":%u", i);
360
0
    layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id);
361
0
  }
362
363
0
  layout_string_write(ls, "}");
364
365
0
  return (0);
366
0
}
367
368
/* Append information for a single cell in the version 1 format. */
369
static int
370
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
371
0
{
372
0
  struct layout_cell  *lcchild;
373
0
  const char    *brackets = "[]";
374
375
0
  if (lc == NULL)
376
0
    return (-1);
377
378
0
  if (lc->wp != NULL) {
379
0
    layout_string_write(ls, "%ux%u,%d,%d,%u", lc->g.sx, lc->g.sy,
380
0
        lc->g.xoff, lc->g.yoff, lc->wp->id);
381
0
  } else {
382
0
    layout_string_write(ls, "%ux%u,%d,%d", lc->g.sx, lc->g.sy,
383
0
        lc->g.xoff, lc->g.yoff);
384
0
  }
385
0
  switch (lc->type) {
386
0
  case LAYOUT_LEFTRIGHT:
387
0
    brackets = "{}";
388
    /* FALLTHROUGH */
389
0
  case LAYOUT_TOPBOTTOM:
390
0
    layout_string_write(ls, "%c", brackets[0]);
391
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
392
0
      if (layout_append_v1(lcchild, ls) != 0)
393
0
        return (-1);
394
0
      layout_string_write(ls, ",");
395
0
    }
396
397
0
    ls->dat[--ls->size] = '\0'; /* removing trailing comma */
398
0
    layout_string_write(ls, "%c", brackets[1]);
399
0
    break;
400
0
  case LAYOUT_WINDOWPANE:
401
0
    break;
402
0
  }
403
404
0
  return (0);
405
0
}
406
407
/*
408
 * Copies the tiled part of a layout. Only populates what is necessary to dump a
409
 * V1 layout string.
410
 */
411
static struct layout_cell *
412
layout_custom_copy_layout(struct layout_cell *lc)
413
0
{
414
0
  struct layout_cell  *lcchild, *lcnewchild, *lconly;
415
0
  struct layout_cell  *lcnew;
416
417
0
  if (lc->type == LAYOUT_WINDOWPANE &&
418
0
      (lc->flags & LAYOUT_CELL_FLOATING))
419
0
    return (NULL);
420
421
0
  lcnew = layout_create_cell(NULL);
422
423
0
  lcnew->type = lc->type;
424
0
  lcnew->flags = lc->flags;
425
0
  if (lc->type == LAYOUT_WINDOWPANE)
426
0
    lcnew->wp = lc->wp;
427
0
  layout_set_size(lcnew, lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff);
428
429
0
  switch (lc->type) {
430
0
  case LAYOUT_WINDOWPANE:
431
0
    break;
432
0
  case LAYOUT_TOPBOTTOM:
433
0
  case LAYOUT_LEFTRIGHT:
434
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
435
0
      lcnewchild = layout_custom_copy_layout(lcchild);
436
0
      if (lcnewchild == NULL)
437
0
        continue;
438
0
      TAILQ_INSERT_TAIL(&lcnew->cells, lcnewchild, entry);
439
0
      lcnewchild->parent = lcnew;
440
0
    }
441
442
0
    lconly = TAILQ_FIRST(&lcnew->cells);
443
0
    if (lconly == NULL) {
444
0
      layout_free_cell(lcnew, 0);
445
0
      return (NULL);
446
0
    }
447
0
    if (TAILQ_NEXT(lconly, entry) == NULL) {
448
0
      TAILQ_REMOVE(&lcnew->cells, lconly, entry);
449
0
      lconly->parent = NULL;
450
0
      layout_free_cell(lcnew, 0);
451
0
      return (lconly);
452
0
    }
453
0
    break;
454
0
  }
455
456
0
  return (lcnew);
457
0
}
458
459
/* Create a compatibility layout for dumping a V1 layout string. */
460
static struct layout_cell *
461
layout_custom_create_compat(struct layout_cell *lcroot)
462
0
{
463
0
  struct layout_cell  *lccompat;
464
465
0
  lccompat = layout_custom_copy_layout(lcroot);
466
0
  if (lccompat != NULL && layout_cell_is_tiled(lccompat)) {
467
0
    lccompat->g.xoff = 0;
468
0
    lccompat->g.yoff = 0;
469
0
  }
470
471
0
  return (lccompat);
472
0
}
473
474
/* Unlinks all panes from the given layout. */
475
static void
476
layout_custom_unlink_panes(struct layout_cell *lc)
477
0
{
478
0
  struct layout_cell  *lcchild;
479
480
0
  switch (lc->type) {
481
0
  case LAYOUT_WINDOWPANE:
482
0
    lc->wp = NULL;
483
0
    break;
484
0
  case LAYOUT_LEFTRIGHT:
485
0
  case LAYOUT_TOPBOTTOM:
486
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
487
0
      layout_custom_unlink_panes(lcchild);
488
0
    break;
489
0
  }
490
0
}
491
492
/* Frees the compatibility layout. */
493
static void
494
layout_custom_free_compat(struct layout_cell *lcroot)
495
0
{
496
0
  if (lcroot == NULL)
497
0
    return;
498
0
  layout_custom_unlink_panes(lcroot);
499
0
  layout_free_cell(lcroot, 0);
500
0
}
501
502
/* Dispatch to append the appropriate version. */
503
static int
504
layout_append(struct layout_cell *lcroot, struct layout_string *ls, int flags)
505
0
{
506
0
  struct layout_cell  *lccompat;
507
0
  int      result;
508
509
0
  if (flags & LAYOUT_CUSTOM_OLD_FORMAT) {
510
0
    if (!layout_cell_is_tiled(lcroot) &&
511
0
        !layout_cell_has_tiled_child(lcroot))
512
0
      return (-1);
513
0
    lccompat = layout_custom_create_compat(lcroot);
514
0
    result = layout_append_v1(lccompat, ls);
515
0
    layout_custom_free_compat(lccompat);
516
0
  } else
517
0
    result = layout_append_v2(lcroot, ls);
518
519
0
  return (result);
520
0
}
521
522
/* Check layout sizes fit. */
523
static int
524
layout_check(struct layout_cell *lc)
525
0
{
526
0
  struct layout_cell  *lcchild;
527
0
  u_int      n = 0;
528
529
0
  switch (lc->type) {
530
0
  case LAYOUT_WINDOWPANE:
531
0
    break;
532
0
  case LAYOUT_LEFTRIGHT:
533
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
534
0
      if (!layout_cell_is_tiled(lcchild) &&
535
0
          !layout_cell_has_tiled_child(lcchild))
536
0
        continue;
537
0
      if (lcchild->g.sy != lc->g.sy)
538
0
        return (0);
539
0
      if (!layout_check(lcchild))
540
0
        return (0);
541
0
      n += lcchild->g.sx + 1;
542
0
    }
543
0
    if (n != 0 && n - 1 != lc->g.sx)
544
0
      return (0);
545
0
    break;
546
0
  case LAYOUT_TOPBOTTOM:
547
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
548
0
      if (!layout_cell_is_tiled(lcchild) &&
549
0
          !layout_cell_has_tiled_child(lcchild))
550
0
        continue;
551
0
      if (lcchild->g.sx != lc->g.sx)
552
0
        return (0);
553
0
      if (!layout_check(lcchild))
554
0
        return (0);
555
0
      n += lcchild->g.sy + 1;
556
0
    }
557
0
    if (n != 0 && n - 1 != lc->g.sy)
558
0
      return (0);
559
0
    break;
560
0
  }
561
0
  return (1);
562
0
}
563
564
/* Parse a layout string and arrange window as layout. */
565
int
566
layout_parse(struct window *w, const char *input, char **cause)
567
0
{
568
0
  struct window_pane  *wp;
569
0
  struct layout_cell  *lcchild, *lc = NULL;
570
0
  struct layout_parse_ctx  pctx;
571
0
  u_int      npanes, ncells, sx = 0, sy = 0;
572
0
  int      with_floating;
573
574
  /* Build the layout. */
575
0
  layout_parse_init_ctx(&pctx, cause);
576
0
  if (layout_construct(input, &pctx) != 0) {
577
0
    layout_parse_free_ctx(&pctx);
578
0
    return (-1);
579
0
  }
580
0
  with_floating = pctx.version > 1;
581
582
  /* Check this window will fit into the layout. */
583
0
  npanes = window_count_panes(w, with_floating);
584
0
  if (npanes == 0) {
585
0
    xasprintf(cause, "window @%u has no panes", w->id);
586
0
    goto fail;
587
0
  }
588
0
  for (;;) {
589
0
    ncells = layout_count_cells(pctx.root, with_floating);
590
0
    if (npanes > ncells) {
591
0
      xasprintf(cause, "have %u panes but need %u", npanes,
592
0
          ncells);
593
0
      goto fail;
594
0
    }
595
0
    if (npanes == ncells)
596
0
      break;
597
598
    /*
599
     * Fewer panes than cells, close the bottom right until none
600
     * remain.
601
     */
602
0
    lcchild = layout_find_bottomright(pctx.root);
603
0
    if (pctx.version > 1 && layout_parse_remove_cctx(&pctx,
604
0
        lcchild) != 0) {
605
0
      *cause = xstrdup("empty/missing layout parse context");
606
0
      goto fail;
607
0
    }
608
0
    layout_destroy_cell(NULL, lcchild, &pctx.root);
609
0
  }
610
611
  /* The root is now owned by lc. */
612
0
  lc = pctx.root;
613
0
  pctx.root = NULL;
614
615
  /*
616
   * It appears older versions of tmux were able to generate layouts with
617
   * an incorrect top cell size - if it is larger than the top child then
618
   * correct that (if this is still wrong the check code will catch it).
619
   */
620
0
  switch (lc->type) {
621
0
  case LAYOUT_WINDOWPANE:
622
0
    break;
623
0
  case LAYOUT_LEFTRIGHT:
624
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
625
0
      if (layout_cell_is_tiled(lcchild) ||
626
0
          layout_cell_has_tiled_child(lcchild)) {
627
0
        sy = lcchild->g.sy + 1;
628
0
        sx += lcchild->g.sx + 1;
629
0
      }
630
0
    }
631
0
    break;
632
0
  case LAYOUT_TOPBOTTOM:
633
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
634
0
      if (layout_cell_is_tiled(lcchild) ||
635
0
          layout_cell_has_tiled_child(lcchild)) {
636
0
        sx = lcchild->g.sx + 1;
637
0
        sy += lcchild->g.sy + 1;
638
0
      }
639
0
    }
640
0
    break;
641
0
  }
642
0
  if (lc->type != LAYOUT_WINDOWPANE && sx != 0 && sy != 0 &&
643
0
      (lc->g.sx != sx || lc->g.sy != sy)) {
644
0
    layout_print_cell(lc, __func__, 0);
645
0
    lc->g.sx = sx - 1; lc->g.sy = sy - 1;
646
0
  }
647
648
  /* Check the new layout. */
649
0
  if (!layout_check(lc)) {
650
0
    *cause = xstrdup("size mismatch after applying layout");
651
0
    goto fail;
652
0
  }
653
654
  /* Resize window to the layout size. */
655
0
  if (layout_cell_is_tiled(lc) ||
656
0
      layout_cell_has_tiled_child(lc))
657
0
    window_resize(w, lc->g.sx, lc->g.sy, -1, -1);
658
659
  /* Preserve floating panes for version 1. */
660
0
  if (pctx.version == 1) {
661
0
    TAILQ_FOREACH(wp, &w->panes, entry) {
662
0
      if (!window_pane_is_floating(wp))
663
0
        continue;
664
0
      lcchild = wp->layout_cell;
665
0
      TAILQ_REMOVE(&lcchild->parent->cells, lcchild, entry);
666
0
      lcchild->parent = NULL;
667
0
    }
668
0
  }
669
670
  /* Destroy the old layout and swap to the new. */
671
0
  layout_free_cell(w->layout_root, 0);
672
0
  w->layout_root = lc;
673
674
  /* Assign the panes into the cells. */
675
0
  layout_assign(w, &pctx);
676
677
  /* Update pane attributes. */
678
0
  layout_fix_offsets(w);
679
0
  layout_fix_panes(w, NULL);
680
0
  if (pctx.version > 1)
681
0
    layout_parse_apply_ctx(w, &pctx);
682
0
  recalculate_sizes();
683
0
  layout_print_cell(lc, __func__, 0);
684
685
  /* Backwards compatibility. */
686
0
  if (pctx.version == 1)
687
0
    events_fire_window("window-layout-changed", w);
688
689
0
  layout_parse_free_ctx(&pctx);
690
0
  return (0);
691
692
0
fail:
693
0
  layout_free_cell(lc, 0);
694
0
  layout_parse_free_ctx(&pctx);
695
0
  return (-1);
696
0
}
697
698
/* Assign panes into cells from the cell contexts. */
699
static void
700
layout_assign_from_ctx(struct window *w, struct layout_parse_ctx *pctx)
701
0
{
702
0
  struct layout_cell  *lc;
703
0
  struct window_pane  *wp;
704
0
  int      i;
705
706
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
707
0
      layout_parse_index_cmp);
708
709
0
  wp = TAILQ_FIRST(&w->panes);
710
0
  for (i = 0; i < pctx->size; i++) {
711
0
    lc = pctx->cctxs[i].lc;
712
0
    layout_make_leaf(lc, wp);
713
0
    wp = TAILQ_NEXT(wp, entry);
714
0
  }
715
0
}
716
717
/*
718
 * Assign tiled cells to available panes. Panes that already have a cell are
719
 * floating and are skipped over.
720
 */
721
static void
722
layout_assign_fallback_tiled(struct window_pane **wp, struct layout_cell *lc)
723
0
{
724
0
  struct layout_cell  *lcchild;
725
726
0
  if (lc == NULL)
727
0
    return;
728
729
0
  switch (lc->type) {
730
0
  case LAYOUT_WINDOWPANE:
731
0
    while (*wp != NULL && (*wp)->layout_cell != NULL)
732
0
      *wp = TAILQ_NEXT(*wp, entry);
733
0
    if (*wp == NULL)
734
0
      return;
735
0
    layout_make_leaf(lc, *wp);
736
0
    *wp = TAILQ_NEXT(*wp, entry);
737
0
    return;
738
0
  case LAYOUT_LEFTRIGHT:
739
0
  case LAYOUT_TOPBOTTOM:
740
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
741
0
      layout_assign_fallback_tiled(wp, lcchild);
742
0
    }
743
0
    return;
744
0
  }
745
0
}
746
747
/*
748
 * Assign panes into cells when there are no cell contexts. This will be removed
749
 * when the non-JSON format is deprecated.
750
 */
751
static void
752
layout_assign_fallback(struct window *w, struct layout_cell *lcroot)
753
0
{
754
0
  struct window_pane  *wp = TAILQ_FIRST(&w->panes);
755
0
  struct layout_cell  *lc;
756
757
0
  layout_assign_fallback_tiled(&wp, lcroot);
758
759
0
  if (window_count_panes(w, 1) > 1 &&
760
0
      lcroot->type == LAYOUT_WINDOWPANE)
761
0
    lcroot = layout_replace_with_node(w, lcroot, LAYOUT_TOPBOTTOM);
762
763
0
  wp = TAILQ_FIRST(&w->panes);
764
0
  while (wp != NULL) {
765
0
    if (window_pane_is_floating(wp)) {
766
0
      lc = wp->layout_cell;
767
0
      lc->parent = lcroot;
768
0
      TAILQ_INSERT_TAIL(&lcroot->cells, lc, entry);
769
0
    }
770
0
    wp = TAILQ_NEXT(wp, entry);
771
0
  }
772
0
}
773
774
/* Assign panes into cells. Number of cells must match the number of panes. */
775
static void
776
layout_assign(struct window *w, struct layout_parse_ctx *pctx)
777
0
{
778
0
  if (pctx->size > 0)
779
0
    layout_assign_from_ctx(w, pctx);
780
0
  else
781
0
    layout_assign_fallback(w, w->layout_root);
782
0
}
783
784
/* Construct a cell from the version 1 format. */
785
static struct layout_cell *
786
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
787
0
{
788
0
  struct layout_cell     *lc;
789
0
  u_int     sx, sy;
790
0
  int     xoff, yoff;
791
0
  const char         *saved;
792
793
0
  if (!isdigit((u_char) **layout))
794
0
    return (NULL);
795
0
  if (sscanf(*layout, "%ux%u,%d,%d", &sx, &sy, &xoff, &yoff) != 4)
796
0
    return (NULL);
797
798
0
  while (isdigit((u_char) **layout))
799
0
    (*layout)++;
800
0
  if (**layout != 'x')
801
0
    return (NULL);
802
0
  (*layout)++;
803
0
  while (isdigit((u_char) **layout))
804
0
    (*layout)++;
805
0
  if (**layout != ',')
806
0
    return (NULL);
807
0
  (*layout)++;
808
0
  while (isdigit((u_char) **layout))
809
0
    (*layout)++;
810
0
  if (**layout != ',')
811
0
    return (NULL);
812
0
  (*layout)++;
813
0
  while (isdigit((u_char) **layout))
814
0
    (*layout)++;
815
0
  if (**layout == ',') {
816
0
    saved = *layout;
817
0
    (*layout)++;
818
0
    while (isdigit((u_char) **layout))
819
0
      (*layout)++;
820
0
    if (**layout == 'x')
821
0
      *layout = saved;
822
0
  }
823
824
0
  lc = layout_create_cell(lcparent);
825
0
  lc->g.sx = sx;
826
0
  lc->g.sy = sy;
827
0
  lc->g.xoff = xoff;
828
0
  lc->g.yoff = yoff;
829
830
0
  return (lc);
831
0
}
832
833
/* Construct a layout from the version 1 format. */
834
static struct layout_cell *
835
layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
836
0
{
837
0
  struct layout_cell  *lc, *lcchild;
838
839
0
  if (depth > LAYOUT_V1_MAX_DEPTH)
840
0
    return (NULL);
841
842
0
  lc = layout_construct_cell(lcparent, layout);
843
0
  if (lc == NULL)
844
0
    return (NULL);
845
846
0
  switch (**layout) {
847
0
  case ',':
848
0
  case '}':
849
0
  case ']':
850
0
  case '\0':
851
0
    return (lc);
852
0
  case '{':
853
0
    lc->type = LAYOUT_LEFTRIGHT;
854
0
    break;
855
0
  case '[':
856
0
    lc->type = LAYOUT_TOPBOTTOM;
857
0
    break;
858
0
  default:
859
0
    goto fail;
860
0
  }
861
862
0
  do {
863
0
    (*layout)++;
864
0
    lcchild = layout_construct_v1(lc, layout, depth + 1);
865
0
    if (lcchild == NULL)
866
0
      goto fail;
867
0
    TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
868
0
  } while (**layout == ',');
869
870
0
  switch (lc->type) {
871
0
  case LAYOUT_LEFTRIGHT:
872
0
    if (**layout != '}')
873
0
      goto fail;
874
0
    break;
875
0
  case LAYOUT_TOPBOTTOM:
876
0
    if (**layout != ']')
877
0
      goto fail;
878
0
    break;
879
0
  default:
880
0
    goto fail;
881
0
  }
882
0
  (*layout)++;
883
884
0
  return (lc);
885
886
0
fail:
887
0
  layout_free_cell(lc, 0);
888
0
  return (NULL);
889
0
}
890
891
/*
892
 * Evaluate parsed JSON. Check metadata at the top level and return the new
893
 * layout root. Consumes json input.
894
 */
895
static int
896
layout_parse_json(struct json_node *jnroot, struct layout_parse_ctx *pctx)
897
0
{
898
0
  struct json_node   *jn, *object;
899
0
  int64_t       num;
900
0
  char      **cause = pctx->cause;
901
902
0
  if (json_get_object(jnroot, &jn) != 0) {
903
0
    *cause = xstrdup("invalid layout json");
904
0
    goto fail;
905
0
  }
906
907
0
  if (json_find_number(jn, "V", &num, cause) != 0)
908
0
    goto fail;
909
0
  pctx->version = num;
910
911
0
  if (json_find_object(jn, "L", &object, cause) != 0)
912
0
    goto fail;
913
0
  pctx->root = layout_parse_json_layout(object, NULL, pctx);
914
0
  if (pctx->root == NULL)
915
0
    goto fail;
916
917
0
  json_destroy_node(jnroot);
918
919
0
  return (0);
920
921
0
fail:
922
0
  json_destroy_node(jnroot);
923
0
  if (pctx->root != NULL)
924
0
    layout_free_cell(pctx->root, 0);
925
0
  pctx->root = NULL;
926
0
  return (-1);
927
0
}
928
929
/* Parse nodes into layout cells. */
930
static struct layout_cell *
931
layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
932
    struct layout_parse_ctx *pctx)
933
0
{
934
0
  struct json_node   *member, *array;
935
0
  struct layout_cell   *lc = layout_create_cell(lcparent), *lcchild;
936
0
  const char     *str;
937
0
  int64_t       num;
938
0
  char      **cause = pctx->cause;
939
0
  int       boolean, index, zindex, active = -1;
940
0
  int       last = -1;
941
942
0
  if (json_find_string(node, "t", &str, cause) != 0)
943
0
    goto fail;
944
0
  if (strcmp(str, "p") == 0)
945
0
    lc->type = LAYOUT_WINDOWPANE;
946
0
  else if (strcmp(str, "v") == 0)
947
0
    lc->type = LAYOUT_TOPBOTTOM;
948
0
  else if (strcmp(str, "h") == 0)
949
0
    lc->type = LAYOUT_LEFTRIGHT;
950
0
  else {
951
0
    xasprintf(cause, "unknown cell type \"%s\"", str);
952
0
    goto fail;
953
0
  }
954
955
0
  if (json_find_number(node, "w", &num, cause) != 0)
956
0
    goto fail;
957
0
  if (num < PANE_MINIMUM || num > PANE_MAXIMUM) {
958
0
    xasprintf(cause, "invalid width %lld", (long long)num);
959
0
    goto fail;
960
0
  }
961
0
  lc->g.sx = num;
962
963
0
  if (json_find_number(node, "h", &num, cause) != 0)
964
0
    goto fail;
965
0
  if (num < PANE_MINIMUM || num > PANE_MAXIMUM) {
966
0
    xasprintf(cause, "invalid height %lld", (long long)num);
967
0
    goto fail;
968
0
  }
969
0
  lc->g.sy = num;
970
971
0
  if (json_find_number(node, "x", &num, cause) != 0)
972
0
    goto fail;
973
0
  if (num < -WINDOW_MAXIMUM || num > WINDOW_MAXIMUM) {
974
0
    xasprintf(cause, "invalid x-offset %lld", (long long)num);
975
0
    goto fail;
976
0
  }
977
0
  lc->g.xoff = num;
978
979
0
  if (json_find_number(node, "y", &num, cause) != 0)
980
0
    goto fail;
981
0
  if (num < -WINDOW_MAXIMUM || num > WINDOW_MAXIMUM) {
982
0
    xasprintf(cause, "invalid y-offset %lld", (long long)num);
983
0
    goto fail;
984
0
  }
985
0
  lc->g.yoff = num;
986
987
0
  if (lc->type == LAYOUT_WINDOWPANE) { /* "I" is currently ignored */
988
0
    if (json_find(node, "c") != NULL) {
989
0
      *cause = xstrdup("panes cannot have children");
990
0
      goto fail;
991
0
    }
992
0
    if (json_find_number(node, "i", &num, cause) != 0)
993
0
      goto fail;
994
0
    if (num < 0 || num > INT_MAX) {
995
0
      xasprintf(cause, "invalid index %lld", (long long)num);
996
0
      goto fail;
997
0
    }
998
0
    index = num;
999
1000
0
    if (json_find(node, "a") != NULL) {
1001
0
      if (json_find_boolean(node, "a", &boolean, cause) != 0)
1002
0
        goto fail;
1003
0
      active = boolean;
1004
0
      if (active)
1005
0
        pctx->num_active++;
1006
0
    } else if (json_find(node, "l") != NULL) {
1007
0
      if (json_find_number(node, "l", &num, cause) != 0)
1008
0
        goto fail;
1009
0
      if (num < 0 || num > INT_MAX) {
1010
0
        xasprintf(cause, "invalid last %lld",
1011
0
            (long long)num);
1012
0
        goto fail;
1013
0
      }
1014
0
      last = num;
1015
0
    }
1016
1017
0
    if (json_find(node, "z") != NULL) {
1018
0
      if (json_find_number(node, "z", &num, cause) != 0)
1019
0
        goto fail;
1020
0
      if (num < 0 || num > INT_MAX - 1) {
1021
0
        xasprintf(cause, "invalid floating zindex %lld",
1022
0
            (long long)num);
1023
0
        goto fail;
1024
0
      }
1025
0
      zindex = num;
1026
0
      lc->flags |= LAYOUT_CELL_FLOATING;
1027
0
    } else
1028
0
      zindex = INT_MAX;
1029
1030
0
    layout_parse_add_cctx(pctx, lc, active, last, index, zindex);
1031
0
  } else {
1032
0
    if (json_find_array(node, "c", &array, cause) != 0)
1033
0
      goto fail;
1034
0
    if ((member = json_array_first(array)) == NULL ||
1035
0
        json_array_next(member) == NULL) {
1036
0
      *cause = xstrdup("nodes must have more than one child");
1037
0
      goto fail;
1038
0
    }
1039
0
    while (member != NULL) {
1040
0
      lcchild = layout_parse_json_layout(member, lc,
1041
0
        pctx);
1042
0
      if (lcchild == NULL)
1043
0
        goto fail;
1044
0
      TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
1045
0
      member = json_array_next(member);
1046
0
    }
1047
0
  }
1048
1049
0
  return (lc);
1050
1051
0
fail:
1052
0
  layout_free_cell(lc, 0);
1053
0
  return (NULL);
1054
0
}
1055
1056
/* Construct a layout root from a formatted string. */
1057
static int
1058
layout_construct(const char *input, struct layout_parse_ctx *pctx)
1059
0
{
1060
0
  struct json_node  *json;
1061
0
  u_short      csum;
1062
0
  int      n = 0;
1063
1064
0
  while (isspace((u_char) *input))
1065
0
    input++;
1066
1067
0
  if (*input != '{') { /* sniffing version */
1068
0
    if (sscanf(input, "%hx,%n", &csum, &n) != 1 || n != 5) {
1069
0
      *pctx->cause = xstrdup("malformed layout header");
1070
0
      return (-1);
1071
0
    }
1072
0
    input += n;
1073
0
    if (csum != layout_checksum(input)) {
1074
0
      *pctx->cause = xstrdup("invalid layout checksum");
1075
0
      return (-1);
1076
0
    }
1077
0
    pctx->root = layout_construct_v1(NULL, &input, 0);
1078
0
    if (pctx->root == NULL) {
1079
0
      *pctx->cause = xstrdup("invalid layout");
1080
0
      return (-1);
1081
0
    }
1082
0
    if (*input != '\0') {
1083
0
      *pctx->cause = xstrdup("trailing data");
1084
0
      return (-1);
1085
0
    }
1086
0
    pctx->version = 1;
1087
0
  } else {
1088
0
    if ((json = json_parse(input, pctx->cause)) == NULL)
1089
0
      return (-1);
1090
1091
0
    if (layout_parse_json(json, pctx) != 0)
1092
0
      return (-1);
1093
1094
0
    if (pctx->version != 2) {
1095
0
      *pctx->cause = xstrdup("version mismatch");
1096
0
      return (-1);
1097
0
    }
1098
0
    if (pctx->num_active > 1) {
1099
0
      *pctx->cause = xstrdup("more than one active pane");
1100
0
      return (-1);
1101
0
    }
1102
0
    if (pctx->size == 0) {
1103
0
      *pctx->cause = xstrdup("no panes");
1104
0
      return (-1);
1105
0
    }
1106
0
    if (!layout_parse_ctx_check_indexes(pctx))
1107
0
      return (-1);
1108
0
  }
1109
1110
0
  return (0);
1111
0
}
1112
1113
/* Apply the remaining context to the layout. */
1114
static void
1115
layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
1116
0
{
1117
0
  struct layout_parse_cell_ctx   *cctx;
1118
0
  struct window_pane     *wp, *wpnext;
1119
0
  int         i;
1120
1121
  /* Apply z-indexes. */
1122
0
  wp = TAILQ_FIRST(&w->z_index);
1123
0
  while (wp != NULL) {
1124
0
    wpnext = TAILQ_NEXT(wp, zentry);
1125
0
    if (window_pane_is_floating(wp))
1126
0
      TAILQ_REMOVE(&w->z_index, wp, zentry);
1127
0
    wp = wpnext;
1128
0
  }
1129
1130
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1131
0
      layout_parse_zindex_cmp);
1132
1133
0
  for (i = 0; i < pctx->size; i++) {
1134
0
    cctx = &pctx->cctxs[i];
1135
0
    wp = cctx->lc->wp;
1136
0
    if (window_pane_is_floating(wp))
1137
0
      TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
1138
0
  }
1139
1140
  /* Set the active pane. */
1141
0
  for (i = 0; i < pctx->size; i++) {
1142
0
    cctx = &pctx->cctxs[i];
1143
0
    if (cctx->active == 1) {
1144
0
      window_set_active_pane(w, cctx->lc->wp, 1);
1145
0
      break;
1146
0
    }
1147
0
  }
1148
1149
  /* Apply last panes. */
1150
0
  while (!TAILQ_EMPTY(&w->last_panes)) {
1151
0
    wp = TAILQ_FIRST(&w->last_panes);
1152
0
    window_pane_stack_remove(&w->last_panes, wp);
1153
0
  }
1154
1155
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1156
0
      layout_parse_last_cmp);
1157
1158
0
  for (i = 0; i < pctx->size; i++) {
1159
0
    cctx = &pctx->cctxs[i];
1160
0
    wp = cctx->lc->wp;
1161
0
    if (cctx->last < 0 || cctx->active == 1)
1162
0
      continue;
1163
0
    window_pane_stack_push(&w->last_panes, wp);
1164
0
  }
1165
0
}
1166
1167
/* Checks for duplicate pane indexes, z-indexes, and last indexes. */
1168
static int
1169
layout_parse_ctx_check_indexes(struct layout_parse_ctx *pctx)
1170
0
{
1171
0
  int i, n;
1172
1173
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1174
0
      layout_parse_index_cmp);
1175
1176
0
  for (i = 1; i < pctx->size; i++) {
1177
0
    if (pctx->cctxs[i].index == pctx->cctxs[i - 1].index) {
1178
0
      *pctx->cause = xstrdup("duplicate pane index");
1179
0
      return (0);
1180
0
    }
1181
0
  }
1182
1183
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1184
0
      layout_parse_zindex_cmp);
1185
1186
  /*
1187
   * Sorted in descending order, so the panes without a z-index come first
1188
   * and the floating panes run to the end.
1189
   */
1190
0
  n = 0;
1191
0
  while (n < pctx->size && pctx->cctxs[n].zindex == INT_MAX)
1192
0
    n++;
1193
0
  for (i = n + 1; i < pctx->size; i++) {
1194
0
    if (pctx->cctxs[i].zindex == pctx->cctxs[i - 1].zindex) {
1195
0
      *pctx->cause = xstrdup("duplicate pane z-index");
1196
0
      return (0);
1197
0
    }
1198
0
  }
1199
1200
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1201
0
      layout_parse_last_cmp);
1202
1203
  /*
1204
   * Sorted in descending order, so the panes without a last index come
1205
   * last.
1206
   */
1207
0
  n = 0;
1208
0
  while (n < pctx->size && pctx->cctxs[n].last >= 0)
1209
0
    n++;
1210
0
  for (i = 1; i < n; i++) {
1211
0
    if (pctx->cctxs[i].last == pctx->cctxs[i - 1].last) {
1212
0
      *pctx->cause = xstrdup("duplicate last pane index");
1213
0
      return (0);
1214
0
    }
1215
0
  }
1216
1217
0
  return (1);
1218
0
}