/************************************************************************ * * * Copyright (c) 1982, Fred Fish * * All Rights Reserved * * * * This software and/or documentation is released for public * * distribution for personal, non-commercial use only. * * Limited rights to use, modify, and redistribute are hereby * * granted for non-commercial purposes, provided that all * * copyright notices remain intact and all changes are clearly * * documented. The author makes no warranty of any kind with * * respect to this product and explicitly disclaims any implied * * warranties of merchantability or fitness for any particular * * purpose. * * * ************************************************************************ */ #include #include #include #include #include "termcap.h" static char *fgetlr(char *bp, int bpsize, FILE *fp); static FILE *find_file(char *bp); static gotcha(char *bp, char *name); static char *decode(char *bp, char **area); static char *do_esc(char *out, char *in); static void process(void); #define TRUE 1 #define FALSE 0 #define BUFSIZE 1024 /* Assumed size of external buffer */ #define NO_FILE -1 /* Returned if can't open file */ #define NO_ENTRY 0 /* Returned if can't find entry */ #define SUCCESS 1 /* Returned if entry found ok */ #define TRUNCATED 2 /* Returned if entry found but trunc */ # ifdef DGK # define DEFAULT_ROOT "termcap.cnf" /* name without path component */ FILE *fopenp(); # endif # ifdef MSDOS # define DEFAULT_FILE "termcap.dat" # else # define DEFAULT_FILE "/etc/termcap" /* default termcap filename */ # endif char *_tcpbuf; /* Place to remember buffer pointer */ # ifdef MSDOS # define index strchr # endif /* * LIBRARY FUNCTION * * fgetlr get logical record from a file * * KEY WORDS * * fgetlr * string functions * * SYNOPSIS * * char *fgetlr(bp,bpsize,fp) * char *bp; * int bpsize; * FILE *fp; * * DESCRIPTION * * Reads the next logical record from stream "fp" into buffer "bp" * until next unescaped newline, "bpsize" minus one characters * have been read, end of file, or read error. * The last character read is followed by a NULL. * * A logical record may span several physical records by having * each newline escaped with the standard C escape character * (backslash). * * This is particularly useful for things like the termcap * file, where a single entry is too long for one physical * line, yet needs to be treated as a single record. * * Returns its first argument unless an end of file or read * error occurs prior to any characters being read. * * BUGS * * The only way to know if read was terminated due to buffer size * limitation is to test for a newline before the terminating * null. * */ /* * PSEUDO CODE * * Begin fgetlr * If read fails then * Return NULL. * Else * Find out how many characters were read. * Initialize pointer to terminating null. * If last char read was newline then * If newline was escaped then * Replace backslash with the newline. * Replace newline with null. * Read and append more. * End if * End if * Return buffer pointer. * End if * End fgetlr * */ static char *fgetlr(bp,bpsize,fp) char *bp; int bpsize; FILE *fp; { int numch; char *cp; if (fgets(bp,bpsize,fp) == NULL) { return(NULL); } else { numch = strlen(bp); cp = &bp[numch]; if (*--cp == '\n') { if (numch > 1 && *--cp == '\\') { *cp++ = '\n'; *cp = 0; fgetlr(cp,bpsize-numch+1,fp); } } return(bp); } } #ifndef isdigit /* * LIBRARY FUNCTION * * isdigit test character for numeric property * * SYNOPSIS * * int isdigit(ch) * char ch; * * DESCRIPTION * * Returns TRUE or FALSE depending upon whether the specified * character is a numeric character or not. * * BUGS * * May fail on machines in which native character set is not ASCII. * */ int isdigit(ch) char ch; { if (ch > '9' || ch < '0') { return(FALSE); } else { return(TRUE); } } #endif /* * LIBRARY FUNCTION * * tgetent load buffer with entry for specified terminal * * KEY WORDS * * termcap functions * utility routines * * SYNOPSIS * * int tgetent(bp,name) * char *bp; * char *name; * * DESCRIPTION * * Extracts the entry for terminal from the termcap file * and places it in the character buffer . It is currently * assumed that bp is at least 1024 characters. If the entry in * the termcap file is larger than 1023 characters the excess * characters will be discarded and appropriate status will * be returned. * * Also note that since bp is used by other termcap * routines, the storage associated with the termcap entry * cannot be freed until all termcap calls are completed. * * Tgetent can be directed to look in a file other than * the default (/etc/termcap) by defining an environment * variable called TERMCAP to be the pathname of the desired * termcap file. This is useful for debugging new entries. * NOTE: the pathname MUST begin with a '/' character. * * Also, if the string assigned to TERMCAP does not begin with * a '/' and if the environment variable TERM matches then * the string assigned to TERMCAP is copied to buffer * instead of reading a termcap file. * * RETURNS * * -1 if the termcap file cannot be opened * 0 if no entry in termcap file matches * 1 if extraction is successful with no errors * 2 if extraction is successful but entry truncated * * SEE ALSO * * tgetnum extract numeric type capability * tgetflag test boolean type capability * tgetstr get string value of capability * * AUTHOR * * Fred Fish * */ /* * PSEUDO CODE * * Begin tgetent * Erase any previous buffer contents. * Remember the buffer pointer. * If termcap file is not found then * If buffer was filled anyway then * Return SUCCESS. * Else * Return NO_FILE. * End if * Else * While records left to process * If this is entry is what we want then * Close the termcap file. * If entry was truncated then * Return TRUNCATED status * Else * Return SUCCESS status. * End if * End if * End while * Return NO_ENTRY status. * End if * End tgetent * */ int tgetent(bp,name) char *bp; /* Pointer to buffer (1024 char min) */ char *name; /* Pointer to terminal entry to find */ { FILE *fp; *bp = 0; _tcpbuf = bp; if ((fp = find_file(bp)) == NULL) { if (*bp != 0) { return(SUCCESS); } else { return(NO_FILE); } } else { while (fgetlr(bp,BUFSIZE,fp)) { if (gotcha(bp,name)) { fclose(fp); if (bp[strlen(bp)-1] != '\n') { return(TRUNCATED); } else { return(SUCCESS); } } } return(NO_ENTRY); } } /* * INTERNAL FUNCTION * * find_file find the termcap file and open it if possible * * KEY WORDS * * internal functions * find_file * * SYNOPSIS * * static FILE *find_file(bp) * char *bp; * * DESCRIPTION * * Attempts to locate and open the termcap file. Also handles * using the environment TERMCAP string as the actual buffer * (that's why bp has to be an input parameter). * * If TERMCAP is defined an begins with a '/' character then * it is taken to be the pathname of the termcap file and * an attempt is made to open it. If this fails then * the default termcap file is used instead. * * If TERMCAP is defined but does not begin with a '/' then * it is assumed to be the actual buffer contents provided * that matches the environment variable TERM. * * BUGS * * There is currently no way to be sure which termcap * file was opened since the default will always be * tried. * */ /* * PSEUDO CODE * * Begin find_file * If there is a TERMCAP environment string then * If the string is not null then * If the string is a pathname then * If that file is opened successfully then * Return its pointer. * End if * Else * If there is a TERM environment string then * If TERM matches then * Copy TERMCAP string to buffer. * Return NULL for no file. * End if * End if * End if * End if * End if * Open default termcap file and return results. * End find_file * */ static FILE *find_file(bp) char *bp; { FILE *fp; char *cp, *ncp; if ((cp = getenv("TERMCAP")) != NULL) { if (*cp != 0) { if (*cp == '/' || *cp == '\\') { if ((fp = fopen(cp,"r")) != NULL) { return(fp); } } else { if ((ncp = getenv("TERM")) != NULL) { if (strcmp(cp,ncp) == 0) { strcpy(bp,cp); return((FILE *)NULL); } } } } } # ifdef DGK /* Try current directory, then /etc/termcap, then along the path */ if (fp = fopen(DEFAULT_ROOT, "r")) return fp; else if (fp = fopen(DEFAULT_FILE, "r")) return fp; else return fopenp(DEFAULT_ROOT, "r", NULL); # else # ifdef MSDOS { char path[128]; _searchenv(DEFAULT_FILE, "INIT", path); if ( path[0] == 0 ) _searchenv(DEFAULT_FILE, "PATH", path); if ( path[0] == 0 ) _searchenv(DEFAULT_FILE, "DPATH", path); return(fopen(path,"r")); } # else return(fopen(DEFAULT_FILE,"r")); # endif # endif } /* * INTERNAL FUNCTION * * gotcha test to see if entry is for specified terminal * * SYNOPSIS * * gotcha(bp,name) * char *bp; * char *name; * * DESCRIPTION * * Tests to see if the entry in buffer bp matches the terminal * specified by name. Returns TRUE if match is detected, FALSE * otherwise. * */ /* * PSEUDO CODE * * Begin gotcha * If buffer character is comment character then * Return FALSE since remainder is comment * Else * Initialize name scan pointer. * Compare name and buffer until end or mismatch. * If valid terminators for both name and buffer strings * Return TRUE since a match was found. * Else * Find next non-name character in buffer. * If not an alternate name separater character * Return FALSE since no more names to check. * Else * Test next name and return results. * End if * End if * End if * End gotcha * */ static int gotcha(bp,name) char *bp; char *name; { char *np; if (*bp == '#') { return(FALSE); } else { np = name; while (*np == *bp && *np != 0) {np++; bp++;} if (*np == 0 && (*bp == 0 || *bp == '|' || *bp == ':')) { return(TRUE); } else { while (*bp != 0 && *bp != ':' && *bp != '|') {bp++;} if (*bp != '|') { return(FALSE); } else { return(gotcha(++bp,name)); } } } } /* * LIBRARY FUNCTION * * tgetflag extract boolean termcap capability * * KEY WORDS * * termcap * * SYNOPSIS * * tgetflag(id) * char *id; * * DESCRIPTION * * Returns TRUE if specified id is present in terminal * entry, FALSE otherwise. * */ /* * PSEUDO CODE * * Begin tgetflag * Initialize pointer to the termcap entry buffer. * While there is a field to process * Skip over the field separator character. * If this is the entry we want then * If entry is identifier only then * Return TRUE * Else * Return FALSE * End if * End if * End while * Return FALSE as default. * End tgetflag * */ tgetflag(id) char *id; { char *bp; bp = _tcpbuf; while ((bp = index(bp,':')) != NULL) { bp++; if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) { if (*bp == 0 || *bp++ == ':') { return(TRUE); } else { return(FALSE); } } } return(FALSE); } /* * LIBRARY FUNCTION * * tgetnum extract numeric option from termcap entry * * KEY WORDS * * termcap * ce functions * * SYNOPSIS * * tgetnum(id) * char *id; * * DESCRIPTION * * Returns numeric value of capability , or -1 if * is not found. Knows about octal numbers, which * begin with 0. * */ /* * PSEUDO CODE * * Begin tgetnum * Initialize pointer to the termcap entry buffer. * While there is a field to process * Skip over the field separator character. * If this is the entry we want then * If the entry is not a numeric then * Return failure value. * Else * Initialize value to zero. * If number begins with zero then * Set accumulation base to 8. * Else * Set accumulation base to 10. * End if * While there is a numeric character * Accumulate the value. * End while * Return value. * End if * End if * End while * Return failure value. * End tgetnum * */ tgetnum(id) char *id; { int value, base; char *bp; bp = _tcpbuf; while ((bp = index(bp,':')) != NULL) { bp++; if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) { if (*bp != 0 && *bp++ != '#') { return(-1); } else { value = 0; if (*bp == '0') { base = 8; } else { base = 10; } while (isdigit(*bp)) { value *= base; value += (*bp++ - '0'); } return(value); } } } return(-1); } /* * LIBRARY FUNCTION * * tgetstr extract string capability from termcap entry * * KEY WORDS * * termcap * * SYNOPSIS * * char *tgetstr(id,area) * char *id; * char **area; * * DESCRIPTION * * Gets the string capability for , placing it in * the buffer at *area, and advancing *area to point * to next available storage. * * For example, if the following capabilities are * in the termcap file: * * ZZ=zzzz * YY=yyyyyy * WW=www * * then successive calls using YY, ZZ, and WW will * build the following buffer: * * yyyyyy0zzzz0www0 * * The first call will return a pointer to yyyyyy, the * second will return a pointer to zzzz and the third * will return a pointer to www. Note that each * string is null terminated, as are all C strings. * * Characters preceded by the carot character (\136) * are mapped into the corresponding control character. * For example, the two character sequence ^A becomes * a single control-A (\001) character. * * The escape character is the normal C backslash and * the normal C escape sequences are recognized, along * with a special sequence for the ASCII escape character * (\033). The recognized sequences are: * * \E => '\033' (ASCII escape character) * \b => '\010' (ASCII backspace character) * \f => '\014' (ASCII form feed character) * \n => '\012' (ASCII newline/linefeed char) * \r => '\015' (ASCII carriage return char) * \t => '\011' (ASCII tab character) * \ddd => '\ddd' (arbitrary ASCII digit) * \x => 'x' (ordinary ASCII character) * */ /* * PSEUDO CODE * * Begin tgetstr * Initialize pointer to the termcap entry buffer. * While there is a field to process * Skip over the field separator character. * If this is the entry we want then * If the entry is not a string then * Return NULL. * Else * Transfer string and rtn pointer. * End if * End if * End while * Return NULL * End tgetstr * */ char *tgetstr(id,area) char *id; char **area; { char *bp; char *decode(); bp = _tcpbuf; while ((bp = index(bp,':')) != NULL) { bp++; if (*bp++ == id[0] && *bp != 0 && *bp++ == id[1]) { if (*bp != 0 && *bp++ != '=') { return(NULL); } else { return(decode(bp,area)); } } } return(NULL); } /* * INTERNAL FUNCTION * * decode transfer string capability, decoding escapes * * SYNOPSIS * * static char *decode(bp,area) * char *bp; * char **area; * * DESCRIPTION * * Transfers the string capability, up to the next ':' * character, or null, to the buffer pointed to by * the pointer in *area. Note that the initial * value of *area and *area is updated to point * to the next available location after the null * terminating the transfered string. * * BUGS * * There is no overflow checking done on the destination * buffer, so it better be large enough to hold * all expected strings. * */ /* * PSEUDO CODE * * Begin decode * Initialize the transfer pointer. * While there is an input character left to process * Switch on input character * Case ESCAPE: * Decode and xfer the escaped sequence. * Break * Case CONTROLIFY: * Controlify and xfer the next character. * Advance the buffer pointer. * Break * Default: * Xfer a normal character. * End switch * End while * Null terminate the output string. * Remember where the output string starts. * Update the output buffer pointer. * Return pointer to the output string. * End decode * */ static char *decode(bp,area) char *bp; char **area; { char *cp, *bgn; char *do_esc(); cp = *area; while (*bp != 0 && *bp != ':') { switch(*bp) { case '\\': bp = do_esc(cp++,++bp); break; case '^': *cp++ = (char) (*++bp & 037); bp++; break; default: *cp++ = *bp++; break; } } *cp++ = 0; bgn = *area; *area = cp; return(bgn); } /* * INTERNAL FUNCTION * * do_esc process an escaped sequence * * SYNOPSIS * * char *do_esc(out,in); * char *out; * char *in; * * DESCRIPTION * * Processes an escape sequence pointed to by * in, transfering it to location pointed to * by out, and updating the pointer to in. * */ /* * PSEUDO CODE * * Begin do_esc * If the first character is not a NULL then * If is a digit then * Set value to zero. * For up to 3 digits * Accumulate the sum. * End for * Transfer the sum. * Else if character is in remap list then * Transfer the remapped character. * Advance the input pointer once. * Else * Simply transfer the character. * End if * End if * Return updated input pointer. * End do_esc * */ static char *maplist = { "E\033b\bf\fn\nr\rt\t" }; char *do_esc(out,in) char *out; char *in; { int count; char ch; char *cp; if (*in != 0) { if (isdigit(*in)) { ch = 0; for (count = 0; count < 3 && isdigit(*in); in++) { ch <<= 3; ch |= (*in - '0'); } *out++ = ch; } else if ((cp = index(maplist,*in)) != NULL) { *out++ = *++cp; in++; } else { *out++ = *in++; } } return(in); } /* * LIBRARY FUNCTION * * tgoto expand cursor addressing string from cm capability * * KEY WORDS * * termcap * * SYNOPSIS * * char *tgoto(cm,destcol,destline) * char *cm; * int destcol; * int destline; * * DESCRIPTION * * Returns cursor addressing string, decoded from the cm * capability string, to move cursor to column destcol on * line destline. * * The following sequences uses one input argument, either * line or column, and place the appropriate substitution * in the output string: * * %d substitute decimal value (in ASCII) * %2 like %d but forces field width to 2 * %3 like %d but forces field width to 3 * %. like %c * %+x like %c but adds ASCII value of x * * The following sequences cause processing modifications * but do not "use up" one of the arguments. If they * act on an argument they act on the next one to * be converted. * * %>xy if next value to be converted is * greater than value of ASCII char x * then add value of ASCII char y. * %r reverse substitution of line * and column (line is substituted * first by default). * %i causes input values destcol and * destline to be incremented. * %% gives single % character in output. * * BUGS * * Does not implement some of the more arcane sequences for * radically weird terminals (specifically %n, %B, & %D). * If you have one of these you deserve whatever happens. * */ #define MAXARGS 2 static char *in; /* Internal copy of input string pointer */ static char *out; /* Pointer to output array */ static int args[MAXARGS]; /* Maximum number of args to convert */ static int pcount; /* Count of args processed */ static char output[64]; /* Converted string */ /* * PSEUDO CODE * * Begin tgoto * If no string to process then * Return pointer to error string. * Else * Initialize pointer to input string. * Initialize pointer to result string. * First arg is line number by default. * Second arg is col number by default. * No arguments processed yet. * While there is another character to process * If character is a not a % character then * Simply copy to output. * Else * Process the control sequence. * End if * End while * Return pointer to static output string. * End if * End tgoto * */ char *tgoto(cm,destcol,destline) char *cm; int destcol; int destline; { if (cm == NULL) { return("OOPS"); } else { in = cm; out = output; args[0] = destline; args[1] = destcol; pcount = 0; while (*in != 0) { if (*in != '%') { *out++ = *in++; } else { process(); } } *out++ = 0; return(output); } } /* * INTERNAL FUNCTION * * process process the conversion/command sequence * * SYNOPSIS * * static process() * * DESCRIPTION * * Processes the sequence beginning with the % character. * Directly manipulates the input string pointer, the * output string pointer, and the arguments. Leaves * the input string pointer pointing to the next character * to be processed, and the output string pointer pointing * to the next output location. If conversion of * one of the numeric arguments occurs, then the pcount * is incremented. * */ /* * PSEUDO CODE * * Begin process * Skip over the % character. * Switch on next character after % * Case 'd': * Process %d type conversion (variable width). * Reinitialize output pointer. * Break; * Case '2': * Process %d type conversion (width 2). * Reinitialize output pointer. * Break; * Case '3': * Process %d type conversion (width 3). * Reinitialize output pointer. * Break; * Case '.' * Process %c type conversion. * Break; * Case '+': * Process %c type conversion with offset. * Break; * Case '>': * Process argument modification. * Break; * Case 'r': * Process argument reversal. * Break; * Case 'i': * Increment argument values. * Break; * Case '%': * Copy to output, incrementing pointers. * Break; * End switch * End process * */ static void process() { int temp; in++; switch(*in++) { case 'd': sprintf(out,"%d",args[pcount++]); out = &output[strlen(output)]; break; case '2': sprintf(out,"%02d",args[pcount++]); out = &output[strlen(output)]; break; case '3': sprintf(out,"%03d",args[pcount++]); out = &output[strlen(output)]; break; case '.': *out++ = (char) args[pcount++]; break; case '+': *out++ = (char) args[pcount++] + *in++; break; case '>': if (args[pcount] > (int) *in++) { args[pcount] += *in++; } else { in++; } break; case 'r': temp = args[pcount]; args[pcount] = args[pcount+1]; args[pcount+1] = temp; break; case 'i': args[pcount]++; args[pcount+1]++; break; case '%': *out++ = '%'; break; } } /* * LIBRARY FUNCTION * * tputs output string with appropriate padding * * KEY WORDS * * termcap * * SYNOPSIS * * tputs(cp,affcnt,outc) * char *cp; * int affcnt; * int (*outc)(); * * DESCRIPTION * * Outputs string pointed to by cp, using function outc, and * following it with the appropriate number of padding characters. * Affcnt contains the number of lines affected, which is used * as a multiplier for the specified per line pad time. If * per line pad count is not applicable, affcnt should be 1, * NOT zero. * * The format of the string pointed to by cp is: * * [pad time][*] * * where: pad time => time to delay in milliseconds * * => specifies that time is per line * * The pad character is assumed to reside in the external * variable "PC". Also, the external variable "ospeed" * should contain the output speed of the terminal as * encoded in /usr/include/sgtty.h (B0-B9600). * * BUGS * * Digit conversion is based on native character set * being ASCII. * */ /* * Miscellaneous stuff */ # ifndef MSDOS extern char PC; /* Pad character to use */ extern char ospeed; /* Encoding of output speed */ static int times[] = { 0, /* Tenths of ms per char 0 baud */ 2000, /* Tenths of ms per char 50 baud */ 1333, /* Tenths of ms per char 75 baud */ 909, /* Tenths of ms per char 110 baud */ 743, /* Tenths of ms per char 134 baud */ 666, /* Tenths of ms per char 150 baud */ 500, /* Tenths of ms per char 200 baud */ 333, /* Tenths of ms per char 300 baud */ 166, /* Tenths of ms per char 600 baud */ 83, /* Tenths of ms per char 1200 baud */ 55, /* Tenths of ms per char 1800 baud */ 41, /* Tenths of ms per char 2400 baud */ 20, /* Tenths of ms per char 4800 baud */ 10 /* Tenths of ms per char 9600 baud */ }; # endif /* * PSEUDO CODE * * Begin tgoto * If string pointer is invalid then * Return without doing anything. * Else * For each pad digit (if any) * Do decimal left shift. * Accumulate the lower digit. * End for * Adjust scale to tenths of milliseconds * If there is a fractional field * Skip the decimal point. * If there is a valid tenths digit * Accumulate the tenths. * End if * Discard remaining digits. * End if * If per line is specified then * Adjust the pad time. * Discard the per line flag char. * End if * While there are any characters left * Send them out via output function. * End while * Transmit any padding required. * End if * End tgoto * */ void tputs(cp,affcnt,outc) char *cp; int affcnt; int (*outc)(int); { int ptime; /* Pad time in tenths of milliseconds */ if (cp == NULL || *cp == 0) { return; } else { for (ptime = 0; isdigit(*cp); cp++) { ptime *= 10; ptime += (*cp - '0'); } ptime *= 10; if (*cp == '.') { cp++; if (isdigit(*cp)) { ptime += (*cp++ - '0'); } while (isdigit(*cp)) {cp++;} } if (*cp == '*') { ptime *= affcnt; cp++; } while (*cp != 0) { (*outc)(*cp++); } # ifndef MSDOS do_padding(ptime,outc); # endif } } # ifndef MSDOS /* * FUNCTION * * do_padding transmit any pad characters required * * SYNOPSIS * * static do_padding(ptime,outc) * int ptime; * int (*outc)(); * * DESCRIPTION * * Does any padding required as specified by ptime (in tenths * of milliseconds), the output speed given in the external * variable ospeed, and the pad character given in the * external variable PC. * */ /* * PSEUDO CODE * * Begin do_padding * If there is a non-zero pad time then * If the external speed is in range then * Look up the delay per pad character. * Round pad time up by half a character. * Compute number of characters to send. * For each pad character to send * Transmit the pad character. * End for * End if * End if * End do_padding * */ static do_padding(ptime,outc) int ptime; int (*outc)(); { register int nchars; register int tpc; if (ptime != 0) { if (ospeed >= 0 && ospeed <= (sizeof(times)/ sizeof(int))) { tpc = times[ospeed]; ptime += (tpc / 2); nchars = ptime / tpc; for ( ; nchars > 0; --nchars) { (*outc)(PC); } } } } # endif