Coverage Report

Created: 2026-10-28 06:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/resize.c
Line
Count
Source
1
/* $OpenBSD: resize.c,v 1.62 2026/10/16 07:59:06 nicm Exp $ */
2
3
/*
4
 * Copyright (c) 2007 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 <string.h>
22
23
#include "tmux.h"
24
25
static void
26
resize_fire_window_resized(struct window *w, u_int old_sx, u_int old_sy)
27
0
{
28
0
  struct event_payload  *ep;
29
0
  struct cmd_find_state  fs;
30
31
0
  ep = event_payload_create();
32
0
  cmd_find_from_window(&fs, w, 0);
33
0
  event_payload_set_target(ep, &fs);
34
0
  event_payload_set_window(ep, "window", w);
35
0
  event_payload_set_uint(ep, "width", w->sx);
36
0
  event_payload_set_uint(ep, "height", w->sy);
37
0
  event_payload_set_uint(ep, "old_width", old_sx);
38
0
  event_payload_set_uint(ep, "old_height", old_sy);
39
0
  events_fire("window-resized", ep);
40
0
}
41
42
void
43
resize_window(struct window *w, u_int sx, u_int sy, int xpixel, int ypixel)
44
0
{
45
0
  struct window_pane  *zwp;
46
0
  u_int      old_sx = w->sx, old_sy = w->sy;
47
48
  /* Check size limits. */
49
0
  if (sx < WINDOW_MINIMUM)
50
0
    sx = WINDOW_MINIMUM;
51
0
  if (sx > WINDOW_MAXIMUM)
52
0
    sx = WINDOW_MAXIMUM;
53
0
  if (sy < WINDOW_MINIMUM)
54
0
    sy = WINDOW_MINIMUM;
55
0
  if (sy > WINDOW_MAXIMUM)
56
0
    sy = WINDOW_MAXIMUM;
57
58
  /* If the window is zoomed, unzoom. */
59
0
  zwp = window_zoomed_pane(w);
60
0
  if (zwp != NULL)
61
0
    window_unzoom(w, 1);
62
63
  /* Resize the layout first. */
64
0
  layout_resize(w, sx, sy);
65
66
  /* Resize the window, it can be no smaller than the layout. */
67
0
  if (sx < w->layout_root->g.sx)
68
0
    sx = w->layout_root->g.sx;
69
0
  if (sy < w->layout_root->g.sy)
70
0
    sy = w->layout_root->g.sy;
71
0
  window_resize(w, sx, sy, xpixel, ypixel);
72
0
  log_debug("%s: @%u resized to %ux%u; layout %ux%u", __func__, w->id,
73
0
      sx, sy, w->layout_root->g.sx, w->layout_root->g.sy);
74
75
  /* Restore the window zoom state. */
76
0
  if (zwp != NULL && window_has_pane(w, zwp))
77
0
    window_zoom(zwp);
78
79
0
  tty_update_window_offset(w);
80
0
  server_redraw_window(w);
81
0
  events_fire_window("window-layout-changed", w);
82
0
  resize_fire_window_resized(w, old_sx, old_sy);
83
0
  w->flags &= ~WINDOW_RESIZE;
84
0
}
85
86
static int
87
ignore_client_size(struct client *c)
88
0
{
89
0
  struct client *loop;
90
91
0
  if (c->session == NULL)
92
0
    return (1);
93
0
  if (c->flags & CLIENT_NOSIZEFLAGS)
94
0
    return (1);
95
0
  if (c->flags & CLIENT_IGNORESIZE) {
96
    /*
97
     * Ignore flagged clients if there are any attached clients
98
     * that aren't flagged.
99
     */
100
0
    TAILQ_FOREACH (loop, &clients, entry) {
101
0
      if (loop->session == NULL)
102
0
        continue;
103
0
      if (loop->flags & CLIENT_NOSIZEFLAGS)
104
0
        continue;
105
0
      if (~loop->flags & CLIENT_IGNORESIZE)
106
0
        return (1);
107
0
    }
108
0
  }
109
0
  if ((c->flags & CLIENT_CONTROL) &&
110
0
      (~c->flags & CLIENT_SIZECHANGED) &&
111
0
      (~c->flags & CLIENT_WINDOWSIZECHANGED))
112
0
    return (1);
113
0
  return (0);
114
0
}
115
116
static u_int
117
clients_with_window(struct window *w)
118
0
{
119
0
  struct client *loop;
120
0
  u_int    n = 0;
121
122
0
  TAILQ_FOREACH(loop, &clients, entry) {
123
0
    if (ignore_client_size(loop) || !session_has(loop->session, w))
124
0
      continue;
125
0
    if (++n > 1)
126
0
      break;
127
0
  }
128
0
  return (n);
129
0
}
130
131
static int
132
clients_calculate_size(int type, int current, struct client *c,
133
    struct session *s, struct window *w, int (*skip_client)(struct client *,
134
    int, int, struct session *, struct window *), u_int *sx, u_int *sy,
135
    u_int *xpixel, u_int *ypixel)
136
0
{
137
0
  struct client *loop;
138
0
  u_int    cx, cy, n = 0;
139
140
  /*
141
   * Start comparing with 0 for largest and UINT_MAX for smallest or
142
   * latest.
143
   */
144
0
  if (type == WINDOW_SIZE_LARGEST) {
145
0
    *sx = 0;
146
0
    *sy = 0;
147
0
  } else if (w != NULL && type == WINDOW_SIZE_MANUAL) {
148
0
    *sx = w->manual_sx;
149
0
    *sy = w->manual_sy;
150
0
    log_debug("%s: manual size %ux%u", __func__, *sx, *sy);
151
0
  } else {
152
0
    *sx = UINT_MAX;
153
0
    *sy = UINT_MAX;
154
0
  }
155
0
  if (w != NULL && type == WINDOW_SIZE_MANUAL) {
156
0
    *xpixel = w->xpixel;
157
0
    *ypixel = w->ypixel;
158
0
  } else {
159
0
    *xpixel = 0;
160
0
    *ypixel = 0;
161
0
  }
162
163
  /*
164
   * For latest, count the number of clients with this window. We only
165
   * care if there is more than one.
166
   */
167
0
  if (type == WINDOW_SIZE_LATEST && w != NULL)
168
0
    n = clients_with_window(w);
169
170
  /* Skip setting the size if manual */
171
0
  if (type == WINDOW_SIZE_MANUAL)
172
0
    goto skip;
173
174
  /* Loop over the clients and work out the size. */
175
0
  TAILQ_FOREACH(loop, &clients, entry) {
176
0
    if (loop != c && ignore_client_size(loop)) {
177
0
      log_debug("%s: ignoring %s (1)", __func__, loop->name);
178
0
      continue;
179
0
    }
180
0
    if (loop != c && skip_client(loop, type, current, s, w)) {
181
0
      log_debug("%s: skipping %s (1)", __func__, loop->name);
182
0
      continue;
183
0
    }
184
185
    /*
186
     * If there are multiple clients attached, only accept the
187
     * latest client; otherwise let the only client be chosen as
188
     * for smallest.
189
     */
190
0
    if (type == WINDOW_SIZE_LATEST && n > 1 && loop != w->latest) {
191
0
      log_debug("%s: %s is not latest", __func__, loop->name);
192
0
      continue;
193
0
    }
194
195
    /* Work out this client's size. */
196
0
    if (w == NULL ||
197
0
        !control_get_window_size(loop, w->id, &cx, &cy) ||
198
0
        cx == 0 || cy == 0) {
199
0
      cx = loop->tty.sx - status_side_size(loop);
200
0
      cy = loop->tty.sy - status_line_size(loop);
201
0
    }
202
203
    /*
204
     * If it is larger or smaller than the best so far, update the
205
     * new size.
206
     */
207
0
    if (type == WINDOW_SIZE_LARGEST) {
208
0
      if (cx > *sx)
209
0
        *sx = cx;
210
0
      if (cy > *sy)
211
0
        *sy = cy;
212
0
    } else {
213
0
      if (cx < *sx)
214
0
        *sx = cx;
215
0
      if (cy < *sy)
216
0
        *sy = cy;
217
0
    }
218
0
    if (loop->tty.xpixel > *xpixel && loop->tty.ypixel > *ypixel) {
219
0
      *xpixel = loop->tty.xpixel;
220
0
      *ypixel = loop->tty.ypixel;
221
0
    }
222
0
    log_debug("%s: after %s (%ux%u), size is %ux%u", __func__,
223
0
        loop->name, cx, cy, *sx, *sy);
224
0
  }
225
0
  if (*sx != UINT_MAX && *sy != UINT_MAX)
226
0
    log_debug("%s: calculated size %ux%u", __func__, *sx, *sy);
227
0
  else
228
0
    log_debug("%s: no calculated size", __func__);
229
230
0
skip:
231
  /*
232
   * Do not allow any size to be larger than the per-client window size
233
   * if one exists.
234
   */
235
0
  if (w != NULL) {
236
0
    TAILQ_FOREACH(loop, &clients, entry) {
237
0
      if (loop != c && ignore_client_size(loop))
238
0
        continue;
239
0
      if (loop != c && skip_client(loop, type, current, s, w))
240
0
        continue;
241
242
      /* Look up per-window size if any. */
243
0
      if (~loop->flags & CLIENT_WINDOWSIZECHANGED)
244
0
        continue;
245
0
      if (!control_get_window_size(loop, w->id, &cx, &cy))
246
0
        continue;
247
248
      /* Clamp the size. */
249
0
      log_debug("%s: %s size for @%u is %ux%u", __func__,
250
0
          loop->name, w->id, cx, cy);
251
0
      if (cx != 0 && *sx > cx)
252
0
        *sx = cx;
253
0
      if (cy != 0 && *sy > cy)
254
0
        *sy = cy;
255
0
    }
256
0
  }
257
0
  if (*sx != UINT_MAX && *sy != UINT_MAX)
258
0
    log_debug("%s: calculated size %ux%u", __func__, *sx, *sy);
259
0
  else
260
0
    log_debug("%s: no calculated size", __func__);
261
262
  /* Return whether a suitable size was found. */
263
0
  if (type == WINDOW_SIZE_MANUAL) {
264
0
    log_debug("%s: type is manual", __func__);
265
0
    return (w != NULL);
266
0
  }
267
0
  if (type == WINDOW_SIZE_LARGEST) {
268
0
    log_debug("%s: type is largest", __func__);
269
0
    return (*sx != 0 && *sy != 0);
270
0
  }
271
0
  if (type == WINDOW_SIZE_LATEST)
272
0
    log_debug("%s: type is latest", __func__);
273
0
  else
274
0
    log_debug("%s: type is smallest", __func__);
275
0
  return (*sx != UINT_MAX && *sy != UINT_MAX);
276
0
}
277
278
static int
279
default_window_size_skip_client(struct client *loop, __unused int type,
280
    __unused int current, struct session *s, struct window *w)
281
0
{
282
0
  if (w != NULL && !session_has(loop->session, w))
283
0
    return (1);
284
0
  if (w == NULL && loop->session != s)
285
0
    return (1);
286
0
  return (0);
287
0
}
288
289
void
290
default_window_size(struct client *c, struct session *s, struct window *w,
291
  u_int *sx, u_int *sy, u_int *xpixel, u_int *ypixel, int type)
292
0
{
293
0
  const char  *value;
294
295
  /* Get type if not provided. */
296
0
  if (type == -1)
297
0
    type = options_get_number(global_w_options, "window-size");
298
299
  /*
300
   * Latest clients can use the given client if suitable. If there is no
301
   * client and no window, use the default size as for manual type.
302
   */
303
0
  if (type == WINDOW_SIZE_LATEST && c != NULL && !ignore_client_size(c)) {
304
0
    *sx = c->tty.sx - status_side_size(c);
305
0
    *sy = c->tty.sy - status_line_size(c);
306
0
    *xpixel = c->tty.xpixel;
307
0
    *ypixel = c->tty.ypixel;
308
0
    log_debug("%s: using %ux%u from %s", __func__, *sx, *sy,
309
0
        c->name);
310
0
    goto done;
311
0
  }
312
313
  /*
314
   * Ignore the given client if it is a control client - the creating
315
   * client should only affect the size if it is not a control client.
316
   */
317
0
  if (c != NULL && (c->flags & CLIENT_CONTROL))
318
0
    c = NULL;
319
320
  /*
321
   * Look for a client to base the size on. If none exists (or the type
322
   * is manual), use the default-size option.
323
   */
324
0
  if (!clients_calculate_size(type, 0, c, s, w,
325
0
      default_window_size_skip_client, sx, sy, xpixel, ypixel)) {
326
0
    value = options_get_string(s->options, "default-size");
327
0
    if (sscanf(value, "%ux%u", sx, sy) != 2) {
328
0
      *sx = 80;
329
0
      *sy = 24;
330
0
    }
331
0
    log_debug("%s: using %ux%u from default-size", __func__, *sx,
332
0
        *sy);
333
0
  }
334
335
0
done:
336
  /* Make sure the limits are enforced. */
337
0
  if (*sx < WINDOW_MINIMUM)
338
0
    *sx = WINDOW_MINIMUM;
339
0
  if (*sx > WINDOW_MAXIMUM)
340
0
    *sx = WINDOW_MAXIMUM;
341
0
  if (*sy < WINDOW_MINIMUM)
342
0
    *sy = WINDOW_MINIMUM;
343
0
  if (*sy > WINDOW_MAXIMUM)
344
0
    *sy = WINDOW_MAXIMUM;
345
0
  log_debug("%s: resulting size is %ux%u", __func__, *sx, *sy);
346
0
}
347
348
static int
349
recalculate_size_skip_client(struct client *loop, __unused int type,
350
    int current, __unused struct session *s, struct window *w)
351
0
{
352
  /*
353
   * If the current flag is set, then skip any client where this window
354
   * is not the current window - this is used for aggressive-resize.
355
   * Otherwise skip any session that doesn't contain the window.
356
   */
357
0
  if (loop->session->curw == NULL)
358
0
    return (1);
359
0
  if (current)
360
0
    return (loop->session->curw->window != w);
361
0
  return (session_has(loop->session, w) == 0);
362
0
}
363
364
void
365
recalculate_size(struct window *w, int now)
366
0
{
367
0
  u_int sx, sy, xpixel = 0, ypixel = 0;
368
0
  int type, current, changed;
369
370
  /*
371
   * Do not attempt to resize windows which have no pane, they must be on
372
   * the way to destruction.
373
   */
374
0
  if (w->active == NULL)
375
0
    return;
376
0
  log_debug("%s: @%u is %ux%u", __func__, w->id, w->sx, w->sy);
377
378
  /*
379
   * Type is manual, smallest, largest, latest. Current is the
380
   * aggressive-resize option (do not resize based on clients where the
381
   * window is not the current window).
382
   */
383
0
  type = options_get_number(w->options, "window-size");
384
0
  current = options_get_number(w->options, "aggressive-resize");
385
386
  /* Look for a suitable client and get the new size. */
387
0
  changed = clients_calculate_size(type, current, NULL, NULL, w,
388
0
      recalculate_size_skip_client, &sx, &sy, &xpixel, &ypixel);
389
0
  if (xpixel == 0)
390
0
    xpixel = DEFAULT_XPIXEL;
391
0
  if (ypixel == 0)
392
0
    ypixel = DEFAULT_YPIXEL;
393
394
  /*
395
   * Make sure the size has actually changed. If the window has already
396
   * got a resize scheduled, then use the new size; otherwise the old.
397
   */
398
0
  if (w->flags & WINDOW_RESIZE) {
399
0
    if (!now &&
400
0
        changed &&
401
0
        w->new_sx == sx &&
402
0
        w->new_sy == sy &&
403
0
        w->new_xpixel == xpixel &&
404
0
        w->new_ypixel == ypixel)
405
0
      changed = 0;
406
0
  } else {
407
0
    if (!now &&
408
0
        changed &&
409
0
        w->sx == sx &&
410
0
        w->sy == sy &&
411
0
        w->xpixel == xpixel &&
412
0
        w->ypixel == ypixel)
413
0
      changed = 0;
414
0
  }
415
416
  /*
417
   * If the size hasn't changed, update the window offset but not the
418
   * size.
419
   */
420
0
  if (!changed) {
421
0
    log_debug("%s: @%u no size change", __func__, w->id);
422
0
    tty_update_window_offset(w);
423
0
    return;
424
0
  }
425
426
  /*
427
   * If the now flag is set or if the window is sized manually, change
428
   * the size immediately. Otherwise set the flag and it will be done
429
   * later.
430
   */
431
0
  log_debug("%s: @%u new size %ux%u", __func__, w->id, sx, sy);
432
0
  if (now || type == WINDOW_SIZE_MANUAL)
433
0
    resize_window(w, sx, sy, xpixel, ypixel);
434
0
  else {
435
0
    w->new_sx = sx;
436
0
    w->new_sy = sy;
437
0
    w->new_xpixel = xpixel;
438
0
    w->new_ypixel = ypixel;
439
440
0
    w->flags |= WINDOW_RESIZE;
441
0
    tty_update_window_offset(w);
442
0
  }
443
0
}
444
445
void
446
recalculate_sizes(void)
447
0
{
448
0
  recalculate_sizes_now(0);
449
0
}
450
451
void
452
recalculate_sizes_now(int now)
453
0
{
454
0
  struct session  *s;
455
0
  struct client *c;
456
0
  struct window *w;
457
458
  /*
459
   * Clear attached count and update saved status line information for
460
   * each session.
461
   */
462
0
  RB_FOREACH(s, sessions, &sessions) {
463
0
    s->attached = 0;
464
0
    status_update_cache(s);
465
0
  }
466
467
  /*
468
   * Increment attached count and check the status line size for each
469
   * client.
470
   */
471
0
  TAILQ_FOREACH(c, &clients, entry) {
472
0
    s = c->session;
473
0
    if (s != NULL && !(c->flags & CLIENT_UNATTACHEDFLAGS))
474
0
      s->attached++;
475
0
    if (s == NULL)
476
0
      continue;
477
0
    if (c->tty.sy <= s->statuslines || (c->flags & CLIENT_CONTROL))
478
0
      c->flags |= CLIENT_STATUSOFF;
479
0
    else
480
0
      c->flags &= ~CLIENT_STATUSOFF;
481
0
    if (s->sidestatusat != -1 && c->tty.sx <= s->sidestatuswidth)
482
0
      c->flags |= CLIENT_SIDESTATUSOFF;
483
0
    else
484
0
      c->flags &= ~CLIENT_SIDESTATUSOFF;
485
0
  }
486
487
  /* Walk each window and adjust the size. */
488
0
  RB_FOREACH(w, windows, &windows)
489
0
    recalculate_size(w, now);
490
0
}