[mod.computers.vax] VAX C problem

MHICKEY@UDCVM.BITNET (Mike Hickey) (07/02/86)

    We recently got VAX-C ver 2.2 and I've been busy porting programs
from UNIX/DOS.  When I was working with the CURSES package, I found that
getch() wasn't breaking on a character.  Instead, it would block until
a RETURN was hit.  I then tried the getchar() function in the standard
library and, lo and behold, it reacted the same way.

    On every other system I've worked with, these functions "woke up" on
any character input whereas these act more like incremental gets().  Have
I missed something somewhere?  I borrowed the ttxxxx() functions from
Micro Emacs which perform character-breaking I/O but they won't work with
CURSES.


                        Thanks in advance,

                              Mike Hickey
                              Systems programmer
                              University of DC

   "Structured programmers DO it OD"

gwyn@BRL.ARPA.UUCP (07/03/86)

Any implementation of getchar() that does not require an entire
line to be typed before returning data from the terminal in the
default case is broken.  This behavior is permitted only in raw
(non-canonicalizing) mode, which is not supposed to be enabled by
default.  Sounds like VAX-C ver 2.2 is doing this right.

gu044150%pitt@cisunx.UUCP (elliot m) (07/07/86)

In article <8607031156.AA07330@ucbvax.Berkeley.EDU> MHICKEY@UDCVM.BITNET writes:
>
>    We recently got VAX-C ver 2.2 and I've been busy porting programs
>from UNIX/DOS.  When I was working with the CURSES package, I found that
>getch() wasn't breaking on a character.  Instead, it would block until
>a RETURN was hit.  I then tried the getchar() function in the standard
>library and, lo and behold, it reacted the same way.
>

	I haven't worked with curses that much but it seems to me that even 
when you do curses on UNIX you have to throw your terminal into cbreak
mode to get a character without hitting a <RET>. I believe the curses
routine which does this is CRMODE. Unfortunately VMS ignores the CRMODE
routine because VMS doesn't have a cbreak mode.

	The only way I have been able to get around this is to use the SMG
routines.

				Mike

				cadre!pitt!cisunx!gu044150	

sam@brspyr1.UUCP.UUCP (07/16/86)

> Mike Hickey writes ...
>
> ... When I was working with the CURSES package, I found that
> getch() wasn't breaking on a character.  Instead, it would block until
> a RETURN was hit...

> Doug Gwyn writes ...
> 
> ... This behavior is permitted only in raw
> (non-canonicalizing) mode, which is not supposed to be enabled by
> default.  Sounds like VAX-C ver 2.2 is doing this right.

Take a look in the VAX-11 C manual.  Hmmm ... CRMODE(): provided for link
compatibility only, no function performed.   Hmmm ... RAW(): same story.
So what is a Unix hack supposed to do?  Grab those dozen orange binders
and ...

We discovered that the following reasonably simple routines emulate grabbing
a single character off the VMS terminal, similar to UNIX raw mode. 
Efficiency?  No promises here, but they work.  For good measure, I threw
in a VMS "getpass()" (now why doesn't the C RTL have one of these?)

/*
**  The  following  code segment is likely copyrighted by my employer, but
**  since I originally wrote it and hacked it before posting  it  and  you
**  WILL hack it more before even THINKING about using it, well ...
**
**  Okay,  okay,  whatever  you  do don't make any money on it, huh?   But
**  then again, what are we  doing  reading  this  net  and  asking  these
**  question  if  we didn't (ultimately) want to make money for someone or
**  something.  Hmmmmm.  What a dilemma.
**
**  Okay,  you  and me, we'll just pretend this should have been published
**  in one of those twenty or so orange  books  DEC  calls  documentation,
**  right?  And anyway, it is all in there if you look long enough.  So it
**  must be _almost_ PD.
**
**  No flames, dammit.  I was only trying to help.
*/

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

#define	VMS$IOKFLGS	(IO$_READVBLK | IO$M_NOECHO | IO$M_NOFILTR)
#define	VMS$IOPFLGS	(IO$_READVBLK | IO$M_NOECHO )
#define	VMS$INTR	'\003'	/* ^C */
#define VMS$TTYDEV	"TT"
#define MAX$PASSWD	8

static int kb_chan;	/* hold keyboard channel here */

/*
**  Assigns  'kb_chan'  to  the terminal I/O channel for later use.   The
**  system logical "TT" is used as the terminal device.
*/
void initialize()		/* call me once at program start */

{
	static $DESCRIPTOR(name,VMS$TTYDEV);

	if (sys$assign(&name,&kb_chan,0,0) != SS$_NORMAL)
		perror("sys$assign(TT) failed");

	return;
}

/*
**  This  subroutine  performs  the opposite of initialize(): it deassigns
**  the channel.  This should be done before the application is exited.
*/
void de_initialize()		/* call me once at program end */

{
	if (sys$dassgn(kb_chan) != SS$_NORMAL)
		perror("sys$dassgn(TT) failed");

	return;
}

/*
**  This  subroutine  recovers  a  single  byte  from the user's keyboard.
**  This subroutine may not be used unless initialize() has  been  called,
**  but no enforcement of this is made.
**
**  Returns one of:
**	(char)	- byte retrieved
**	NULL	- interrupt encountered
*/
char get_key_stroke()

{
	int i;			/* for catching return value */
	char c;			/* place for char */

	/* sys$qiow() ignores EOF's and does not modify 'c' if interrupt. */
	/* better handling here is probably more than possible; VMS$IOKFLGS */
	/* (defined at top) has impact on special char handling. */

	do {
		c = VMS$INTR;
		i = sys$qiow(0,kb_chan,VMS$IOKFLGS,0,0,0,&c,1,0,0,0,0);

	} while (i != SS$_NORMAL);

	return(c == VMS$INTR ? NULL : c);
	}

/*
**  As  in getpass(3), basically.  Very minimum; probably should check for
**  EOF or INTR.  Dependent on a prior call to initialize().
*/
char *getpass(p)

char *p;
{
	static char passwd[MAX$PASSWD+1];

	fputs(p,stdout);
	fflush(stdout);

	*passwd = NULL;
	sys$qiow(0,kb_chan,VMS$IOPFLGS,0,0,0,passwd,MAX$PASSWD,0,0,0,0);

	for (p = passwd; p - passwd < MAX$PASSWD; p++)
		if (*p == '\n' || *p == '\r' || *p == NULL)
			break;

	*p = NULL;

	return(passwd);
}

From postnews Wed Jul 16 01:02:28 1986
Subject: Re: getch for VAX C (was: VAX C problem)
Newsgroups: mod.computers.vax
To: info-vax@sri-kl.arpa
References: <8607031156.AA07330@ucbvax.Berkeley.EDU>

> Mike Hickey writes ...
>
> ... When I was working with the CURSES package, I found that
> getch() wasn't breaking on a character.  Instead, it would block until
> a RETURN was hit...

> Doug Gwyn writes ...
> 
> ... This behavior is permitted only in raw
> (non-canonicalizing) mode, which is not supposed to be enabled by
> default.  Sounds like VAX-C ver 2.2 is doing this right.

Take a look in the VAX-11 C manual.  Hmmm ... CRMODE(): provided for link
compatibility only, no function performed.   Hmmm ... RAW(): same story.
So what is a Unix hack supposed to do?  Grab those dozen orange binders
and ...

We discovered that the following reasonably simple routines emulate grabbing
a single character off the VMS terminal, similar to UNIX raw mode. 
Efficiency?  No promises here, but they work.  For good measure, I threw
in a VMS "getpass()" (now why doesn't the C RTL have one of these?)

/*
**  The  following  code segment is likely copyrighted by my employer, but
**  since I originally wrote it and hacked it before posting  it  and  you
**  WILL hack it more before even THINKING about using it, well ...
**
**  Okay,  okay,  whatever  you  do don't make any money on it, huh?   But
**  then again, what are we  doing  reading  this  net  and  asking  these
**  question  if  we didn't (ultimately) want to make money for someone or
**  something.  Hmmmmm.  What a dilemma.
**
**  Okay,  you  and me, we'll just pretend this should have been published
**  in one of those twenty or so orange  books  DEC  calls  documentation,
**  right?  And anyway, it is all in there if you look long enough.  So it
**  must be _almost_ PD.
**
**  No flames, dammit.  I was only trying to help.
*/

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

#define	VMS$IOKFLGS	(IO$_READVBLK | IO$M_NOECHO | IO$M_NOFILTR)
#define	VMS$IOPFLGS	(IO$_READVBLK | IO$M_NOECHO )
#define	VMS$INTR	'\003'	/* ^C */
#define VMS$TTYDEV	"TT"
#define MAX$PASSWD	8

static int kb_chan;	/* hold keyboard channel here */

/*
**  Assigns  'kb_chan'  to  the terminal I/O channel for later use.   The
**  system logical "TT" is used as the terminal device.
*/
void initialize()		/* call me once at program start */

{
	static $DESCRIPTOR(name,VMS$TTYDEV);

	if (sys$assign(&name,&kb_chan,0,0) != SS$_NORMAL)
		perror("sys$assign(TT) failed");

	return;
}

/*
**  This  subroutine  performs  the opposite of initialize(): it deassigns
**  the channel.  This should be done before the application is exited.
*/
void de_initialize()		/* call me once at program end */

{
	if (sys$dassgn(kb_chan) != SS$_NORMAL)
		perror("sys$dassgn(TT) failed");

	return;
}

/*
**  This  subroutine  recovers  a  single  byte  from the user's keyboard.
**  This subroutine may not be used unless initialize() has  been  called,
**  but no enforcement of this is made.
**
**  Returns one of:
**	(char)	- byte retrieved
**	NULL	- interrupt encountered
*/
char get_key_stroke()

{
	int i;			/* for catching return value */
	char c;			/* place for char */

	/* sys$qiow() ignores EOF's and does not modify 'c' if interrupt. */
	/* better handling here is probably more than possible; VMS$IOKFLGS */
	/* (defined at top) has impact on special char handling. */

	do {
		c = VMS$INTR;
		i = sys$qiow(0,kb_chan,VMS$IOKFLGS,0,0,0,&c,1,0,0,0,0);

	} while (i != SS$_NORMAL);

	return(c == VMS$INTR ? NULL : c);
	}

/*
**  As  in getpass(3), basically.  Very minimum; probably should check for
**  EOF or INTR.  Dependent on a prior call to initialize().
*/
char *getpass(p)

char *p;
{
	static char passwd[MAX$PASSWD+1];

	fputs(p,stdout);
	fflush(stdout);

	*passwd = NULL;
	sys$qiow(0,kb_chan,VMS$IOPFLGS,0,0,0,passwd,MAX$PASSWD,0,0,0,0);

	for (p = passwd; p - passwd < MAX$PASSWD; p++)
		if (*p == '\n' || *p == '\r' || *p == NULL)
			break;

	*p = NULL;

	return(passwd);
}

LEICHTER-JERRY@YALE.ARPA.UUCP (07/20/86)

A recent message from Samuel Baxter (brspyr1!sam) provided a simple program
to do single-character terminal I/O on VMS.  Enclosed below is a program that
does the same thing, though often much more efficiently, since it buffers up
typed-ahead stuff.  In addition, it can be do "non-blocking" terminal reads.
The program has a long history and several people have hacked at it - I'm just
the last.  It's been used successfully for quite some time.  It differs form
Mr. Baxter's code in not attempting to trap CTRL/C.  That, and related
changes, can be made easily enough.

Mr. Baxter also asks why VAX C provides crmode() only as a dummy function, and
does not provide getpass().  A guess at the answers:  crmode() has some very
specific effects on Unix that would be very difficult to duplicate exactly
on VMS - different purposes of crmode() would probably want to use slightly
different combinations of VMS terminal settings.  For example, should crmode()
disable xon/xoff (CTRL/Q - CTRL/S) handling?  To be exactly like Unix, probab-
ly, but for most purposes, in a VMS environment, the probably not.  The Unix
and VMS terminal drivers are so different that any attempt to provide anything
beyond the usual buffered I/O is going to run into problems.

As to getpass(), it's pretty rarely used outside of the kind of system pro-
grams that are unlikely to be moved to VMS - I'll bet at least 90% of Unix
programmers have never heard of it - and it's pretty easy to write anyway.
So, it must have come far down on the list of things to get written.

							-- Jerry

------------------------------Cut Here------------------------------------------
/*#define	TESTING
*/
/*
 *				k b i n . c
 *
 * Read tt: one byte at a time without echo, with or without waiting if there
 * is no typeahead.
 *
 * Synopsis
 *
 *	int
 *	_kbin(wait)
 *	int	wait;
 *
 *	int
 *	kbin()
 *
 *	int
 *	kbinr_()
 *
 * Description
 *
 *	Returns the next character.  Returns EOF on error, or if there is no
 *	typeahead and "no wait" was requested.
 *
 *	_kbin(wait) waits if wait is TRUE, returns EOF if there is no
 *	typeahead and wait is FALSE.  kbin() is the same as _kbin(TRUE);
 *	kbinr() is the same as _kbin(FALSE).
 *
 *	Note -- this routine does not prevent CTRL/C or CTRL/Y aborts
 *	from occurring.
 *
 * Revision History
 * 0.0	19-Jun-79 SB	Original version due to Stoney Ballard.
 * 0.1	19-Oct-79 MM	"Munged".
 * 0.2  31-Aug-81 MM	Name changed to kbin().
 * 1.0  ??-???-?? MM	Made part of VAXLIB.
 * 1.1	22-Jul-83 JSL	Old kbin() ==> _kbin() and gets the "wait" argument;
 *			kbin(), kbinr() in terms of _kbin().
 */

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

#define	FALSE	0
#define	TRUE	1
#define	EOS	0

#define	BUFFLEN	10			/* Size of typeahead buffer	*/

/*
 * Local static database:
 */

static $DESCRIPTOR(inpdev, "TT");	/* Terminal to use for input	*/
static long termset[2] = { 0, 0 };	/* No terminator		*/

/*
 * Local variables
 */

static long	ichan;			/* Gets channel number for TT:	*/
static char	opened = FALSE;		/* TRUE when opened		*/
static char	ibuff[BUFFLEN];		/* Input buffer -- one byte	*/
static char	*buffptr = ibuff;	/* For typeahead processing	*/
static char	*buffend = ibuff;	/* For typeahead processing	*/

int
_kbin(wait)
int	wait;
/*
 * Get one byte without echoing, with or without waiting.
 */
{
	register int	errorcode;
	struct IOSTAB {
		short int	status;
		short int	offset_to_terminator;
		short int	terminator;
		short int	terminator_size;
	} iostab;

	if (buffptr < buffend)
	    return (*buffptr++ & 0377);	/* Empty our typeahead buffer	*/
	if (!opened) {
	    if ((errorcode = sys$assign(&inpdev, &ichan, 0, 0)) != SS$_NORMAL) {
		fprintf(stderr, "KBIN assign failed.  code = %X\n", errorcode);
		exit(errorcode);
	    }
            else opened = TRUE;
	}
	/*
	 * See if there's something in the system typeahead buffer
	 * Read up to BUFLEN bytes with "zero" timeout.  This will return
	 * whatever's in the timeout buffer.  The iostab.offset_to_terminator
	 * and iostab.terminator_size will yield the number of bytes read.
	 */
	errorcode = sys$qiow(1,		/* Event flag 			*/
		ichan,			/* Input channel		*/
		IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR | IO$M_TIMED,
					/* Timed read with zero wait	*/
		&iostab,		/* I/O status block		*/
		NULL,			/* AST block (none)		*/
		0,			/* AST parameter		*/
		&ibuff,			/* P1 - input buffer		*/
		BUFFLEN,		/* P2 - buffer length		*/
		0,			/* P3 - ignored (timeout)	*/
		&termset,		/* P4 - terminator set		*/
		NULL,			/* P5 - ignored (prompt buffer)	*/
		0);			/* P6 - ignored (prompt size)	*/
#ifdef	TESTING
	printf("timed read code = %X, ", errorcode);
	printf("status = %d, offset = %d, terminator = %d, size = %d\n",
		iostab.status, iostab.offset_to_terminator,
		iostab.terminator, iostab.terminator_size);
#endif
	buffend = &ibuff[iostab.offset_to_terminator + iostab.terminator_size];
	if (buffend > ibuff) {
	    buffptr = &ibuff[1];	/* Setup typeahead pointer and	*/
	    return (ibuff[0] & 0377);	/* Return the first character	*/
	}
	/*
	 * Nothing in typeahead buffer, nothing read.  If the user doesn't
	 * want us to wait, just return EOF; else read one character.
	 */
	if (!wait)
		return (EOF);

	errorcode = sys$qiow(1,		/* Event flag 			*/
		ichan,			/* Input channel		*/
		IO$_READLBLK | IO$M_NOECHO | IO$M_NOFILTR,
					/* Read, no echo, no translate	*/
		&iostab,		/* I/O status block		*/
		NULL,			/* AST block (none)		*/
		0,			/* AST parameter		*/
		&ibuff,			/* P1 - input buffer		*/
		1,			/* P2 - buffer length		*/
		0,			/* P3 - ignored (timeout)	*/
		&termset,		/* P4 - terminator set		*/
		NULL,			/* P5 - ignored (prompt buffer)	*/
		0);			/* P6 - ignored (prompt size)	*/
#ifdef	TESTING
	    printf("read one byte, code = %X, ", errorcode);
	    printf("status = %d, offset = %d, terminator = %d, size = %d\n",
		iostab.status, iostab.offset_to_terminator,
		iostab.terminator, iostab.terminator_size);
#endif
	if (errorcode == SS$_NORMAL) {
	    return (ibuff[0] & 0377);
	}
	else {
	    return (EOF);
	}
}

int
kbin()
/*
 * Get one byte without echoing, with waiting.
 */
{
	return (_kbin(TRUE));
}

int
kbinr()
/*
 * Get one byte without echoing, without waiting.
 */
{
	return (_kbin(FALSE));
}

#ifdef	TESTING

main() {
	register int	datum;

	printf("kbin() testing - CTRL/Z to exit\n");
	while ((datum = kbin()) != EOF) {
	    printf("%03o '", datum);
	    dumpc(datum);
	    printf("'\n");
	    if (datum == ('Z' - 0100))
		break;
	}
	printf("EOF\n");

	printf("kbinr() testing - CTRL/Z to exit\n");
	while (1) {
	    datum = kbinr();
	    if (datum == EOF)
	    {	printf("EOF - sleeping...");
		sleep(2);
		printf("\n");
		continue;
	    }
	    printf("%03o '", datum);
	    dumpc(datum);
	    printf("'\n");
	    if (datum == ('Z' - 0100))
		break;
	}
	printf("EOF\n");
}

dumpc(datum)
int		datum;
/*
 * Dump a character readably
 */
{
	datum &= 0377;
	if ((datum & 0200) != 0) {
	    putchar('~');
	    datum &= 0177;
	}
	if (datum < ' ') {
	    putchar('^');
	    putchar(datum + '@');
	}
	else if (datum > 0176) {
	    printf("<RUB>");
	}
	else putchar(datum);
}
#endif
-------

carl@CITHEX.CALTECH.EDU (Carl J Lydick) (07/21/86)

/*****************************************************************************\
 *  >   ...a program that does the same thing, though often much more        *
 *  >   efficiently, since it buffers up typed-ahead stuff....               *
 *                                                                           *
 *  Not quite the same thing, Jerry; your "more efficient" implementation    *
 *  has the following rather odd properties:                                 *
 *      1)  An image exit may flush any, all, or none of the type-ahead.     *
 *      2)  The amount of type-ahead flushed when the image exits            *
 *          depends, in principle, on every keystroke typed since the        *
 *          first call to your _kbin().  In fact, the dependence is upon     *
 *          the number of characters typed since the last time the           *
 *          terminal's type-ahead queue was empty.                           *
 *  The reason for this, of course, is that you and the terminal driver are  *
 *  both buffering typeahead, but you neither return whatever you've got     *
 *  left at image exit to the terminal data stream (which is reasonable,     *
 *  since the VMS terminal drivers don't have any provision for this) and    *
 *  you don't do anything to force a flush of the terminal's type-ahead      *
 *  buffer at image exit (which is not reasonable, since you can declare     *
 *  your own exit handler to take care of this problem.  What you want is    *
 *  somthing like:                                                           *
\*****************************************************************************/
/* clean up after single-character I/O software */
void flush_stream(status, chan) 
long *status, chan;
{	SYS$QIOW(0, chan, IO$_READVBLK | IO$M_TIMED | IO$M_NOFILTR | 
		IO$M_NOECHO | IO$M_PURGE, 0, 0, 0,0, 0, 0, 0, 0, 0);
	SYS$DASSGN(chan);
}

/* exit handler descriptor block */
struct EXH$DESBLK { 
	long exh$l_flink;
	void (*exh$a_handler)();
	long exh$b_nargs;
	long *exh$a_status;
	long exh$l_chan;
	long exh$l_stat;
} flush = { 0, flush_stream, 2, &(flush.exh$l_stat), 0, 0 };
/*****************************************************************************\
 *   Then add something like:                                                *
 *                                                                           *
 *  	flush.exh$l_chan = chan;                                             *
 *  	SYS$DCLEXH(&flush);                                                  *
 *                                                                           *
 *   to the code that opens the channel on the first call.  When your image  *
 *   exits, flush will be called, and it will flush all typeahead on the     *
 *   terminal channel and deassign the channel.                              *
\*****************************************************************************/