OS-9/OSK Answers! by Joel Mathew Hegberg
Tackling Termcap, Part I


One of the most useful concepts used under OS-9 is "termcap", which is short for terminal capabilities. Within this standard, all the terminal-specific control sequences and special-key strings are kept within a /dd/SYS/termcap file. This allows your software to run on any terminal, whether it's VT-100, ANSI, K-Windows, ADM3A, Hazeltine, ABM85, TVI920... well, you get the idea. There are so many standards for terminals that it is obviously a tremendous benefit to be able to obtain this information easily at run-time for the type of terminal the user is on.

As it turns out, this concept is also one of the most difficult to implement! Those of you who have tried to use termcap know what I mean. For those who've only glanced at it, this may come as a surprise. After all, the OS-9 C compiler comes with an entire library (termlib.l) dedicated to helping C programmers implement termcap within their software. The problem is the library doesn't handle the tough stuff. Many complexities are left for the programmer to handle, and the documentation for the termcap library is scarce, severely lacking in detail, and even self-contradictory in some parts. What I hope to do in the next couple issues is fully explain termcap to the fullest and give everyone the tools and knowledge to use termcap in their software.

Due to space considerations, I'm only going to present the source-code for an example program in this issue. I could have done it the other way around (explained what I want to do and then give the source in the next issue), but I know how programmers prefer to get experimenting with actual source code! The program is fairly large for what it does, which shows just how complex it is to implement in even just a simple manner.

The program, 'termcaptest.c' (listing #2), merely clears the screen for the user and lets him/her type anywhere on the screen. The arrow keys may be used to move the cursor around the screen, and the backspace string is also supported. When you want to quit the program, press your keyboard interrupt key (usually ^C or ^E) and the program will clear the screen and exit back to OS-9.

This program requires the following terminal capabilities to be defined within the termcap file: up, cl, cm, ku, kd, kl, kr, kb. If any of these are missing, you will receive an "incomplete termcap entry" message. For more information on editing your termcap file and what to put inside it, read pages 8-31 through 8-37 in your "Using Professional OS-9" manual. I've included my termcap entry for my K-Windows system (vsc) as listing #1.

Next issue, I'll fully explain how the program works and give some insight on what the library functions within termlib.l actually do. You will then be well on your way to understanding termcap and employing this powerful standard within your own software!


Listing #1: example termcap entry
=================================
k1|vsc|Signetics Vsc Video driver by RMC:\
        :am:bs:cl=^L:li#26:co#80:ho=^A:\
        :cd=^K:ce=^D:cm=^B%r%+ %+ :pt:\
        :do=^J:up=\E[A:nd=^F:so=\037 :se=\037!:\
        :us=\037":ue=\037#:al=\0370:dl=\0371:\
        :ku=^P:kd=^N:kr=^F:kl=^B:kb=^H:sf=\012:\
        :ve=\005!:vi=\005 :bl=\007:



Listing #2: termcaptest.c
=========================

#include <stdio.h>
#include <sgstat.h>
#include <termcap.h>


#define TCAPSLEN 400
#define BUFFSIZE  20
#define STDIN      0
#define STDOUT     1

/* user-defined values for special keys */
/* values are not important, but must   */
/* be greater than 255.  ENTER is just  */
/* the value 13 for CR.                 */
#define UP_ARROW    400
#define DOWN_ARROW  401
#define LEFT_ARROW  402
#define RIGHT_ARROW 403
#define BACKSPACE   404
#define ENTER       0x0D

extern char *getenv();
int    user_tputc(), user_touts(), putpad(), sigtrap();

/* variables for termcap stuff */
char   tcapbuf[TCAPSLEN];
short  lines, columns, ospeed;
char  PC_,
    *UP,   /* required by tgoto() */
    *BC,   /* required by tgoto() */
    *CL,
    *CM,
    *KU,
    *KD,
    *KL,
    *KR,
    *KB;

/* variables for my program */
int    keybuflen, keybufpos, keyflag = 0, done = 0;
char   keybuff[BUFFSIZE];
struct sgbuf sgbuffer, oldbuffer;

main()
{
   int  keypress,
        cx = 0, cy = 0;   /* initial cursor position */
   char keychar;

   /* program initialization */
   initialize_termcap();
   intercept(sigtrap);
   getstat(0, STDIN, &sgbuffer);
   _strass(&oldbuffer, &sgbuffer, 32);
   sgbuffer.sg_pause = 0;
   sgbuffer.sg_echo = 0;
   sgbuffer.sg_eofch = '\0';
   setstat(0, STDIN, &sgbuffer);

   /* clear screen & position cursor */
   putpad(CL);
   putpad(tgoto(CM, cx, cy));

   while(!done)
   {
      if (keyflag == -1)
         /* old keys waiting */
         keypress = process_key();
      else
      {
         /* get a key & process it */
         read(STDIN, &keychar, 1);
         keypress = process_key(keychar);
      }

      if (keypress)
      {
         switch(keypress)
         {
            case UP_ARROW:
               if (--cy < 0) cy = 0;
               break;
            case DOWN_ARROW:
               if (++cy >= lines) cy = lines - 1;
               break;
            case LEFT_ARROW:
               if (--cx < 0) cx = 0;
               break;
            case RIGHT_ARROW:
               if (++cx >= columns) cx = columns - 1;
               break;
            case BACKSPACE:
               if (--cx < 0) cx = 0;
               putpad(tgoto(CM, cx, cy));
               tputc(' ');
               break;
            case ENTER:
               cx = 0;
               if (++cy >= lines) cy = lines - 1;
               break;
            default:
               /* make sure character is printable! */
               if (keypress >= 0x20 && keypress <= 0x7e)
               {
                  /* put it on the screen */
                  tputc((char)keypress);
                  if (++cx >= columns)
                  {
                     cx = 0;
                     if (++cy >= lines) cy = lines - 1;
                  }
               }
               break;
         }
         /* make sure cursor is where it should be */
         putpad(tgoto(CM, cx, cy));
      }
   }

   /* cleanup before exit */
   /* clear screen */
   putpad(CL);
   /* home cursor position */
   putpad(tgoto(CM, 0, 0));
   /* reset stdin characteristics */
   setstat(0, STDIN, &oldbuffer);
}

/* process_key is given a character to be processed. */
/* The processed character (of type int) is returned */
/* or zero (0) if key was swallowed.                 */
/* keyflag may have the following values:            */
/*    -1 = old keys are being removed from buffer.   */
/*     0 = no keys are in buffer.                    */
/*     1 = keys are being swallowed into buffer.     */
int process_key(key)
char key;
{
   int retkey, t;

   switch(keyflag)
   {
      case -1:
         /* remove next key from buffer */
         retkey = (int)keybuff[keybufpos++];
         if (!(--keybuflen)) keyflag = 0;
         break;
      case 0:
         keybuff[0] = key;
         keybufpos = 1;
         keybuflen = 1;
         t = compare_special();
         if (!t) retkey = (int)key;
         else
         {
            if (t == 1)
            {
               retkey = 0;
               keyflag = 1;
            }
            else retkey = t;
         }
         break;
      case 1:
         keybuff[keybufpos++] = key;
         keybuflen++;
         t=compare_special();
         if (!t)
         {
            keyflag = -1;
            retkey = keybufpos = 0;
         }
         else
         {
            if (t == 1) retkey = 0;
            else
            {
               retkey = t;
               keyflag = 0;
            }
         }
         break;
   }
   return(retkey);
}

/* compare_special checks to see if the string   */
/* within the buffer matches, partially matches, */
/* or does not match any of the special-keys     */
/* supported by termcap.                         */
/* The return value will be:                     */
/*     0 = no match at all.                      */
/*     1 = partial match... swallow more chars.  */
/*     ? = user-defined value of special key.    */
int compare_special()
{
   int t = 0;
   
   keybuff[keybufpos] = '\0';
   /* check for exact match */
   if (!strcmp(keybuff, KU)) t = UP_ARROW;
   if (!strcmp(keybuff, KD)) t = DOWN_ARROW;
   if (!strcmp(keybuff, KL)) t = LEFT_ARROW;
   if (!strcmp(keybuff, KR)) t = RIGHT_ARROW;
   if (!strcmp(keybuff, KB)) t = BACKSPACE;
   if (t) return(t);
   
   /* check for partial match */
   if (!strncmp(keybuff, KU, keybuflen)) t = 1;
   if (!strncmp(keybuff, KD, keybuflen)) t = 1;
   if (!strncmp(keybuff, KL, keybuflen)) t = 1;
   if (!strncmp(keybuff, KR, keybuflen)) t = 1;
   if (!strncmp(keybuff, KB, keybuflen)) t = 1;
   return(t);
}

initialize_termcap()
{
   char tcbuf[1024], *term_type, *temp, *ptr;

   if ((term_type = getenv("TERM")) == NULL)
   {
      fprintf(stderr, "Environment variable TERM not defined.\n");
      exit(1);
   }
   
   if (tgetent(tcbuf, term_type) <= 0)
   {
      fprintf(stderr, "Unknown terminal type '%s'.\n", term_type);
      exit(1);
   }
   
   /* read the termcap entry */
   ptr = tcapbuf;
   if (temp = tgetstr("PC", &ptr)) PC_ = *temp;
   CL = tgetstr("cl", &ptr);
   CM = tgetstr("cm", &ptr);
   KU = tgetstr("ku", &ptr);
   KD = tgetstr("kd", &ptr);
   KL = tgetstr("kl", &ptr);
   KR = tgetstr("kr", &ptr);
   KB = tgetstr("kb", &ptr);
   UP = tgetstr("up", &ptr);
   lines = tgetnum("li");
   columns = tgetnum("co");
   ospeed = -1;  /* no padding */
   
   if (lines < 1 || columns < 1)
   {
      fprintf(stderr, "No rows or columns!\n");
      exit(1);
   }
   
   /* make sure we have everything */
   if (!(CL && CM && KU && KD && KL && KR && KB && UP))
   {
      fprintf(stderr, "Incomplete termcap entry.\n");
      exit(1);
   }
   
   if (ptr >= &tcapbuf[TCAPSLEN])
   {
      fprintf(stderr, "Terminal description too big!\n");
      exit(1);
   }
}

/* writes one character to terminal.   */
/* needed by tputs() library function. */
int user_tputc(c)
char c;
{
   return (write(STDOUT, &c, 1));
}

/* writes an entire string to terminal. */
user_touts(s)
char *s;
{
   write(STDOUT, s, strlen(s));
}

/* writes out a special sequence */
/* to the terminal.              */
putpad(s)
char *s;
{
   user_tputs(s, 1, user_tputc);
}

/* when a signal is received, we're done! */
sigtrap(signal)
int signal;
{
   done = 1;
}

* THE END *