home *** CD-ROM | disk | FTP | other *** search
- Newsgroups: comp.lang.c,alt.sources
- From: scs@adam.mit.edu (Steve Summit)
- Subject: trivial "portable" terminal driver interface package (cbreak, noecho)
- Message-ID: <1991Mar2.065303.1709@athena.mit.edu>
- Date: Sat, 2 Mar 91 06:53:03 GMT
-
- This posting contains sources and documentation for a small
- package which implements a quasi-portable interface to the
- notoriously variegated terminal driver facilities provided by
- various operating systems. This code springs from a widespread
- desire, voiced frequently on comp.lang.c (though not, I hasten to
- add, by me) for a defined, standard way to ask for certain
- services of the terminal driver, so that "simple" programs having
- "simple" needs (i.e. character-at-a-time input) would not need to
- have #ifdefs in their low-level I/O code for 37 different
- operating systems.
-
- This is not, by any stretch of the imagination, production-
- quality code. It is intended mainly as a pedagogical example.
-
- To use this simple package, a program must #include "ttyio.h",
- and call tty_init. It can then call tty_setmode to control tty
- operating modes (only two are defined: character-at-a-time-ness
- and echo), and tty_getchar to read single characters. tty_navail
- reports the number of characters immediately available, if known.
- tty_reset cleans up the terminal driver before exiting.
-
- This posting contains implementations for V7/bsd Unix and MS-DOS,
- an untested implementation based on curses, and a dubious,
- completely untested implementation for Posix. (The MS-DOS
- version has been tested under Microsoft C, but I believe most if
- not all PC C RTL's supply getch and kbhit.) A System V version
- would probably be very close to Posix, but I don't have access to
- a System V system for testing. It has been several years since I
- worked with VMS, and I have forgotten the details of its terminal
- driver, so I am not able to provide a VMS implementation at the
- moment.
-
- The shar file following my signature contains these seven files:
-
- ttyio.3 man page
- ttyio.h header file
- ttytest.c test program
- ttyio.c BSD/V7 implementation
- ttyio.curses.c curses implementation (untested)
- ttyio.msdos.c MS-DOS implementation
- ttyio.posix.c Posix/SysVr4 implementation (completely untested)
-
- After writing ttyio.curses.c, I discovered (because this system
- does not have them!) that the cbreak(), nocbreak(), echo(),
- noecho(), and getch() curses functions upon which it is based do
- not appear in all versions of curses. Caveat emptor.
-
- Anyone who knows anything about Posix/SysV termios is going to
- look at ttyio.posix.c and laugh. This is the first time I've
- actually read the termios documentation and tried to write any
- code against it, and since I don't have a system to test it on,
- it is guaranteed to be wrong. I confess that I don't understand
- the distinction between c_cc[MIN] and c_cc[VMIN] (similarly for
- TIME and VTIME), and I know that there is some ineffable nonsense
- having to do with the possibility of VMIN and VTIME overlapping
- VEOF and VEOL, requiring circumlocutions when playing with
- ICANON, which I have certainly gotten wrong. (Since this is the
- first time I've looked at termios, I'll withhold judgement on it;
- perhaps it has attractions I'm overlooking. Presumably it was
- defined and adopted in its present form because of its inherent
- advantages and superiority over other alternatives.)
-
- This package is in the public domain; use it in good health.
- I couldn't put copyright notices on it if I wanted to; the code
- (with the exception of the termios stuff, which is wrong, anyway)
- is obvious, trivial, and appears as examples in countless other
- published works.
-
- I'm not sure what to suggest be done with the bugfixes and
- improvements to this package that some people are going to insist
- upon providing. Definitely don't post them to comp.lang.c .
- Post them to alt.sources if you must. You can mail them to me,
- and I'll mail them back out to anyone who asks, but I'm not
- going to spend much, if any, time integrating them or maintaining
- this "package."
-
- Steve Summit
- scs@adam.mit.edu
-
- echo extracting ttyio.3
- cat > ttyio.3 <<\%
- .TH TTYIO 3
- .SH NAME
- ttyio \- trivial terminal driver interface
- .SH SYNOPSIS
- .nf
- #include "ttyio.h"
-
- tty_init()
-
- tty_setmode(mode)
- int mode;
-
- tty_getchar()
-
- tty_navail()
-
- tty_reset()
- .fi
- .SH DESCRIPTION
- .PP
- These routines implement a trivial interface
- to a few popular features
- of the terminal drivers
- usually supplied with interactive operating systems.
- All facilities are defined
- in terms of the "controlling terminal"
- of the calling process
- (standard input,
- or file descriptor 0,
- for Unix systems).
- .PP
- The header file "ttyio.h" must be #included by any source file
- making use of these facilities.
- It contains the symbolic constants used by the
- tty_setmode
- routine,
- and may also implement some of these routines
- as function-like macros.
- .PP
- tty_init
- must be called first,
- before any of the other routines.
- .PP
- tty_setmode
- is used to set operating modes.
- The
- mode
- argument is formed by or-ing together symbolic constants,
- which are #defined in "ttyio.h".
- Separate values are used to turn on and off each mode.
- The available values are:
- .sp
- .nf
- .ta 1i +\w'TTY_NOCANON'u+3m
- TTY_ECHO
- TTY_NOECHO
- TTY_CANON "canonical" erase/newline processing
- TTY_NOCANON character-at-a-time processing
- .fi
- .sp
- TTY_ECHO and TTY_NOECHO have the obvious meanings.
- TTY_NOCANON turns off the "canonical" processing
- of the various line editing characters
- (backspace,
- delete/rubout,
- and any word or line kill characters)
- and makes input characters available immediately,
- without waiting for a newline (carriage return) character.
- (Interrupt characters, however, remain active.)
- TTY_CANON returns to "canonical" processing.
- .PP
- tty_setmode returns 0 if it is successful
- and -1 if the requested mode(s) could not be set.
- After a failure,
- errno may or may not be useful;
- the most likely cause for failure
- is that the "controlling terminal" is not really a terminal (ENOTTY)
- but is rather a file or a pipe.
- .PP
- tty_getchar
- gets and returns one character,
- with processing performed
- according to the modes set by
- previous calls to
- tty_setmode.
- tty_getchar blocks until character(s) are available.
- If TTY_NOCANON mode is in effect,
- the operating system's end-of-file character
- (usually control-D for Unix,
- control-Z for VMS and MS-DOS)
- may or may not be translated
- to the <stdio.h> value EOF.
- .PP
- (tty_getchar must be used for input
- while this package is being used;
- any other input routines --
- getchar(3),
- read(2),
- etc. --
- may behave unpredictably.)
- .PP
- tty_navail
- returns the number of characters
- which are immediately available
- for reading via
- tty_getchar,
- if this number can be determined.
- tty_navail
- is not implementable under all systems,
- and may return an approximation
- on systems where it does work.
- It returns -1
- if it cannot determine
- the number of characters available;
- the calling program will then have to make
- its own conservative approximation.
- (Whether it is better to assume
- that characters are or aren't available
- when the answer is not definitively determinable
- is a decision which is best made by the calling program.)
- Some operating systems can report
- that there are characters available,
- but not how many;
- tty_navail
- returns 1 in this case.
- .PP
- tty_reset
- should be called before the calling program exits,
- to restore the terminal driver
- to its default or initial state.
- tty_reset can also be called
- before the calling program suspends operation
- or otherwise gives up control.
- It is permissible to call
- tty_setmode
- again,
- to re-establish non-default modes,
- after calling
- tty_reset,
- as long as tty_reset is called
- a final time before exiting.
- .SH BUGS
- .PP
- Not general enough to satisfy anybody
- (let alone "Tenex fans").
- .PP
- Under some systems, calling
- setmode
- with only the value TTY_NOECHO
- may implicitly turn on TTY_NOCANON.
- .PP
- There is no way to specify
- the file descriptor
- of the tty to be controlled;
- standard input (fd 0) is assumed.
- .PP
- No provision for output or output modes.
- %
- chmod 664 ttyio.3
- if test `wc -c < ttyio.3` -ne 3878; then
- echo "error extracting ttyio.3" 1>&2
- fi
- echo extracting ttyio.h
- cat > ttyio.h <<\%
- #ifndef TTYIO_H
- #define TTYIO_H
-
- /* values for tty_setmode(): */
-
- #define TTY_ECHO 0x01
- #define TTY_NOECHO 0x02
- #define TTY_CANON 0x04 /* "canonical" erase/newline processing */
- #define TTY_NOCANON 0x08 /* character-at-a-time processing */
-
- #endif
- %
- chmod 664 ttyio.h
- if test `wc -c < ttyio.h` -ne 248; then
- echo "error extracting ttyio.h" 1>&2
- fi
- echo extracting ttytest.c
- cat > ttytest.c <<\%
- #include <stdio.h>
- #include <ctype.h>
- #include "ttyio.h"
-
- #define Streq(s1, s2) (strcmp(s1, s2) == 0)
-
- main(argc, argv)
- int argc;
- char *argv[];
- {
- int flags = 0;
- int i;
- int c;
- int r;
-
- for(i = 1; i < argc; i++)
- {
- if(Streq(argv[i], "echo"))
- flags |= TTY_ECHO;
- else if(Streq(argv[i], "noecho"))
- flags |= TTY_NOECHO;
- else if(Streq(argv[i], "canon"))
- flags |= TTY_CANON;
- else if(Streq(argv[i], "nocanon"))
- flags |= TTY_NOCANON;
- else fprintf(stderr, "ttytest: unknown mode \"%s\"\n", argv[i]);
- }
-
- tty_init();
-
- if(tty_setmode(flags) < 0)
- {
- fprintf(stderr, "ttytest: can't set requested mode(s)\n");
- exit(1);
- }
-
- while(1)
- {
- while((r = tty_navail()) == 0)
- ;
-
- printf("tty_navail returned %d\n", r);
-
- if(r <= 0)
- r = 1;
-
- for(i = 0; i < r; i++)
- {
- c = tty_getchar();
-
- if(isprint(c))
- printf("you typed %c\n", c);
- else printf("you typed %d\n", c);
-
- if(c == EOF)
- break;
- }
-
- if(c == EOF)
- break;
- }
-
- tty_reset();
- }
- %
- chmod 664 ttytest.c
- if test `wc -c < ttytest.c` -ne 942; then
- echo "error extracting ttytest.c" 1>&2
- fi
- echo extracting ttyio.c
- cat > ttyio.c <<\%
- #include <stdio.h>
- #include <sgtty.h>
- #include "ttyio.h"
-
- #define TRUE 1
- #define FALSE 0
-
- static struct sgttyb savetty;
- static struct sgttyb tty;
- static int havetty = FALSE;
-
- tty_init()
- {
- }
-
- tty_setmode(flags)
- int flags;
- {
- if(!havetty)
- {
- if(ioctl(0, TIOCGETP, &savetty) < 0)
- return -1;
-
- tty = savetty;
- havetty = TRUE;
- }
-
- if(flags & TTY_ECHO)
- tty.sg_flags |= ECHO;
-
- if(flags & TTY_NOECHO)
- tty.sg_flags &= ~ECHO;
-
- if(flags & TTY_CANON)
- {
- tty.sg_flags &= ~CBREAK;
- tty.sg_flags |= CRMOD;
- }
-
- if(flags & TTY_NOCANON)
- {
- tty.sg_flags |= CBREAK;
- tty.sg_flags &= ~CRMOD;
- }
-
- if(ioctl(0, TIOCSETN, &tty) < 0)
- return -1;
-
- return 0;
- }
-
- tty_getchar()
- {
- return getchar();
- }
-
- tty_navail()
- {
- #ifdef FIONREAD
-
- int nchars;
-
- if(ioctl(0, FIONREAD, &nchars) < 0)
- return -1;
-
- return nchars;
-
- #else
-
- return -1;
-
- #endif
- }
-
- tty_reset()
- {
- if(havetty)
- ioctl(0, TIOCSETN, &savetty);
- }
- %
- chmod 664 ttyio.c
- if test `wc -c < ttyio.c` -ne 875; then
- echo "error extracting ttyio.c" 1>&2
- fi
- echo extracting ttyio.curses.c
- cat > ttyio.curses.c <<\%
- #include <stdio.h>
- #include "ttyio.h"
-
- tty_init()
- {
- initscr();
- }
-
- tty_setmode(flags)
- int flags;
- {
- if(flags & TTY_ECHO)
- echo();
-
- if(flags & TTY_NOECHO)
- noecho();
-
- if(flags & TTY_CANON)
- {
- nocbreak();
- nl();
- }
-
- if(flags & TTY_NOCANON)
- {
- cbreak();
- nonl();
- }
-
- return 0;
- }
-
- tty_getchar()
- {
- return getch();
- }
-
- tty_navail()
- {
- /* I don't know how to do this in curses */
- return -1;
- }
-
- tty_reset()
- {
- endwin();
- }
- %
- chmod 664 ttyio.curses.c
- if test `wc -c < ttyio.curses.c` -ne 411; then
- echo "error extracting ttyio.curses.c" 1>&2
- fi
- echo extracting ttyio.msdos.c
- cat > ttyio.msdos.c <<\%
- #include <stdio.h>
-
- #include "ttyio.h"
-
- static int mode = 0;
-
- /* values for internal mode word: */
-
- #define NOCANON 1
- #define NOECHO 2
-
- tty_init()
- {
- }
-
- tty_setmode(flags)
- int flags;
- {
- if(!isatty(0)) /* if you don't have isatty(), just delete this test */
- return -1;
-
- if(flags & TTY_ECHO)
- mode &= ~NOECHO;
-
- if(flags & TTY_NOECHO)
- mode |= NOECHO;
-
- if(flags & TTY_CANON)
- mode &= ~NOCANON;
-
- if(flags & TTY_NOCANON)
- mode |= NOCANON;
-
- return 0;
- }
-
- tty_getchar()
- {
- switch(mode)
- {
- case 0:
- return getchar();
-
- case NOCANON:
- return getche();
-
- case NOCANON | NOECHO:
- case NOECHO: /* oh, well, noecho gets you nocanon */
- return getch();
- }
- }
-
- tty_navail()
- {
- if(!(mode & NOCANON))
- return -1;
-
- return kbhit() ? 1 : 0;
- }
-
- tty_reset()
- {
- mode = 0;
- }
-
- %
- chmod 664 ttyio.msdos.c
- if test `wc -c < ttyio.msdos.c` -ne 753; then
- echo "error extracting ttyio.msdos.c" 1>&2
- fi
- echo extracting ttyio.posix.c
- cat > ttyio.posix.c <<\%
- #include <stdio.h>
- #include <termios.h>
- #include "ttyio.h"
-
- #define TRUE 1
- #define FALSE 0
-
- static struct termios savetty;
- static struct termios tty;
- static int havetty = FALSE;
-
- static int savevmin, savevtime;
- static int havevminvtime = FALSE;
-
- tty_init()
- {
- }
-
- tty_setmode(flags)
- int flags;
- {
- if(!havetty)
- {
- if(tcgetattr(0, &savetty) < 0)
- return -1;
-
- tty = savetty;
- havetty = TRUE;
- }
-
- if(flags & TTY_ECHO)
- tty.c_lflag |= ECHO;
-
- if(flags & TTY_NOECHO)
- tty.c_lflag &= ~ECHO;
-
- if(flags & TTY_CANON)
- {
- tty.c_lflag |= ICANON;
- tty.c_iflag |= ICRNL;
- if(havevminvtime)
- {
- tty.c_cc[VMIN] = savevmin;
- tty.c_cc[VTIME] = savevtime;
- }
- }
-
- if(flags & TTY_NOCANON)
- {
- tty.c_lflag &= ~ICANON;
- tty.c_iflag &= ~ICRNL;
- savevmin = tty.c_cc[VMIN];
- savevtime = tty.c_cc[VTIME];
- havevminvtime = TRUE;
- tty.c_cc[VMIN] = 1;
- tty.c_cc[VTIME] = 0;
- }
-
- if(tcsetattr(0, TCSANOW, &tty) < 0)
- return -1;
-
- return 0;
- }
-
- tty_getchar()
- {
- return getchar();
- }
-
- tty_navail()
- {
- #ifdef FIONREAD
-
- int nchars;
-
- if(ioctl(0, FIONREAD, &nchars) < 0)
- return -1;
-
- return nchars;
-
- #else
-
- return -1;
-
- #endif
- }
-
- tty_reset()
- {
- if(havetty)
- {
- if(!(tty.c_lflag & ICANON) && havevminvtime)
- {
- savetty.c_cc[VMIN] = savevmin;
- savetty.c_cc[VTIME] = savevtime;
- }
-
- tcsetattr(0, TCSANOW, &savetty);
- }
- }
- %
- chmod 664 ttyio.posix.c
- if test `wc -c < ttyio.posix.c` -ne 1280; then
- echo "error extracting ttyio.posix.c" 1>&2
- fi
-