Coverage Report

Created: 2026-10-28 06:08

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