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