Coverage Report

Created: 2026-09-03 06:29

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