Coverage Report

Created: 2026-08-13 06:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/screen.c
Line
Count
Source
1
/* $OpenBSD: screen.c,v 1.107 2026/07/26 09:20:54 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 <stdlib.h>
22
#include <string.h>
23
#include <unistd.h>
24
25
#include "tmux.h"
26
27
/* Selected area in screen. */
28
struct screen_sel {
29
  int    hidden;
30
  int    rectangle;
31
  int    modekeys;
32
33
  u_int    sx;
34
  u_int    sy;
35
36
  u_int    ex;
37
  u_int    ey;
38
39
  u_int    clipx;
40
41
  struct grid_cell cell;
42
};
43
44
/* Entry on title stack. */
45
struct screen_title_entry {
46
  char        *text;
47
48
  TAILQ_ENTRY(screen_title_entry)  entry;
49
};
50
TAILQ_HEAD(screen_titles, screen_title_entry);
51
52
static void screen_resize_y(struct screen *, u_int, int, u_int *);
53
static void screen_reflow(struct screen *, u_int, u_int *, u_int *, int);
54
55
/* Free titles stack. */
56
static void
57
screen_free_titles(struct screen *s)
58
45.6k
{
59
45.6k
  struct screen_title_entry *title_entry;
60
61
45.6k
  if (s->titles == NULL)
62
45.6k
    return;
63
64
273
  while ((title_entry = TAILQ_FIRST(s->titles)) != NULL) {
65
221
    TAILQ_REMOVE(s->titles, title_entry, entry);
66
221
    free(title_entry->text);
67
221
    free(title_entry);
68
221
  }
69
70
52
  free(s->titles);
71
52
  s->titles = NULL;
72
52
  s->ntitles = 0;
73
52
}
74
75
/* Create a new screen. */
76
void
77
screen_init(struct screen *s, u_int sx, u_int sy, u_int hlimit)
78
22.8k
{
79
22.8k
  s->grid = grid_create(sx, sy, hlimit);
80
22.8k
  s->saved_grid = NULL;
81
82
22.8k
  s->title = xstrdup("");
83
22.8k
  s->titles = NULL;
84
22.8k
  s->ntitles = 0;
85
22.8k
  s->path = NULL;
86
87
22.8k
  s->cstyle = SCREEN_CURSOR_DEFAULT;
88
22.8k
  s->default_cstyle = SCREEN_CURSOR_DEFAULT;
89
22.8k
  s->mode = MODE_CURSOR;
90
22.8k
  s->default_mode = 0;
91
22.8k
  s->ccolour = -1;
92
22.8k
  s->default_ccolour = -1;
93
22.8k
  s->tabs = NULL;
94
22.8k
  s->sel = NULL;
95
96
#ifdef ENABLE_SIXEL
97
  TAILQ_INIT(&s->images);
98
  TAILQ_INIT(&s->saved_images);
99
#endif
100
101
22.8k
  s->write_list = NULL;
102
22.8k
  s->hyperlinks = NULL;
103
104
22.8k
  screen_reinit(s, 1);
105
22.8k
}
106
107
/* Reinitialise screen. */
108
void
109
screen_reinit(struct screen *s, int check)
110
22.8k
{
111
22.8k
  s->cx = 0;
112
22.8k
  s->cy = 0;
113
114
22.8k
  s->rupper = 0;
115
22.8k
  s->rlower = screen_size_y(s) - 1;
116
117
22.8k
  s->mode = MODE_CURSOR|MODE_WRAP|(s->mode & MODE_CRLF);
118
119
22.8k
  if (options_get_number(global_options, "extended-keys") == 2)
120
0
    s->mode = (s->mode & ~EXTENDED_KEY_MODES)|MODE_KEYS_EXTENDED;
121
122
22.8k
  if (SCREEN_IS_ALTERNATE(s))
123
0
    screen_alternate_off(s, NULL, 0);
124
22.8k
  s->saved_cx = UINT_MAX;
125
22.8k
  s->saved_cy = UINT_MAX;
126
127
22.8k
  screen_reset_tabs(s);
128
22.8k
  if (check)
129
22.8k
    grid_check_is_clear(s->grid);
130
22.8k
  grid_clear_lines(s->grid, s->grid->hsize, s->grid->sy, 8);
131
132
22.8k
  screen_clear_selection(s);
133
22.8k
  screen_free_titles(s);
134
135
#ifdef ENABLE_SIXEL
136
  image_free_all(s);
137
#endif
138
139
22.8k
  screen_set_progress_bar(s, PROGRESS_BAR_HIDDEN, 0);
140
22.8k
  screen_reset_hyperlinks(s);
141
22.8k
}
142
143
/* Reset hyperlinks of a screen. */
144
void
145
screen_reset_hyperlinks(struct screen *s)
146
22.8k
{
147
22.8k
  if (s->hyperlinks == NULL)
148
22.8k
    s->hyperlinks = hyperlinks_init();
149
0
  else
150
0
    hyperlinks_reset(s->hyperlinks);
151
22.8k
}
152
153
/* Destroy a screen. */
154
void
155
screen_free(struct screen *s)
156
22.8k
{
157
#ifdef ENABLE_SIXEL
158
  struct image  *im;
159
#endif
160
161
22.8k
  free(s->sel);
162
22.8k
  free(s->tabs);
163
22.8k
  free(s->path);
164
22.8k
  free(s->title);
165
166
22.8k
  if (s->write_list != NULL)
167
11.4k
    screen_write_free_list(s);
168
169
22.8k
  if (SCREEN_IS_ALTERNATE(s))
170
72
    grid_destroy(s->saved_grid);
171
22.8k
  grid_destroy(s->grid);
172
173
22.8k
  if (s->hyperlinks != NULL)
174
22.8k
    hyperlinks_free(s->hyperlinks);
175
22.8k
  screen_free_titles(s);
176
177
#ifdef ENABLE_SIXEL
178
  /*
179
   * Images saved when entering the alternate screen stay linked in the
180
   * global list; move them back so they are freed and unlinked here, or
181
   * a later eviction would write through this freed screen.
182
   */
183
  TAILQ_CONCAT(&s->images, &s->saved_images, entry);
184
  TAILQ_FOREACH(im, &s->images, entry)
185
    im->list = &s->images;
186
  image_free_all(s);
187
#endif
188
22.8k
}
189
190
/* Reset tabs to default, eight spaces apart. */
191
void
192
screen_reset_tabs(struct screen *s)
193
24.0k
{
194
24.0k
  u_int i;
195
196
24.0k
  free(s->tabs);
197
198
24.0k
  if ((s->tabs = bit_alloc(screen_size_x(s))) == NULL)
199
0
    fatal("bit_alloc failed");
200
137k
  for (i = 8; i < screen_size_x(s); i += 8)
201
113k
    bit_set(s->tabs, i);
202
24.0k
}
203
204
/* Set default cursor style and colour from options. */
205
void
206
screen_set_default_cursor(struct screen *s, struct options *oo)
207
11.4k
{
208
11.4k
  struct grid_cell  gc;
209
11.4k
  int     c;
210
211
11.4k
  style_apply(&gc, oo, "cursor-colour", NULL);
212
11.4k
  s->default_ccolour = gc.fg;
213
214
11.4k
  c = options_get_number(oo, "cursor-style");
215
11.4k
  s->default_mode = 0;
216
11.4k
  screen_set_cursor_style(c, &s->default_cstyle, &s->default_mode);
217
11.4k
}
218
219
/* Set screen cursor style and mode. */
220
void
221
screen_set_cursor_style(u_int style, enum screen_cursor_style *cstyle,
222
    int *mode)
223
12.2k
{
224
12.2k
  switch (style) {
225
11.6k
  case 0:
226
11.6k
    *cstyle = SCREEN_CURSOR_DEFAULT;
227
11.6k
    break;
228
68
  case 1:
229
68
    *cstyle = SCREEN_CURSOR_BLOCK;
230
68
    *mode |= MODE_CURSOR_BLINKING;
231
68
    break;
232
76
  case 2:
233
76
    *cstyle = SCREEN_CURSOR_BLOCK;
234
76
    *mode &= ~MODE_CURSOR_BLINKING;
235
76
    break;
236
70
  case 3:
237
70
    *cstyle = SCREEN_CURSOR_UNDERLINE;
238
70
    *mode |= MODE_CURSOR_BLINKING;
239
70
    break;
240
70
  case 4:
241
70
    *cstyle = SCREEN_CURSOR_UNDERLINE;
242
70
    *mode &= ~MODE_CURSOR_BLINKING;
243
70
    break;
244
70
  case 5:
245
70
    *cstyle = SCREEN_CURSOR_BAR;
246
70
    *mode |= MODE_CURSOR_BLINKING;
247
70
    break;
248
71
  case 6:
249
71
    *cstyle = SCREEN_CURSOR_BAR;
250
71
    *mode &= ~MODE_CURSOR_BLINKING;
251
71
    break;
252
12.2k
  }
253
12.2k
}
254
255
/* Set screen cursor colour. */
256
void
257
screen_set_cursor_colour(struct screen *s, int colour)
258
185
{
259
185
  s->ccolour = colour;
260
185
}
261
262
/* Set screen title. */
263
int
264
screen_set_title(struct screen *s, const char *title, int untrusted)
265
14.3k
{
266
14.3k
  char  *new_title;
267
268
14.3k
  new_title = clean_name(title, untrusted);
269
14.3k
  if (new_title == NULL)
270
319
    return (0);
271
14.0k
  free(s->title);
272
14.0k
  s->title = new_title;
273
14.0k
  return (1);
274
14.3k
}
275
276
/* Set screen path. */
277
int
278
screen_set_path(struct screen *s, const char *path, int untrusted)
279
269
{
280
269
  char  *new_path;
281
282
269
  new_path = clean_name(path, untrusted);
283
269
  if (new_path == NULL)
284
0
    return (0);
285
269
  free(s->path);
286
269
  s->path = new_path;
287
269
  return (1);
288
269
}
289
290
/* Push the current title onto the stack. */
291
void
292
screen_push_title(struct screen *s)
293
551
{
294
551
  struct screen_title_entry *title_entry;
295
296
551
  log_debug("%s: %u", __func__, s->ntitles);
297
298
703
  while (s->ntitles >= 10) {
299
152
    title_entry = TAILQ_LAST(s->titles, screen_titles);
300
152
    free(title_entry->text);
301
152
    TAILQ_REMOVE(s->titles, title_entry, entry);
302
152
    free(title_entry);
303
152
    s->ntitles--;
304
152
  }
305
306
551
  if (s->titles == NULL) {
307
52
    s->titles = xmalloc(sizeof *s->titles);
308
52
    TAILQ_INIT(s->titles);
309
52
  }
310
551
  title_entry = xmalloc(sizeof *title_entry);
311
551
  title_entry->text = xstrdup(s->title);
312
551
  TAILQ_INSERT_HEAD(s->titles, title_entry, entry);
313
551
  s->ntitles++;
314
551
}
315
316
/*
317
 * Pop a title from the stack and set it as the screen title. If the stack is
318
 * empty, do nothing.
319
 */
320
void
321
screen_pop_title(struct screen *s)
322
344
{
323
344
  struct screen_title_entry *title_entry;
324
325
344
  if (s->titles == NULL)
326
100
    return;
327
244
  log_debug("%s: %u", __func__, s->ntitles);
328
329
244
  title_entry = TAILQ_FIRST(s->titles);
330
244
  if (title_entry != NULL) {
331
178
    free(s->title);
332
178
    s->title = title_entry->text;
333
178
    TAILQ_REMOVE(s->titles, title_entry, entry);
334
178
    free(title_entry);
335
178
    s->ntitles--;
336
178
  }
337
244
}
338
339
/*
340
 * Set the progress bar state and progress. The progress will not be updated
341
 * if p is negative.
342
 */
343
void
344
screen_set_progress_bar(struct screen *s, enum progress_bar_state pbs, int p)
345
23.1k
{
346
23.1k
  s->progress_bar.state = pbs;
347
23.1k
  if (p >= 0 && pbs != PROGRESS_BAR_INDETERMINATE)
348
22.9k
    s->progress_bar.progress = p;
349
23.1k
}
350
351
352
/* Resize screen with options. */
353
void
354
screen_resize_cursor(struct screen *s, u_int sx, u_int sy, int reflow,
355
    int eat_empty, int cursor)
356
636
{
357
636
  u_int cx = s->cx, cy = s->grid->hsize + s->cy;
358
359
636
  if (s->write_list != NULL)
360
636
    screen_write_free_list(s);
361
362
636
  log_debug("%s: new size %ux%u, now %ux%u (cursor %u,%u = %u,%u)",
363
636
      __func__, sx, sy, screen_size_x(s), screen_size_y(s), s->cx, s->cy,
364
636
      cx, cy);
365
366
636
  if (sx < 1)
367
0
    sx = 1;
368
636
  if (sy < 1)
369
0
    sy = 1;
370
371
636
  if (sx != screen_size_x(s)) {
372
0
    s->grid->sx = sx;
373
0
    screen_reset_tabs(s);
374
0
  } else
375
636
    reflow = 0;
376
377
636
  if (sy != screen_size_y(s))
378
0
    screen_resize_y(s, sy, eat_empty, &cy);
379
380
#ifdef ENABLE_SIXEL
381
  image_free_all(s);
382
#endif
383
384
636
  if (reflow)
385
0
    screen_reflow(s, sx, &cx, &cy, cursor);
386
387
636
  if (cy >= s->grid->hsize) {
388
636
    s->cx = cx;
389
636
    s->cy = cy - s->grid->hsize;
390
636
  } else {
391
0
    s->cx = 0;
392
0
    s->cy = 0;
393
0
  }
394
395
636
  log_debug("%s: cursor finished at %u,%u = %u,%u", __func__, s->cx,
396
636
      s->cy, cx, cy);
397
398
636
  if (s->write_list != NULL)
399
636
    screen_write_make_list(s);
400
636
}
401
402
/* Resize screen. */
403
void
404
screen_resize(struct screen *s, u_int sx, u_int sy, int reflow)
405
636
{
406
636
  screen_resize_cursor(s, sx, sy, reflow, 1, 1);
407
636
}
408
409
static void
410
screen_resize_y(struct screen *s, u_int sy, int eat_empty, u_int *cy)
411
0
{
412
0
  struct grid *gd = s->grid;
413
0
  u_int    needed, available, oldy, i;
414
415
0
  if (sy == 0)
416
0
    fatalx("zero size");
417
0
  oldy = screen_size_y(s);
418
419
  /*
420
   * When resizing:
421
   *
422
   * If the height is decreasing, delete lines from the bottom until
423
   * hitting the cursor, then push lines from the top into the history.
424
   *
425
   * When increasing, pull as many lines as possible from scrolled
426
   * history (not explicitly cleared from view) to the top, then fill the
427
   * remaining with blanks at the bottom.
428
   */
429
430
  /* Size decreasing. */
431
0
  if (sy < oldy) {
432
0
    needed = oldy - sy;
433
434
    /* Delete as many lines as possible from the bottom. */
435
0
    if (eat_empty) {
436
0
      available = oldy - 1 - s->cy;
437
0
      if (available > 0) {
438
0
        if (available > needed)
439
0
          available = needed;
440
0
        grid_view_delete_lines(gd, oldy - available,
441
0
            available, 8);
442
0
      }
443
0
      needed -= available;
444
0
    }
445
446
    /*
447
     * Now just increase the history size, if possible, to take
448
     * over the lines which are left. If history is off, delete
449
     * lines from the top.
450
     */
451
0
    available = s->cy;
452
0
    if (gd->flags & GRID_HISTORY) {
453
0
      gd->hscrolled += needed;
454
0
      gd->hsize += needed;
455
0
    } else if (needed > 0 && available > 0) {
456
0
      if (available > needed)
457
0
        available = needed;
458
0
      grid_view_delete_lines(gd, 0, available, 8);
459
0
      (*cy) -= available;
460
0
    }
461
0
  }
462
463
  /* Resize line array. */
464
0
  grid_adjust_lines(gd, gd->hsize + sy);
465
466
  /* Size increasing. */
467
0
  if (sy > oldy) {
468
0
    needed = sy - oldy;
469
470
    /*
471
     * Try to pull as much as possible out of scrolled history, if
472
     * it is enabled.
473
     */
474
0
    available = gd->hscrolled;
475
0
    if (gd->flags & GRID_HISTORY && available > 0) {
476
0
      if (available > needed)
477
0
        available = needed;
478
0
      gd->hscrolled -= available;
479
0
      gd->hsize -= available;
480
0
    } else
481
0
      available = 0;
482
0
    needed -= available;
483
484
    /* Then fill the rest in with blanks. */
485
0
    for (i = gd->hsize + sy - needed; i < gd->hsize + sy; i++)
486
0
      grid_empty_line(gd, i, 8);
487
0
  }
488
489
  /* Set the new size, and reset the scroll region. */
490
0
  gd->sy = sy;
491
0
  s->rupper = 0;
492
0
  s->rlower = screen_size_y(s) - 1;
493
0
}
494
495
/* Set selection. */
496
void
497
screen_set_selection(struct screen *s, u_int sx, u_int sy,
498
    u_int ex, u_int ey, u_int rectangle, u_int clipx, int modekeys,
499
    struct grid_cell *gc)
500
0
{
501
0
  if (s->sel == NULL)
502
0
    s->sel = xcalloc(1, sizeof *s->sel);
503
504
0
  memcpy(&s->sel->cell, gc, sizeof s->sel->cell);
505
0
  s->sel->hidden = 0;
506
0
  s->sel->rectangle = rectangle;
507
0
  s->sel->modekeys = modekeys;
508
509
0
  s->sel->sx = sx;
510
0
  s->sel->sy = sy;
511
0
  s->sel->ex = ex;
512
0
  s->sel->ey = ey;
513
0
  s->sel->clipx = clipx;
514
0
}
515
516
/* Clear selection. */
517
void
518
screen_clear_selection(struct screen *s)
519
22.8k
{
520
22.8k
  free(s->sel);
521
22.8k
  s->sel = NULL;
522
22.8k
}
523
524
/* Hide selection. */
525
void
526
screen_hide_selection(struct screen *s)
527
0
{
528
0
  if (s->sel != NULL)
529
0
    s->sel->hidden = 1;
530
0
}
531
532
/* Check if cell in selection. */
533
int
534
screen_check_selection(struct screen *s, u_int px, u_int py)
535
41.6k
{
536
41.6k
  struct screen_sel *sel = s->sel;
537
41.6k
  u_int      xx;
538
539
41.6k
  if (sel == NULL || sel->hidden)
540
41.6k
    return (0);
541
0
  if (px < sel->clipx)
542
0
    return (0);
543
544
0
  if (sel->rectangle) {
545
0
    if (sel->sy < sel->ey) {
546
      /* start line < end line -- downward selection. */
547
0
      if (py < sel->sy || py > sel->ey)
548
0
        return (0);
549
0
    } else if (sel->sy > sel->ey) {
550
      /* start line > end line -- upward selection. */
551
0
      if (py > sel->sy || py < sel->ey)
552
0
        return (0);
553
0
    } else {
554
      /* starting line == ending line. */
555
0
      if (py != sel->sy)
556
0
        return (0);
557
0
    }
558
559
    /*
560
     * Need to include the selection start row, but not the cursor
561
     * row, which means the selection changes depending on which
562
     * one is on the left.
563
     */
564
0
    if (sel->ex < sel->sx) {
565
      /* Cursor (ex) is on the left. */
566
0
      if (px < sel->ex)
567
0
        return (0);
568
569
0
      if (px > sel->sx)
570
0
        return (0);
571
0
    } else {
572
      /* Selection start (sx) is on the left. */
573
0
      if (px < sel->sx)
574
0
        return (0);
575
576
0
      if (px > sel->ex)
577
0
        return (0);
578
0
    }
579
0
  } else {
580
    /*
581
     * Like emacs, keep the top-left-most character, and drop the
582
     * bottom-right-most, regardless of copy direction.
583
     */
584
0
    if (sel->sy < sel->ey) {
585
      /* starting line < ending line -- downward selection. */
586
0
      if (py < sel->sy || py > sel->ey)
587
0
        return (0);
588
589
0
      if (py == sel->sy && px < sel->sx)
590
0
        return (0);
591
592
0
      if (sel->modekeys == MODEKEY_EMACS)
593
0
        xx = (sel->ex == 0 ? 0 : sel->ex - 1);
594
0
      else
595
0
        xx = sel->ex;
596
0
      if (py == sel->ey && px > xx)
597
0
        return (0);
598
0
    } else if (sel->sy > sel->ey) {
599
      /* starting line > ending line -- upward selection. */
600
0
      if (py > sel->sy || py < sel->ey)
601
0
        return (0);
602
603
0
      if (py == sel->ey && px < sel->ex)
604
0
        return (0);
605
606
0
      if (sel->modekeys == MODEKEY_EMACS)
607
0
        xx = sel->sx - 1;
608
0
      else
609
0
        xx = sel->sx;
610
0
      if (py == sel->sy && (sel->sx == 0 || px > xx))
611
0
        return (0);
612
0
    } else {
613
      /* starting line == ending line. */
614
0
      if (py != sel->sy)
615
0
        return (0);
616
617
0
      if (sel->ex < sel->sx) {
618
        /* cursor (ex) is on the left */
619
0
        if (sel->modekeys == MODEKEY_EMACS)
620
0
          xx = sel->sx - 1;
621
0
        else
622
0
          xx = sel->sx;
623
0
        if (px > xx || px < sel->ex)
624
0
          return (0);
625
0
      } else {
626
        /* selection start (sx) is on the left */
627
0
        if (sel->modekeys == MODEKEY_EMACS)
628
0
          xx = (sel->ex == 0 ? 0 : sel->ex - 1);
629
0
        else
630
0
          xx = sel->ex;
631
0
        if (px < sel->sx || px > xx)
632
0
          return (0);
633
0
      }
634
0
    }
635
0
  }
636
637
0
  return (1);
638
0
}
639
640
/* Get selected grid cell. */
641
int
642
screen_select_cell(struct screen *s, struct grid_cell *dst,
643
    const struct grid_cell *src)
644
0
{
645
0
  if (s->sel == NULL || s->sel->hidden)
646
0
    return (0);
647
648
0
  memcpy(dst, &s->sel->cell, sizeof *dst);
649
0
  if (COLOUR_DEFAULT(dst->fg))
650
0
    dst->fg = src->fg;
651
0
  if (COLOUR_DEFAULT(dst->bg))
652
0
    dst->bg = src->bg;
653
0
  utf8_copy(&dst->data, &src->data);
654
0
  dst->flags = src->flags;
655
656
0
  if (dst->attr & GRID_ATTR_NOATTR)
657
0
    dst->attr |= (src->attr & GRID_ATTR_CHARSET);
658
0
  else
659
0
    dst->attr |= src->attr;
660
0
  return (1);
661
0
}
662
663
/* Reflow wrapped lines. */
664
static void
665
screen_reflow(struct screen *s, u_int new_x, u_int *cx, u_int *cy, int cursor)
666
0
{
667
0
  u_int wx, wy;
668
669
0
  if (cursor) {
670
0
    grid_wrap_position(s->grid, *cx, *cy, &wx, &wy);
671
0
    log_debug("%s: cursor %u,%u is %u,%u", __func__, *cx, *cy, wx,
672
0
        wy);
673
0
  }
674
675
0
  grid_reflow(s->grid, new_x);
676
677
0
  if (cursor) {
678
0
    grid_unwrap_position(s->grid, cx, cy, wx, wy);
679
0
    log_debug("%s: new cursor is %u,%u", __func__, *cx, *cy);
680
0
  }
681
0
  else {
682
0
    *cx = 0;
683
0
    *cy = s->grid->hsize;
684
0
  }
685
0
}
686
687
/*
688
 * Enter alternative screen mode. A copy of the visible screen is saved and the
689
 * history is not updated.
690
 */
691
int
692
screen_alternate_on(struct screen *s, struct grid_cell *gc, int cursor)
693
755
{
694
755
  u_int    sx, sy;
695
#ifdef ENABLE_SIXEL
696
  struct image  *im;
697
#endif
698
699
755
  if (SCREEN_IS_ALTERNATE(s))
700
365
    return 0;
701
390
  sx = screen_size_x(s);
702
390
  sy = screen_size_y(s);
703
704
390
  s->saved_grid = grid_create(sx, sy, 0);
705
390
  grid_duplicate_lines(s->saved_grid, 0, s->grid, screen_hsize(s), sy);
706
390
  if (cursor) {
707
94
    s->saved_cx = s->cx;
708
94
    s->saved_cy = s->cy;
709
94
  }
710
390
  memcpy(&s->saved_cell, gc, sizeof s->saved_cell);
711
712
#ifdef ENABLE_SIXEL
713
  TAILQ_CONCAT(&s->saved_images, &s->images, entry);
714
  TAILQ_FOREACH(im, &s->saved_images, entry)
715
      im->list = &s->saved_images;
716
#endif
717
718
390
  grid_view_clear(s->grid, 0, 0, sx, sy, 8);
719
720
390
  s->saved_flags = s->grid->flags;
721
390
  s->grid->flags &= ~GRID_HISTORY;
722
723
390
  return 1;
724
755
}
725
726
/* Exit alternate screen mode and restore the copied grid. */
727
int
728
screen_alternate_off(struct screen *s, struct grid_cell *gc, int cursor)
729
788
{
730
788
  u_int    sx = screen_size_x(s), sy = screen_size_y(s);
731
#ifdef ENABLE_SIXEL
732
  struct image  *im;
733
#endif
734
735
  /*
736
   * If the current size is different, temporarily resize to the old size
737
   * before copying back.
738
   */
739
788
  if (SCREEN_IS_ALTERNATE(s))
740
318
    screen_resize(s, s->saved_grid->sx, s->saved_grid->sy, 0);
741
742
  /*
743
   * Restore the cursor position and cell. This happens even if not
744
   * currently in the alternate screen.
745
   */
746
788
  if (cursor && s->saved_cx != UINT_MAX && s->saved_cy != UINT_MAX) {
747
134
    s->cx = s->saved_cx;
748
134
    s->cy = s->saved_cy;
749
134
    if (gc != NULL)
750
134
      memcpy(gc, &s->saved_cell, sizeof *gc);
751
134
  }
752
753
  /* If not in the alternate screen, do nothing more. */
754
788
  if (!SCREEN_IS_ALTERNATE(s)) {
755
470
    if (s->cx > screen_size_x(s) - 1)
756
66
      s->cx = screen_size_x(s) - 1;
757
470
    if (s->cy > screen_size_y(s) - 1)
758
0
      s->cy = screen_size_y(s) - 1;
759
470
    return 0;
760
470
  }
761
762
  /* Restore the saved grid. */
763
318
  grid_duplicate_lines(s->grid, screen_hsize(s), s->saved_grid, 0,
764
318
      s->saved_grid->sy);
765
766
  /*
767
   * Turn history back on (so resize can use it) and then resize back to
768
   * the current size.
769
   */
770
318
  if (s->saved_flags & GRID_HISTORY)
771
0
    s->grid->flags |= GRID_HISTORY;
772
318
  screen_resize(s, sx, sy, 1);
773
774
318
  grid_destroy(s->saved_grid);
775
318
  s->saved_grid = NULL;
776
777
#ifdef ENABLE_SIXEL
778
  image_free_all(s);
779
  TAILQ_CONCAT(&s->images, &s->saved_images, entry);
780
  TAILQ_FOREACH(im, &s->images, entry)
781
      im->list = &s->images;
782
#endif
783
784
318
  if (s->cx > screen_size_x(s) - 1)
785
69
    s->cx = screen_size_x(s) - 1;
786
318
  if (s->cy > screen_size_y(s) - 1)
787
0
    s->cy = screen_size_y(s) - 1;
788
789
318
  return 1;
790
788
}
791
792
/* Get mode as a string. */
793
const char *
794
screen_mode_to_string(int mode)
795
0
{
796
0
  static char tmp[1024];
797
798
0
  if (mode == 0)
799
0
    return ("NONE");
800
0
  if (mode == ALL_MODES)
801
0
    return ("ALL");
802
803
0
  *tmp = '\0';
804
0
  if (mode & MODE_CURSOR)
805
0
    strlcat(tmp, "CURSOR,", sizeof tmp);
806
0
  if (mode & MODE_INSERT)
807
0
    strlcat(tmp, "INSERT,", sizeof tmp);
808
0
  if (mode & MODE_KCURSOR)
809
0
    strlcat(tmp, "KCURSOR,", sizeof tmp);
810
0
  if (mode & MODE_KKEYPAD)
811
0
    strlcat(tmp, "KKEYPAD,", sizeof tmp);
812
0
  if (mode & MODE_WRAP)
813
0
    strlcat(tmp, "WRAP,", sizeof tmp);
814
0
  if (mode & MODE_MOUSE_STANDARD)
815
0
    strlcat(tmp, "MOUSE_STANDARD,", sizeof tmp);
816
0
  if (mode & MODE_MOUSE_BUTTON)
817
0
    strlcat(tmp, "MOUSE_BUTTON,", sizeof tmp);
818
0
  if (mode & MODE_CURSOR_BLINKING)
819
0
    strlcat(tmp, "CURSOR_BLINKING,", sizeof tmp);
820
0
  if (mode & MODE_CURSOR_VERY_VISIBLE)
821
0
    strlcat(tmp, "CURSOR_VERY_VISIBLE,", sizeof tmp);
822
0
  if (mode & MODE_CURSOR_BLINKING_SET)
823
0
    strlcat(tmp, "CURSOR_BLINKING_SET,", sizeof tmp);
824
0
  if (mode & MODE_MOUSE_UTF8)
825
0
    strlcat(tmp, "MOUSE_UTF8,", sizeof tmp);
826
0
  if (mode & MODE_MOUSE_SGR)
827
0
    strlcat(tmp, "MOUSE_SGR,", sizeof tmp);
828
0
  if (mode & MODE_BRACKETPASTE)
829
0
    strlcat(tmp, "BRACKETPASTE,", sizeof tmp);
830
0
  if (mode & MODE_FOCUSON)
831
0
    strlcat(tmp, "FOCUSON,", sizeof tmp);
832
0
  if (mode & MODE_MOUSE_ALL)
833
0
    strlcat(tmp, "MOUSE_ALL,", sizeof tmp);
834
0
  if (mode & MODE_ORIGIN)
835
0
    strlcat(tmp, "ORIGIN,", sizeof tmp);
836
0
  if (mode & MODE_CRLF)
837
0
    strlcat(tmp, "CRLF,", sizeof tmp);
838
0
  if (mode & MODE_KEYS_EXTENDED)
839
0
    strlcat(tmp, "KEYS_EXTENDED,", sizeof tmp);
840
0
  if (mode & MODE_KEYS_EXTENDED_2)
841
0
    strlcat(tmp, "KEYS_EXTENDED_2,", sizeof tmp);
842
0
  if (mode & MODE_THEME_UPDATES)
843
0
    strlcat(tmp, "THEME_UPDATES,", sizeof tmp);
844
0
  if (mode & MODE_SYNC)
845
0
    strlcat(tmp, "SYNC,", sizeof tmp);
846
0
  if (*tmp != '\0')
847
0
    tmp[strlen(tmp) - 1] = '\0';
848
0
  return (tmp);
849
0
}
850
851
/* Convert screen to a string. */
852
const char *
853
screen_print(struct screen *s, int line)
854
0
{
855
0
  static char   *buf;
856
0
  static size_t    len = 16384;
857
0
  const char    *acs;
858
0
  u_int      x, y;
859
0
  int      n;
860
0
  size_t       last = 0;
861
0
  struct utf8_data   ud;
862
0
  struct grid_line  *gl;
863
0
  struct grid_cell_entry  *gce;
864
865
0
  if (buf == NULL)
866
0
    buf = xmalloc(len);
867
868
0
  for (y = 0; y < screen_hsize(s) + s->grid->sy; y++) {
869
0
    if (line >= 0 && y != (u_int)line)
870
0
      continue;
871
0
    n = snprintf(buf + last, len - last, "%.4d \"", y);
872
0
    if (n <= 0 || (u_int)n >= len - last)
873
0
      goto out;
874
0
    last += n;
875
876
0
    gl = &s->grid->linedata[y];
877
0
    for (x = 0; x < gl->cellused; x++) {
878
0
      gce = &gl->celldata[x];
879
0
      if (gce->flags & GRID_FLAG_PADDING)
880
0
        continue;
881
882
0
      if (~gce->flags & GRID_FLAG_EXTENDED) {
883
0
        if (last + 2 >= len)
884
0
          goto out;
885
0
        buf[last++] = gce->data.data;
886
0
      } else if (gce->flags & GRID_FLAG_TAB) {
887
0
        if (last + 2 >= len)
888
0
          goto out;
889
0
        buf[last++] = '\t';
890
0
      } else if (gce->flags & GRID_ATTR_CHARSET) {
891
0
        acs = tty_acs_get(NULL, gce->data.data);
892
0
        if (acs != NULL)
893
0
          n = strlen(acs);
894
0
        else {
895
0
          acs = &gce->data.data;
896
0
          n = 1;
897
0
        }
898
0
        if (last + n + 1 >= len)
899
0
          goto out;
900
0
        memcpy(buf + last, acs, n);
901
0
        last += n;
902
0
      } else {
903
0
        utf8_to_data(gl->extddata[gce->offset].data,
904
0
            &ud);
905
0
        if (ud.size > 0) {
906
0
          if (last + ud.size + 1 >= len)
907
0
            goto out;
908
0
          memcpy(buf + last, ud.data, ud.size);
909
0
          last += ud.size;
910
0
        }
911
0
      }
912
0
    }
913
914
0
    if (last + 3 >= len)
915
0
      goto out;
916
0
    buf[last++] = '"';
917
0
    buf[last++] = '\n';
918
0
  }
919
920
0
out:
921
0
  buf[last] = '\0';
922
0
  return (buf);
923
0
}