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