[comp.os.vms] single char input in VAX C

GG.SPY@ISUMVS.BITNET ("John Hascall") (03/11/88)

> From:         F026@CPC865.UEA.AC.UK
> Subject:      single char input in VAX C
>
> (as far as I know) the only way you can get single character input on a VAX
> is to use the $QIO (Queued Input/Output) system service, which involves
> setting what is effectively an interrupt vector which you then have to
> service. To make it simpler, VMS also has the $QIOW (QIO with Wait) which
> will wait for your 'interrupt'. Look it up in the System Service Manual.
>
> Mike Salmon

    To elucidate:

    #include <iodef.h>
    #include <descrip.h>
    typedef struct IOSB {
       unsigned short status;
       unsigned short count;
       unsigned long  other;
    } IOSB;
    typedef unsigned short CHANNEL;
    $DESCRIPTOR("TT",tty_name);
    unsigned long status;
    CHANNEL tty_chan;
    IOSB tty_iosb;
    char inchar;

    status = SYS$ASSIGN(&tty_name,&tty_chan);  /* only need to do this once */

    status = SYS$QIOW(0,tty_chan,IO$_READVBLK,&tty_iosb,0,0,&inchar,1,0,0,0,0);

John Hascall
Iowa State University Computation Center
GGUUU@ISUMVS.BITNET

hildum@iris.ucdavis.edu (Eric Hildum) (03/14/88)

You may also use SMG$ to get single characters...

				Eric

				dehildum@ucdavis.ucdavis.edu	(Internet)
				dehildum@ucdavis.bitnet	(BITNET)
				ucbvax!ucdavis!dehildum	(uucp)

ERICMC@USU.BITNET (Eric McQueen) (04/02/88)

John Hascall (GG.SPY@ISUMVS.BITNET) in <8803130715.AA24642@ucbvax.Berkeley.EDU>
gives a quick example of calling $QIOW to get a single character from the
keyboard under VMS.  Here's two short routines that do proper checking for
errors and can be used in place of getchar().  You could even port your Un*x
code by writing a ctio() front end for takecharset():

Eric Tye McQueen          Mathematics Department        Also at (after some
ericmc@usu.bitnet         Utah State University       time in March[June?!]):
 (801) 753-4683           Logan, Utah  84322-3900       ericmc@usu.usu.edu

   UUCP:  ...{psuvax1,uunet}!usu.bitnet!ericmc       "Doodle doodle dee
   Arpa:  ericmc%usu.bitnet@cunyvm.cuny.edu           wubba wubba wubba."

/* takechar.c -- by Eric McQueen   Version of 1-Apr-1988
 * Replacement for getchar() that allows control over the following:
 *      Whether to wait if there are no characters in the buffer yet.
 *      Whether to wait for a line terminator (which allows VMS line editing).
 *      Whether to echo input.
 * Two routines are provided:
 *    - takechar():  works very much like getchar().
 *    - takecharset(opts):  modifies the behavior of takechar():  `opts' is
 *        a string of characters from "wilces" (letter case is not important)
 *        which stand for Wait/Immediate, Line/Character, Echo/Silent.  If you
 *        don't call takecharset(), takechar() will behave as if you made the
 *        call takecharset("wle") (wait for input, read by line, echo input).
 * takechar() returns -2 if in "immediate" mode and no characters are in the
 * type-ahead buffer.  takechar() returns EOF (-1) for CTRL-Z and '\n' for '\r'
 * to be compatible with getchar().  takechar() (when in "character" mode)
 * return '\r' for '\n' to allow programs to distinguish '\r' from '\n'.  Since
 * '\n' is not normally typed by users, this should cause no problems.  If in
 * both "immediate" and "line" modes, partial lines may be read.
 *
 * These capabilities could also be provided by having a routine set the
 * terminal /[NO]ECHO and /[NO]PASTHROUGH, but this way is much simpler
 * [unless you want to use scanf()] and I hate to take CTRL-C, -O, -T, and -Y
 * away from the user. If the user wants to give them to you via SET NOCONTROL
 * or SET TERM/PAST, they can.  Also, CTRL-S and CTRL-Q cannot be read by this
 * routine.
 */

#include <stdio.h>      /* stderr EOF */

#define   odd( stat )   ( (stat) & 1 )

/* Custom data type names: */
#define   bool    char  /* smallest addressible signed object */
#define   uchar   unsigned char
#define   ushort  unsigned short
#define   uint    unsigned int

/* General VMS descriptor: */
/* I could include <DESCRIP.H>, but I prefer my short field names */
struct descr {
        ushort leng;    /* Length of data area */
        uchar  type;    /* Type of data in area */
        uchar  class;   /* Class of descriptor (static/dynamic/etc.) */
        char  *addr;    /* Address of start of data area */
};
/* String descriptors: */
globalvalue dsc$k_dtype_t; /* Text */
globalvalue dsc$k_class_s; /* Static */
/* to allocate a descriptor (dsc) for array of char (arr): */
#define desc_arr(dsc,arr)       struct descr dsc = \
  { (sizeof arr)-1, dsc$k_dtype_t, dsc$k_class_s, arr }
/* to allocate a descriptor (dsc) for null-terminated string (str): */
#define desc_str(dsc,str)       struct descr dsc = \
  { strlen(str), dsc$k_dtype_t, dsc$k_class_s, str }


static ushort   chan = 0;       /* I/O channel assigned to terminal. */
static bool     wait = 1;       /* Whether to wait if no input present. */
static bool     echo = 1;       /* Whether to echo characters read. */
static bool     line = 1;       /* Whether to wait for line terminator. */
static uint     func = 0;       /* I/O function to use in sys$qiow(). */


/* setfunc():  Sets `func' according to `wait', `echo', and `line'. */

static void
setfunc()
{
  globalvalue
    io$_readlblk, io$m_noecho, io$m_trmnoecho, io$m_nofiltr, io$m_timed;
        func = io$_readlblk;
        if(  !echo  )
                func |= io$m_noecho | io$m_trmnoecho;
        if(  !wait  )
                func |= io$m_timed;
        if(  !line  )
                func |= io$m_nofiltr;
}


/* takecharset(opts):  Set behavior of takechar().  `opts' is a string of zero
 * or more of the following characters:
 *      I = Immediate:  takechar() returns -1 if type-ahead buffer is empty,
 *      W = Wait:       takechar() waits if type-ahead buffer is empty.
 *
 *      E = Echo:       takechar() echoes each character/line as it is read,
 *      S = Silent:     takechar() does not echo characters (or lines).
 *
 *      C = Character:  takechar() doesn't wait for a line terminator,
 *      L = Line:       takechar() reads by line, allowing line editing.
 * Case of letters is ignored. */

void
takecharset( opts )
  char *opts;
{
        while( *opts )
          switch( *opts++ ) {
            case 'w':  case 'W':        /* Wait */
                wait = 1;
                break;
            case 'i':  case 'I':        /* Immediate */
                wait = 0;
                break;
            case 'e':  case 'E':        /* Echo */
                echo = 1;
                break;
            case 's':  case 'S':        /* Silent */
                echo = 0;
                break;
            case 'l':  case 'L':        /* By line */
                line = 1;
                break;
            case 'c':  case 'C':        /* By Character */
                line = 0;
                break;
            default:
                fprintf( stderr,
                  "Invalid character '%c' (0x%x) in takecharset().\n",
                  *opts, (int) *opts );
          }
          setfunc();
}


/* assign():  Assigns a channel to the terminal.  The real way to do this is
 * to use sys$getjpi() to get the ID of the master process and then get the
 * name of the terminal associated with that process.  The method we use here
 * is to translate the logical name SYS$INPUT to find the name of the terminal.
 * Obviosly, this won't work if the user redefines SYS$INPUT, but then, most
 * programs won't read from the keyboard if SYS$INPUT isn't pointing to the
 * terminal.  Also, by not calling sys$getjpi() we shorten the code (excluding
 * this comment) by a factor of 6. */

static void
assign()
{
  char tt_name[] = "SYS$INPUT"; /* or "TT" or "SYS$COMMAND" */
  desc_str( ttdsc, tt_name );
  uint status, sys$assign();
        status = sys$assign( &ttdsc, &chan, 0, 0 );
        if(  odd( status )  )   /* We succeeded. */
                return;
        fprintf( stderr, "Can't assign a channel to the terminal.\n" );
        sys$exit( status );
}


/* takechar():  Reads a single key from the keyboard.  If `wait' is 0 and
 * there are no characters in the type-ahead buffer, -1 is returned.  Other-
 * wise the next character is returned. */

int
takechar()
{
  uchar c;                      /* So eight-bit characters can't be -1 or -2 */
  ushort iosb[4];
  static ushort len = 0;        /* Number chars in `buf' to be sent */
  static char buf[4096], *cp = buf;
  uint status, sys$qiow();
  globalvalue ss$_timeout;
        if(  !chan  )   assign();       /* Assign a channel to the terminal. */
        if(  !func  )   setfunc();      /* Set `func' to be appropriate. */
        if(  len > 0  ) {
                iosb[0] = status = 1;
        } else if(  line  ) {           /* Read an entire line: */
                status = sys$qiow( 0, chan, func, iosb, 0, 0,
                  buf, sizeof(buf), 0, 0, 0, 0 );
                len = iosb[1] + iosb[3];        /* Total bytes read. */
                cp = buf;
        } else {
                status = sys$qiow( 0, chan, func, iosb, 0, 0,
                  &c, 1, 0, 0, 0, 0 );
        }
        if(  ss$_timeout == status  ||  ss$_timeout == iosb[0]  )
                return( -2 );
        if(  !odd(status)  ||  !odd(iosb[0])  ) {       /* Error occured: */
                len = 0;
                fprintf( stderr, "Error reading from terminal.\n" );
                sys$exit( odd(status) ? iosb[0] : status );
        }
        if(  len > 0  ) {
                len--;
                c = *cp++;
        }
        switch(  c  ) {
          case '\r':    return( '\n' );         /* Act like getchar()... */
          case '\n':    return( '\r' );         /* ...but distinguish '\n' */
          case 'Z'&31:  return( EOF );          /* CTRL-Z is End Of File */
          default:      return( c );
        }
}
/* End of takechar.c */