Coverage Report

Created: 2025-08-24 07:01

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