Coverage Report

Created: 2026-09-13 06:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/tmux/tty-features.c
Line
Count
Source
1
/* $OpenBSD: tty-features.c,v 1.43 2026/08/31 12:41:03 kirill Exp $ */
2
3
/*
4
 * Copyright (c) 2020 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
24
#if defined(HAVE_CURSES_H)
25
#include <curses.h>
26
#elif defined(HAVE_NCURSES_H)
27
#include <ncurses.h>
28
#endif
29
30
#include "tmux.h"
31
32
/*
33
 * Still hardcoded:
34
 * - default colours (under AX or op capabilities);
35
 * - AIX colours (under colors >= 16);
36
 * - alternate escape (if terminal is VT100-like).
37
 *
38
 * Also:
39
 * - DECFRA uses a flag instead of capabilities;
40
 * - UTF-8 is a separate flag on the client; needed for unattached clients.
41
 */
42
43
/* A named terminal feature. */
44
struct tty_feature {
45
  const char    *name;
46
  const char *const *capabilities;
47
  int      flags;
48
};
49
50
/* Terminal has xterm(1) title setting. */
51
static const char *const tty_feature_title_capabilities[] = {
52
  "tsl=\\E]0;", /* should be using TS really */
53
  "fsl=\\a",
54
  NULL
55
};
56
static const struct tty_feature tty_feature_title = {
57
  "title",
58
  tty_feature_title_capabilities,
59
  0
60
};
61
62
/* Terminal has OSC 7 working directory. */
63
static const char *const tty_feature_osc7_capabilities[] = {
64
  "Swd=\\E]7;",
65
  "fsl=\\a",
66
  NULL
67
};
68
static const struct tty_feature tty_feature_osc7 = {
69
  "osc7",
70
  tty_feature_osc7_capabilities,
71
  0
72
};
73
74
/* Terminal has mouse support. */
75
static const char *const tty_feature_mouse_capabilities[] = {
76
  "kmous=\\E[M",
77
  NULL
78
};
79
static const struct tty_feature tty_feature_mouse = {
80
  "mouse",
81
  tty_feature_mouse_capabilities,
82
  0
83
};
84
85
/* Terminal can set the clipboard with OSC 52. */
86
static const char *const tty_feature_clipboard_capabilities[] = {
87
  "Ms=\\E]52;%p1%s;%p2%s\\a",
88
  NULL
89
};
90
static const struct tty_feature tty_feature_clipboard = {
91
  "clipboard",
92
  tty_feature_clipboard_capabilities,
93
  0
94
};
95
96
/* Terminal supports OSC 8 hyperlinks. */
97
static const char *tty_feature_hyperlinks_capabilities[] = {
98
#if defined (__OpenBSD__) || (defined(NCURSES_VERSION_MAJOR) && \
99
  (NCURSES_VERSION_MAJOR > 5 || \
100
  (NCURSES_VERSION_MAJOR == 5 && NCURSES_VERSION_MINOR > 8)))
101
  "Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\",
102
#endif
103
  NULL
104
};
105
static const struct tty_feature tty_feature_hyperlinks = {
106
  "hyperlinks",
107
  tty_feature_hyperlinks_capabilities,
108
  0
109
};
110
111
/*
112
 * Terminal supports RGB colour. This replaces setab and setaf also since
113
 * terminals with RGB have versions that do not allow setting colours from the
114
 * 256 palette.
115
 */
116
static const char *const tty_feature_rgb_capabilities[] = {
117
  "AX",
118
  "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm",
119
  "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm",
120
  "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
121
  "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
122
  NULL
123
};
124
static const struct tty_feature tty_feature_rgb = {
125
  "RGB",
126
  tty_feature_rgb_capabilities,
127
  TERM_256COLOURS|TERM_RGBCOLOURS
128
};
129
130
/* Terminal supports 256 colours. */
131
static const char *const tty_feature_256_capabilities[] = {
132
  "AX",
133
  "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m",
134
  "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m",
135
  NULL
136
};
137
static const struct tty_feature tty_feature_256 = {
138
  "256",
139
  tty_feature_256_capabilities,
140
  TERM_256COLOURS
141
};
142
143
/* Terminal supports overline. */
144
static const char *const tty_feature_overline_capabilities[] = {
145
  "Smol=\\E[53m",
146
  NULL
147
};
148
static const struct tty_feature tty_feature_overline = {
149
  "overline",
150
  tty_feature_overline_capabilities,
151
  0
152
};
153
154
/* Terminal supports underscore styles. */
155
static const char *const tty_feature_usstyle_capabilities[] = {
156
  "Smulx=\\E[4::%p1%dm",
157
  "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m",
158
  "Setulc1=\\E[58::5::%p1%dm",
159
  "ol=\\E[59m",
160
  NULL
161
};
162
static const struct tty_feature tty_feature_usstyle = {
163
  "usstyle",
164
  tty_feature_usstyle_capabilities,
165
  0
166
};
167
168
/* Terminal supports bracketed paste. */
169
static const char *const tty_feature_bpaste_capabilities[] = {
170
  "Enbp=\\E[?2004h",
171
  "Dsbp=\\E[?2004l",
172
  NULL
173
};
174
static const struct tty_feature tty_feature_bpaste = {
175
  "bpaste",
176
  tty_feature_bpaste_capabilities,
177
  0
178
};
179
180
/* Terminal supports focus reporting. */
181
static const char *const tty_feature_focus_capabilities[] = {
182
  "Enfcs=\\E[?1004h",
183
  "Dsfcs=\\E[?1004l",
184
  NULL
185
};
186
static const struct tty_feature tty_feature_focus = {
187
  "focus",
188
  tty_feature_focus_capabilities,
189
  0
190
};
191
192
/* Terminal supports cursor styles. */
193
static const char *const tty_feature_cstyle_capabilities[] = {
194
  "Ss=\\E[%p1%d q",
195
  "Se=\\E[2 q",
196
  NULL
197
};
198
static const struct tty_feature tty_feature_cstyle = {
199
  "cstyle",
200
  tty_feature_cstyle_capabilities,
201
  0
202
};
203
204
/* Terminal supports cursor colours. */
205
static const char *const tty_feature_ccolour_capabilities[] = {
206
  "Cs=\\E]12;%p1%s\\a",
207
  "Cr=\\E]112\\a",
208
  NULL
209
};
210
static const struct tty_feature tty_feature_ccolour = {
211
  "ccolour",
212
  tty_feature_ccolour_capabilities,
213
  0
214
};
215
216
/* Terminal supports strikethrough. */
217
static const char *const tty_feature_strikethrough_capabilities[] = {
218
  "smxx=\\E[9m",
219
  NULL
220
};
221
static const struct tty_feature tty_feature_strikethrough = {
222
  "strikethrough",
223
  tty_feature_strikethrough_capabilities,
224
  0
225
};
226
227
/* Terminal supports synchronized updates. */
228
static const char *const tty_feature_sync_capabilities[] = {
229
  "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;",
230
  NULL
231
};
232
static const struct tty_feature tty_feature_sync = {
233
  "sync",
234
  tty_feature_sync_capabilities,
235
  0
236
};
237
238
/* Terminal supports extended keys. */
239
static const char *const tty_feature_extkeys_capabilities[] = {
240
  "Eneks=\\E[>4;2m",
241
  "Dseks=\\E[>4m",
242
  NULL
243
};
244
static const struct tty_feature tty_feature_extkeys = {
245
  "extkeys",
246
  tty_feature_extkeys_capabilities,
247
  0
248
};
249
250
/* Terminal supports DECSLRM margins. */
251
static const char *const tty_feature_margins_capabilities[] = {
252
  "Enmg=\\E[?69h",
253
  "Dsmg=\\E[?69l",
254
  "Clmg=\\E[s",
255
  "Cmg=\\E[%i%p1%d;%p2%ds",
256
  NULL
257
};
258
static const struct tty_feature tty_feature_margins = {
259
  "margins",
260
  tty_feature_margins_capabilities,
261
  TERM_DECSLRM
262
};
263
264
/* Terminal supports DECFRA rectangle fill. */
265
static const char *const tty_feature_rectfill_capabilities[] = {
266
  "Rect",
267
  NULL
268
};
269
static const struct tty_feature tty_feature_rectfill = {
270
  "rectfill",
271
  tty_feature_rectfill_capabilities,
272
  TERM_DECFRA
273
};
274
275
/* Use builtin function keys only. */
276
static const char *const tty_feature_ignorefkeys_capabilities[] = {
277
  "kf0@",
278
  "kf1@",
279
  "kf2@",
280
  "kf3@",
281
  "kf4@",
282
  "kf5@",
283
  "kf6@",
284
  "kf7@",
285
  "kf8@",
286
  "kf9@",
287
  "kf10@",
288
  "kf11@",
289
  "kf12@",
290
  "kf13@",
291
  "kf14@",
292
  "kf15@",
293
  "kf16@",
294
  "kf17@",
295
  "kf18@",
296
  "kf19@",
297
  "kf20@",
298
  "kf21@",
299
  "kf22@",
300
  "kf23@",
301
  "kf24@",
302
  "kf25@",
303
  "kf26@",
304
  "kf27@",
305
  "kf28@",
306
  "kf29@",
307
  "kf30@",
308
  "kf31@",
309
  "kf32@",
310
  "kf33@",
311
  "kf34@",
312
  "kf35@",
313
  "kf36@",
314
  "kf37@",
315
  "kf38@",
316
  "kf39@",
317
  "kf40@",
318
  "kf41@",
319
  "kf42@",
320
  "kf43@",
321
  "kf44@",
322
  "kf45@",
323
  "kf46@",
324
  "kf47@",
325
  "kf48@",
326
  "kf49@",
327
  "kf50@",
328
  "kf51@",
329
  "kf52@",
330
  "kf53@",
331
  "kf54@",
332
  "kf55@",
333
  "kf56@",
334
  "kf57@",
335
  "kf58@",
336
  "kf59@",
337
  "kf60@",
338
  "kf61@",
339
  "kf62@",
340
  "kf63@",
341
  NULL
342
};
343
static const struct tty_feature tty_feature_ignorefkeys = {
344
  "ignorefkeys",
345
  tty_feature_ignorefkeys_capabilities,
346
  0
347
};
348
349
/* Terminal has sixel capability. */
350
static const char *const tty_feature_sixel_capabilities[] = {
351
  "Sxl",
352
  NULL
353
};
354
static const struct tty_feature tty_feature_sixel = {
355
  "sixel",
356
  tty_feature_sixel_capabilities,
357
  TERM_SIXEL
358
};
359
360
/* Terminal supports the OSC 9;4 progress bar. */
361
static const char *const tty_feature_progressbar_capabilities[] = {
362
  "Spb=\\E]9;4;%p1%d;%p2%d\\E\\\\",
363
  NULL
364
};
365
static const struct tty_feature tty_feature_progressbar = {
366
  "progressbar",
367
  tty_feature_progressbar_capabilities,
368
  0
369
};
370
371
/* Terminal supports UTF-8. */
372
static const struct tty_feature tty_feature_utf8 = {
373
  "utf8",
374
  NULL,
375
  0
376
};
377
378
/* Available terminal features. */
379
static const struct tty_feature *const tty_features[] = {
380
  &tty_feature_256,
381
  &tty_feature_bpaste,
382
  &tty_feature_ccolour,
383
  &tty_feature_clipboard,
384
  &tty_feature_hyperlinks,
385
  &tty_feature_cstyle,
386
  &tty_feature_extkeys,
387
  &tty_feature_focus,
388
  &tty_feature_ignorefkeys,
389
  &tty_feature_margins,
390
  &tty_feature_mouse,
391
  &tty_feature_osc7,
392
  &tty_feature_overline,
393
  &tty_feature_progressbar,
394
  &tty_feature_rectfill,
395
  &tty_feature_rgb,
396
  &tty_feature_sixel,
397
  &tty_feature_strikethrough,
398
  &tty_feature_sync,
399
  &tty_feature_title,
400
  &tty_feature_usstyle,
401
  &tty_feature_utf8
402
};
403
404
/* Parse features for client. */
405
void
406
tty_parse_client_features(struct client *c, const char *s, const char *sep)
407
0
{
408
0
  tty_parse_features(s, sep, &c->term_features, &c->term_nofeatures);
409
0
}
410
411
/* Parse features list. */
412
void
413
tty_parse_features(const char *s, const char *sep, int *enabled, int *disabled)
414
0
{
415
0
  const struct tty_feature   *tf;
416
0
  char         *next, *loop, *copy;
417
0
  u_int         i;
418
0
  int         remove;
419
420
0
  log_debug("adding terminal features %s", s);
421
422
0
  loop = copy = xstrdup(s);
423
0
  while ((next = strsep(&loop, sep)) != NULL) {
424
0
    remove = (*next != '\0' && next[strlen(next) - 1] == '@');
425
0
    if (remove)
426
0
      next[strlen(next) - 1] = '\0';
427
0
    for (i = 0; i < nitems(tty_features); i++) {
428
0
      tf = tty_features[i];
429
0
      if (strcasecmp(tf->name, next) == 0)
430
0
        break;
431
0
    }
432
0
    if (i == nitems(tty_features)) {
433
0
      log_debug("unknown terminal feature: %s", next);
434
0
      break;
435
0
    }
436
0
    if (remove) {
437
0
      log_debug("removing terminal feature: %s", tf->name);
438
0
      *enabled &= ~(1 << i);
439
0
      if (disabled != NULL)
440
0
        *disabled |= 1 << i;
441
0
      continue;
442
0
    }
443
0
    if (disabled != NULL && *disabled & (1 << i))
444
0
      continue;
445
0
    if (~(*enabled) & (1 << i)) {
446
0
      log_debug("adding terminal feature: %s", tf->name);
447
0
      (*enabled) |= (1 << i);
448
0
    }
449
0
  }
450
0
  free(copy);
451
0
}
452
453
/* Get features as string. */
454
const char *
455
tty_get_features(int feat)
456
0
{
457
0
  const struct tty_feature  *tf;
458
0
  static char      s[512];
459
0
  u_int        i;
460
461
0
  *s = '\0';
462
0
  for (i = 0; i < nitems(tty_features); i++) {
463
0
    if (~feat & (1 << i))
464
0
      continue;
465
0
    tf = tty_features[i];
466
467
0
    strlcat(s, tf->name, sizeof s);
468
0
    strlcat(s, ",", sizeof s);
469
0
  }
470
0
  if (*s != '\0')
471
0
    s[strlen(s) - 1] = '\0';
472
0
  return (s);
473
0
}
474
475
/* Check if feature is present. */
476
int
477
tty_feature_present(struct tty_term *term, const char *name)
478
0
{
479
0
  const struct tty_feature  *tf = NULL;
480
0
  const char *const   *capability;
481
0
  u_int        i;
482
0
  char        *copy;
483
484
0
  if (strcmp(name, "utf8") == 0)
485
0
    return ((term->tty->client->flags & CLIENT_UTF8) != 0);
486
487
0
  for (i = 0; i < nitems(tty_features); i++) {
488
0
    tf = tty_features[i];
489
0
    if (strcmp(tf->name, name) == 0) {
490
0
      if (term->applied_features & (1 << i))
491
0
        return (1);
492
0
      break;
493
0
    }
494
0
  }
495
496
  /*
497
   * We don't just have the feature flag set. Check if the capabilities
498
   * supported by the client are actual set instead.
499
   */
500
0
  if (tf == NULL || tf->capabilities == NULL ||
501
0
      strcmp(name, "ignorefkeys") == 0)
502
0
    return (0);
503
0
  if (tf->flags != 0 && (term->flags & tf->flags) != tf->flags)
504
0
    return (0);
505
0
  capability = tf->capabilities;
506
0
  while (*capability != NULL) {
507
0
    copy = xstrdup(*capability);
508
0
    copy[strcspn(copy, "=")] = '\0';
509
0
    if (!tty_term_has_name(term, copy)) {
510
0
      free(copy);
511
0
      return (0);
512
0
    }
513
0
    free(copy);
514
0
    capability++;
515
0
  }
516
0
  return (1);
517
0
}
518
519
/* Apply featurs to terminal. */
520
int
521
tty_apply_features(struct tty_term *term)
522
0
{
523
0
  struct client     *c = term->tty->client;
524
0
  const struct tty_feature  *tf;
525
0
  const char *const   *capability;
526
0
  int        feat;
527
0
  u_int        i;
528
529
0
  feat = (c->term_features & ~c->term_nofeatures);
530
0
  if (feat == 0)
531
0
    return (0);
532
0
  log_debug("applying terminal features: %s", tty_get_features(feat));
533
534
0
  for (i = 0; i < nitems(tty_features); i++) {
535
0
    if ((term->applied_features & (1 << i)) || (~feat & (1 << i)))
536
0
      continue;
537
0
    tf = tty_features[i];
538
539
0
    log_debug("applying terminal feature: %s", tf->name);
540
0
    if (tf->capabilities != NULL) {
541
0
      capability = tf->capabilities;
542
0
      while (*capability != NULL) {
543
0
        log_debug("adding capability: %s", *capability);
544
0
        tty_term_apply(term, *capability, 1);
545
0
        capability++;
546
0
      }
547
0
    }
548
0
    term->flags |= tf->flags;
549
0
    if (tf == &tty_feature_utf8)
550
0
      c->flags |= CLIENT_UTF8;
551
0
  }
552
0
  if ((term->applied_features|feat) == term->applied_features)
553
0
    return (0);
554
0
  term->applied_features |= feat;
555
0
  return (1);
556
0
}
557
558
/* Add default features for a terminal identified by name and version. */
559
void
560
tty_default_features(struct client *c, const char *name, u_int version)
561
0
{
562
0
  static const struct {
563
0
    const char  *name;
564
0
    u_int    version;
565
0
    const char  *features;
566
0
  } table[] = {
567
0
#define TTY_FEATURES_BASE_MODERN_XTERM \
568
0
  "256,RGB,bpaste,clipboard,mouse,strikethrough,title"
569
0
    { .name = "mintty",
570
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
571
0
            "ccolour,"
572
0
            "cstyle,"
573
0
            "extkeys,"
574
0
            "margins,"
575
0
            "overline,"
576
0
            "usstyle"
577
0
    },
578
0
    { .name = "tmux",
579
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
580
0
            "ccolour,"
581
0
            "cstyle,"
582
0
            "extkeys,"
583
0
            "focus,"
584
0
            "overline,"
585
0
            "usstyle,"
586
0
            "hyperlinks,"
587
0
              "progressbar"
588
0
    },
589
0
    { .name = "rxvt-unicode",
590
0
      .features = "256,"
591
0
            "bpaste,"
592
0
            "ccolour,"
593
0
            "cstyle,"
594
0
            "mouse,"
595
0
            "title,"
596
0
            "ignorefkeys"
597
0
    },
598
0
    { .name = "iTerm2",
599
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
600
0
            "cstyle,"
601
0
            "extkeys,"
602
0
            "margins,"
603
0
            "usstyle,"
604
0
            "sync,"
605
0
            "osc7,"
606
0
            "hyperlinks,"
607
0
              "progressbar"
608
0
    },
609
0
    { .name = "foot",
610
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
611
0
            "ccolour,"
612
0
            "cstyle,"
613
0
            "extkeys,"
614
0
            "usstyle,"
615
0
            "sync,"
616
0
            "osc7,"
617
0
            "hyperlinks"
618
0
    },
619
0
    { .name = "WezTerm",
620
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
621
0
            "ccolour,"
622
0
            "cstyle,"
623
0
            "extkeys,"
624
0
            "focus,"
625
0
              "hyperlinks,"
626
0
            "usstyle"
627
0
    },
628
0
    { .name = "ghostty",
629
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
630
0
            "ccolour,"
631
0
            "cstyle,"
632
0
            "extkeys,"
633
0
            "focus,"
634
0
            "overline,"
635
0
            "hyperlinks,"
636
0
            "osc7,"
637
0
            "sync,"
638
0
            "usstyle,"
639
0
            "progressbar"
640
0
    },
641
0
    { .name = "Rio",
642
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
643
0
            "ccolour,"
644
0
            "cstyle,"
645
0
            "focus,"
646
0
            "overline,"
647
0
            "hyperlinks,"
648
0
            "osc7,"
649
0
            "sync,"
650
0
            "usstyle,"
651
0
            "progressbar"
652
0
    },
653
0
    { .name = "XTerm",
654
      /*
655
       * xterm also supports DECSLRM and DECFRA, but they can be
656
       * disabled so not set it here - they will be added if
657
       * secondary DA shows VT420.
658
       */
659
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM ","
660
0
            "ccolour,"
661
0
            "cstyle,"
662
0
            "extkeys,"
663
0
            "focus"
664
0
    }
665
0
  };
666
0
  u_int i;
667
668
0
  for (i = 0; i < nitems(table); i++) {
669
0
    if (strcmp(table[i].name, name) != 0)
670
0
      continue;
671
0
    if (version != 0 && version < table[i].version)
672
0
      continue;
673
0
    tty_parse_client_features(c, table[i].features, ",");
674
0
  }
675
0
}