/* SCCS Id: @(#)tclib.c 3.3 96/02/25 */ /* Copyright (c) Robert Patrick Rankin, 1995 */ /* NetHack may be freely redistributed. See license for details. */ /* termcap library implementation */ #include "config.h" #ifndef TERMCAP /* name of default termcap file */ #define TERMCAP "/etc/termcap" #endif #ifndef TCBUFSIZ /* size of tgetent buffer; Unix man page says 1024 */ #define TCBUFSIZ 1024 #endif #define ESC '\033' /* termcap's '\E' */ #define BEL '\007' /* ANSI C's '\a' (we assume ASCII here...) */ /* exported variables, as per man page */ char PC; char *BC, *UP; short ospeed; /* exported routines */ int FDECL(tgetent, (char *,const char *)); int FDECL(tgetflag, (const char *)); int FDECL(tgetnum, (const char *)); char *FDECL(tgetstr, (const char *,char **)); char *FDECL(tgoto, (const char *,int,int)); char *FDECL(tparam, (const char *,char *,int,int,int,int,int)); void FDECL(tputs, (const char *,int,int (*)())); /* local support data */ static char *tc_entry; static char bc_up_buf[24]; #ifndef NO_DELAY_PADDING /* `ospeed' to baud rate conversion table, adapted from GNU termcap-1.2 */ static short baud_rates[] = { 0, 50, 75, 110, 135, 150, # ifdef VMS 300, 600, 1200, 1800, 2000, 2400, 3600, 4800, 7200, # else /* assume Unix */ 200, 300, 600, 1200, 1800, 2400, 4800, # endif 9600, -192, -384, /* negative is used as `100 * abs(entry)' */ # ifdef VMS -576, -768, -1152, # endif }; #endif /* !NO_DELAY_PADDING */ /* local support code */ static int FDECL(tc_store, (const char *,const char *)); static char *FDECL(tc_find, (FILE *,const char *,char *,int)); static char *FDECL(tc_name, (const char *,char *)); static const char *FDECL(tc_field, (const char *,const char **)); #ifndef min #define min(a,b) ((a)<(b)?(a):(b)) #endif /* retrieve the specified terminal entry and return it in `entbuf' */ int tgetent(entbuf, term) char *entbuf; /* size must be at least [TCBUFSIZ] */ const char *term; { int result; FILE *fp; char *tc = getenv("TERMCAP"); tc_entry = entbuf; if (!entbuf || !term) return -1; /* if ${TERMCAP} is found as a file, it's not an inline termcap entry */ if ((fp = fopen(tc ? tc : TERMCAP, "r")) != 0) tc = 0; /* if ${TERMCAP} isn't a file and `term' matches ${TERM}, use ${TERMCAP} */ if (tc) { char *tm = getenv("TERM"); if (tm && strcmp(tm, term) == 0) return tc_store(term, tc); fp = fopen(TERMCAP, "r"); } /* otherwise, look `term' up in the file */ if (fp) { char wrkbuf[TCBUFSIZ]; tc = tc_find(fp, term, wrkbuf, (int)(sizeof wrkbuf - strlen(term))); result = tc_store(term, tc); (void) fclose(fp); } else { result = -1; } return result; } /* copy the entry into the output buffer */ static int tc_store(trm, ent) const char *trm, *ent; { const char *bar, *col; char *s; size_t n; int k; if (!ent || !*ent || !trm || !*trm || (col = index(ent, ':')) == 0) return 0; (void) strcpy(tc_entry, trm); if (((bar = index(ent, '|')) != 0 && bar < col) || ((long)(n = strlen(trm)) == (long)(col - ent) && strncmp(ent, trm, n) == 0)) (void) strcat(tc_entry, col); else if (*ent == ':') (void) strcat(tc_entry, ent); else (void) strcat(strcat(tc_entry, ":"), ent); /* initialize global variables */ k = tgetnum("pc"); PC = (k == -1) ? '\0' : (char)k; BC = s = bc_up_buf; if (!tgetstr("bc", &s)) (void)strcpy(s, "\b"), s += 2; UP = s; (void)tgetstr("up", &s); #ifndef NO_DELAY_PADDING /* caller must set `ospeed' */ if ((int)ospeed >= (int)SIZE(baud_rates)) ospeed = (short)(SIZE(baud_rates) - 1); else if (ospeed < 0) ospeed = 0; #endif /* !NO_DELAY_PADDING */ return 1; } /* search for an entry in the termcap file */ static char * tc_find(fp, term, buffer, bufsiz) FILE *fp; const char *term; char *buffer; int bufsiz; { int in, len, first, skip; char *ip, *op, *tc_fetch, tcbuf[TCBUFSIZ]; buffer[0] = '\0'; do { ip = tcbuf, in = min(bufsiz,TCBUFSIZ); first = 1, skip = 0; /* load entire next entry, including any continuations */ do { if (!fgets(ip, min(in,BUFSIZ), fp)) break; if (first) skip = (*ip == '#'), first = 0; len = (int)strlen(ip); if (!skip && len > 1 && *(ip + len - 1) == '\n' && *(ip + len - 2) == '\\') len -= 2; ip += len, in -= len; } while (*(ip - 1) != '\n' && in > 0); if (ferror(fp) || ip == buffer || *(ip - 1) != '\n') return (char *)0; *--ip = '\0'; /* strip newline */ if (!skip) ip = tc_name(term, tcbuf); } while (skip || !ip); /* we have the desired entry; strip cruft and look for :tc=other: */ tc_fetch = 0; for (op = buffer; *ip; ip++) { if (op == buffer || *(op - 1) != ':' || (*ip != ' ' && *ip != '\t' && *ip != ':')) *op++ = *ip, bufsiz -= 1; if (ip[0] == ':' && ip[1] == 't' && ip[2] == 'c' && ip[3] == '=') { tc_fetch = &ip[4]; if ((ip = index(tc_fetch, ':')) != 0) *ip = '\0'; break; } } *op = '\0'; if (tc_fetch) { rewind(fp); tc_fetch = tc_find(fp, tc_fetch, tcbuf, min(bufsiz,TCBUFSIZ)); if (!tc_fetch) return (char *)0; if (op > buffer && *(op - 1) == ':' && *tc_fetch == ':') ++tc_fetch; strcpy(op, tc_fetch); } return buffer; } /* check whether `ent' contains `nam'; return start of field entries */ static char * tc_name(nam, ent) const char *nam; char *ent; { char *nxt, *lst, *p = ent; size_t n = strlen(nam); if ((lst = index(p, ':')) == 0) lst = p + strlen(p); while (p < lst) { if ((nxt = index(p, '|')) == 0 || nxt > lst) nxt = lst; if ((long)(nxt - p) == (long)n && strncmp(p, nam, n) == 0) return lst; p = nxt + 1; } return (char *)0; } /* look up a numeric entry */ int tgetnum(which) const char *which; { const char *q, *p = tc_field(which, &q); char numbuf[32]; size_t n; if (!p || p[2] != '#') return -1; p += 3; if ((n = (size_t)(q - p)) >= sizeof numbuf) return -1; (void) strncpy(numbuf, p, n); numbuf[n] = '\0'; return atoi(numbuf); } /* look up a boolean entry */ int tgetflag(which) const char *which; { const char *p = tc_field(which, (const char **)0); return (!p || p[2] != ':') ? 0 : 1; } /* look up a string entry; update `*outptr' */ char * tgetstr(which, outptr) const char *which; char **outptr; { int n; char c, *r, *result; const char *q, *p = tc_field(which, &q); if (!p || p[2] != '=') return (char *)0; p += 3; if ((q = index(p, ':')) == 0) q = p + strlen(p); r = result = *outptr; while (p < q) { switch ((*r = *p++)) { case '\\': switch ((c = *p++)) { case 'E': *r = ESC; break; case 'a': *r = BEL; break; case 'b': *r = '\b'; break; case 'f': *r = '\f'; break; case 'n': *r = '\n'; break; case 'r': *r = '\r'; break; case 't': *r = '\t'; break; case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': n = c - '0'; if (*p >= '0' && *p <= '7') n = 8 * n + (*p++ - '0'); if (*p >= '0' && *p <= '7') n = 8 * n + (*p++ - '0'); *r = (char)n; break; /* case '^': case '\\': */ default: *r = c; break; } break; case '^': *r = (*p++ & 037); if (!*r) *r = (char)'\200'; break; default: break; } ++r; } *r++ = '\0'; *outptr = r; return result; } /* look for a particular field name */ static const char * tc_field(field, tc_end) const char *field; const char **tc_end; { const char *end, *q, *p = tc_entry; end = p + strlen(p); while (p < end) { if ((p = index(p, ':')) == 0) break; ++p; if (p[0] == field[0] && p[1] == field[1] && (p[2] == ':' || p[2] == '=' || p[2] == '#' || p[2] == '@')) break; } if (tc_end) { if (p) { if ((q = index(p + 2, ':')) == 0) q = end; } else q = 0; *tc_end = q; } return p; } static char cmbuf[64]; /* produce a string which will position the cursor at if output */ char * tgoto(cm, col, row) const char *cm; int col, row; { return tparam(cm, cmbuf, (int)(sizeof cmbuf), row, col, 0, 0); } /* format a parameterized string, ala sprintf */ char * tparam(ctl, buf, buflen, row, col, row2, col2) const char *ctl; /* parameter control string */ char *buf; /* output buffer */ int buflen; /* ought to have been `size_t'... */ int row, col, row2, col2; { int atmp, ac, av[5]; char c, *r, *z, *bufend, numbuf[32]; const char *fmt; #ifndef NO_SPECIAL_CHARS_FIXUP int bc = 0, up = 0; #endif av[0] = row, av[1] = col, av[2] = row2, av[3] = col2, av[4] = 0; ac = 0; r = buf, bufend = r + buflen - 1; while (*ctl) { if ((*r = *ctl++) == '%') { if (ac > 4) ac = 4; fmt = 0; switch ((c = *ctl++)) { case '%': break; /* '%' already copied */ case 'd': fmt = "%d"; break; case '2': fmt = "%02d"; break; case '3': fmt = "%03d"; break; case '+': /*FALLTHRU*/ case '.': *r = (char)av[ac++]; if (c == '+') *r += *ctl++; if (!*r) { *r = (char)'\200'; } else { #ifndef NO_SPECIAL_CHARS_FIXUP /* avoid terminal driver intervention for various control characters, to prevent LF from becoming CR+LF, for instance; only makes sense if this is a cursor positioning sequence, but we have no way to check that */ while (index("\004\t\n\013\f\r", *r)) { if (ac & 1) { /* row */ if (!UP || !*UP) break; /* can't fix */ ++up; /* incr row now, later move up */ } else { /* column */ if (!BC || !*BC) break; /* can't fix */ ++bc; /* incr column, later backspace */ } (*r)++; } #endif /* !NO_SPECIAL_CHARS_FIXUP */ } break; case '>': if (av[ac] > (*ctl++ & 0377)) av[ac] += *ctl; ++ctl; break; case 'r': atmp = av[0]; av[0] = av[1]; av[1] = atmp; atmp = av[2]; av[2] = av[3]; av[3] = atmp; --r; break; case 'i': ++av[0]; ++av[1]; ++av[2]; ++av[3]; --r; break; case 'n': av[0] ^= 0140; av[1] ^= 0140; av[2] ^= 0140; av[3] ^= 0140; --r; break; case 'B': av[0] = ((av[0] / 10) << 4) + (av[0] % 10); av[1] = ((av[1] / 10) << 4) + (av[1] % 10); av[2] = ((av[2] / 10) << 4) + (av[2] % 10); av[3] = ((av[3] / 10) << 4) + (av[3] % 10); --r; break; case 'D': av[0] -= (av[0] & 15) << 1; av[1] -= (av[1] & 15) << 1; av[2] -= (av[2] & 15) << 1; av[3] -= (av[3] & 15) << 1; --r; break; default: *++r = c; break; /* erroneous entry... */ } if (fmt) { (void) sprintf(numbuf, fmt, av[ac++]); for (z = numbuf; *z && r <= bufend; z++) *r++ = *z; --r; /* will be re-incremented below */ } } if (++r > bufend) return (char *)0; } #ifndef NO_SPECIAL_CHARS_FIXUP if (bc || up) { while (--bc >= 0) for (z = BC; *z && r <= bufend; z++) *r++ = *z; while (--up >= 0) for (z = UP; *z && r <= bufend; z++) *r++ = *z; if (r > bufend) return (char *)0; } #endif /* !NO_SPECIAL_CHARS_FIXUP */ *r = '\0'; return buf; } /* send a string to the terminal, possibly padded with trailing NULs */ void tputs( string, range, output_func ) const char *string; /* characters to output */ int range; /* number of lines affected, used for `*' delays */ int (*output_func)(); /* actual output routine; return value ignored */ { register int c, num = 0; register const char *p = string; if (!p || !*p) return; /* pick out padding prefix, if any */ if (*p >= '0' && *p <= '9') { do { /* note: scale `num' by 10 to accommodate fraction */ num += (*p++ - '0'), num *= 10; } while (*p >= '0' && *p <= '9'); if (*p == '.') ++p, num += (*p >= '0' && *p <= '9') ? (*p++ - '0') : 0; if (*p == '*') ++p, num *= range; } /* output the string */ while ((c = *p++) != '\0') { if (c == '\200') c = '\0'; /* undo tgetstr's encoding */ (void) (*output_func)(c); } #ifndef NO_DELAY_PADDING /* perform padding */ if (num) { long pad; /* figure out how many chars needed to produce desired elapsed time */ pad = (long)baud_rates[ospeed]; if (pad < 0) pad *= -100L; pad *= (long)num; /* 100000 == 10 bits/char * (1000 millisec/sec scaled by 10) */ num = (int)(pad / 100000L); /* number of characters */ c = PC; /* assume output_func isn't allowed to change PC */ while (--num >= 0) (void) (*output_func)(c); } #endif /* !NO_DELAY_PADDING */ return; } /*tclib.c*/