Coverage Report

Created: 2025-07-11 06:20

/src/tmux/tty-features.c
Line
Count
Source (jump to first uncovered line)
1
/* $OpenBSD$ */
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
/* Available terminal features. */
361
static const struct tty_feature *const tty_features[] = {
362
  &tty_feature_256,
363
  &tty_feature_bpaste,
364
  &tty_feature_ccolour,
365
  &tty_feature_clipboard,
366
  &tty_feature_hyperlinks,
367
  &tty_feature_cstyle,
368
  &tty_feature_extkeys,
369
  &tty_feature_focus,
370
  &tty_feature_ignorefkeys,
371
  &tty_feature_margins,
372
  &tty_feature_mouse,
373
  &tty_feature_osc7,
374
  &tty_feature_overline,
375
  &tty_feature_rectfill,
376
  &tty_feature_rgb,
377
  &tty_feature_sixel,
378
  &tty_feature_strikethrough,
379
  &tty_feature_sync,
380
  &tty_feature_title,
381
  &tty_feature_usstyle
382
};
383
384
void
385
tty_add_features(int *feat, const char *s, const char *separators)
386
0
{
387
0
  const struct tty_feature   *tf;
388
0
  char         *next, *loop, *copy;
389
0
  u_int         i;
390
391
0
  log_debug("adding terminal features %s", s);
392
393
0
  loop = copy = xstrdup(s);
394
0
  while ((next = strsep(&loop, separators)) != NULL) {
395
0
    for (i = 0; i < nitems(tty_features); i++) {
396
0
      tf = tty_features[i];
397
0
      if (strcasecmp(tf->name, next) == 0)
398
0
        break;
399
0
    }
400
0
    if (i == nitems(tty_features)) {
401
0
      log_debug("unknown terminal feature: %s", next);
402
0
      break;
403
0
    }
404
0
    if (~(*feat) & (1 << i)) {
405
0
      log_debug("adding terminal feature: %s", tf->name);
406
0
      (*feat) |= (1 << i);
407
0
    }
408
0
  }
409
0
  free(copy);
410
0
}
411
412
const char *
413
tty_get_features(int feat)
414
0
{
415
0
  const struct tty_feature  *tf;
416
0
  static char      s[512];
417
0
  u_int        i;
418
419
0
  *s = '\0';
420
0
  for (i = 0; i < nitems(tty_features); i++) {
421
0
    if (~feat & (1 << i))
422
0
      continue;
423
0
    tf = tty_features[i];
424
425
0
    strlcat(s, tf->name, sizeof s);
426
0
    strlcat(s, ",", sizeof s);
427
0
  }
428
0
  if (*s != '\0')
429
0
    s[strlen(s) - 1] = '\0';
430
0
  return (s);
431
0
}
432
433
int
434
tty_apply_features(struct tty_term *term, int feat)
435
0
{
436
0
  const struct tty_feature  *tf;
437
0
  const char *const   *capability;
438
0
  u_int        i;
439
440
0
  if (feat == 0)
441
0
    return (0);
442
0
  log_debug("applying terminal features: %s", tty_get_features(feat));
443
444
0
  for (i = 0; i < nitems(tty_features); i++) {
445
0
    if ((term->features & (1 << i)) || (~feat & (1 << i)))
446
0
      continue;
447
0
    tf = tty_features[i];
448
449
0
    log_debug("applying terminal feature: %s", tf->name);
450
0
    if (tf->capabilities != NULL) {
451
0
      capability = tf->capabilities;
452
0
      while (*capability != NULL) {
453
0
        log_debug("adding capability: %s", *capability);
454
0
        tty_term_apply(term, *capability, 1);
455
0
        capability++;
456
0
      }
457
0
    }
458
0
    term->flags |= tf->flags;
459
0
  }
460
0
  if ((term->features | feat) == term->features)
461
0
    return (0);
462
0
  term->features |= feat;
463
0
  return (1);
464
0
}
465
466
void
467
tty_default_features(int *feat, const char *name, u_int version)
468
0
{
469
0
  static const struct {
470
0
    const char  *name;
471
0
    u_int    version;
472
0
    const char  *features;
473
0
  } table[] = {
474
0
#define TTY_FEATURES_BASE_MODERN_XTERM \
475
0
  "256,RGB,bpaste,clipboard,mouse,strikethrough,title"
476
0
    { .name = "mintty",
477
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM
478
0
            ",ccolour,cstyle,extkeys,margins,overline,usstyle"
479
0
    },
480
0
    { .name = "tmux",
481
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM
482
0
            ",ccolour,cstyle,focus,overline,usstyle,hyperlinks"
483
0
    },
484
0
    { .name = "rxvt-unicode",
485
0
      .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys"
486
0
    },
487
0
    { .name = "iTerm2",
488
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM
489
0
            ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks"
490
0
    },
491
0
    { .name = "foot",
492
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM
493
0
                  ",cstyle,extkeys"
494
0
    },
495
0
    { .name = "XTerm",
496
      /*
497
       * xterm also supports DECSLRM and DECFRA, but they can be
498
       * disabled so not set it here - they will be added if
499
       * secondary DA shows VT420.
500
       */
501
0
      .features = TTY_FEATURES_BASE_MODERN_XTERM
502
0
            ",ccolour,cstyle,extkeys,focus"
503
0
    }
504
0
  };
505
0
  u_int i;
506
507
0
  for (i = 0; i < nitems(table); i++) {
508
0
    if (strcmp(table[i].name, name) != 0)
509
0
      continue;
510
0
    if (version != 0 && version < table[i].version)
511
0
      continue;
512
0
    tty_add_features(feat, table[i].features, ",");
513
0
  }
514
0
}