Coverage Report

Created: 2026-10-17 06:18

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.44 2026/09/28 09:32:06 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 = { 0 };
287
0
  char      *out;
288
289
0
  if (lcroot == NULL)
290
0
    goto bad;
291
0
  layout_string_init(&layout_string);
292
0
  if (layout_append(lcroot, &layout_string, flags) != 0)
293
0
    goto bad;
294
0
  if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
295
0
    xasprintf(&out, "{\"V\":2,\"L\":%s}", layout_string.dat);
296
0
  else {
297
0
    xasprintf(&out, "%04hx,%s", layout_checksum(layout_string.dat),
298
0
        layout_string.dat);
299
0
  }
300
0
  layout_string_free(&layout_string);
301
0
  return (out);
302
303
0
bad:
304
0
  layout_string_free(&layout_string);
305
0
  if (~flags & LAYOUT_CUSTOM_OLD_FORMAT)
306
0
    return (NULL);
307
0
  return (xstrdup("0000,"));
308
0
}
309
310
/* Get a floating pane cell's z-index in the layout being dumped. */
311
static u_int
312
layout_cell_zindex(struct layout_cell *lc)
313
0
{
314
0
  struct window_pane  *wp = lc->wp, *wq;
315
0
  struct window   *w = wp->window;
316
0
  struct layout_cell  *other;
317
0
  int      saved = (lc == wp->saved_layout_cell);
318
0
  u_int      i = 0;
319
320
0
  if (saved &&
321
0
      w->active != NULL &&
322
0
      (w->active->flags & PANE_ZOOMED) &&
323
0
      (w->active->saved_layout_cell->flags & LAYOUT_CELL_FLOATING)) {
324
0
    if (wp == w->active)
325
0
      return (0);
326
0
    i++;
327
0
  }
328
0
  TAILQ_FOREACH(wq, &w->z_index, zentry) {
329
0
    if (wq == wp)
330
0
      break;
331
0
    if (saved)
332
0
      other = wq->saved_layout_cell;
333
0
    else
334
0
      other = wq->layout_cell;
335
0
    if (other != NULL && (other->flags & LAYOUT_CELL_FLOATING))
336
0
      i++;
337
0
  }
338
0
  return (i);
339
0
}
340
341
/* Append information for a single cell in a JSON (v2) format. */
342
static int
343
layout_append_v2(struct layout_cell *lc, struct layout_string *ls)
344
0
{
345
0
  struct layout_cell  *lcchild;
346
0
  struct window_pane  *wp;
347
0
  enum layout_type   type;
348
0
  char       c;
349
0
  u_int      i, n, z;
350
351
0
  if (lc == NULL)
352
0
    return (-1);
353
354
0
  type = lc->type;
355
0
  if (type == LAYOUT_TOPBOTTOM)
356
0
    c = 'v';
357
0
  else if (type == LAYOUT_LEFTRIGHT)
358
0
    c = 'h';
359
0
  else if (type == LAYOUT_WINDOWPANE)
360
0
    c = 'p';
361
0
  else
362
0
    return (-1);
363
364
0
  layout_string_write(ls, "{\"t\":\"%c\",\"w\":%u,\"h\":%u,\"x\":%d"
365
0
      ",\"y\":%d", c, lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff);
366
0
  if (type != LAYOUT_WINDOWPANE) {
367
0
    layout_string_write(ls, ",\"c\":[");
368
0
    n = 0;
369
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
370
0
      if (layout_append_v2(lcchild, ls) != 0)
371
0
        return (-1);
372
0
      layout_string_write(ls, ",");
373
0
      n++;
374
0
    }
375
0
    if (n == 0)
376
0
      return (-1);
377
0
    ls->dat[--ls->size] = '\0'; /* removing trailing comma */
378
0
    layout_string_write(ls, "]");
379
0
  } else {
380
0
    wp = lc->wp;
381
0
    if (wp == NULL)
382
0
      return (-1);
383
0
    if (wp == wp->window->active)
384
0
      layout_string_write(ls, ",\"a\":true");
385
0
    else if (window_pane_last_index(wp, &i) == 0)
386
0
      layout_string_write(ls, ",\"l\":%u", i);
387
0
    if (window_pane_index(wp, &i) != 0)
388
0
      return (-1);
389
0
    layout_string_write(ls, ",\"i\":%u", i);
390
0
    if (lc->flags & LAYOUT_CELL_FLOATING) {
391
0
      z = layout_cell_zindex(lc);
392
0
      layout_string_write(ls, ",\"z\":%u", z);
393
0
    }
394
0
    layout_string_write(ls, ",\"I\":\"%%%u\"", wp->id);
395
0
  }
396
397
0
  layout_string_write(ls, "}");
398
399
0
  return (0);
400
0
}
401
402
/* Append information for a single cell in the version 1 format. */
403
static int
404
layout_append_v1(struct layout_cell *lc, struct layout_string *ls)
405
0
{
406
0
  struct layout_cell  *lcchild;
407
0
  const char    *brackets = "[]";
408
409
0
  if (lc == NULL)
410
0
    return (-1);
411
412
0
  if (lc->wp != NULL) {
413
0
    layout_string_write(ls, "%ux%u,%d,%d,%u", lc->g.sx, lc->g.sy,
414
0
        lc->g.xoff, lc->g.yoff, lc->wp->id);
415
0
  } else {
416
0
    layout_string_write(ls, "%ux%u,%d,%d", lc->g.sx, lc->g.sy,
417
0
        lc->g.xoff, lc->g.yoff);
418
0
  }
419
0
  switch (lc->type) {
420
0
  case LAYOUT_LEFTRIGHT:
421
0
    brackets = "{}";
422
    /* FALLTHROUGH */
423
0
  case LAYOUT_TOPBOTTOM:
424
0
    layout_string_write(ls, "%c", brackets[0]);
425
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
426
0
      if (layout_append_v1(lcchild, ls) != 0)
427
0
        return (-1);
428
0
      layout_string_write(ls, ",");
429
0
    }
430
431
0
    ls->dat[--ls->size] = '\0'; /* removing trailing comma */
432
0
    layout_string_write(ls, "%c", brackets[1]);
433
0
    break;
434
0
  case LAYOUT_WINDOWPANE:
435
0
    break;
436
0
  }
437
438
0
  return (0);
439
0
}
440
441
/*
442
 * Copies the tiled part of a layout. Only populates what is necessary to dump a
443
 * V1 layout string.
444
 */
445
static struct layout_cell *
446
layout_custom_copy_layout(struct layout_cell *lc)
447
0
{
448
0
  struct layout_cell  *lcchild, *lcnewchild, *lconly;
449
0
  struct layout_cell  *lcnew;
450
451
0
  if (lc->type == LAYOUT_WINDOWPANE &&
452
0
      (lc->flags & LAYOUT_CELL_FLOATING))
453
0
    return (NULL);
454
455
0
  lcnew = layout_create_cell(NULL);
456
457
0
  lcnew->type = lc->type;
458
0
  lcnew->flags = lc->flags;
459
0
  if (lc->type == LAYOUT_WINDOWPANE)
460
0
    lcnew->wp = lc->wp;
461
0
  layout_set_size(lcnew, lc->g.sx, lc->g.sy, lc->g.xoff, lc->g.yoff);
462
463
0
  switch (lc->type) {
464
0
  case LAYOUT_WINDOWPANE:
465
0
    break;
466
0
  case LAYOUT_TOPBOTTOM:
467
0
  case LAYOUT_LEFTRIGHT:
468
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
469
0
      lcnewchild = layout_custom_copy_layout(lcchild);
470
0
      if (lcnewchild == NULL)
471
0
        continue;
472
0
      TAILQ_INSERT_TAIL(&lcnew->cells, lcnewchild, entry);
473
0
      lcnewchild->parent = lcnew;
474
0
    }
475
476
0
    lconly = TAILQ_FIRST(&lcnew->cells);
477
0
    if (lconly == NULL) {
478
0
      layout_free_cell(lcnew, 0);
479
0
      return (NULL);
480
0
    }
481
0
    if (TAILQ_NEXT(lconly, entry) == NULL) {
482
0
      TAILQ_REMOVE(&lcnew->cells, lconly, entry);
483
0
      lconly->parent = NULL;
484
0
      layout_free_cell(lcnew, 0);
485
0
      return (lconly);
486
0
    }
487
0
    break;
488
0
  }
489
490
0
  return (lcnew);
491
0
}
492
493
/* Create a compatibility layout for dumping a V1 layout string. */
494
static struct layout_cell *
495
layout_custom_create_compat(struct layout_cell *lcroot)
496
0
{
497
0
  struct layout_cell  *lccompat;
498
499
0
  lccompat = layout_custom_copy_layout(lcroot);
500
0
  if (lccompat != NULL && layout_cell_is_tiled(lccompat)) {
501
0
    lccompat->g.xoff = 0;
502
0
    lccompat->g.yoff = 0;
503
0
  }
504
505
0
  return (lccompat);
506
0
}
507
508
/* Unlinks all panes from the given layout. */
509
static void
510
layout_custom_unlink_panes(struct layout_cell *lc)
511
0
{
512
0
  struct layout_cell  *lcchild;
513
514
0
  switch (lc->type) {
515
0
  case LAYOUT_WINDOWPANE:
516
0
    lc->wp = NULL;
517
0
    break;
518
0
  case LAYOUT_LEFTRIGHT:
519
0
  case LAYOUT_TOPBOTTOM:
520
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry)
521
0
      layout_custom_unlink_panes(lcchild);
522
0
    break;
523
0
  }
524
0
}
525
526
/* Frees the compatibility layout. */
527
static void
528
layout_custom_free_compat(struct layout_cell *lcroot)
529
0
{
530
0
  if (lcroot == NULL)
531
0
    return;
532
0
  layout_custom_unlink_panes(lcroot);
533
0
  layout_free_cell(lcroot, 0);
534
0
}
535
536
/* Dispatch to append the appropriate version. */
537
static int
538
layout_append(struct layout_cell *lcroot, struct layout_string *ls, int flags)
539
0
{
540
0
  struct layout_cell  *lccompat;
541
0
  int      result;
542
543
0
  if (flags & LAYOUT_CUSTOM_OLD_FORMAT) {
544
0
    if (!layout_cell_is_tiled(lcroot) &&
545
0
        !layout_cell_has_tiled_child(lcroot))
546
0
      return (-1);
547
0
    lccompat = layout_custom_create_compat(lcroot);
548
0
    result = layout_append_v1(lccompat, ls);
549
0
    layout_custom_free_compat(lccompat);
550
0
  } else
551
0
    result = layout_append_v2(lcroot, ls);
552
553
0
  return (result);
554
0
}
555
556
/* Check layout sizes fit. */
557
static int
558
layout_check(struct layout_cell *lc)
559
0
{
560
0
  struct layout_cell  *lcchild;
561
0
  u_int      n = 0;
562
563
0
  switch (lc->type) {
564
0
  case LAYOUT_WINDOWPANE:
565
0
    break;
566
0
  case LAYOUT_LEFTRIGHT:
567
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
568
0
      if (!layout_cell_is_tiled(lcchild) &&
569
0
          !layout_cell_has_tiled_child(lcchild))
570
0
        continue;
571
0
      if (lcchild->g.sy != lc->g.sy)
572
0
        return (0);
573
0
      if (!layout_check(lcchild))
574
0
        return (0);
575
0
      n += lcchild->g.sx + 1;
576
0
    }
577
0
    if (n != 0 && n - 1 != lc->g.sx)
578
0
      return (0);
579
0
    break;
580
0
  case LAYOUT_TOPBOTTOM:
581
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
582
0
      if (!layout_cell_is_tiled(lcchild) &&
583
0
          !layout_cell_has_tiled_child(lcchild))
584
0
        continue;
585
0
      if (lcchild->g.sx != lc->g.sx)
586
0
        return (0);
587
0
      if (!layout_check(lcchild))
588
0
        return (0);
589
0
      n += lcchild->g.sy + 1;
590
0
    }
591
0
    if (n != 0 && n - 1 != lc->g.sy)
592
0
      return (0);
593
0
    break;
594
0
  }
595
0
  return (1);
596
0
}
597
598
/* Parse a layout string and arrange window as layout. */
599
int
600
layout_parse(struct window *w, const char *input, char **cause)
601
0
{
602
0
  struct window_pane  *wp;
603
0
  struct layout_cell  *lcchild, *lc = NULL;
604
0
  struct layout_parse_ctx  pctx;
605
0
  u_int      npanes, ncells, sx = 0, sy = 0;
606
0
  int      with_floating;
607
608
  /* Build the layout. */
609
0
  layout_parse_init_ctx(&pctx, cause);
610
0
  if (layout_construct(input, &pctx) != 0) {
611
0
    layout_parse_free_ctx(&pctx);
612
0
    return (-1);
613
0
  }
614
0
  with_floating = pctx.version > 1;
615
616
  /* Check this window will fit into the layout. */
617
0
  npanes = window_count_panes(w, with_floating);
618
0
  if (npanes == 0) {
619
0
    xasprintf(cause, "window @%u has no panes", w->id);
620
0
    goto fail;
621
0
  }
622
0
  for (;;) {
623
0
    ncells = layout_count_cells(pctx.root, with_floating);
624
0
    if (npanes > ncells) {
625
0
      xasprintf(cause, "have %u panes but need %u", npanes,
626
0
          ncells);
627
0
      goto fail;
628
0
    }
629
0
    if (npanes == ncells)
630
0
      break;
631
632
    /*
633
     * Fewer panes than cells, close the bottom right until none
634
     * remain.
635
     */
636
0
    lcchild = layout_find_bottomright(pctx.root);
637
0
    if (pctx.version > 1 && layout_parse_remove_cctx(&pctx,
638
0
        lcchild) != 0) {
639
0
      *cause = xstrdup("empty/missing layout parse context");
640
0
      goto fail;
641
0
    }
642
0
    layout_destroy_cell(NULL, lcchild, &pctx.root);
643
0
  }
644
645
  /* The root is now owned by lc. */
646
0
  lc = pctx.root;
647
0
  pctx.root = NULL;
648
649
  /*
650
   * It appears older versions of tmux were able to generate layouts with
651
   * an incorrect top cell size - if it is larger than the top child then
652
   * correct that (if this is still wrong the check code will catch it).
653
   */
654
0
  switch (lc->type) {
655
0
  case LAYOUT_WINDOWPANE:
656
0
    break;
657
0
  case LAYOUT_LEFTRIGHT:
658
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
659
0
      if (layout_cell_is_tiled(lcchild) ||
660
0
          layout_cell_has_tiled_child(lcchild)) {
661
0
        sy = lcchild->g.sy + 1;
662
0
        sx += lcchild->g.sx + 1;
663
0
      }
664
0
    }
665
0
    break;
666
0
  case LAYOUT_TOPBOTTOM:
667
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
668
0
      if (layout_cell_is_tiled(lcchild) ||
669
0
          layout_cell_has_tiled_child(lcchild)) {
670
0
        sx = lcchild->g.sx + 1;
671
0
        sy += lcchild->g.sy + 1;
672
0
      }
673
0
    }
674
0
    break;
675
0
  }
676
0
  if (lc->type != LAYOUT_WINDOWPANE && sx != 0 && sy != 0 &&
677
0
      (lc->g.sx != sx || lc->g.sy != sy)) {
678
0
    layout_print_cell(lc, __func__, 0);
679
0
    lc->g.sx = sx - 1; lc->g.sy = sy - 1;
680
0
  }
681
682
  /* Check the new layout. */
683
0
  if (!layout_check(lc)) {
684
0
    *cause = xstrdup("size mismatch after applying layout");
685
0
    goto fail;
686
0
  }
687
688
  /* Resize window to the layout size. */
689
0
  if (layout_cell_is_tiled(lc) ||
690
0
      layout_cell_has_tiled_child(lc))
691
0
    window_resize(w, lc->g.sx, lc->g.sy, -1, -1);
692
693
  /* Preserve floating panes for version 1. */
694
0
  if (pctx.version == 1) {
695
0
    TAILQ_FOREACH(wp, &w->panes, entry) {
696
0
      if (!window_pane_is_floating(wp))
697
0
        continue;
698
0
      lcchild = wp->layout_cell;
699
0
      TAILQ_REMOVE(&lcchild->parent->cells, lcchild, entry);
700
0
      lcchild->parent = NULL;
701
0
    }
702
0
  }
703
704
  /* Destroy the old layout and swap to the new. */
705
0
  layout_free_cell(w->layout_root, 0);
706
0
  w->layout_root = lc;
707
708
  /* Assign the panes into the cells. */
709
0
  layout_assign(w, &pctx);
710
711
  /* Update pane attributes. */
712
0
  layout_fix_offsets(w);
713
0
  layout_fix_panes(w, NULL);
714
0
  if (pctx.version > 1)
715
0
    layout_parse_apply_ctx(w, &pctx);
716
0
  recalculate_sizes();
717
0
  layout_print_cell(lc, __func__, 0);
718
719
  /* Backwards compatibility. */
720
0
  if (pctx.version == 1)
721
0
    events_fire_window("window-layout-changed", w);
722
723
0
  layout_parse_free_ctx(&pctx);
724
0
  return (0);
725
726
0
fail:
727
0
  layout_free_cell(lc, 0);
728
0
  layout_parse_free_ctx(&pctx);
729
0
  return (-1);
730
0
}
731
732
/* Assign panes into cells from the cell contexts. */
733
static void
734
layout_assign_from_ctx(struct window *w, struct layout_parse_ctx *pctx)
735
0
{
736
0
  struct layout_cell  *lc;
737
0
  struct window_pane  *wp;
738
0
  int      i;
739
740
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
741
0
      layout_parse_index_cmp);
742
743
0
  wp = TAILQ_FIRST(&w->panes);
744
0
  for (i = 0; i < pctx->size; i++) {
745
0
    lc = pctx->cctxs[i].lc;
746
0
    layout_make_leaf(lc, wp);
747
0
    wp = TAILQ_NEXT(wp, entry);
748
0
  }
749
0
}
750
751
/*
752
 * Assign tiled cells to available panes. Panes that already have a cell are
753
 * floating and are skipped over.
754
 */
755
static void
756
layout_assign_fallback_tiled(struct window_pane **wp, struct layout_cell *lc)
757
0
{
758
0
  struct layout_cell  *lcchild;
759
760
0
  if (lc == NULL)
761
0
    return;
762
763
0
  switch (lc->type) {
764
0
  case LAYOUT_WINDOWPANE:
765
0
    while (*wp != NULL && (*wp)->layout_cell != NULL)
766
0
      *wp = TAILQ_NEXT(*wp, entry);
767
0
    if (*wp == NULL)
768
0
      return;
769
0
    layout_make_leaf(lc, *wp);
770
0
    *wp = TAILQ_NEXT(*wp, entry);
771
0
    return;
772
0
  case LAYOUT_LEFTRIGHT:
773
0
  case LAYOUT_TOPBOTTOM:
774
0
    TAILQ_FOREACH(lcchild, &lc->cells, entry) {
775
0
      layout_assign_fallback_tiled(wp, lcchild);
776
0
    }
777
0
    return;
778
0
  }
779
0
}
780
781
/*
782
 * Assign panes into cells when there are no cell contexts. This will be removed
783
 * when the non-JSON format is deprecated.
784
 */
785
static void
786
layout_assign_fallback(struct window *w, struct layout_cell *lcroot)
787
0
{
788
0
  struct window_pane  *wp = TAILQ_FIRST(&w->panes);
789
0
  struct layout_cell  *lc;
790
791
0
  layout_assign_fallback_tiled(&wp, lcroot);
792
793
0
  if (window_count_panes(w, 1) > 1 &&
794
0
      lcroot->type == LAYOUT_WINDOWPANE)
795
0
    lcroot = layout_replace_with_node(w, lcroot, LAYOUT_TOPBOTTOM);
796
797
0
  wp = TAILQ_FIRST(&w->panes);
798
0
  while (wp != NULL) {
799
0
    if (window_pane_is_floating(wp)) {
800
0
      lc = wp->layout_cell;
801
0
      lc->parent = lcroot;
802
0
      TAILQ_INSERT_TAIL(&lcroot->cells, lc, entry);
803
0
    }
804
0
    wp = TAILQ_NEXT(wp, entry);
805
0
  }
806
0
}
807
808
/* Assign panes into cells. Number of cells must match the number of panes. */
809
static void
810
layout_assign(struct window *w, struct layout_parse_ctx *pctx)
811
0
{
812
0
  if (pctx->size > 0)
813
0
    layout_assign_from_ctx(w, pctx);
814
0
  else
815
0
    layout_assign_fallback(w, w->layout_root);
816
0
}
817
818
/* Construct a cell from the version 1 format. */
819
static struct layout_cell *
820
layout_construct_cell(struct layout_cell *lcparent, const char **layout)
821
0
{
822
0
  struct layout_cell     *lc;
823
0
  u_int     sx, sy;
824
0
  int     xoff, yoff;
825
0
  const char         *saved;
826
827
0
  if (!isdigit((u_char) **layout))
828
0
    return (NULL);
829
0
  if (sscanf(*layout, "%5ux%5u,%5d,%5d", &sx, &sy, &xoff, &yoff) != 4)
830
0
    return (NULL);
831
0
  if (sx < PANE_MINIMUM || sx > PANE_MAXIMUM ||
832
0
      sy < PANE_MINIMUM || sy > PANE_MAXIMUM ||
833
0
      xoff < 0 || xoff > WINDOW_MAXIMUM ||
834
0
      yoff < 0 || yoff > WINDOW_MAXIMUM)
835
0
    return (NULL);
836
837
0
  while (isdigit((u_char) **layout))
838
0
    (*layout)++;
839
0
  if (**layout != 'x')
840
0
    return (NULL);
841
0
  (*layout)++;
842
0
  while (isdigit((u_char) **layout))
843
0
    (*layout)++;
844
0
  if (**layout != ',')
845
0
    return (NULL);
846
0
  (*layout)++;
847
0
  while (isdigit((u_char) **layout))
848
0
    (*layout)++;
849
0
  if (**layout != ',')
850
0
    return (NULL);
851
0
  (*layout)++;
852
0
  while (isdigit((u_char) **layout))
853
0
    (*layout)++;
854
0
  if (**layout == ',') {
855
0
    saved = *layout;
856
0
    (*layout)++;
857
0
    while (isdigit((u_char) **layout))
858
0
      (*layout)++;
859
0
    if (**layout == 'x')
860
0
      *layout = saved;
861
0
  }
862
863
0
  lc = layout_create_cell(lcparent);
864
0
  lc->g.sx = sx;
865
0
  lc->g.sy = sy;
866
0
  lc->g.xoff = xoff;
867
0
  lc->g.yoff = yoff;
868
869
0
  return (lc);
870
0
}
871
872
/* Construct a layout from the version 1 format. */
873
static struct layout_cell *
874
layout_construct_v1(struct layout_cell *lcparent, const char **layout, u_int depth)
875
0
{
876
0
  struct layout_cell  *lc, *lcchild;
877
878
0
  if (depth > LAYOUT_V1_MAX_DEPTH)
879
0
    return (NULL);
880
881
0
  lc = layout_construct_cell(lcparent, layout);
882
0
  if (lc == NULL)
883
0
    return (NULL);
884
885
0
  switch (**layout) {
886
0
  case ',':
887
0
  case '}':
888
0
  case ']':
889
0
  case '\0':
890
0
    return (lc);
891
0
  case '{':
892
0
    lc->type = LAYOUT_LEFTRIGHT;
893
0
    break;
894
0
  case '[':
895
0
    lc->type = LAYOUT_TOPBOTTOM;
896
0
    break;
897
0
  default:
898
0
    goto fail;
899
0
  }
900
901
0
  do {
902
0
    (*layout)++;
903
0
    lcchild = layout_construct_v1(lc, layout, depth + 1);
904
0
    if (lcchild == NULL)
905
0
      goto fail;
906
0
    TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
907
0
  } while (**layout == ',');
908
909
0
  switch (lc->type) {
910
0
  case LAYOUT_LEFTRIGHT:
911
0
    if (**layout != '}')
912
0
      goto fail;
913
0
    break;
914
0
  case LAYOUT_TOPBOTTOM:
915
0
    if (**layout != ']')
916
0
      goto fail;
917
0
    break;
918
0
  default:
919
0
    goto fail;
920
0
  }
921
0
  (*layout)++;
922
923
0
  return (lc);
924
925
0
fail:
926
0
  layout_free_cell(lc, 0);
927
0
  return (NULL);
928
0
}
929
930
/*
931
 * Evaluate parsed JSON. Check metadata at the top level and return the new
932
 * layout root. Consumes json input.
933
 */
934
static int
935
layout_parse_json(struct json_node *jnroot, struct layout_parse_ctx *pctx)
936
0
{
937
0
  struct json_node   *jn, *object;
938
0
  int64_t       num;
939
0
  char      **cause = pctx->cause;
940
941
0
  if (json_get_object(jnroot, &jn) != 0) {
942
0
    *cause = xstrdup("invalid layout json");
943
0
    goto fail;
944
0
  }
945
946
0
  if (json_find_number(jn, "V", &num, cause) != 0)
947
0
    goto fail;
948
0
  pctx->version = num;
949
950
0
  if (json_find_object(jn, "L", &object, cause) != 0)
951
0
    goto fail;
952
0
  pctx->root = layout_parse_json_layout(object, NULL, pctx);
953
0
  if (pctx->root == NULL)
954
0
    goto fail;
955
956
0
  json_destroy_node(jnroot);
957
958
0
  return (0);
959
960
0
fail:
961
0
  json_destroy_node(jnroot);
962
0
  if (pctx->root != NULL)
963
0
    layout_free_cell(pctx->root, 0);
964
0
  pctx->root = NULL;
965
0
  return (-1);
966
0
}
967
968
/* Parse nodes into layout cells. */
969
static struct layout_cell *
970
layout_parse_json_layout(struct json_node *node, struct layout_cell *lcparent,
971
    struct layout_parse_ctx *pctx)
972
0
{
973
0
  struct json_node   *member, *array;
974
0
  struct layout_cell   *lc = layout_create_cell(lcparent), *lcchild;
975
0
  const char     *str;
976
0
  int64_t       num;
977
0
  char      **cause = pctx->cause;
978
0
  int       boolean, index, zindex, active = -1;
979
0
  int       last = -1;
980
981
0
  if (json_find_string(node, "t", &str, cause) != 0)
982
0
    goto fail;
983
0
  if (strcmp(str, "p") == 0)
984
0
    lc->type = LAYOUT_WINDOWPANE;
985
0
  else if (strcmp(str, "v") == 0)
986
0
    lc->type = LAYOUT_TOPBOTTOM;
987
0
  else if (strcmp(str, "h") == 0)
988
0
    lc->type = LAYOUT_LEFTRIGHT;
989
0
  else {
990
0
    xasprintf(cause, "unknown cell type \"%s\"", str);
991
0
    goto fail;
992
0
  }
993
994
0
  if (json_find_number(node, "w", &num, cause) != 0)
995
0
    goto fail;
996
0
  if (num < PANE_MINIMUM || num > PANE_MAXIMUM) {
997
0
    xasprintf(cause, "invalid width %lld", (long long)num);
998
0
    goto fail;
999
0
  }
1000
0
  lc->g.sx = num;
1001
1002
0
  if (json_find_number(node, "h", &num, cause) != 0)
1003
0
    goto fail;
1004
0
  if (num < PANE_MINIMUM || num > PANE_MAXIMUM) {
1005
0
    xasprintf(cause, "invalid height %lld", (long long)num);
1006
0
    goto fail;
1007
0
  }
1008
0
  lc->g.sy = num;
1009
1010
0
  if (json_find_number(node, "x", &num, cause) != 0)
1011
0
    goto fail;
1012
0
  if (num < -WINDOW_MAXIMUM || num > WINDOW_MAXIMUM) {
1013
0
    xasprintf(cause, "invalid x-offset %lld", (long long)num);
1014
0
    goto fail;
1015
0
  }
1016
0
  lc->g.xoff = num;
1017
1018
0
  if (json_find_number(node, "y", &num, cause) != 0)
1019
0
    goto fail;
1020
0
  if (num < -WINDOW_MAXIMUM || num > WINDOW_MAXIMUM) {
1021
0
    xasprintf(cause, "invalid y-offset %lld", (long long)num);
1022
0
    goto fail;
1023
0
  }
1024
0
  lc->g.yoff = num;
1025
1026
0
  if (lc->type == LAYOUT_WINDOWPANE) { /* "I" is currently ignored */
1027
0
    if (json_find(node, "c") != NULL) {
1028
0
      *cause = xstrdup("panes cannot have children");
1029
0
      goto fail;
1030
0
    }
1031
0
    if (json_find_number(node, "i", &num, cause) != 0)
1032
0
      goto fail;
1033
0
    if (num < 0 || num > INT_MAX) {
1034
0
      xasprintf(cause, "invalid index %lld", (long long)num);
1035
0
      goto fail;
1036
0
    }
1037
0
    index = num;
1038
1039
0
    if (json_find(node, "a") != NULL) {
1040
0
      if (json_find_boolean(node, "a", &boolean, cause) != 0)
1041
0
        goto fail;
1042
0
      active = boolean;
1043
0
      if (active)
1044
0
        pctx->num_active++;
1045
0
    } else if (json_find(node, "l") != NULL) {
1046
0
      if (json_find_number(node, "l", &num, cause) != 0)
1047
0
        goto fail;
1048
0
      if (num < 0 || num > INT_MAX) {
1049
0
        xasprintf(cause, "invalid last %lld",
1050
0
            (long long)num);
1051
0
        goto fail;
1052
0
      }
1053
0
      last = num;
1054
0
    }
1055
1056
0
    if (json_find(node, "z") != NULL) {
1057
0
      if (json_find_number(node, "z", &num, cause) != 0)
1058
0
        goto fail;
1059
0
      if (num < 0 || num > INT_MAX - 1) {
1060
0
        xasprintf(cause, "invalid floating zindex %lld",
1061
0
            (long long)num);
1062
0
        goto fail;
1063
0
      }
1064
0
      zindex = num;
1065
0
      lc->flags |= LAYOUT_CELL_FLOATING;
1066
0
    } else
1067
0
      zindex = INT_MAX;
1068
1069
0
    layout_parse_add_cctx(pctx, lc, active, last, index, zindex);
1070
0
  } else {
1071
0
    if (json_find_array(node, "c", &array, cause) != 0)
1072
0
      goto fail;
1073
0
    if ((member = json_array_first(array)) == NULL ||
1074
0
        json_array_next(member) == NULL) {
1075
0
      *cause = xstrdup("nodes must have more than one child");
1076
0
      goto fail;
1077
0
    }
1078
0
    while (member != NULL) {
1079
0
      lcchild = layout_parse_json_layout(member, lc,
1080
0
        pctx);
1081
0
      if (lcchild == NULL)
1082
0
        goto fail;
1083
0
      TAILQ_INSERT_TAIL(&lc->cells, lcchild, entry);
1084
0
      member = json_array_next(member);
1085
0
    }
1086
0
  }
1087
1088
0
  return (lc);
1089
1090
0
fail:
1091
0
  layout_free_cell(lc, 0);
1092
0
  return (NULL);
1093
0
}
1094
1095
/* Construct a layout root from a formatted string. */
1096
static int
1097
layout_construct(const char *input, struct layout_parse_ctx *pctx)
1098
0
{
1099
0
  struct json_node  *json;
1100
0
  u_short      csum;
1101
0
  int      n = 0;
1102
1103
0
  while (isspace((u_char) *input))
1104
0
    input++;
1105
1106
0
  if (*input != '{') { /* sniffing version */
1107
0
    if (sscanf(input, "%hx,%n", &csum, &n) != 1 || n != 5) {
1108
0
      *pctx->cause = xstrdup("malformed layout header");
1109
0
      return (-1);
1110
0
    }
1111
0
    input += n;
1112
0
    if (csum != layout_checksum(input)) {
1113
0
      *pctx->cause = xstrdup("invalid layout checksum");
1114
0
      return (-1);
1115
0
    }
1116
0
    pctx->root = layout_construct_v1(NULL, &input, 0);
1117
0
    if (pctx->root == NULL) {
1118
0
      *pctx->cause = xstrdup("invalid layout");
1119
0
      return (-1);
1120
0
    }
1121
0
    if (*input != '\0') {
1122
0
      *pctx->cause = xstrdup("trailing data");
1123
0
      return (-1);
1124
0
    }
1125
0
    pctx->version = 1;
1126
0
  } else {
1127
0
    if ((json = json_parse(input, pctx->cause)) == NULL)
1128
0
      return (-1);
1129
1130
0
    if (layout_parse_json(json, pctx) != 0)
1131
0
      return (-1);
1132
1133
0
    if (pctx->version != 2) {
1134
0
      *pctx->cause = xstrdup("version mismatch");
1135
0
      return (-1);
1136
0
    }
1137
0
    if (pctx->num_active > 1) {
1138
0
      *pctx->cause = xstrdup("more than one active pane");
1139
0
      return (-1);
1140
0
    }
1141
0
    if (pctx->size == 0) {
1142
0
      *pctx->cause = xstrdup("no panes");
1143
0
      return (-1);
1144
0
    }
1145
0
    if (!layout_parse_ctx_check_indexes(pctx))
1146
0
      return (-1);
1147
0
  }
1148
1149
0
  return (0);
1150
0
}
1151
1152
/* Apply the remaining context to the layout. */
1153
static void
1154
layout_parse_apply_ctx(struct window *w, struct layout_parse_ctx *pctx)
1155
0
{
1156
0
  struct layout_parse_cell_ctx   *cctx;
1157
0
  struct window_pane     *wp, *wpnext;
1158
0
  int         i;
1159
1160
  /* Apply z-indexes. */
1161
0
  wp = TAILQ_FIRST(&w->z_index);
1162
0
  while (wp != NULL) {
1163
0
    wpnext = TAILQ_NEXT(wp, zentry);
1164
0
    if (window_pane_is_floating(wp))
1165
0
      TAILQ_REMOVE(&w->z_index, wp, zentry);
1166
0
    wp = wpnext;
1167
0
  }
1168
1169
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1170
0
      layout_parse_zindex_cmp);
1171
1172
0
  for (i = 0; i < pctx->size; i++) {
1173
0
    cctx = &pctx->cctxs[i];
1174
0
    wp = cctx->lc->wp;
1175
0
    if (window_pane_is_floating(wp))
1176
0
      TAILQ_INSERT_HEAD(&w->z_index, wp, zentry);
1177
0
  }
1178
1179
  /* Set the active pane. */
1180
0
  for (i = 0; i < pctx->size; i++) {
1181
0
    cctx = &pctx->cctxs[i];
1182
0
    if (cctx->active == 1) {
1183
0
      window_set_active_pane(w, cctx->lc->wp, 1);
1184
0
      break;
1185
0
    }
1186
0
  }
1187
1188
  /* Apply last panes. */
1189
0
  while (!TAILQ_EMPTY(&w->last_panes)) {
1190
0
    wp = TAILQ_FIRST(&w->last_panes);
1191
0
    window_pane_stack_remove(&w->last_panes, wp);
1192
0
  }
1193
1194
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1195
0
      layout_parse_last_cmp);
1196
1197
0
  for (i = 0; i < pctx->size; i++) {
1198
0
    cctx = &pctx->cctxs[i];
1199
0
    wp = cctx->lc->wp;
1200
0
    if (cctx->last < 0 || cctx->active == 1)
1201
0
      continue;
1202
0
    window_pane_stack_push(&w->last_panes, wp);
1203
0
  }
1204
0
}
1205
1206
/* Checks for duplicate pane indexes, z-indexes, and last indexes. */
1207
static int
1208
layout_parse_ctx_check_indexes(struct layout_parse_ctx *pctx)
1209
0
{
1210
0
  int i, n;
1211
1212
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1213
0
      layout_parse_index_cmp);
1214
1215
0
  for (i = 1; i < pctx->size; i++) {
1216
0
    if (pctx->cctxs[i].index == pctx->cctxs[i - 1].index) {
1217
0
      *pctx->cause = xstrdup("duplicate pane index");
1218
0
      return (0);
1219
0
    }
1220
0
  }
1221
1222
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1223
0
      layout_parse_zindex_cmp);
1224
1225
  /*
1226
   * Sorted in descending order, so the panes without a z-index come first
1227
   * and the floating panes run to the end.
1228
   */
1229
0
  n = 0;
1230
0
  while (n < pctx->size && pctx->cctxs[n].zindex == INT_MAX)
1231
0
    n++;
1232
0
  for (i = n + 1; i < pctx->size; i++) {
1233
0
    if (pctx->cctxs[i].zindex == pctx->cctxs[i - 1].zindex) {
1234
0
      *pctx->cause = xstrdup("duplicate pane z-index");
1235
0
      return (0);
1236
0
    }
1237
0
  }
1238
1239
0
  qsort(pctx->cctxs, pctx->size, sizeof pctx->cctxs[0],
1240
0
      layout_parse_last_cmp);
1241
1242
  /*
1243
   * Sorted in descending order, so the panes without a last index come
1244
   * last.
1245
   */
1246
0
  n = 0;
1247
0
  while (n < pctx->size && pctx->cctxs[n].last >= 0)
1248
0
    n++;
1249
0
  for (i = 1; i < n; i++) {
1250
0
    if (pctx->cctxs[i].last == pctx->cctxs[i - 1].last) {
1251
0
      *pctx->cause = xstrdup("duplicate last pane index");
1252
0
      return (0);
1253
0
    }
1254
0
  }
1255
1256
0
  return (1);
1257
0
}