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