1 /* $OpenBSD: tty-features.c,v 1.25 2022/06/30 09:55:53 nicm 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 #include "tmux.h" 25 26 /* 27 * Still hardcoded: 28 * - default colours (under AX or op capabilities); 29 * - AIX colours (under colors >= 16); 30 * - alternate escape (if terminal is VT100-like). 31 * 32 * Also: 33 * - DECFRA uses a flag instead of capabilities; 34 * - UTF-8 is a separate flag on the client; needed for unattached clients. 35 */ 36 37 /* A named terminal feature. */ 38 struct tty_feature { 39 const char *name; 40 const char **capabilities; 41 int flags; 42 }; 43 44 /* Terminal has xterm(1) title setting. */ 45 static const char *tty_feature_title_capabilities[] = { 46 "tsl=\\E]0;", /* should be using TS really */ 47 "fsl=\\a", 48 NULL 49 }; 50 static const struct tty_feature tty_feature_title = { 51 "title", 52 tty_feature_title_capabilities, 53 0 54 }; 55 56 /* Terminal has OSC 7 working directory. */ 57 static const char *tty_feature_osc7_capabilities[] = { 58 "Swd=\\E]7;", 59 "fsl=\\a", 60 NULL 61 }; 62 static const struct tty_feature tty_feature_osc7 = { 63 "osc7", 64 tty_feature_osc7_capabilities, 65 0 66 }; 67 68 /* Terminal has mouse support. */ 69 static const char *tty_feature_mouse_capabilities[] = { 70 "kmous=\\E[M", 71 NULL 72 }; 73 static const struct tty_feature tty_feature_mouse = { 74 "mouse", 75 tty_feature_mouse_capabilities, 76 0 77 }; 78 79 /* Terminal can set the clipboard with OSC 52. */ 80 static const char *tty_feature_clipboard_capabilities[] = { 81 "Ms=\\E]52;%p1%s;%p2%s\\a", 82 NULL 83 }; 84 static const struct tty_feature tty_feature_clipboard = { 85 "clipboard", 86 tty_feature_clipboard_capabilities, 87 0 88 }; 89 90 /* Terminal supports OSC 8 hyperlinks. */ 91 static const char *tty_feature_hyperlinks_capabilities[] = { 92 "*:Hls=\\E]8;%?%p1%l%tid=%p1%s%;;%p2%s\\E\\\\", 93 NULL 94 }; 95 static const struct tty_feature tty_feature_hyperlinks = { 96 "hyperlinks", 97 tty_feature_hyperlinks_capabilities, 98 0 99 }; 100 101 /* 102 * Terminal supports RGB colour. This replaces setab and setaf also since 103 * terminals with RGB have versions that do not allow setting colours from the 104 * 256 palette. 105 */ 106 static const char *tty_feature_rgb_capabilities[] = { 107 "AX", 108 "setrgbf=\\E[38;2;%p1%d;%p2%d;%p3%dm", 109 "setrgbb=\\E[48;2;%p1%d;%p2%d;%p3%dm", 110 "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", 111 "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", 112 NULL 113 }; 114 static const struct tty_feature tty_feature_rgb = { 115 "RGB", 116 tty_feature_rgb_capabilities, 117 TERM_256COLOURS|TERM_RGBCOLOURS 118 }; 119 120 /* Terminal supports 256 colours. */ 121 static const char *tty_feature_256_capabilities[] = { 122 "AX", 123 "setab=\\E[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e48;5;%p1%d%;m", 124 "setaf=\\E[%?%p1%{8}%<%t3%p1%d%e%p1%{16}%<%t9%p1%{8}%-%d%e38;5;%p1%d%;m", 125 NULL 126 }; 127 static const struct tty_feature tty_feature_256 = { 128 "256", 129 tty_feature_256_capabilities, 130 TERM_256COLOURS 131 }; 132 133 /* Terminal supports overline. */ 134 static const char *tty_feature_overline_capabilities[] = { 135 "Smol=\\E[53m", 136 NULL 137 }; 138 static const struct tty_feature tty_feature_overline = { 139 "overline", 140 tty_feature_overline_capabilities, 141 0 142 }; 143 144 /* Terminal supports underscore styles. */ 145 static const char *tty_feature_usstyle_capabilities[] = { 146 "Smulx=\\E[4::%p1%dm", 147 "Setulc=\\E[58::2::%p1%{65536}%/%d::%p1%{256}%/%{255}%&%d::%p1%{255}%&%d%;m", 148 "ol=\\E[59m", 149 NULL 150 }; 151 static const struct tty_feature tty_feature_usstyle = { 152 "usstyle", 153 tty_feature_usstyle_capabilities, 154 0 155 }; 156 157 /* Terminal supports bracketed paste. */ 158 static const char *tty_feature_bpaste_capabilities[] = { 159 "Enbp=\\E[?2004h", 160 "Dsbp=\\E[?2004l", 161 NULL 162 }; 163 static const struct tty_feature tty_feature_bpaste = { 164 "bpaste", 165 tty_feature_bpaste_capabilities, 166 0 167 }; 168 169 /* Terminal supports focus reporting. */ 170 static const char *tty_feature_focus_capabilities[] = { 171 "Enfcs=\\E[?1004h", 172 "Dsfcs=\\E[?1004l", 173 NULL 174 }; 175 static const struct tty_feature tty_feature_focus = { 176 "focus", 177 tty_feature_focus_capabilities, 178 0 179 }; 180 181 /* Terminal supports cursor styles. */ 182 static const char *tty_feature_cstyle_capabilities[] = { 183 "Ss=\\E[%p1%d q", 184 "Se=\\E[2 q", 185 NULL 186 }; 187 static const struct tty_feature tty_feature_cstyle = { 188 "cstyle", 189 tty_feature_cstyle_capabilities, 190 0 191 }; 192 193 /* Terminal supports cursor colours. */ 194 static const char *tty_feature_ccolour_capabilities[] = { 195 "Cs=\\E]12;%p1%s\\a", 196 "Cr=\\E]112\\a", 197 NULL 198 }; 199 static const struct tty_feature tty_feature_ccolour = { 200 "ccolour", 201 tty_feature_ccolour_capabilities, 202 0 203 }; 204 205 /* Terminal supports strikethrough. */ 206 static const char *tty_feature_strikethrough_capabilities[] = { 207 "smxx=\\E[9m", 208 NULL 209 }; 210 static const struct tty_feature tty_feature_strikethrough = { 211 "strikethrough", 212 tty_feature_strikethrough_capabilities, 213 0 214 }; 215 216 /* Terminal supports synchronized updates. */ 217 static const char *tty_feature_sync_capabilities[] = { 218 "Sync=\\EP=%p1%ds\\E\\\\", 219 NULL 220 }; 221 static const struct tty_feature tty_feature_sync = { 222 "sync", 223 tty_feature_sync_capabilities, 224 0 225 }; 226 227 /* Terminal supports extended keys. */ 228 static const char *tty_feature_extkeys_capabilities[] = { 229 "Eneks=\\E[>4;1m", 230 "Dseks=\\E[>4m", 231 NULL 232 }; 233 static const struct tty_feature tty_feature_extkeys = { 234 "extkeys", 235 tty_feature_extkeys_capabilities, 236 0 237 }; 238 239 /* Terminal supports DECSLRM margins. */ 240 static const char *tty_feature_margins_capabilities[] = { 241 "Enmg=\\E[?69h", 242 "Dsmg=\\E[?69l", 243 "Clmg=\\E[s", 244 "Cmg=\\E[%i%p1%d;%p2%ds", 245 NULL 246 }; 247 static const struct tty_feature tty_feature_margins = { 248 "margins", 249 tty_feature_margins_capabilities, 250 TERM_DECSLRM 251 }; 252 253 /* Terminal supports DECFRA rectangle fill. */ 254 static const char *tty_feature_rectfill_capabilities[] = { 255 "Rect", 256 NULL 257 }; 258 static const struct tty_feature tty_feature_rectfill = { 259 "rectfill", 260 tty_feature_rectfill_capabilities, 261 TERM_DECFRA 262 }; 263 264 /* Use builtin function keys only. */ 265 static const char *tty_feature_ignorefkeys_capabilities[] = { 266 "kf0@", 267 "kf1@", 268 "kf2@", 269 "kf3@", 270 "kf4@", 271 "kf5@", 272 "kf6@", 273 "kf7@", 274 "kf8@", 275 "kf9@", 276 "kf10@", 277 "kf11@", 278 "kf12@", 279 "kf13@", 280 "kf14@", 281 "kf15@", 282 "kf16@", 283 "kf17@", 284 "kf18@", 285 "kf19@", 286 "kf20@", 287 "kf21@", 288 "kf22@", 289 "kf23@", 290 "kf24@", 291 "kf25@", 292 "kf26@", 293 "kf27@", 294 "kf28@", 295 "kf29@", 296 "kf30@", 297 "kf31@", 298 "kf32@", 299 "kf33@", 300 "kf34@", 301 "kf35@", 302 "kf36@", 303 "kf37@", 304 "kf38@", 305 "kf39@", 306 "kf40@", 307 "kf41@", 308 "kf42@", 309 "kf43@", 310 "kf44@", 311 "kf45@", 312 "kf46@", 313 "kf47@", 314 "kf48@", 315 "kf49@", 316 "kf50@", 317 "kf51@", 318 "kf52@", 319 "kf53@", 320 "kf54@", 321 "kf55@", 322 "kf56@", 323 "kf57@", 324 "kf58@", 325 "kf59@", 326 "kf60@", 327 "kf61@", 328 "kf62@", 329 "kf63@", 330 NULL 331 }; 332 static const struct tty_feature tty_feature_ignorefkeys = { 333 "ignorefkeys", 334 tty_feature_ignorefkeys_capabilities, 335 0 336 }; 337 338 /* Available terminal features. */ 339 static const struct tty_feature *tty_features[] = { 340 &tty_feature_256, 341 &tty_feature_bpaste, 342 &tty_feature_ccolour, 343 &tty_feature_clipboard, 344 &tty_feature_hyperlinks, 345 &tty_feature_cstyle, 346 &tty_feature_extkeys, 347 &tty_feature_focus, 348 &tty_feature_ignorefkeys, 349 &tty_feature_margins, 350 &tty_feature_mouse, 351 &tty_feature_osc7, 352 &tty_feature_overline, 353 &tty_feature_rectfill, 354 &tty_feature_rgb, 355 &tty_feature_strikethrough, 356 &tty_feature_sync, 357 &tty_feature_title, 358 &tty_feature_usstyle 359 }; 360 361 void 362 tty_add_features(int *feat, const char *s, const char *separators) 363 { 364 const struct tty_feature *tf; 365 char *next, *loop, *copy; 366 u_int i; 367 368 log_debug("adding terminal features %s", s); 369 370 loop = copy = xstrdup(s); 371 while ((next = strsep(&loop, separators)) != NULL) { 372 for (i = 0; i < nitems(tty_features); i++) { 373 tf = tty_features[i]; 374 if (strcasecmp(tf->name, next) == 0) 375 break; 376 } 377 if (i == nitems(tty_features)) { 378 log_debug("unknown terminal feature: %s", next); 379 break; 380 } 381 if (~(*feat) & (1 << i)) { 382 log_debug("adding terminal feature: %s", tf->name); 383 (*feat) |= (1 << i); 384 } 385 } 386 free(copy); 387 } 388 389 const char * 390 tty_get_features(int feat) 391 { 392 const struct tty_feature *tf; 393 static char s[512]; 394 u_int i; 395 396 *s = '\0'; 397 for (i = 0; i < nitems(tty_features); i++) { 398 if (~feat & (1 << i)) 399 continue; 400 tf = tty_features[i]; 401 402 strlcat(s, tf->name, sizeof s); 403 strlcat(s, ",", sizeof s); 404 } 405 if (*s != '\0') 406 s[strlen(s) - 1] = '\0'; 407 return (s); 408 } 409 410 int 411 tty_apply_features(struct tty_term *term, int feat) 412 { 413 const struct tty_feature *tf; 414 const char **capability; 415 u_int i; 416 417 if (feat == 0) 418 return (0); 419 log_debug("applying terminal features: %s", tty_get_features(feat)); 420 421 for (i = 0; i < nitems(tty_features); i++) { 422 if ((term->features & (1 << i)) || (~feat & (1 << i))) 423 continue; 424 tf = tty_features[i]; 425 426 log_debug("applying terminal feature: %s", tf->name); 427 if (tf->capabilities != NULL) { 428 capability = tf->capabilities; 429 while (*capability != NULL) { 430 log_debug("adding capability: %s", *capability); 431 tty_term_apply(term, *capability, 1); 432 capability++; 433 } 434 } 435 term->flags |= tf->flags; 436 } 437 if ((term->features | feat) == term->features) 438 return (0); 439 term->features |= feat; 440 return (1); 441 } 442 443 void 444 tty_default_features(int *feat, const char *name, u_int version) 445 { 446 static struct { 447 const char *name; 448 u_int version; 449 const char *features; 450 } table[] = { 451 #define TTY_FEATURES_BASE_MODERN_XTERM \ 452 "256,RGB,bpaste,clipboard,mouse,strikethrough,title" 453 { .name = "mintty", 454 .features = TTY_FEATURES_BASE_MODERN_XTERM 455 ",ccolour,cstyle,extkeys,margins,overline,usstyle" 456 }, 457 { .name = "tmux", 458 .features = TTY_FEATURES_BASE_MODERN_XTERM 459 ",ccolour,cstyle,focus,overline,usstyle,hyperlinks" 460 }, 461 { .name = "rxvt-unicode", 462 .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" 463 }, 464 { .name = "iTerm2", 465 .features = TTY_FEATURES_BASE_MODERN_XTERM 466 ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" 467 }, 468 { .name = "XTerm", 469 /* 470 * xterm also supports DECSLRM and DECFRA, but they can be 471 * disabled so not set it here - they will be added if 472 * secondary DA shows VT420. 473 */ 474 .features = TTY_FEATURES_BASE_MODERN_XTERM 475 ",ccolour,cstyle,extkeys,focus" 476 } 477 }; 478 u_int i; 479 480 for (i = 0; i < nitems(table); i++) { 481 if (strcmp(table[i].name, name) != 0) 482 continue; 483 if (version != 0 && version < table[i].version) 484 continue; 485 tty_add_features(feat, table[i].features, ","); 486 } 487 } 488