1 /* $OpenBSD: tty-features.c,v 1.30 2023/11/14 15:38:33 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 *const *capabilities; 41 int flags; 42 }; 43 44 /* Terminal has xterm(1) title setting. */ 45 static const char *const 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 *const 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 *const 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 *const 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 *const 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 *const 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 *const 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 *const 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 *const 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 "Setulc1=\\E[58::5::%p1%dm", 149 "ol=\\E[59m", 150 NULL 151 }; 152 static const struct tty_feature tty_feature_usstyle = { 153 "usstyle", 154 tty_feature_usstyle_capabilities, 155 0 156 }; 157 158 /* Terminal supports bracketed paste. */ 159 static const char *const tty_feature_bpaste_capabilities[] = { 160 "Enbp=\\E[?2004h", 161 "Dsbp=\\E[?2004l", 162 NULL 163 }; 164 static const struct tty_feature tty_feature_bpaste = { 165 "bpaste", 166 tty_feature_bpaste_capabilities, 167 0 168 }; 169 170 /* Terminal supports focus reporting. */ 171 static const char *const tty_feature_focus_capabilities[] = { 172 "Enfcs=\\E[?1004h", 173 "Dsfcs=\\E[?1004l", 174 NULL 175 }; 176 static const struct tty_feature tty_feature_focus = { 177 "focus", 178 tty_feature_focus_capabilities, 179 0 180 }; 181 182 /* Terminal supports cursor styles. */ 183 static const char *const tty_feature_cstyle_capabilities[] = { 184 "Ss=\\E[%p1%d q", 185 "Se=\\E[2 q", 186 NULL 187 }; 188 static const struct tty_feature tty_feature_cstyle = { 189 "cstyle", 190 tty_feature_cstyle_capabilities, 191 0 192 }; 193 194 /* Terminal supports cursor colours. */ 195 static const char *const tty_feature_ccolour_capabilities[] = { 196 "Cs=\\E]12;%p1%s\\a", 197 "Cr=\\E]112\\a", 198 NULL 199 }; 200 static const struct tty_feature tty_feature_ccolour = { 201 "ccolour", 202 tty_feature_ccolour_capabilities, 203 0 204 }; 205 206 /* Terminal supports strikethrough. */ 207 static const char *const tty_feature_strikethrough_capabilities[] = { 208 "smxx=\\E[9m", 209 NULL 210 }; 211 static const struct tty_feature tty_feature_strikethrough = { 212 "strikethrough", 213 tty_feature_strikethrough_capabilities, 214 0 215 }; 216 217 /* Terminal supports synchronized updates. */ 218 static const char *const tty_feature_sync_capabilities[] = { 219 "Sync=\\E[?2026%?%p1%{1}%-%tl%eh%;", 220 NULL 221 }; 222 static const struct tty_feature tty_feature_sync = { 223 "sync", 224 tty_feature_sync_capabilities, 225 0 226 }; 227 228 /* Terminal supports extended keys. */ 229 static const char *const tty_feature_extkeys_capabilities[] = { 230 "Eneks=\\E[>4;1m", 231 "Dseks=\\E[>4m", 232 NULL 233 }; 234 static const struct tty_feature tty_feature_extkeys = { 235 "extkeys", 236 tty_feature_extkeys_capabilities, 237 0 238 }; 239 240 /* Terminal supports DECSLRM margins. */ 241 static const char *const tty_feature_margins_capabilities[] = { 242 "Enmg=\\E[?69h", 243 "Dsmg=\\E[?69l", 244 "Clmg=\\E[s", 245 "Cmg=\\E[%i%p1%d;%p2%ds", 246 NULL 247 }; 248 static const struct tty_feature tty_feature_margins = { 249 "margins", 250 tty_feature_margins_capabilities, 251 TERM_DECSLRM 252 }; 253 254 /* Terminal supports DECFRA rectangle fill. */ 255 static const char *const tty_feature_rectfill_capabilities[] = { 256 "Rect", 257 NULL 258 }; 259 static const struct tty_feature tty_feature_rectfill = { 260 "rectfill", 261 tty_feature_rectfill_capabilities, 262 TERM_DECFRA 263 }; 264 265 /* Use builtin function keys only. */ 266 static const char *const tty_feature_ignorefkeys_capabilities[] = { 267 "kf0@", 268 "kf1@", 269 "kf2@", 270 "kf3@", 271 "kf4@", 272 "kf5@", 273 "kf6@", 274 "kf7@", 275 "kf8@", 276 "kf9@", 277 "kf10@", 278 "kf11@", 279 "kf12@", 280 "kf13@", 281 "kf14@", 282 "kf15@", 283 "kf16@", 284 "kf17@", 285 "kf18@", 286 "kf19@", 287 "kf20@", 288 "kf21@", 289 "kf22@", 290 "kf23@", 291 "kf24@", 292 "kf25@", 293 "kf26@", 294 "kf27@", 295 "kf28@", 296 "kf29@", 297 "kf30@", 298 "kf31@", 299 "kf32@", 300 "kf33@", 301 "kf34@", 302 "kf35@", 303 "kf36@", 304 "kf37@", 305 "kf38@", 306 "kf39@", 307 "kf40@", 308 "kf41@", 309 "kf42@", 310 "kf43@", 311 "kf44@", 312 "kf45@", 313 "kf46@", 314 "kf47@", 315 "kf48@", 316 "kf49@", 317 "kf50@", 318 "kf51@", 319 "kf52@", 320 "kf53@", 321 "kf54@", 322 "kf55@", 323 "kf56@", 324 "kf57@", 325 "kf58@", 326 "kf59@", 327 "kf60@", 328 "kf61@", 329 "kf62@", 330 "kf63@", 331 NULL 332 }; 333 static const struct tty_feature tty_feature_ignorefkeys = { 334 "ignorefkeys", 335 tty_feature_ignorefkeys_capabilities, 336 0 337 }; 338 339 /* Terminal has sixel capability. */ 340 static const char *const tty_feature_sixel_capabilities[] = { 341 "Sxl", 342 NULL 343 }; 344 static const struct tty_feature tty_feature_sixel = { 345 "sixel", 346 tty_feature_sixel_capabilities, 347 TERM_SIXEL 348 }; 349 350 /* Available terminal features. */ 351 static const struct tty_feature *const tty_features[] = { 352 &tty_feature_256, 353 &tty_feature_bpaste, 354 &tty_feature_ccolour, 355 &tty_feature_clipboard, 356 &tty_feature_hyperlinks, 357 &tty_feature_cstyle, 358 &tty_feature_extkeys, 359 &tty_feature_focus, 360 &tty_feature_ignorefkeys, 361 &tty_feature_margins, 362 &tty_feature_mouse, 363 &tty_feature_osc7, 364 &tty_feature_overline, 365 &tty_feature_rectfill, 366 &tty_feature_rgb, 367 &tty_feature_sixel, 368 &tty_feature_strikethrough, 369 &tty_feature_sync, 370 &tty_feature_title, 371 &tty_feature_usstyle 372 }; 373 374 void 375 tty_add_features(int *feat, const char *s, const char *separators) 376 { 377 const struct tty_feature *tf; 378 char *next, *loop, *copy; 379 u_int i; 380 381 log_debug("adding terminal features %s", s); 382 383 loop = copy = xstrdup(s); 384 while ((next = strsep(&loop, separators)) != NULL) { 385 for (i = 0; i < nitems(tty_features); i++) { 386 tf = tty_features[i]; 387 if (strcasecmp(tf->name, next) == 0) 388 break; 389 } 390 if (i == nitems(tty_features)) { 391 log_debug("unknown terminal feature: %s", next); 392 break; 393 } 394 if (~(*feat) & (1 << i)) { 395 log_debug("adding terminal feature: %s", tf->name); 396 (*feat) |= (1 << i); 397 } 398 } 399 free(copy); 400 } 401 402 const char * 403 tty_get_features(int feat) 404 { 405 const struct tty_feature *tf; 406 static char s[512]; 407 u_int i; 408 409 *s = '\0'; 410 for (i = 0; i < nitems(tty_features); i++) { 411 if (~feat & (1 << i)) 412 continue; 413 tf = tty_features[i]; 414 415 strlcat(s, tf->name, sizeof s); 416 strlcat(s, ",", sizeof s); 417 } 418 if (*s != '\0') 419 s[strlen(s) - 1] = '\0'; 420 return (s); 421 } 422 423 int 424 tty_apply_features(struct tty_term *term, int feat) 425 { 426 const struct tty_feature *tf; 427 const char *const *capability; 428 u_int i; 429 430 if (feat == 0) 431 return (0); 432 log_debug("applying terminal features: %s", tty_get_features(feat)); 433 434 for (i = 0; i < nitems(tty_features); i++) { 435 if ((term->features & (1 << i)) || (~feat & (1 << i))) 436 continue; 437 tf = tty_features[i]; 438 439 log_debug("applying terminal feature: %s", tf->name); 440 if (tf->capabilities != NULL) { 441 capability = tf->capabilities; 442 while (*capability != NULL) { 443 log_debug("adding capability: %s", *capability); 444 tty_term_apply(term, *capability, 1); 445 capability++; 446 } 447 } 448 term->flags |= tf->flags; 449 } 450 if ((term->features | feat) == term->features) 451 return (0); 452 term->features |= feat; 453 return (1); 454 } 455 456 void 457 tty_default_features(int *feat, const char *name, u_int version) 458 { 459 static const struct { 460 const char *name; 461 u_int version; 462 const char *features; 463 } table[] = { 464 #define TTY_FEATURES_BASE_MODERN_XTERM \ 465 "256,RGB,bpaste,clipboard,mouse,strikethrough,title" 466 { .name = "mintty", 467 .features = TTY_FEATURES_BASE_MODERN_XTERM 468 ",ccolour,cstyle,extkeys,margins,overline,usstyle" 469 }, 470 { .name = "tmux", 471 .features = TTY_FEATURES_BASE_MODERN_XTERM 472 ",ccolour,cstyle,focus,overline,usstyle,hyperlinks" 473 }, 474 { .name = "rxvt-unicode", 475 .features = "256,bpaste,ccolour,cstyle,mouse,title,ignorefkeys" 476 }, 477 { .name = "iTerm2", 478 .features = TTY_FEATURES_BASE_MODERN_XTERM 479 ",cstyle,extkeys,margins,usstyle,sync,osc7,hyperlinks" 480 }, 481 { .name = "XTerm", 482 /* 483 * xterm also supports DECSLRM and DECFRA, but they can be 484 * disabled so not set it here - they will be added if 485 * secondary DA shows VT420. 486 */ 487 .features = TTY_FEATURES_BASE_MODERN_XTERM 488 ",ccolour,cstyle,extkeys,focus" 489 } 490 }; 491 u_int i; 492 493 for (i = 0; i < nitems(table); i++) { 494 if (strcmp(table[i].name, name) != 0) 495 continue; 496 if (version != 0 && version < table[i].version) 497 continue; 498 tty_add_features(feat, table[i].features, ","); 499 } 500 } 501