1/* $XTermId: xtermcap.c,v 1.63 2024/12/01 20:17:29 tom Exp $ */ 2 3/* 4 * Copyright 2007-2023,2024 by Thomas E. Dickey 5 * 6 * All Rights Reserved 7 * 8 * Permission is hereby granted, free of charge, to any person obtaining a 9 * copy of this software and associated documentation files (the 10 * "Software"), to deal in the Software without restriction, including 11 * without limitation the rights to use, copy, modify, merge, publish, 12 * distribute, sublicense, and/or sell copies of the Software, and to 13 * permit persons to whom the Software is furnished to do so, subject to 14 * the following conditions: 15 * 16 * The above copyright notice and this permission notice shall be included 17 * in all copies or substantial portions of the Software. 18 * 19 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 20 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 21 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 22 * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY 23 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, 24 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE 25 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 26 * 27 * Except as contained in this notice, the name(s) of the above copyright 28 * holders shall not be used in advertising or otherwise to promote the 29 * sale, use or other dealings in this Software without prior written 30 * authorization. 31 */ 32 33#include <xtermcap.h> 34#include <data.h> 35 36#include <X11/keysym.h> 37#include <ctype.h> 38 39#include <xstrings.h> 40 41#if USE_TERMINFO && defined(NCURSES_VERSION) && defined(HAVE_USE_EXTENDED_NAMES) 42#define USE_EXTENDED_NAMES 1 43#else 44#define USE_EXTENDED_NAMES 0 45#endif 46 47#if USE_TERMINFO 48#define TcapInit(buffer, name) (setupterm(name, fileno(stdout), &ignored) == OK) 49#define TcapFree() (del_curterm(cur_term)) 50#else 51#define TcapInit(buffer, name) (tgetent(buffer, name) == 1) 52#define TcapFree() /*nothing */ 53#endif 54 55#define NO_STRING (char *)(-1) 56 57#if OPT_TCAP_QUERY || OPT_TCAP_FKEYS 58 59#define SHIFT (MOD_NONE + MOD_SHIFT) 60 61typedef struct { 62 const char *tc; 63 const char *ti; 64 int code; 65 unsigned param; /* see xtermStateToParam() */ 66} TCAPINFO; 67/* *INDENT-OFF* */ 68#define DATA(tc,ti,x,y) { tc, ti, x, y } 69static const TCAPINFO table[] = { 70 /* tcap terminfo code state */ 71 DATA( "%1", "khlp", XK_Help, 0 ), 72 DATA( "#1", "kHLP", XK_Help, SHIFT ), 73 DATA( "@0", "kfnd", XK_Find, 0 ), 74 DATA( "*0", "kFND", XK_Find, SHIFT ), 75 DATA( "*6", "kslt", XK_Select, 0 ), 76 DATA( "#6", "kSLT", XK_Select, SHIFT ), 77 78 DATA( "kh", "khome", XK_Home, 0 ), 79 DATA( "#2", "kHOM", XK_Home, SHIFT ), 80 DATA( "@7", "kend", XK_End, 0 ), 81 DATA( "*7", "kEND", XK_End, SHIFT ), 82 83 DATA( "kl", "kcub1", XK_Left, 0 ), 84 DATA( "kr", "kcuf1", XK_Right, 0 ), 85 DATA( "ku", "kcuu1", XK_Up, 0 ), 86 DATA( "kd", "kcud1", XK_Down, 0 ), 87 88 DATA( "#4", "kLFT", XK_Left, SHIFT ), 89 DATA( "%i", "kRIT", XK_Right, SHIFT ), 90 DATA( "kF", "kind", XK_Down, SHIFT ), 91 DATA( "kR", "kri", XK_Up, SHIFT ), 92 93 DATA( "k1", "kf1", XK_Fn(1), 0 ), 94 DATA( "k2", "kf2", XK_Fn(2), 0 ), 95 DATA( "k3", "kf3", XK_Fn(3), 0 ), 96 DATA( "k4", "kf4", XK_Fn(4), 0 ), 97 DATA( "k5", "kf5", XK_Fn(5), 0 ), 98 DATA( "k6", "kf6", XK_Fn(6), 0 ), 99 DATA( "k7", "kf7", XK_Fn(7), 0 ), 100 DATA( "k8", "kf8", XK_Fn(8), 0 ), 101 DATA( "k9", "kf9", XK_Fn(9), 0 ), 102 DATA( "k;", "kf10", XK_Fn(10), 0 ), 103 104 DATA( "F1", "kf11", XK_Fn(11), 0 ), 105 DATA( "F2", "kf12", XK_Fn(12), 0 ), 106 DATA( "F3", "kf13", XK_Fn(13), 0 ), 107 DATA( "F4", "kf14", XK_Fn(14), 0 ), 108 DATA( "F5", "kf15", XK_Fn(15), 0 ), 109 DATA( "F6", "kf16", XK_Fn(16), 0 ), 110 DATA( "F7", "kf17", XK_Fn(17), 0 ), 111 DATA( "F8", "kf18", XK_Fn(18), 0 ), 112 DATA( "F9", "kf19", XK_Fn(19), 0 ), 113 DATA( "FA", "kf20", XK_Fn(20), 0 ), 114 DATA( "FB", "kf21", XK_Fn(21), 0 ), 115 DATA( "FC", "kf22", XK_Fn(22), 0 ), 116 DATA( "FD", "kf23", XK_Fn(23), 0 ), 117 DATA( "FE", "kf24", XK_Fn(24), 0 ), 118 DATA( "FF", "kf25", XK_Fn(25), 0 ), 119 DATA( "FG", "kf26", XK_Fn(26), 0 ), 120 DATA( "FH", "kf27", XK_Fn(27), 0 ), 121 DATA( "FI", "kf28", XK_Fn(28), 0 ), 122 DATA( "FJ", "kf29", XK_Fn(29), 0 ), 123 DATA( "FK", "kf30", XK_Fn(30), 0 ), 124 DATA( "FL", "kf31", XK_Fn(31), 0 ), 125 DATA( "FM", "kf32", XK_Fn(32), 0 ), 126 DATA( "FN", "kf33", XK_Fn(33), 0 ), 127 DATA( "FO", "kf34", XK_Fn(34), 0 ), 128 DATA( "FP", "kf35", XK_Fn(35), 0 ), 129 130 DATA( "FQ", "kf36", -36, 0 ), 131 DATA( "FR", "kf37", -37, 0 ), 132 DATA( "FS", "kf38", -38, 0 ), 133 DATA( "FT", "kf39", -39, 0 ), 134 DATA( "FU", "kf40", -40, 0 ), 135 DATA( "FV", "kf41", -41, 0 ), 136 DATA( "FW", "kf42", -42, 0 ), 137 DATA( "FX", "kf43", -43, 0 ), 138 DATA( "FY", "kf44", -44, 0 ), 139 DATA( "FZ", "kf45", -45, 0 ), 140 DATA( "Fa", "kf46", -46, 0 ), 141 DATA( "Fb", "kf47", -47, 0 ), 142 DATA( "Fc", "kf48", -48, 0 ), 143 DATA( "Fd", "kf49", -49, 0 ), 144 DATA( "Fe", "kf50", -50, 0 ), 145 DATA( "Ff", "kf51", -51, 0 ), 146 DATA( "Fg", "kf52", -52, 0 ), 147 DATA( "Fh", "kf53", -53, 0 ), 148 DATA( "Fi", "kf54", -54, 0 ), 149 DATA( "Fj", "kf55", -55, 0 ), 150 DATA( "Fk", "kf56", -56, 0 ), 151 DATA( "Fl", "kf57", -57, 0 ), 152 DATA( "Fm", "kf58", -58, 0 ), 153 DATA( "Fn", "kf59", -59, 0 ), 154 DATA( "Fo", "kf60", -60, 0 ), 155 DATA( "Fp", "kf61", -61, 0 ), 156 DATA( "Fq", "kf62", -62, 0 ), 157 DATA( "Fr", "kf63", -63, 0 ), 158 159 DATA( "K1", "ka1", XK_KP_Home, 0 ), 160 DATA( "K4", "kc1", XK_KP_End, 0 ), 161 DATA( "K3", "ka3", XK_KP_Prior, 0 ), 162 DATA( "K5", "kc3", XK_KP_Next, 0 ), 163 164#ifdef XK_ISO_Left_Tab 165 DATA( "kB", "kcbt", XK_ISO_Left_Tab, 0 ), 166#endif 167 DATA( "kC", "kclr", XK_Clear, 0 ), 168 DATA( "kD", "kdch1", XK_Delete, 0 ), 169 DATA( "kI", "kich1", XK_Insert, 0 ), 170 171 DATA( "kN", "knp", XK_Next, 0 ), 172 DATA( "kP", "kpp", XK_Prior, 0 ), 173 DATA( "%c", "kNXT", XK_Next, SHIFT ), 174 DATA( "%e", "kPRV", XK_Prior, SHIFT ), 175 176 DATA( "&8", "kund", XK_Undo, 0 ), 177 DATA( "kb", "kbs", XK_BackSpace, 0 ), 178# if OPT_TCAP_QUERY && OPT_ISO_COLORS 179 /* XK_COLORS is a fake code. */ 180 DATA( "Co", "colors", XK_COLORS, 0 ), 181# if OPT_DIRECT_COLOR 182 /* note - termcap cannot support RGB */ 183 DATA( "Co", "RGB", XK_RGB, 0 ), 184 185# endif 186# endif 187 DATA( "TN", "name", XK_TCAPNAME, 0 ), 188#if USE_EXTENDED_NAMES 189#define DEXT(name, parm, code) DATA("", name, code, parm) 190#define D1ST(name, parm, code) DEXT("k" #name, parm, code) 191#define DMOD(name, parm, code) DEXT("k" #name #parm, parm, code) 192 193#define DGRP(name, code) \ 194 D1ST(name, 2, code), \ 195 DMOD(name, 3, code), \ 196 DMOD(name, 4, code), \ 197 DMOD(name, 5, code), \ 198 DMOD(name, 6, code), \ 199 DMOD(name, 7, code), \ 200 DMOD(name, 8, code) 201 202 /* the terminfo codes here are ncurses extensions */ 203 /* ignore the termcap names, which are empty */ 204 DATA( "", "kUP", XK_Up, SHIFT ), 205 DATA( "", "kDN", XK_Up, SHIFT ), 206 207 DGRP(DN, XK_Down), 208 DGRP(LFT, XK_Left), 209 DGRP(RIT, XK_Right), 210 DGRP(UP, XK_Up), 211 DGRP(DC, XK_Delete), 212 DGRP(END, XK_End), 213 DGRP(HOM, XK_Home), 214 DGRP(IC, XK_Insert), 215 DGRP(NXT, XK_Next), 216 DGRP(PRV, XK_Prior), 217#endif 218}; 219#undef DATA 220/* *INDENT-ON* */ 221 222#if OPT_TCAP_FKEYS 223static Boolean 224loadTermcapStrings(TScreen *screen) 225{ 226 Boolean result = True; 227 228 if (screen->tcap_fkeys == NULL) { 229 Cardinal want = XtNumber(table); 230 Cardinal have; 231#if !USE_TERMINFO 232 char *area = screen->tcap_area; 233#endif 234 235 TRACE(("loadTermcapStrings\n")); 236 if ((screen->tcap_fkeys = TypeCallocN(char *, want)) != NULL) { 237 238 for (have = 0; have < want; ++have) { 239 char name[80]; 240 char *fkey; 241 242#if USE_TERMINFO 243 fkey = tigetstr(strcpy(name, table[have].ti)); 244#else 245 fkey = tgetstr(strcpy(name, table[have].tc), &area); 246#endif 247 if (fkey != NULL && fkey != NO_STRING) { 248 screen->tcap_fkeys[have] = x_strdup(fkey); 249 } else { 250 screen->tcap_fkeys[have] = NO_STRING; 251 } 252 } 253 } else { 254 result = False; 255 } 256 } 257 return result; 258} 259#endif 260 261#if OPT_TCAP_QUERY 262static Boolean 263keyIsDistinct(XtermWidget xw, int which) 264{ 265 Boolean result = True; 266 267 switch (xw->keyboard.type) { 268 case keyboardIsTermcap: 269#if OPT_TCAP_FKEYS 270 if (table[which].param == SHIFT) { 271 TScreen *screen = TScreenOf(xw); 272 Cardinal k; 273 274 if (loadTermcapStrings(screen) 275 && screen->tcap_fkeys[which] != NO_STRING) { 276 277 for (k = 0; k < XtNumber(table); k++) { 278 279 if (table[k].code == table[which].code 280 && table[k].param == 0) { 281 char *fkey; 282 283 if ((fkey = screen->tcap_fkeys[k]) != NO_STRING 284 && !strcmp(fkey, screen->tcap_fkeys[which])) { 285 TRACE(("shifted/unshifted keys do not differ\n")); 286 result = False; 287 } 288 break; 289 } 290 } 291 } else { 292 /* there is no data for the shifted key */ 293 result = False; 294 } 295 } 296#endif 297 break; 298 /* 299 * The vt220-keyboard will not return distinct key sequences for 300 * shifted cursor-keys. Just pretend they do not exist, since some 301 * programs may be confused if we return the same data for 302 * shifted/unshifted keys. 303 */ 304 case keyboardIsVT220: 305 if (table[which].param == SHIFT) { 306 TRACE(("shifted/unshifted keys do not differ\n")); 307 result = False; 308 } 309 break; 310 case keyboardIsLegacy: 311 case keyboardIsDefault: 312 case keyboardIsHP: 313 case keyboardIsSCO: 314 case keyboardIsSun: 315 break; 316 } 317 318 return result; 319} 320 321static int 322lookupTcapByName(const char *name) 323{ 324 int result = -2; 325 Cardinal j; 326 327 if (!IsEmpty(name)) { 328 for (j = 0; j < XtNumber(table); j++) { 329 if (!strcmp(table[j].ti, name) || !strcmp(table[j].tc, name)) { 330 result = (int) j; 331 break; 332 } 333 } 334 } 335 336 if (result >= 0) { 337 TRACE(("lookupTcapByName(%s) tc=%s, ti=%s code %#x, param %#x\n", 338 name, 339 table[result].tc, 340 table[result].ti, 341 table[result].code, 342 table[result].param)); 343 } else { 344 TRACE(("lookupTcapByName(%s) FAIL\n", name)); 345 } 346 return result; 347} 348 349/* 350 * Parse the termcap/terminfo name from the string, returning a positive number 351 * (the keysym) if found, otherwise -1. Update the string pointer. 352 * Returns the (shift, control) state in *state. 353 * 354 * This does not attempt to construct control/shift modifiers to construct 355 * function-key values. Instead, it sets the *fkey flag to pass to Input() 356 * and bypass the lookup of keysym altogether. 357 */ 358int 359xtermcapKeycode(XtermWidget xw, const char **params, unsigned *state, Bool *fkey) 360{ 361 const TCAPINFO *data; 362 int code = -1; 363 char *name; 364 const char *p; 365 366 TRACE(("xtermcapKeycode(%s)\n", *params)); 367 368 /* Convert hex encoded name to ascii */ 369 name = x_decode_hex(*params, &p); 370 *params = p; 371 372 *state = 0; 373 *fkey = False; 374 375 if (!IsEmpty(name) && (*p == 0 || *p == ';')) { 376 int which; 377 378 if ((which = lookupTcapByName(name)) >= 0) { 379 if (keyIsDistinct(xw, which)) { 380 data = table + which; 381 code = data->code; 382 *state = xtermParamToState(xw, data->param); 383 if (IsFunctionKey(code)) { 384 *fkey = True; 385 } else if (code < 0) { 386 *fkey = True; 387 code = XK_Fn((-code)); 388 } 389#if OPT_SUN_FUNC_KEYS 390 if (*fkey && xw->keyboard.type == keyboardIsSun) { 391 int num = code - XK_Fn(0); 392 393 /* match function-key case in sunfuncvalue() */ 394 if (num > 20) { 395 if (num <= 30 || num > 47) { 396 code = -1; 397 } else { 398 code -= 10; 399 switch (num) { 400 case 37: /* khome */ 401 case 39: /* kpp */ 402 case 41: /* kb2 */ 403 case 43: /* kend */ 404 case 45: /* knp */ 405 code = -1; 406 break; 407 } 408 } 409 } 410 } 411#endif 412 } else { 413 TRACE(("... name ok, data not ok\n")); 414 code = -1; 415 } 416 } else { 417 TRACE(("... name not ok\n")); 418 code = -2; 419 } 420 } else { 421 TRACE(("... name not ok\n")); 422 code = -2; 423 } 424 425 TRACE(("... xtermcapKeycode(%s, %u, %d) -> %#06x\n", 426 name, *state, *fkey, code)); 427 free(name); 428 return code; 429} 430#endif /* OPT_TCAP_QUERY */ 431 432#if OPT_TCAP_FKEYS 433static int 434nextTcapByCode(int code, unsigned param, int last) 435{ 436 int result = -1; 437 int n; 438 439 TRACE(("lookupTcapByCode %#x:%#x\n", code, param)); 440 for (n = last + 1; n < (int) XtNumber(table); n++) { 441 if (table[n].code == code && 442 table[n].param == param) { 443 TRACE(("->lookupTcapByCode %d:%s\n", n, table[n].ti)); 444 result = n; 445 break; 446 } 447 } 448 return result; 449} 450 451static int 452firstTcapByCode(int code, unsigned param) 453{ 454 return nextTcapByCode(code, param, -1); 455} 456 457int 458xtermcapString(XtermWidget xw, int keycode, unsigned mask) 459{ 460 int result = 0; 461 unsigned param = xtermStateToParam(xw, mask); 462 int which; 463 464 if ((which = firstTcapByCode(keycode, param)) >= 0) { 465 TScreen *screen = TScreenOf(xw); 466 467 if (loadTermcapStrings(screen)) { 468 do { 469 char *fkey; 470 471 if ((fkey = screen->tcap_fkeys[which]) != NO_STRING) { 472 StringInput(xw, (Char *) fkey, strlen(fkey)); 473 result = 1; 474 break; 475 } 476 } while ((which = nextTcapByCode(keycode, param, which)) >= 0); 477 } 478 } 479 480 TRACE(("xtermcapString(keycode=%#x, mask=%#x) ->%d\n", 481 keycode, mask, result)); 482 483 return result; 484} 485#endif /* OPT_TCAP_FKEYS */ 486 487#endif /* OPT_TCAP_QUERY || OPT_TCAP_FKEYS */ 488 489/* 490 * If we're linked to terminfo, tgetent() will return an empty buffer. We 491 * cannot use that to adjust the $TERMCAP variable. 492 */ 493Bool 494get_termcap(XtermWidget xw, char *name) 495{ 496#if USE_TERMINFO 497 int ignored = 0; 498#endif 499 char *buffer = get_tcap_buffer(xw); 500 501 *buffer = 0; /* initialize, in case we're using terminfo's tgetent */ 502 503#if USE_EXTENDED_NAMES 504 use_extended_names(TRUE); 505#endif 506 if (!IsEmpty(name)) { 507 if (TcapInit(buffer, name)) { 508 TRACE(("get_termcap(%s) succeeded (%s)\n", name, 509 (*buffer 510 ? "ok:termcap, we can update $TERMCAP" 511 : "assuming this is terminfo"))); 512 return True; 513 } else { 514 *buffer = 0; /* just in case */ 515 } 516 } 517 return False; 518} 519 520/* 521 * Retrieve the termcap-buffer. 522 */ 523char * 524get_tcap_buffer(XtermWidget xw) 525{ 526 TScreen *screen = TScreenOf(xw); 527 char *buffer; 528 529#if OPT_TEK4014 530 if (TEK4014_ACTIVE(xw)) { 531 buffer = TekScreenOf(tekWidget)->tcapbuf; 532 } else 533#endif 534 { 535 buffer = screen->tcapbuf; 536 } 537 return buffer; 538} 539 540/* 541 * Retrieve the erase-key, for initialization in main program. 542 */ 543#if OPT_INITIAL_ERASE 544char * 545get_tcap_erase(XtermWidget xw) 546{ 547#if !USE_TERMINFO 548 char *area = TScreenOf(xw)->tcap_area; 549#endif 550 char *fkey; 551 552 (void) xw; 553#if USE_TERMINFO 554 fkey = tigetstr("kbs"); 555#else 556 fkey = tgetstr("kb", &area); 557#endif 558 559 if (fkey == NO_STRING) 560 fkey = NULL; 561 if (fkey != NULL) 562 fkey = x_strdup(fkey); 563 return fkey; 564} 565#endif /* OPT_INITIAL_ERASE */ 566 567/* 568 * A legal termcap (or terminfo) name consists solely of graphic characters, 569 * excluding the punctuation used to delimit fields of the source description. 570 */ 571static Bool 572isLegalTcapName(const char *name) 573{ 574 Bool result = False; 575 576 if (*name != '\0') { 577 int length = 0; 578 result = True; 579 while (*name != '\0') { 580 if (++length < 32 && isgraph(CharOf(*name))) { 581 if (strchr("\\|,:'\"", *name) != NULL) { 582 result = False; 583 break; 584 } 585 } else { 586 result = False; 587 break; 588 } 589 ++name; 590 } 591 } 592 593 return result; 594} 595 596void 597set_termcap(XtermWidget xw, const char *name) 598{ 599 Boolean success = False; 600#if USE_TERMINFO 601 int ignored = 0; 602#else 603 TScreen *screen = TScreenOf(xw); 604 char buffer[sizeof(screen->tcapbuf)]; 605#endif 606 607 TRACE(("set_termcap(%s)\n", NonNull(name))); 608 if (IsEmpty(name)) { 609 Bell(xw, XkbBI_MinorError, 0); 610 } else { 611 const char *temp; 612 char *value; 613 614 if ((value = x_decode_hex(name, &temp)) != NULL) { 615 if (*temp == '\0' && isLegalTcapName(value)) { 616 if (TcapInit(buffer, value)) { 617 TRACE(("...set_termcap(%s)\n", NonNull(value))); 618#if !USE_TERMINFO 619 memcpy(screen->tcapbuf, buffer, sizeof(buffer)); 620#endif 621 free_termcap(xw); 622 success = True; 623 } 624 } 625 free(value); 626 } 627 } 628 if (!success) 629 Bell(xw, XkbBI_MinorError, 0); 630} 631 632void 633free_termcap(XtermWidget xw) 634{ 635#if OPT_TCAP_FKEYS 636 TScreen *screen = TScreenOf(xw); 637 638 if (screen->tcap_fkeys != NULL) { 639 Cardinal want = XtNumber(table); 640 Cardinal have; 641 642 for (have = 0; have < want; ++have) { 643 char *fkey = screen->tcap_fkeys[have]; 644 if (fkey != NO_STRING) { 645 free(fkey); 646 } 647 } 648 FreeAndNull(screen->tcap_fkeys); 649 } 650#else 651 (void) xw; 652#endif 653 TcapFree(); 654} 655