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