[comp.os.vms] Termcap library for VAX/VMS SOURCE

jv@mhres.mh.nl (Johan Vromans) (09/02/87)

Here is a UNIX compatible termcap library for VAX/VMS. It implements 
the following termcap(3) routines:

	tgetent (buf, termname)
	tgetnum (id)
	tgetflag (id)
	tgetstr (id)
	tgoto (cm, x, y)
	tputs (str, affcnt, outc)

	all according to the UNIX termcap(3) documentation.

It includes a vms-specific getenv routine, which allows changing your
terminal type with a DEFINE TERM "type" command, e.g.

	DEFINE TERM "hpex"

Don't forget the quotes around the (lowercased) terminal type.

It can read termcap info from a termcap file, but also has a number of
terminal types built-in. See the source for details.

BTW - it is also usable on Unix. (If you don't want to use the curses
emulation of termcap).

Enjoy!

--
Johan Vromans                              | jv@mh.nl via European backbone
Multihouse N.V., Gouda, the Netherlands    | uucp: ..{?????!}mcvax!mh.nl!jv
"It is better to light a candle than to curse the darkness"

[ watch out - there will be another signature at the end of this message ]

-------------------------------- CUT HERE --------------------------------
/*-**-* !<sar> */
/*-**-* termcap.c */

/* tcap.c - termcap routines
 *
 *	This is a termcap compatible library, usable on UNIX and VAX/VMS
 *	systems.
 *
 *	It can read a termcap file (predefined by TERMCAP) or use built-in
 *	entries.
 *
 *	The calling sequence of all routines, and their behaviour is as
 *	documented in termcap(3). Therefore, no additiona documentation
 *	is supplied.
 *
 *	Compile-time options:
 *
 *	TERMCAP		the name of the termcap file, if different from
 *			the default values
 *	GETENV		use vms specific getenv routine, which translates
 *			logical names before passing to the 'real' getenv.
 *			This is required when you want to change your
 *			terminal type using a DEFINE command.
 *	DEBUG		compile into a test program, which can be used for
 *			debugging.
 *
 *	Note: padding of delay characters (PC and ospeed) is not supported.
 *
 *	(c) COPYRIGHT 1986, 1987 by Johan Vromans, Multihouse Research
 *
 *	This software is in public domain and may be used and
 *	distributed freely for non-commercial and non-military purposes,
 *	provided that the above copyright notice and this distribution
 *	restriction is retained.
 *
 *	Bugs, remarks and such to jv@mh.nl via european backbone (mcvax)
 */

#ifdef DEBUG
#include <stdio.h>
#endif	/* DEBUG */

#include <ctype.h>
#define EOS	'\0'

/* built-in terminal types.
 *
 * vtXXX-YY is normally delivered by getenv("TERM") on most VMS systems.
 *
 */

static char *tcp[] = {
  /* common vt100. Note that XA and XD make use of the fact that
     the second argument of tgoto is repeated */

  "vt|vt100|vt100-80|vt100-80am|vt100-am:\
	:am:bl=^G:bs:cd=50\\E[J:ce=3\\E[K:cl=50\\E[;H\\E[2J:cm=5\\E[%i%d;%dH:\
	:co#80:cr=^M:cs=\\E[%i%d;%dr:do=^J:ho=\\E[H:is=\\E[1;24r\\E[24;1H:\
	:k1=\\EOP:k2=\\EOQ:k3=\\EOR:k4=\\EOS:kb=^H:kd=\\EOB:ke=\\E[?1l\\E>:\
	:kl=\\EOD:kr=\\EOC:ks=\\E[?1h\\E=:ku=\\EOA:le=^H:li#24:mb=2\\E[5m:\
	:md=2\\E[1m:me=2\\E[m:mr=2\\E[7m:nd=2\\E[C:nl=^J:pt:rc=\\E8:\
	:rf=/usr/lib/tabset/vt100:rs=\\E>\\E[?3l\\E[?4l\\E[?5l\\E[?7h\\E[?8h:\
	:sc=\\E7:se=2\\E[m:so=2\\E[7m:sr=5\\EM:ta=^I:ue=2\\E[m:up=2\\E[A:\
	:us=2\\E[4m:vt#3:xn:xo:",

  "vt|vt100-w|vt100-132|vt100-132am|vt100-w-am:\
	:co#132:tc=vt100:",

  "vt|vt102|vt102-80|vt102-80am|vt102-am:\
	al=\\E[L:de=\\E[M:dc=\\E[P:im=\\E[4h:ei=\\E[4l:\
	:tc=vt100:",

  "vt|vt102-w|vt102-132|vt102-132am|vt102-w-am:\
	:co#132:tc=vt102:",

  "vt|vt200|vt200-80|vt200-80am|vt200-am:\
   :kh=\\E[2~:X1=\\E[29~:X2=\\E[28~:kH=\\E[5~:\
   :K1=\\E[1~:K5=\\E[6~:K3=\\E[3~:K4=\\E[4~:tc=vt102:",

  "vt|vt200-w|vt200-132|vt200-132am|vt200-w-am:\
   :co#132:tc=vt200:",

  "hp|hpex:\
   :am:xs:xo:da:db:mi:bs:co#80:li#24:lm#0:bt=\\Ei:bl=^G:cr=^M:ct=\\E3:\
   :cl=\\E&a0y0C\\EJ:ce=\\EK:cd=\\EJ:ch=\\E&a%dC:cm=\\E&a%dy%dC:\
   :do=\\EB:le=\\b:nd=\\EC:up=\\EA:dc=\\EP:dl=\\EM:im=\\EQ:so=\\E&dB:\
   :us=\\E&dD:me=\\E&d@:ei=\\ER:se=\\E&d@:ue=\\E&d@:\
   :al=\\EL:kb=\\b:kd=\\EB:kh=\\Eh:kl=\\ED:\
   :kr=\\EC:ku=\\EA:ke=\\E&s0A:ks=\\E&s1A:cv=\\E&a%dY:sf=\\n:st=\\E1:\
   :ta=\\t:",

  "hp|hp2622|hp2382|hp2392:\
   :tc=hpex:",

  0 };

char *getenv();

#ifdef vaxc

/* Specific vms-routines */

#include <descrip.h>
#include <ssdef.h>

#ifdef GETENV

/* Vaxc uses built-in values for environment variables like HOME and TERM.
 * Therefore, it is not possible to change one's TERM setting with a
 * DEFINE command.
 *
 * This version of getenv first uses sys$trnlog to translate any logicals,
 * and passes to the 'real' getenv only if this translation fails.
 */

char *
vms_getenv(arg)
char *arg;
{
    int	status;
    $DESCRIPTOR (desc, "");
    $DESCRIPTOR (dd, "");
    static char buffer[64] = "";
    short cnt;

    desc.dsc$a_pointer = arg;
    desc.dsc$w_length = strlen(arg);
    dd.dsc$a_pointer = buffer;
    dd.dsc$w_length = sizeof (buffer);
    status = sys$trnlog (&desc, &cnt, &dd, 0, 0, 0);
    if (status == SS$_NORMAL) {
	    buffer[cnt] = EOS;
	    return(buffer);
    }

    return(getenv(arg));
}

# define getenv vms_getenv

#endif	/* GETENV */
#endif	/* vaxc */

char *strcpy ();
static char *data;

#define	BUFSIZ	1024
#define MAX_TC	32	/* max number of tc= indirections */

static	int tccount;	/* detect infinite loops in termcap, init 0 */

#ifdef vaxc
#define TERMCAP	"sys$library:termcap.dat"
#endif

#ifndef TERMCAP
#define TERMCAP	"/etc/termcap"	/* UNIX compatible */
#endif

#define STDERR	2
#define wrerr(txt)	write (STDERR, txt, sizeof (txt))

int
tgetent (bp, name)
char *bp;
char *name;
{
    register int c;
    register int i = 0, cnt = 0;
    char ibuf[BUFSIZ];
    int fd = 0;
    register char *cp = getenv ("TERMCAP");   
    char *term = getenv ("TERM");

    data = bp;

#ifdef DEBUG
    fprintf (stderr, "TERM = \"%s\"\n", term ? term : "<null>");
#endif	/* DEBUG */

    /* TERMCAP can be the name of a file to use instead of /etc/termcap,
     * or it can be an entry.
     */
    if (cp && *cp) {
#ifdef DEBUG
	fprintf (stderr, "TERMCAP = \"%s\"\n", cp);
#endif	/* DEBUG */

	/* consider it to be a filename if it start with [ or _ (VMS)
	 * or / (UNIX). Note VMS does also understand filenames with slashes.
	 */

	if (
#ifdef vaxc
	    *cp == '[' || *cp == "_" ||
#endif	/* vaxc */
	    *cp == '/' ) {
#ifdef DEBUG
	    fprintf (stderr, "using file \"%s\"\n", cp);
#endif	/* DEBUG */
	    fd = open (cp, 0);
	}
	else {
	    if (term == (char *) 0 || strcmp (name, term) == 0) {
		strcpy (bp, cp);
		return (tc_check ());
	    }
	}
    }

    if (fd == 0) {
#ifdef DEBUG
	fprintf (stderr, "using file \"%s\"\n", TERMCAP);
#endif	/* DEBUG */
	fd = open (TERMCAP, 0);
    }

#ifdef vaxc
    if (term && !strcmp (term, name) && getenv("TERM"))
	term = name = getenv("TERM");
#endif	/* vaxc */

    if (fd < 0) {                           
	char **tcpp = tcp;
#ifdef DEBUG
	perror ("termcap");
	fprintf (stderr, "using built-ins\n");
#endif	/* DEBUG                */
	while (*tcpp) {
	    strcpy (data, *tcpp);
	    if (lku_name (name))
		return (tc_check ());
	    else
		tcpp++;          
	}      
	*data = EOS;
	return (-1);
    }
    for (;;) {
	cp = bp;
	for (;;) {
	    if (i == cnt) {
		cnt = read (fd, ibuf, BUFSIZ);
		if (cnt <= 0) {
		    close (fd);
#ifdef DEBUG
		    perror ("termcap");
#endif	/* DEBUG */
		    return (0);
		}
		i = 0;
		while ((c = ibuf[i]) == ' ' || c == '\t')
		    i++;
	    }
	    c = ibuf[i++];
	    if (c == '\n') {
		if (cp > bp && cp[-1] == '\\'){
		    cp--;
		    continue;
		}
		break;
	    }
	    if (cp >= bp+BUFSIZ) {
		wrerr ("termcap entry too long\n");
		break;
	    }
	    else
		*cp++ = c;
	}
	*cp = EOS;

	if (lku_name (name)) {
	    close (fd);
	    return (tc_check ());
	}
    }
}

/* tc_check: check the last entry for "tc=xyz". If so, find xyz and append it.
 */

static int
tc_check ()
{
    register char *p, *q;
    char tcname[16];	/* name of similar terminal */
    char tcbuf[BUFSIZ];
    char *holddata = data;
    int l;

    p = data + strlen (data) - 2;	/* before the last colon */
    while (*--p != ':')
	if (p<data) {
	    wrerr ("bad termcap entry\n");
	    return (0);
	}
    p++;
    /* p now points to beginning of last field */
    if (p[0] != 't' || p[1] != 'c')
	return (1);
    strcpy (tcname, p+3);
    q = tcname;
    while (q && *q != ':')
	q++;
    *q = EOS;
    if (++tccount > MAX_TC) {
	wrerr ("too much tc='s\n");
	return (0);
    }
    if (tgetent (tcbuf, tcname) != 1)
	return (0);
    for (q = tcbuf; *q != ':'; q++)
	;
    l = p - holddata + strlen (q);
    if (l > BUFSIZ) {
	wrerr ("termcap entry too long\n");
	q[BUFSIZ - (p-data)] = EOS;
    }
    strcpy (p, q+1);
    data = holddata;
    return (1);
}

/* The first field of the termcap entry is a sequence of names separated
 * by |'s, so we compare against each such name. The : after the last name
 * terminates the search.
 */

static int
lku_name (namep)
char *namep;
{   
    char *cp1;
    char *cp2 = data;

#ifdef DEBUG
	fprintf (stderr, "searching \"%s\"\n", namep);
#endif	/* DEBUG */

    if (*cp2 == '#')
	return (0);
    for (;;) {
	for (cp1 = namep; *cp1 && *cp2 == *cp1; cp2++, cp1++)
	    continue;
	if (*cp1 == EOS && (*cp2 == '|' || *cp2 == ':' || *cp2 == EOS))
	    return (1);
	while (*cp2 && *cp2 != ':' && *cp2 != '|')
	    cp2++;
	if (*cp2 == EOS || *cp2 == ':')
	    return (0);
	cp2++;
    }
}                                                   

static advance (ptr)
char **ptr;
{
    while (**ptr) {
	if (**ptr == '\\') (*ptr) += 2;
	else
	if (**ptr == ':') {
	    (*ptr)++;
	    return (1);
	}
	else
	(*ptr)++;
    }
    return (0);
}

tgetnum (id)
char *id;
{
    char *cp = data;
    char c = id[0];
    int len = strlen (id);

    while (advance (&cp))
	if (*cp == c && !strncmp (id, cp, len))
	    if (cp[len] == '@')
		break;
	    else
	    if (cp[len] == '#')
		return (atoi (cp+len+1));
    return (-1);
}

tgetflag (id)
char *id;
{
    char *cp = data;
    char c = id[0];
    int len = strlen (id);

    while (advance (&cp))
	if (*cp == c && !strncmp (id, cp, len))
	    if (cp[len] == '@')
		break;
	    else
		return (1);
    return (0);
}

char *tgetstr (id, area)
char *id;
char **area;
{
    char *cp = data;
    char c = id[0];
    int len = strlen (id);

    while (advance (&cp))
	if (*cp == c && !strncmp (id, cp, len))
	    if (cp[len] == '@')
		return (0);
	    else
	    if (cp[len] == '=') {
		char *here = *area;
		cp += len+1;
		while (*cp) {
		    if ((c = *cp++) == '\\' && *cp) {
			if ((c = *cp++) == 'E') c = '\033';
			else
			if (c == 'b') c = '\b';
			else
			if (c == 'n') c = '\n';
			else
			if (c == 'r') c = '\r';
			else
			if (c == 't') c = '\t';
			else
			if (c == 'f') c = '\f';
			else
			if (isdigit (c)) {
			    c = c - '0';
			    if (isdigit (*cp)) {
				c = (c << 3) + (*cp++) - '0';
				if (isdigit (*cp)) {
				    c = (c << 3) + (*cp++) - '0';
				}
			    }
			}
			else
			    ;
		    }
		    else
		    if (c == '^' && *cp) {
			c = (*cp++) & 037;
		    }
		    else
		    if (c == ':') 
			break;
		    *(*area)++ = c;
		}
		*(*area)++ = EOS;
		return (here);
	    }
    return (0);
}

/*-**-* tgoto.c */

char *BC;
char *UP;

char *tgoto (cm, next, first)
char *cm;
int first;
int next;
{
    static char buf [128];
    static char oops[] = "OOPS";
    char *cp = buf;
    register c;

    if (!cm || !*cm)
	return (oops);

    while (*cm) {
	if (*cm == '%') {
	    cm++;
	    if ((c = *cm++) == 'd') {
		cp += sprintf (cp, "%d", first);
		first = next;
	    }
	    else
	    if (c == '2') {
		cp += sprintf (cp, "%02d", first);
		first = next;
	    }
	    else
	    if (c == '3') {
		cp += sprintf (cp, "%03d", first);
		first = next;
	    }
	    else
	    if (c == '.') {
		*cp++ = first;
		first = next;
	    }
	    else
	    if (c == '+')
		if (*cm) {
		    first += *cm++;
		    *cp++ = first;
		    first = next;
		}
		else
		    return (oops);
	    else
	    if (c == '>')
		if (*cm && *(cm+1)) {
		    if (first > *cm++)
			first += *cm++;
		}
		else
		    return (oops);
	    else
	    if (c == 'B') {
		first = 16*(first/10) + (first%10);
	    }
	    else
	    if (c == 'D') {
		first = first - 2*(first%16);
	    }
	    else
	    if (c == 'r') {
		c = next;
		next = first;
		first = c;
	    }
	    else
	    if (c == 'i') {
		next++;
		first++;
	    }
	    else
	    if (c == '%') {
		*cp++ = '%';
	    }
	    else
	    if (c == 'n') {
		next ^= 0140;
		first ^= 0140;
	    }
	    else
		return (oops);
	}
	else
	    *cp++ = *cm++;
    }
    *cp = EOS;
    return (buf);
}

/*-**-* tputs.c */

char PC;
short ospeed;

tputs (cp, affcnt, outc)
char *cp;
int affcnt;
int (*outc)();
{
    if (!cp)
	return;
    while (*cp >= '0' && *cp <= '9')
	cp++;
    while (*cp)

/* On VMS sending a linefeed will be interpreted as sending <CR> and <LF> */
/* setting high-order bit will prevent this */
/* All other characters will have high-order bit stripped */
#if vms
	if (*cp == '\n')
		(*outc)((*cp++) | 0x80);
	else
#endif
	        (*outc)((*cp++) & 0x7f);
}

/*-**-* test.c */

#ifdef DEBUG
#include <stdio.h>
static char buf [1024];
oc (c)
char c;
{
    putchar (c);
}
putcap (c)
char *c;
{
    tputs (c, 0, oc);
    fflush (stdout);
}
main ()
{
    static char bb[1024];
    char *cp = bb;
    char *getenv ();

    tgetent (buf, getenv ("TERM"));
    fprintf (stderr, "entry = \"%s\"\n", buf);
}
#endif	/* DEBUG */
-- 
Johan Vromans                              | jv@mh.nl via European backbone
Multihouse N.V., Gouda, the Netherlands    | uucp: ..{?????!}mcvax!mh.nl!jv
"It is better to light a candle than to curse the darkness"