1 /* SCCS Id: @(#)tclib.c 3.4 1996/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