1 /*	SCCS Id: @(#)tclib.c	3.3	96/02/25	*/
2 /* Copyright (c) Robert Patrick Rankin, 1995			  */
3 /* NetHack may be freely redistributed.  See license for details. */
4 
5 /* termcap library implementation */
6 
7 #include "config.h"
8 
9 #ifndef TERMCAP		/* name of default termcap file */
10 #define TERMCAP "/etc/termcap"
11 #endif
12 #ifndef TCBUFSIZ	/* size of tgetent buffer; Unix man page says 1024 */
13 #define TCBUFSIZ 1024
14 #endif
15 #define ESC	'\033'	/* termcap's '\E' */
16 #define BEL	'\007'	/* ANSI C's '\a' (we assume ASCII here...) */
17 
18 /* exported variables, as per man page */
19 char  PC;
20 char *BC, *UP;
21 short ospeed;
22 
23 /* exported routines */
24 int   FDECL(tgetent,  (char *,const char *));
25 int   FDECL(tgetflag, (const char *));
26 int   FDECL(tgetnum,  (const char *));
27 char *FDECL(tgetstr,  (const char *,char **));
28 char *FDECL(tgoto,    (const char *,int,int));
29 char *FDECL(tparam,   (const char *,char *,int,int,int,int,int));
30 void  FDECL(tputs,    (const char *,int,int (*)()));
31 
32 /* local support data */
33 static char *tc_entry;
34 static char bc_up_buf[24];
35 #ifndef NO_DELAY_PADDING
36 /* `ospeed' to baud rate conversion table, adapted from GNU termcap-1.2 */
37 static short baud_rates[] = {
38 	0,   50,   75,  110,  135,  150,
39 # ifdef VMS
40 	    300,  600, 1200, 1800, 2000, 2400, 3600, 4800, 7200,
41 # else		/* assume Unix */
42       200,  300,  600, 1200, 1800,       2400,       4800,
43 # endif
44      9600, -192, -384,		/* negative is used as `100 * abs(entry)' */
45 # ifdef VMS
46      -576, -768, -1152,
47 # endif
48 };
49 #endif	/* !NO_DELAY_PADDING */
50 
51 /* local support code */
52 static int	   FDECL(tc_store, (const char *,const char *));
53 static char	  *FDECL(tc_find,  (FILE *,const char *,char *,int));
54 static char	  *FDECL(tc_name,  (const char *,char *));
55 static const char *FDECL(tc_field, (const char *,const char **));
56 
57 #ifndef min
58 #define min(a,b) ((a)<(b)?(a):(b))
59 #endif
60 
61 /* retrieve the specified terminal entry and return it in `entbuf' */
62 int
tgetent(entbuf,term)63 tgetent(entbuf, term)
64     char *entbuf;	/* size must be at least [TCBUFSIZ] */
65     const char *term;
66 {
67     int result;
68     FILE *fp;
69     char *tc = getenv("TERMCAP");
70 
71     tc_entry = entbuf;
72     if (!entbuf || !term)
73 	return -1;
74     /* if ${TERMCAP} is found as a file, it's not an inline termcap entry */
75     if ((fp = fopen(tc ? tc : TERMCAP, "r")) != 0)
76 	tc = 0;
77     /* if ${TERMCAP} isn't a file and `term' matches ${TERM}, use ${TERMCAP} */
78     if (tc) {
79 	char *tm = getenv("TERM");
80 	if (tm && strcmp(tm, term) == 0)
81 	    return tc_store(term, tc);
82 	fp = fopen(TERMCAP, "r");
83     }
84     /* otherwise, look `term' up in the file */
85     if (fp) {
86 	char wrkbuf[TCBUFSIZ];
87 	tc = tc_find(fp, term, wrkbuf, (int)(sizeof wrkbuf - strlen(term)));
88 	result = tc_store(term, tc);
89 	(void) fclose(fp);
90     } else {
91 	result = -1;
92     }
93     return result;
94 }
95 
96 /* copy the entry into the output buffer */
97 static int
tc_store(trm,ent)98 tc_store(trm, ent)
99     const char *trm, *ent;
100 {
101     const char *bar, *col;
102     char *s;
103     size_t n;
104     int k;
105 
106     if (!ent || !*ent || !trm || !*trm || (col = index(ent, ':')) == 0)
107 	return 0;
108     (void) strcpy(tc_entry, trm);
109     if (((bar = index(ent, '|')) != 0 && bar < col)
110      || ((long)(n = strlen(trm)) == (long)(col - ent)
111 	    && strncmp(ent, trm, n) == 0))
112 	(void) strcat(tc_entry, col);
113     else if (*ent == ':')
114 	(void) strcat(tc_entry, ent);
115     else
116 	(void) strcat(strcat(tc_entry, ":"), ent);
117 
118     /* initialize global variables */
119     k = tgetnum("pc");
120     PC = (k == -1) ? '\0' : (char)k;
121     BC = s = bc_up_buf;
122     if (!tgetstr("bc", &s))  (void)strcpy(s, "\b"),  s += 2;
123     UP = s;
124     (void)tgetstr("up", &s);
125 #ifndef NO_DELAY_PADDING
126     /* caller must set `ospeed' */
127     if ((int)ospeed >= (int)SIZE(baud_rates))
128 	ospeed = (short)(SIZE(baud_rates) - 1);
129     else if (ospeed < 0)
130 	ospeed = 0;
131 #endif	/* !NO_DELAY_PADDING */
132 
133     return 1;
134 }
135 
136 /* search for an entry in the termcap file */
137 static char *
tc_find(fp,term,buffer,bufsiz)138 tc_find(fp, term, buffer, bufsiz)
139     FILE *fp;
140     const char *term;
141     char *buffer;
142     int bufsiz;
143 {
144     int in, len, first, skip;
145     char *ip, *op, *tc_fetch, tcbuf[TCBUFSIZ];
146 
147     buffer[0] = '\0';
148     do {
149 	ip = tcbuf,  in = min(bufsiz,TCBUFSIZ);
150 	first = 1,  skip = 0;
151 	/* load entire next entry, including any continuations */
152 	do {
153 	    if (!fgets(ip, min(in,BUFSIZ), fp))  break;
154 	    if (first)  skip = (*ip == '#'),  first = 0;
155 	    len = (int)strlen(ip);
156 	    if (!skip && len > 1
157 	     && *(ip + len - 1) == '\n' && *(ip + len - 2) == '\\')
158 		len -= 2;
159 	    ip += len,  in -= len;
160 	} while (*(ip - 1) != '\n' && in > 0);
161 	if (ferror(fp) || ip == buffer || *(ip - 1) != '\n')
162 	    return (char *)0;
163 	*--ip = '\0';		/* strip newline */
164 	if (!skip)  ip = tc_name(term, tcbuf);
165     } while (skip || !ip);
166 
167     /* we have the desired entry; strip cruft and look for :tc=other: */
168     tc_fetch = 0;
169     for (op = buffer; *ip; ip++) {
170 	if (op == buffer || *(op - 1) != ':'
171 	 || (*ip != ' ' && *ip != '\t' && *ip != ':'))
172 	    *op++ = *ip,  bufsiz -= 1;
173 	if (ip[0] == ':' && ip[1] == 't' && ip[2] == 'c' && ip[3] == '=') {
174 	    tc_fetch = &ip[4];
175 	    if ((ip = index(tc_fetch, ':')) != 0)  *ip = '\0';
176 	    break;
177 	}
178     }
179     *op = '\0';
180 
181     if (tc_fetch) {
182 	rewind(fp);
183 	tc_fetch = tc_find(fp, tc_fetch, tcbuf, min(bufsiz,TCBUFSIZ));
184 	if (!tc_fetch)
185 	    return (char *)0;
186 	if (op > buffer && *(op - 1) == ':' && *tc_fetch == ':')
187 	    ++tc_fetch;
188 	strcpy(op, tc_fetch);
189     }
190     return buffer;
191 }
192 
193 /* check whether `ent' contains `nam'; return start of field entries */
194 static char *
tc_name(nam,ent)195 tc_name(nam, ent)
196     const char *nam;
197     char *ent;
198 {
199     char *nxt, *lst, *p = ent;
200     size_t n = strlen(nam);
201 
202     if ((lst = index(p, ':')) == 0)  lst = p + strlen(p);
203 
204     while (p < lst) {
205 	if ((nxt = index(p, '|')) == 0 || nxt > lst)  nxt = lst;
206 	if ((long)(nxt - p) == (long)n && strncmp(p, nam, n) == 0)
207 	    return lst;
208 	p = nxt + 1;
209     }
210     return (char *)0;
211 }
212 
213 /* look up a numeric entry */
214 int
tgetnum(which)215 tgetnum(which)
216     const char *which;
217 {
218     const char *q, *p = tc_field(which, &q);
219     char numbuf[32];
220     size_t n;
221 
222     if (!p || p[2] != '#')
223 	return -1;
224     p += 3;
225     if ((n = (size_t)(q - p)) >= sizeof numbuf)
226 	return -1;
227     (void) strncpy(numbuf, p, n);
228     numbuf[n] = '\0';
229     return atoi(numbuf);
230 }
231 
232 /* look up a boolean entry */
233 int
tgetflag(which)234 tgetflag(which)
235     const char *which;
236 {
237     const char *p = tc_field(which, (const char **)0);
238 
239     return (!p || p[2] != ':') ? 0 : 1;
240 }
241 
242 /* look up a string entry; update `*outptr' */
243 char *
tgetstr(which,outptr)244 tgetstr(which, outptr)
245     const char *which;
246     char **outptr;
247 {
248     int n;
249     char c, *r, *result;
250     const char *q, *p = tc_field(which, &q);
251 
252     if (!p || p[2] != '=')
253 	return (char *)0;
254     p += 3;
255     if ((q = index(p, ':')) == 0)  q = p + strlen(p);
256     r = result = *outptr;
257     while (p < q) {
258 	switch ((*r = *p++)) {
259 	 case '\\':
260 	    switch ((c = *p++)) {
261 	     case 'E':	*r = ESC;   break;
262 	     case 'a':	*r = BEL;   break;
263 	     case 'b':	*r = '\b';  break;
264 	     case 'f':	*r = '\f';  break;
265 	     case 'n':	*r = '\n';  break;
266 	     case 'r':	*r = '\r';  break;
267 	     case 't':	*r = '\t';  break;
268 	     case '0': case '1': case '2': case '3':
269 	     case '4': case '5': case '6': case '7':
270 			n = c - '0';
271 			if (*p >= '0' && *p <= '7')  n = 8 * n + (*p++ - '0');
272 			if (*p >= '0' && *p <= '7')  n = 8 * n + (*p++ - '0');
273 			*r = (char)n;
274 			break;
275 	  /* case '^': case '\\': */
276 	     default:	*r = c;     break;
277 	    }
278 	    break;
279 	 case '^':
280 	    *r = (*p++ & 037);
281 	    if (!*r)  *r = (char)'\200';
282 	    break;
283 	 default:
284 	    break;
285 	}
286 	++r;
287     }
288     *r++ = '\0';
289     *outptr = r;
290     return result;
291 }
292 
293 /* look for a particular field name */
294 static const char *
tc_field(field,tc_end)295 tc_field(field, tc_end)
296     const char *field;
297     const char **tc_end;
298 {
299     const char *end, *q, *p = tc_entry;
300 
301     end = p + strlen(p);
302     while (p < end) {
303 	if ((p = index(p, ':')) == 0)
304 	    break;
305 	++p;
306 	if (p[0] == field[0] && p[1] == field[1]
307 	 && (p[2] == ':' || p[2] == '=' || p[2] == '#' || p[2] == '@'))
308 	    break;
309     }
310     if (tc_end) {
311 	if (p) {
312 	    if ((q = index(p + 2, ':')) == 0)  q = end;
313 	} else
314 	    q = 0;
315 	*tc_end = q;
316     }
317     return p;
318 }
319 
320 static char cmbuf[64];
321 
322 /* produce a string which will position the cursor at <row,col> if output */
323 char *
tgoto(cm,col,row)324 tgoto(cm, col, row)
325     const char *cm;
326     int col, row;
327 {
328     return tparam(cm, cmbuf, (int)(sizeof cmbuf), row, col, 0, 0);
329 }
330 
331 /* format a parameterized string, ala sprintf */
332 char *
tparam(ctl,buf,buflen,row,col,row2,col2)333 tparam(ctl, buf, buflen, row, col, row2, col2)
334     const char *ctl;	/* parameter control string */
335     char *buf;		/* output buffer */
336     int buflen;		/* ought to have been `size_t'... */
337     int row, col, row2, col2;
338 {
339     int atmp, ac, av[5];
340     char c, *r, *z, *bufend, numbuf[32];
341     const char *fmt;
342 #ifndef NO_SPECIAL_CHARS_FIXUP
343     int bc = 0, up = 0;
344 #endif
345 
346     av[0] = row,  av[1] = col,  av[2] = row2,  av[3] = col2,  av[4] = 0;
347     ac = 0;
348     r = buf,  bufend = r + buflen - 1;
349     while (*ctl) {
350 	if ((*r = *ctl++) == '%') {
351 	    if (ac > 4)  ac = 4;
352 	    fmt = 0;
353 	    switch ((c = *ctl++)) {
354 	     case '%':			break;  /* '%' already copied */
355 	     case 'd':	fmt = "%d";	break;
356 	     case '2':	fmt = "%02d";	break;
357 	     case '3':	fmt = "%03d";	break;
358 	     case '+':	/*FALLTHRU*/
359 	     case '.':	*r = (char)av[ac++];
360 			if (c == '+')  *r += *ctl++;
361 			if (!*r) {
362 			    *r = (char)'\200';
363 			} else {
364 #ifndef NO_SPECIAL_CHARS_FIXUP
365 			    /* avoid terminal driver intervention for
366 			       various control characters, to prevent
367 			       LF from becoming CR+LF, for instance; only
368 			       makes sense if this is a cursor positioning
369 			       sequence, but we have no way to check that */
370 			    while (index("\004\t\n\013\f\r", *r)) {
371 				if (ac & 1) {		/* row */
372 				    if (!UP || !*UP)  break;	/* can't fix */
373 				    ++up;   /* incr row now, later move up */
374 				} else {		/* column */
375 				    if (!BC || !*BC)  break;	/* can't fix */
376 				    ++bc;   /* incr column, later backspace */
377 				}
378 				(*r)++;
379 			    }
380 #endif	/* !NO_SPECIAL_CHARS_FIXUP */
381 			}						break;
382 	     case '>':	if (av[ac] > (*ctl++ & 0377))
383 			    av[ac] += *ctl;
384 			++ctl;						break;
385 	     case 'r':	atmp = av[0];  av[0] = av[1];  av[1] = atmp;
386 			atmp = av[2];  av[2] = av[3];  av[3] = atmp;
387 			--r;						break;
388 	     case 'i':	++av[0];  ++av[1];  ++av[2];  ++av[3];
389 			--r;						break;
390 	     case 'n':	av[0] ^= 0140;  av[1] ^= 0140;
391 			av[2] ^= 0140;  av[3] ^= 0140;
392 			--r;						break;
393 	     case 'B':	av[0] = ((av[0] / 10) << 4) + (av[0] % 10);
394 			av[1] = ((av[1] / 10) << 4) + (av[1] % 10);
395 			av[2] = ((av[2] / 10) << 4) + (av[2] % 10);
396 			av[3] = ((av[3] / 10) << 4) + (av[3] % 10);
397 			--r;						break;
398 	     case 'D':	av[0] -= (av[0] & 15) << 1;
399 			av[1] -= (av[1] & 15) << 1;
400 			av[2] -= (av[2] & 15) << 1;
401 			av[3] -= (av[3] & 15) << 1;
402 			--r;						break;
403 	     default:	*++r = c;	break;	/* erroneous entry... */
404 	    }
405 	    if (fmt) {
406 		(void) sprintf(numbuf, fmt, av[ac++]);
407 		for (z = numbuf; *z && r <= bufend; z++)
408 		    *r++ = *z;
409 		--r;		/* will be re-incremented below */
410 	    }
411 	}
412 	if (++r > bufend)
413 	    return (char *)0;
414     }
415 #ifndef NO_SPECIAL_CHARS_FIXUP
416     if (bc || up) {
417 	while (--bc >= 0)
418 	    for (z = BC; *z && r <= bufend; z++)
419 		*r++ = *z;
420 	while (--up >= 0)
421 	    for (z = UP; *z && r <= bufend; z++)
422 		*r++ = *z;
423 	if (r > bufend)
424 	    return (char *)0;
425     }
426 #endif	/* !NO_SPECIAL_CHARS_FIXUP */
427     *r = '\0';
428     return buf;
429 }
430 
431 /* send a string to the terminal, possibly padded with trailing NULs */
432 void
tputs(string,range,output_func)433 tputs( string, range, output_func )
434 const char *string;	/* characters to output */
435 int range;		/* number of lines affected, used for `*' delays */
436 int (*output_func)();	/* actual output routine; return value ignored */
437 {
438     register int c, num = 0;
439     register const char *p = string;
440 
441     if (!p || !*p)
442 	return;
443 
444     /* pick out padding prefix, if any */
445     if (*p >= '0' && *p <= '9') {
446 	do {		/* note: scale `num' by 10 to accommodate fraction */
447 	    num += (*p++ - '0'),  num *= 10;
448 	} while (*p >= '0' && *p <= '9');
449 	if (*p == '.')
450 	    ++p,  num += (*p >= '0' && *p <= '9') ? (*p++ - '0') : 0;
451 	if (*p == '*')
452 	    ++p,  num *= range;
453     }
454 
455     /* output the string */
456     while ((c = *p++) != '\0') {
457 	if (c == '\200')  c = '\0';	/* undo tgetstr's encoding */
458 	(void) (*output_func)(c);
459     }
460 
461 #ifndef NO_DELAY_PADDING
462     /* perform padding */
463     if (num) {
464 	long pad;
465 
466 	/* figure out how many chars needed to produce desired elapsed time */
467 	pad = (long)baud_rates[ospeed];
468 	if (pad < 0)  pad *= -100L;
469 	pad *= (long)num;
470 	/* 100000 == 10 bits/char * (1000 millisec/sec scaled by 10) */
471 	num = (int)(pad / 100000L);	/* number of characters */
472 
473 	c = PC;		/* assume output_func isn't allowed to change PC */
474 	while (--num >= 0)
475 	    (void) (*output_func)(c);
476     }
477 #endif	/* !NO_DELAY_PADDING */
478 
479     return;
480 }
481 
482 /*tclib.c*/
483