.help tty Dec83 "Terminal Control Interface"
.nh
Introduction

    The TTY interface is a table driven, device independent interface for
controlling terminal and printer devices.  Devices are described either by
environment definitions, or by an entry in the TTY database file.  The TTY
database file is the standard Berkeley UNIX termcap terminal capability
database file (a text file), to which we have added entries for our printer
devices.  Accessing the UNIX termcap file directly without modification
is sometimes awkward, but the benefits of accessing a widely used, standard
database more than compensate for any clumsiness.

When the CL starts up, the following environment variables are defined to
describe the default terminal and printer devices.  The user may subsequently
change the values of these variables with the SET statement or with the STTY
program.


.ks
.nf
	printer		default printer (i.e., "versatec")
	terminal	default terminal (i.e., "vt100", "tek4012")
	termcap		terminal/printer database filename
	ttybaud		baud rate, default 9600
	ttyncols	number of characters per line
	ttynlines	number of lines per screen
.fi
.ke


The variables defining the names of the default terminal and printer
devices will normally correspond to the names of device entries in the
termcap file.  The name of a file containing a single termcap entry
for the device may optionally be given; the file name must contain a
VFN or OSFN directory prefix to be recognized as a filename.  The default
termcap file is dev$termcap.  Terminal initialization files (used to
set tabstops) are files of the form dev$tty.tbi, where "tty" is the last
field of the UNIX pathname in the "if" termcap entry.  If the first
character of the "if" filename string is not a /, an IRAF VFN should
be given.

The value strings for the environment variables TTYNCOLS and TTYNLINES,
defining the screen dimensions, are extracted from the termcap file by the
STTY program during startup.  The screen dimensions are defined in the
environment for two reasons: (1) efficiency (the TTY package is not needed
to learn the screen dimensions), and (2) if a window is used, the logical
screen dimensions may be less than the physical screen dimensions.
Most applications programs should therefore use ENVGETI rather than TTYGETI
to get the screen dimensions.  TTYGETI returns the physical screen dimensions
as given in the termcap file.

.nh
Library Procedures

    Before any TTY control sequences can be output, the TTY device descriptor
must be read from the termcap file into a buffer for efficient access.
TYODES is used to "open" the TTY descriptor; TTYCDES should be called when
done to close the descriptor, returning all buffer space used.  If "ttyname"
is "terminal" or "printer", the descriptor for the default terminal or printer
is accessed.


.ks
.nf
Open/Close descriptor:

	tty =	ttyodes (ttyname)		# pointer ttyodes()
		ttycdes (tty)
.fi
.ke


.ks
.nf
Low level database access, tty control:

	  int = ttygeti (tty, cap)
	 real = ttygetr (tty, cap)
	 bool = ttygetb (tty, cap)
       nchars = ttygets (tty, cap, outstr, maxch)
		ttyputs (fd, tty, ctrlstr, afflncnt)
		ttysubi (ctrlstr, outstr, maxch, arg1, arg2)
.fi
.ke


.ks
.nf
High level control

       OK|ERR = ttyctrl (fd, tty, cap, afflncnt)

		  ttyso (fd, tty, YES|NO)	# turn standout mode on|off
		ttygoto (fd, tty, col, line)	# move cursor absolute
NI		ttyhorz (fd, tty, from, to)	# move cursor on line
NI		ttyvert (fd, tty, from, to)	# move cursor on column
		ttyinit (fd, tty)		# send :is & :if, if defined
	       ttyclear (fd, tty)		# clear screen
	     ttyclearln (fd, tty)		# clear the current line
	     ttyputline (fd, tty, textline, map_cc)	# put text line
.fi
.ke


The TTYGET procedures are used to get capabilities from the database entry.
If the named capability is not found, TTYGETI returns zero, TTYGETB returns
false, and TTYGETS returns the null string.  TTYSUBI performs argument
substitution on a control sequence containing at most two integer arguments
(such as a cursor motion control sequence), generating an output sequence
suitable for input to TTYPUTS.  TTYPUTS puts the control sequence to the
output file, padding as required given the number of affected lines.
The baud rate and pad character, used to generate padding, are evaluated at
TTYODES time and are conveyed to TTYPUTS in the tty descriptor.

TTYSO turns standout mode on or off.
TTYCNTRL calls TTYGETS and TTYPUTS to process and output a control sequence
(slightly less efficiently than if the control string is buffered by
the user code).  TTYGOTO moves the cursor to the
desired column and line.  TTYHORZ and TTYVERT move the cursor on a line
or column, using knowledge of the terminal's capabilities to generate the
most efficient sequence.

TTYPUTLINE is like the FIO PUTLINE, except that it processes any form feeds,
standout mode directives, and other control characters (including tabs)
embedded in the text.  Lines longer than TTYNCOLS are broken into several
output lines.  TTYPUTLINE is used by the help, page, type, and lprint utilities 
to map tabs and standout mode directives for a particular output device.
Standout mode is mapped as reverse video on most VDT's, and as underscore
on most printers and on overstrike terminals such as the tek4012.

.nh
Implementation Notes

    Of the low level routines, only TTYODES and TTYSUBI are at all complex.
The high level routines can be fairly complex due to the logic required to
perform a function on devices which may vary greatly in capabilities.

.nh 2
Structure

.ks
.nf
open descriptor:

	ttyodes
	    envget[si]
	    calloc			[alloc tty descriptor]
	    open,close,seek		[read termcap file]
	    tty_fetch_entry		[fetch entry from file]
		getline,getc
		realloc
	    realloc
	    tty_index_caplist		[encode, sort caplist]
	    ttygeti			[set padchar]
.fi
.ke


.ks
.nf
close descriptor:

	ttycdes
	    ttyctrl			[sometimes needed]
	    mfree			[free tty descriptor]
.fi
.ke


.ks
.nf
get integer capability:

	ttygeti
	    tty_find_capability		[binary search of caplist]
	    ctoi
.fi
.ke


.ks
.nf
get string capability:

	ttygets
	    tty_find_capability		[binary search caplist]
	    tty_map_escapes		[get control string]
.fi
.ke


.ks
.nf
perform argument substitution on control string:

	ttysubi
	    gltoc
.fi
.ke


.ks
.nf
output control string:
	ttyputs
	    putc
.fi
.ke

.nh 2
Data Structures

    The tty descriptor contains several parameters describing the most basic
features of the device, the raw termcap entry, an encoded index into the
termcap entry used to speed up searches, and any status variables for the
device.  Since a fixed length array is used to index capabilities, there is
an upper limit on the number of capabilities, but there is no limit on the
length of a termcap entry.


.ks
.nf
	struct tty_descriptor {
		int	t_len		# length of descriptor
		int	t_op		# offset into caplist
		int	t_padchar	# pad character
		int	t_baud		# baud rate, bits/sec
		int	t_nlines	# lines per screen
		int	t_ncols		# chars per line
		int	t_ncaps		# number of capabilities
		int	t_capcode	# sorted, encoded caplist
		int	t_capindex	# indices of cap strings
		char	caplist[ARB]	# termcap entry
	}
.fi
.ke


TTYODES "opens" the tty descriptor, i.e., allocates a descriptor,
scans the termcap file and reads the terminal entry into the descriptor,
expanding any tc references, and then sets the baud and padchar fields.
There is no limit on the size of a termcap entry, unlike the UNIX
implementation which uses a fixed size, 1024 character caplist buffer.


.tp 10
.nf
procedure ttyodes (ttyname)

begin
	# Get device name or file name.
	if (ttyname equals "terminal")
	    ttysource = fetch environment variable "terminal"
	else if (ttyname equals "printer")
	    ttysource = fetch environment variable "printer"
	else
	    ttysource = ttyname

	# Get device name AND file name.
	if (ttysource is a filename) {
	    file = ttysource
	    entry = "" (first entry)
	} else {
	    file = fetch termcap filename from environment
	    entry = ttysource
	}

	allocate tty descriptor structure "tty"
	tty.len = default length of descriptor
	tty.op = pointer to first char of caplist string

	# Fetch termcap entry into tty descriptor, expanding any "tc"
	# references to other records by rescanning the file.

	fd = open (termcap file)
	repeat {
	    tty_fetch_entry (fd, entry, tty)
	    if (last field of entry is a "tc") {
		entry = value of "tc" field
		set tty.op to overwrite tc field
		rewind (fd)
	    } else
		break
	} 
	close (fd)

	# Get pad character and baud rate and store explicitly in the
	# descriptor, since these values are used in every TTYPUTS call.

	call realloc to return unused space in tty descriptor
	call tty_index_caplist to index the caplist for efficient
	    searches
	tty.baud = fetch "baud" from environment
	tty.padchar = ttygeti (tty, "pc")

	return (tty)
end
.fi


Scan the termcap file until the desired entry is found.  Copy the entry
into the caplist field of the tty descriptor.  If the caplist buffer
overflows, reallocate a larger buffer.

.nf
procedure tty_fetch_entry (fd, entry, tty)

begin
	# Locate entry.
	repeat {
	    advance to next record
	    if (at eof)
		error: termcap entry not found
	} until (positioned to the entry we are looking for)

	# Fetch entry.
	while (getc (fd, ch) != EOF) {
	    if (ch equals \ and next ch is newline) {
		throw both out
	    } else if (ch equals newline) {
		deposit EOS
		return
	    } else
		deposit ch

	    bump output pointer
	    if (buffer is full)
		allocate more space
	}

	error: EOF encountered while reading termcap entry
end
.fi


Prepare the caplist index.  Each two-character capability name maps into
an unique integer code.  We prepare an list of capcodes and an associated
list of indices into the caplist, then sort the capcode list.  At runtime
we will perform a binary search of the capcode list to find the desired
capcode.

.nf
procedure tty_index_caplist (caplist, capcode_list, capindex_list)

begin
	for (each entry in caplist) {
	    compute capcode
	    if (capcode is not already in capcode_list) {
		bump list pointer
		if (list overflows)
		    error: too many capabilities in termcap entry

		# Termcap flags capabilities that are not available 
		# with the char '@'.
		if (first char of entry is '@')
		    index = 0
		save capcode, index in lists
	    }
	}

	if (two or more elements in list)
	    sort list
end
.fi


Process a capability string containing arguments.  Examples of such capability
strings are cursor motion to [x,y], and set scrolling region to [l1,l2].
Note that arguments in the termcap database are zero-indexed by default,
while the TTYSUBI arguments are one-indexed.  The control string given as
input has already been processed to reduce all escape sequences to single
characters.

.nf
procedure ttysubi (ctrlstr, outstr, maxch, arg1, arg2)

ctrlstr:	device control string
outstr:		receives processed string
arg1,arg2:	on input, the arguments to be expanded in the control
		    string.  on output, the difference between the
		    input arg values and the values actually used
		    to produce outstr; these may differ if necessary
		    to avoid special control chars in outstr.

begin
	# Make a local copy of the arguments to make reversal easy.  Also
	# switch to zero-indexing internally, since the termcap entry is
	# zero-indexed.

	arg[1] = arg1 - 1
	arg1 = 0
	arg[2] = arg2 - 1
	arg2 = 0
	argnum = 1

	op = 1
	for (ip=1;  ctrlstr[ip] != EOS;  ip=ip+1) {
	    ch = ctrlstr[ip]
	    if (ch != '%') {
		put char in outstr, bump op
		next
	    }

	    switch (ch)
	    case 'd', '2', '3', '+':
		format coord and concat to outstr
		increment argnum

	    case '.':
		# Binary output format.  Coordinate output in binary is a
		# problem because the OS driver may see a tab, newline, or
		# whatever and map it into something else.  If the value of
		# arg[argnum] corresponds to a special control character,
		# we increment it until we have an acceptable value, leaving
		# it up to our caller to do the rest.

		while (arg[argnum] is a special control character) {
		    increment arg[argnum]
		    increment arg1 or arg2
		}
		put binary value of arg[argnum] to outstr
		increment argnum

	    case '>':
		conditionally modify arg[argnum]
	    case 'r':
		swap arg[1] and arg[2]
	    case 'i':
		increment arg[1] and arg[2]
	    case '%':
		put % to outstr, bump op
	    case 'B':
		BCD encode next arg
	    case 'D':
		backwards BCD encode next arg (ugh)
	    }
	}

	deposit EOS
end
.fi


The technique used for cursor addressing depends on the characteristics
of the terminal.  If the terminal has no cursor motion capability, we
try to position the cursor using primitive motion commands.  Otherwise
we use TTYSUBI to generate the cursor motion sequence.  If TTYSUBI returns
a residual, we use primitive motion commands (up line and backspace) to go
the rest of the way in.


.nf
procedure ttygoto (fd, tty, col, line)

begin
	if (tty has no cursor addressing capability) {
	    # If possible, the calling program should keep track of the
	    # cursor position and use primitive motion commands for
	    # positioning when possible.  We do the best we can without
	    # knowledge of the current position.

	    # First get to a known position.
	    goto home or lower-left, depending on which is closest to
		desired position and on capabilities of terminal.

	    # Now move to the desired position.
	    step to desired line using ttyvert
	    advance to desired column using ttyhorz

	} else {
	    get cursor motion control string
	    call ttysubi to substitute in desired coordinates
	    call ttyputs to put control string to file

	    # In certain cases, ttysubi cannot get us all the way there,
	    # and we have to step in the rest of the way.

	    if (line residual)
		call ttyhorz to adjust position within line
	    if (col residual)
		call ttyvert to adjust vertical position
	}
end
.fi
