1 /* NetHack 3.7	tclib.c	$NHDT-Date: 1596498287 2020/08/03 23:44:47 $  $NHDT-Branch: NetHack-3.7 $:$NHDT-Revision: 1.10 $ */
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 tgetent(char *, const char *);
25 int tgetflag(const char *);
26 int tgetnum(const char *);
27 char *tgetstr(const char *, char **);
28 char *tgoto(const char *, int, int);
29 char *tparam(const char *, char *, int, int, int, int, int);
30 void tputs(const char *, int, 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 tc_store(const char *, const char *);
53 static char *tc_find(FILE *, const char *, char *, int);
54 static char *tc_name(const char *, char *);
55 static const char *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(char * entbuf,const char * term)63 tgetent(char *entbuf, /* size must be at least [TCBUFSIZ] */
64         const char *term)
65 {
66     int result;
67     FILE *fp;
68     char *tc = getenv("TERMCAP");
69 
70     tc_entry = entbuf;
71     if (!entbuf || !term)
72         return -1;
73     /* if ${TERMCAP} is found as a file, it's not an inline termcap entry */
74     if ((fp = fopen(tc ? tc : TERMCAP, "r")) != 0)
75         tc = 0;
76     /* if ${TERMCAP} isn't a file and `term' matches ${TERM}, use ${TERMCAP}
77      */
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(const char * trm,const char * ent)98 tc_store(const char *trm, const char *ent)
99 {
100     const char *bar, *col;
101     char *s;
102     size_t n;
103     int k;
104 
105     if (!ent || !*ent || !trm || !*trm || (col = index(ent, ':')) == 0)
106         return 0;
107     (void) strcpy(tc_entry, trm);
108     if (((bar = index(ent, '|')) != 0 && bar < col)
109         || ((long) (n = strlen(trm)) == (long) (col - ent)
110             && strncmp(ent, trm, n) == 0))
111         (void) strcat(tc_entry, col);
112     else if (*ent == ':')
113         (void) strcat(tc_entry, ent);
114     else
115         (void) strcat(strcat(tc_entry, ":"), ent);
116 
117     /* initialize global variables */
118     k = tgetnum("pc");
119     PC = (k == -1) ? '\0' : (char) k;
120     BC = s = bc_up_buf;
121     if (!tgetstr("bc", &s))
122         (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(FILE * fp,const char * term,char * buffer,int bufsiz)138 tc_find(FILE *fp, const char *term, char *buffer, int bufsiz)
139 {
140     int in, len, first, skip;
141     char *ip, *op, *tc_fetch, tcbuf[TCBUFSIZ];
142 
143     buffer[0] = '\0';
144     do {
145         ip = tcbuf, in = min(bufsiz, TCBUFSIZ);
146         first = 1, skip = 0;
147         /* load entire next entry, including any continuations */
148         do {
149             if (!fgets(ip, min(in, BUFSIZ), fp))
150                 break;
151             if (first)
152                 skip = (*ip == '#'), first = 0;
153             len = (int) strlen(ip);
154             if (!skip && len > 1 && *(ip + len - 1) == '\n'
155                 && *(ip + len - 2) == '\\')
156                 len -= 2;
157             ip += len, in -= len;
158         } while (*(ip - 1) != '\n' && in > 0);
159         if (ferror(fp) || ip == buffer || *(ip - 1) != '\n')
160             return (char *) 0;
161         *--ip = '\0'; /* strip newline */
162         if (!skip)
163             ip = tc_name(term, tcbuf);
164     } while (skip || !ip);
165 
166     /* we have the desired entry; strip cruft and look for :tc=other: */
167     tc_fetch = 0;
168     for (op = buffer; *ip; ip++) {
169         if (op == buffer || *(op - 1) != ':'
170             || (*ip != ' ' && *ip != '\t' && *ip != ':'))
171             *op++ = *ip, bufsiz -= 1;
172         if (ip[0] == ':' && ip[1] == 't' && ip[2] == 'c' && ip[3] == '=') {
173             tc_fetch = &ip[4];
174             if ((ip = index(tc_fetch, ':')) != 0)
175                 *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(const char * nam,char * ent)195 tc_name(const char *nam, char *ent)
196 {
197     char *nxt, *lst, *p = ent;
198     size_t n = strlen(nam);
199 
200     if ((lst = index(p, ':')) == 0)
201         lst = p + strlen(p);
202 
203     while (p < lst) {
204         if ((nxt = index(p, '|')) == 0 || nxt > lst)
205             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(const char * which)215 tgetnum(const char *which)
216 {
217     const char *q, *p = tc_field(which, &q);
218     char numbuf[32];
219     size_t n;
220 
221     if (!p || p[2] != '#')
222         return -1;
223     p += 3;
224     if ((n = (size_t)(q - p)) >= sizeof numbuf)
225         return -1;
226     (void) strncpy(numbuf, p, n);
227     numbuf[n] = '\0';
228     return atoi(numbuf);
229 }
230 
231 /* look up a boolean entry */
232 int
tgetflag(const char * which)233 tgetflag(const char *which)
234 {
235     const char *p = tc_field(which, (const char **) 0);
236 
237     return (!p || p[2] != ':') ? 0 : 1;
238 }
239 
240 /* look up a string entry; update `*outptr' */
241 char *
tgetstr(const char * which,char ** outptr)242 tgetstr(const char *which, char **outptr)
243 {
244     int n;
245     char c, *r, *result;
246     const char *q, *p = tc_field(which, &q);
247 
248     if (!p || p[2] != '=')
249         return (char *) 0;
250     p += 3;
251     if ((q = index(p, ':')) == 0)
252         q = p + strlen(p);
253     r = result = *outptr;
254     while (p < q) {
255         switch ((*r = *p++)) {
256         case '\\':
257             switch ((c = *p++)) {
258             case 'E':
259                 *r = ESC;
260                 break;
261             case 'a':
262                 *r = BEL;
263                 break;
264             case 'b':
265                 *r = '\b';
266                 break;
267             case 'f':
268                 *r = '\f';
269                 break;
270             case 'n':
271                 *r = '\n';
272                 break;
273             case 'r':
274                 *r = '\r';
275                 break;
276             case 't':
277                 *r = '\t';
278                 break;
279             case '0':
280             case '1':
281             case '2':
282             case '3':
283             case '4':
284             case '5':
285             case '6':
286             case '7':
287                 n = c - '0';
288                 if (*p >= '0' && *p <= '7')
289                     n = 8 * n + (*p++ - '0');
290                 if (*p >= '0' && *p <= '7')
291                     n = 8 * n + (*p++ - '0');
292                 *r = (char) n;
293                 break;
294             /* case '^': case '\\': */
295             default:
296                 *r = c;
297                 break;
298             }
299             break;
300         case '^':
301             *r = (*p++ & 037);
302             if (!*r)
303                 *r = (char) '\200';
304             break;
305         default:
306             break;
307         }
308         ++r;
309     }
310     *r++ = '\0';
311     *outptr = r;
312     return result;
313 }
314 
315 /* look for a particular field name */
316 static const char *
tc_field(const char * field,const char ** tc_end)317 tc_field(const char *field, const char **tc_end)
318 {
319     const char *end, *q, *p = tc_entry;
320 
321     end = p + strlen(p);
322     while (p < end) {
323         if ((p = index(p, ':')) == 0)
324             break;
325         ++p;
326         if (p[0] == field[0] && p[1] == field[1]
327             && (p[2] == ':' || p[2] == '=' || p[2] == '#' || p[2] == '@'))
328             break;
329     }
330     if (tc_end) {
331         if (p) {
332             if ((q = index(p + 2, ':')) == 0)
333                 q = end;
334         } else
335             q = 0;
336         *tc_end = q;
337     }
338     return p;
339 }
340 
341 static char cmbuf[64];
342 
343 /* produce a string which will position the cursor at <row,col> if output */
344 char *
tgoto(const char * cm,int col,int row)345 tgoto(const char *cm, int col, int row)
346 {
347     return tparam(cm, cmbuf, (int) (sizeof cmbuf), row, col, 0, 0);
348 }
349 
350 /* format a parameterized string, ala sprintf */
351 char *
tparam(const char * ctl,char * buf,int buflen,int row,int col,int row2,int col2)352 tparam(const char *ctl, /* parameter control string */
353        char *buf,       /* output buffer */
354        int buflen,      /* ought to have been `size_t'... */
355        int row, int col, int row2, int col2)
356 {
357     int atmp, ac, av[5];
358     char c, *r, *z, *bufend, numbuf[32];
359     const char *fmt;
360 #ifndef NO_SPECIAL_CHARS_FIXUP
361     int bc = 0, up = 0;
362 #endif
363 
364     av[0] = row, av[1] = col, av[2] = row2, av[3] = col2, av[4] = 0;
365     ac = 0;
366     r = buf, bufend = r + buflen - 1;
367     while (*ctl) {
368         if ((*r = *ctl++) == '%') {
369             if (ac > 4)
370                 ac = 4;
371             fmt = 0;
372             switch ((c = *ctl++)) {
373             case '%':
374                 break; /* '%' already copied */
375             case 'd':
376                 fmt = "%d";
377                 break;
378             case '2':
379                 fmt = "%02d";
380                 break;
381             case '3':
382                 fmt = "%03d";
383                 break;
384             case '+': /*FALLTHRU*/
385             case '.':
386                 *r = (char) av[ac++];
387                 if (c == '+')
388                     *r += *ctl++;
389                 if (!*r) {
390                     *r = (char) '\200';
391                 } else {
392 #ifndef NO_SPECIAL_CHARS_FIXUP
393                     /* avoid terminal driver intervention for
394                        various control characters, to prevent
395                        LF from becoming CR+LF, for instance; only
396                        makes sense if this is a cursor positioning
397                        sequence, but we have no way to check that */
398                     while (index("\004\t\n\013\f\r", *r)) {
399                         if (ac & 1) { /* row */
400                             if (!UP || !*UP)
401                                 break; /* can't fix */
402                             ++up;      /* incr row now, later move up */
403                         } else {       /* column */
404                             if (!BC || !*BC)
405                                 break; /* can't fix */
406                             ++bc;      /* incr column, later backspace */
407                         }
408                         (*r)++;
409                     }
410 #endif /* !NO_SPECIAL_CHARS_FIXUP */
411                 }
412                 break;
413             case '>':
414                 if (av[ac] > (*ctl++ & 0377))
415                     av[ac] += *ctl;
416                 ++ctl;
417                 break;
418             case 'r':
419                 atmp = av[0];
420                 av[0] = av[1];
421                 av[1] = atmp;
422                 atmp = av[2];
423                 av[2] = av[3];
424                 av[3] = atmp;
425                 --r;
426                 break;
427             case 'i':
428                 ++av[0];
429                 ++av[1];
430                 ++av[2];
431                 ++av[3];
432                 --r;
433                 break;
434             case 'n':
435                 av[0] ^= 0140;
436                 av[1] ^= 0140;
437                 av[2] ^= 0140;
438                 av[3] ^= 0140;
439                 --r;
440                 break;
441             case 'B':
442                 av[0] = ((av[0] / 10) << 4) + (av[0] % 10);
443                 av[1] = ((av[1] / 10) << 4) + (av[1] % 10);
444                 av[2] = ((av[2] / 10) << 4) + (av[2] % 10);
445                 av[3] = ((av[3] / 10) << 4) + (av[3] % 10);
446                 --r;
447                 break;
448             case 'D':
449                 av[0] -= (av[0] & 15) << 1;
450                 av[1] -= (av[1] & 15) << 1;
451                 av[2] -= (av[2] & 15) << 1;
452                 av[3] -= (av[3] & 15) << 1;
453                 --r;
454                 break;
455             default:
456                 *++r = c;
457                 break; /* erroneous entry... */
458             }
459             if (fmt) {
460                 (void) sprintf(numbuf, fmt, av[ac++]);
461                 for (z = numbuf; *z && r <= bufend; z++)
462                     *r++ = *z;
463                 --r; /* will be re-incremented below */
464             }
465         }
466         if (++r > bufend)
467             return (char *) 0;
468     }
469 #ifndef NO_SPECIAL_CHARS_FIXUP
470     if (bc || up) {
471         while (--bc >= 0)
472             for (z = BC; *z && r <= bufend; z++)
473                 *r++ = *z;
474         while (--up >= 0)
475             for (z = UP; *z && r <= bufend; z++)
476                 *r++ = *z;
477         if (r > bufend)
478             return (char *) 0;
479     }
480 #endif /* !NO_SPECIAL_CHARS_FIXUP */
481     *r = '\0';
482     return buf;
483 }
484 
485 /* send a string to the terminal, possibly padded with trailing NULs */
486 void
tputs(const char * string,int range,int (* output_func)(int))487 tputs(const char *string, /* characters to output */
488       int range,          /* number of lines affected, used for `*' delays */
489       int (*output_func)(int)) /* actual output routine;
490                                   * return value ignored */
491 {
492     register int c, num = 0;
493     register const char *p = string;
494 
495     if (!p || !*p)
496         return;
497 
498     /* pick out padding prefix, if any */
499     if (*p >= '0' && *p <= '9') {
500         do { /* note: scale `num' by 10 to accommodate fraction */
501             num += (*p++ - '0'), num *= 10;
502         } while (*p >= '0' && *p <= '9');
503         if (*p == '.')
504             ++p, num += (*p >= '0' && *p <= '9') ? (*p++ - '0') : 0;
505         if (*p == '*')
506             ++p, num *= range;
507     }
508 
509     /* output the string */
510     while ((c = *p++) != '\0') {
511         if (c == '\200')
512             c = '\0'; /* undo tgetstr's encoding */
513         (void) (*output_func)(c);
514     }
515 
516 #ifndef NO_DELAY_PADDING
517     /* perform padding */
518     if (num) {
519         long pad;
520 
521         /* figure out how many chars needed to produce desired elapsed time */
522         pad = (long) baud_rates[ospeed];
523         if (pad < 0)
524             pad *= -100L;
525         pad *= (long) num;
526         /* 100000 == 10 bits/char * (1000 millisec/sec scaled by 10) */
527         num = (int) (pad / 100000L); /* number of characters */
528 
529         c = PC; /* assume output_func isn't allowed to change PC */
530         while (--num >= 0)
531             (void) (*output_func)(c);
532     }
533 #endif /* !NO_DELAY_PADDING */
534 
535     return;
536 }
537 
538 /*tclib.c*/
539