DPY - New terminal display routines (part 1 of 2)

David I. Bell dbell at daisy.UUCP
Tue Jan 29 14:19:09 AEST 1985


This message (and the next) contains a set of library routines which in many
cases can replace curses(3).  My main reasons for writing this are:
	1.	Curses is too slow.
	2.	The way that curses handles windows is obscure.
	3.	These new routines were fun to write.

Suggestions and bug fixes are welcome.  Enjoy!

#---Cut here and place in it's own directory, then feed to Bourne shell---
# This is a shell archive.  Remove anything before this line, then
# unpack it by saving it in a file and typing "sh file".  (Files
# unpacked will be owned by you and have default permissions.)
# This archive contains:
#   README (1213 chars)
#   dpy.doc (14459 chars)
#   makefile (1058 chars)
#   test1.c (503 chars)
#   test2.c (1314 chars)
#
echo x - README
sed -e 's/^X//' > "README" << '//E*O*F README//'
XThese routines were built for a 4.2 BSD UNIX system running on a VAX 750.
X
XIf you are running under another version of UNIX, you have to fix the
Xplaces where signals or ioctls are used (and probably more too).  Since
Xthis is the only version of UNIX I have access to, I don't know how well
Xit will port.
X
XIf you are running under a different machine, you have to change the makefile
Xto use gensubs.c instead of vaxsubs.s (this contains generic subroutines which
Xrun on all machines).  If you are ambitious, you can speed up dpy by writing
Xyour own assembly level routines to duplicate what gensubs.c does.  Not much
Xassembly code is needed.  (If you are real lucky, you are on a 32016, and the
Xnecessary routines are already written in m16subs.s.)  I am willing to collect
Xand distribute such subroutines for some other machines.
X
XI have included two toy programs (test1.c and test2.c) which demonstrate some
Xof the capabilities of dpy.  Along with dpy.doc, they should show you how to
Xuse the module.  I am sorry that dpy.doc is not formatted.  If I waited until
XI got around to making it pretty, this module would probably never get out!
X
XComments and bug fixes are welcome.
X			David I. Bell
X			nsc!daisy!dbell
//E*O*F README//
echo x - dpy.doc
sed -e 's/^X//' > "dpy.doc" << '//E*O*F dpy.doc//'
X		DPY - New Screen Updating Routines
X			by David I. Bell
X
X
XDpy is a terminal display package much like curses(3).  Dpy does not
Xprovide all of the capabilities of curses, but instead tries to focus on
Xthe following two goals:
X1.	Allow the programmer to easily define and update many different
X	rectangles of data on the screen at the same time.
X2.	Be as fast as possible.
X
XThe routines in the dpy library are called directly by the user program.
XNone of these routines are a macro, so that there is no need to include a
Xheader file to use dpy.  These routines use the termlib (or termcap) library
Xroutines to obtain the proper terminal escape sequences.  Therefore, you
Xload your program as in the following example:
X	cc -o yourprog yourprog.c -ldpy -ltermlib.
X
XDpy keeps two arrays which hold images of the terminal screen.  The first
Xarray (the "current screen") is a copy of what the terminal screen really
Xlooks like.  The second array (the "future screen") is a copy of what the
Xcalling program wants the screen to look like.  The use of dpy proceeds in
Xtwo phases under the control of the calling program, as follows:
X
XIn the first phase, only the future screen is manipulated.  The calling
Xprogram positions the "current write location" as desired within the future
Xscreen, and writes new information within it.  During this phase, no actual
XI/O occurs and the terminal screen remains unchanged.
X
XIn the second phase, the calling program asks dpy to update the screen.
XDpy compares the future screen contents with the current screen contents,
Xand does whatever terminal I/O is required in order to make the current
Xscreen look like the future screen.  After this is done, the two screen
Ximages are identical.
X
XThe calling program usually uses dpy by looping between the above two
Xphases.  First, it defines what the screen should look like, and then
Xthe screen is updated, then it defines the screen again, and so on.  In
Xdoing so, the program can be "dumb" or "smart".  A dumb program rewrites
Xall of the data in its windows each iteration of the loop, and depends on
Xdpy to prevent terminal I/O for unchanging data.  Thus a dumb program
Xcan be very trivial, and doesn't have to know anything about what is
Xhappening on the screen.  A smart program knows the exact locations of the
Xdesired screen changes each iteration of the loop, and only rewrites those
Xlocations.  This runs somewhat faster than a dumb program, but has the
Xdisadvantage of introducing complexity and possible bugs into the program.
XIf generating a new screen of data is too much work for each iteration,
Xa good compromise is to keep an internal copy of the screen in the program,
Xupdate that appropriately, and give that whole screen to dpy each iteration.
X
XPutting data into the future screen is much like writing to a real terminal.
XThere is a "current write location", which is similar to the cursor of the
Xterminal.  Like a terminal, characters written to dpy appear at the current
Xwrite location, and automatically advance its location.  When the rightmost
Xlocation on a line is reached, the current write location is automatically
Xmoved to the leftmost location on the next line.  Finally, some control
Xcharacters have special effects like on a terminal.  In particular, linefeed
Xmoves to the beginning of the next line, return moves back to the beginning
Xof the current line, tab moves to the next tab stop as if the corresponding
Xnumber of spaces were given, and backspace backs up by one location.  Other
Xcontrol characters appear in ^X format.
X
XThere are some differences between writing to the future screen and most real
Xterminals, however.  Firstly, scrolling does not usually occur.  If the end of
Xthe screen is reached, any further characters are ignored.  Secondly, it is
Xpossible to limit output to a "window", which is a rectangle of any size on
Xthe screen.  The location and size of a window is specified by the program
Xwhen it wants to limit output to a rectangle.  This window acts just like
Xa regular terminal screen of the appropriate size.  Furthermore, coordinates
Xare relative to the window's upper left corner, so a routine which writes
Xin the window does not need to know where it is.  Data in the future screen
Xwhich lies outside of the window is untouched, no matter what is done within
Xthe window.  Typically, a program divides the screen up into several windows
Xwhich do not overlap.  Data can then be written to each window independently,
Xwithout regard to where each window is.  For example, a linefeed character
Xmoves to the beginning of the next line in the current window, instead of
Xto the beginning of the next line of the screen.
X
XThe following is a description of the procedures available in the dpy library:
X
Xdpyinit(ttytype, modeset, modeclear)
X	This routine must be called before any other call to dpy (except for
X	dpyclose).  It allocates memory for the two screen images for dpy,
X	defines the current window to be the whole screen, sets the current
X	write location to the upper left corner of the screen, and uses
X	signal(2) to cause the terminal stop character to trap to dpystop for
X	pretty program stopping.  The terminal screen itself is not cleared
X	until the first dpyupdate call is made, so that you can initialize
X	your program based upon the terminal size before deciding to continue.
X	Returns nonzero with an error message typed if cannot initialize.
X	ttytype/	the terminal type string, or NULL to use the TERM
X			environment variable value
X	modeset/	bits in terminal flags to set (usually CBREAK)
X	modeclear/	bits in terminal flags to clear (usually ECHO)
X
Xdpyclose()
X	Homes down to the lower left corner and clears the last line of the
X	terminal screen, frees the memory allocated by dpyinit, and resets
X	the original terminal modes.  Useful when exiting your program.
X	If dpyinit has not yet been called, nothing is done, so it is safe
X	to call dpyclose at any time.
X
Xdpywrite(buf, count)
X	Writes characters to the future screen image at the current write
X	location in the current window, and updates the current write
X	location appropriately.  This does not do any I/O to the terminal
X	(dpyupdate does that).  Control characters are handled appropriately,
X	as is running off the end of a line or the window.  This routine is
X	called by dpychar, dpystr, and dpyprintf, and is therefore the most
X	efficient way to give characters to dpy.  Returns nonzero if not all
X	characters fit in the window.
X	buf/		address of characters to write
X	count/		number of characters to write
X
Xdpychar(ch)
X	Writes a single character to the future screen image.  Returns nonzero
X	if the character couldn't fit in the window.
X	ch/		character to write
X
Xdpystr(str)
X	Writes a null terminated string to the future screen image.  Returns
X	nonzero if some of string couldn't fit in the window.
X	str/		address of string to write
X
Xdpyprintf(fmt [,args] ...)
X	Writes a formated string to the future screen image, in the
X	manner of printf.  Returns nonzero if some of the string couldn't
X	fit in the window.
X	fmt/		format string
X	args/		list of arguments for format string
X
Xdpyclearline()
X	Clears the rest of the line in the future screen image (by changing
X	the characters to spaces), but does not change the current write
X	location.  Writing a linefeed to the future screen performs this
X	function, in addition to moving the write location to the next line.
X
Xdpyclearwindow()
X	Clears the rest of the window in the future screen image, but
X	does not change the current write location.  When rewriting a
X	window completely, this should be called when done so that any
X	old contents of the window will be sure to be cleared out.
X
Xdpyhome()
X	Moves the current write location to the top left corner of the window.
X	When being a dumb program which rewrites the whole window, this needs
X	to be called between iterations.
X
Xdpygetrow()
X	Returns the row number of the current write location.  This is
X	the row number where the next character written would go.  If the
X	next character written would not fit in the window, -1 is returned.
X	This number is relative to the first line of the current window.
X	For example, if the current write location is at the beginning of
X	the top line of the window, this function returns zero.
X
Xdpygetcol()
X	Returns the column number of the current write location.  This is
X	the column number where is next character written would go.  If the
X	next character written would not fit in the window, -1 is returned.
X	This number is relative to the current window.  For example, if
X	the current write location is at the beginning of a line in the
X	window, this function returns zero.
X
Xdpyupdate()
X	Makes the terminal screen look like the future screen image,
X	using a minimal amount of terminal I/O.  The cursor is positioned
X	at the current write location when this function is done.
X
Xdpyredraw()
X	Redraws the screen to make it look like the current screen image.
X	Used to fix the screen when it becomes trashed due to glitches or
X	other programs also writing to the screen.  This does not change
X	the current or future screen images.
X
Xdpystop()
X	Suspends execution of the process in a nice way.  Homes down to
X	the lower left corner, clears the last line, resets terminal modes,
X	and then stops the process.  If the process is continued, terminal
X	modes are restored, the screen is redrawn, and execution proceeds.
X	This is called automatically when the terminal stop character is typed
X	by the user.
X
Xdpymove(row, col)
X	Changes the current write location to the given row and column
X	numbers, relative to the upper left corner of the current window.
X	Coordinates start at zero.  Negative numbers measure from the last
X	row or column of the window.  For example, dpymove(-1, 0) positions
X	to the beginning of the last line of the window.  This does not set
X	the actual terminal's cursor location, unless this is also followed
X	by a call to dpyupdate.  Returns nonzero if the coordinates are
X	illegal.
X
Xdpyplace(row, col, ch)
X	Place the given character at the given coordinates within the
X	current window, without changing the current write location.  The
X	character placed should not be a control character.  The coordinates
X	can be negative to measure from the last row or column of the window.
X	Like dpywrite and similar routines, this routine only affects the
X	future screen image, and does no terminal I/O.  Returns nonzero if
X	the coordinates are illegal.
X
Xdpyget(row, col)
X	Returns the character which is at the given coordinates within the
X	current window, without changing the current write location.  The
X	coordinates can be negative to measure from the last row or column of
X	the window.  The character returned is from the future screen image,
X	not the current screen image.  Returns negative if the coordinates
X	are illegal.
X
Xdpyread(prompt, routine, buf, count)
X	This is used to read data from the user, while showing the input
X	data on the screen.  The current window is used for this purpose.
X	Editing of the input and updating of the screen is automatically
X	performed by dpy.  To use this feature properly do the following:
X	1.	Set a window to the region where you want input to occur.
X		Usually this is one line at the top or bottom of the screen.
X	2.	Call dpyread to read the input.
X	If the prompt string pointer is not NULL, then the prompt string will
X	appear at the top of the window, followed by the data typed by the user.
X	To display the user's input without any prompt, use a pointer to a
X	null string.  If the prompt pointer is NULL, then no terminal I/O at
X	all will be performed (useful when input is from a script or file).
X	The buf and count specify the area in the calling program where the
X	data being read is stored.  The data will be what was typed by the
X	user, not what is seen on the screen (for example, control characters
X	appear on the screen as ^X, but appear in the buffer as themselves).
X	If more data is typed than fits in the window, the data in the window
X	is automatically scrolled to keep the current input location visible.
X	Routine is a function variable which specifies a routine which will
X	provide the input characters for dpy.  The routine is called with the
X	previous character read (-1 on the first call).  It returns the
X	next character read, or -1 to return from dpyread.  Providing the
X	previous character as an argument allows a routine to easily return
X	a break character as input, and then end the input on the next call.
X	If 0 is supplied as a routine, a default routine will be used which
X	reads from the standard input until an end of file or newline is typed
X	(which is included in the buffer).  If the character count is exceeded,
X	dpyread will warn the user with a bell and discard further input.
X	Dpyread returns the number of characters read into the buffer, which
X	is not guaranteed to contain a terminating null or newline character.
X
Xdpywindow(minrow, maxrow, mincol, maxcol)
X	Specifies a rectangle where characters will be placed in the future
X	screen image, and sets the current write location to the top left
X	corner of the rectangle.  The rectangle is specified by the minimum
X	and maximum row numbers, and minimum and maximum column numbers,
X	where the top left corner of the screen is row 0 and column 0.
X	The coordinates are specified in absolute screen coordinates, and
X	negative numbers specify row or column numbers from the bottom or
X	right edges of the screen.  For example, dpywindow(0, -1, 0, -1)
X	defines a window which is the whole screen.  Returns nonzero if the
X	coordinates are illegal.
X
X
XFinal hints:
X
XA window can be filled with a background character by simply writing that
Xcharacter to the window until a nonzero return value is obtained, meaning
Xthe window is full.
X
XIf a region of the screen is never changed (such as a help text), then that
Xregion should be in its own window.  Then it only needs to be written once.
X
XThe terminal size can be found after calling dpyinit by simply calling
Xdpymove(-1, -1) to move to the lower right of the screen, and then calling
Xdpygetrow and dpygetcol to return the row and column numbers.
X
XWhile writing data to the window, dpygetrow and dpygetcol are useful to
Xremember the location of a particular position in the window, so that
Xthe terminal cursor can be positioned back to that location when all the
Xdata is written.  In this way, you don't have worry about line wrapping
Xand tab and other control character expansions when computing how to
Xposition the cursor on a particular character.
//E*O*F dpy.doc//
echo x - makefile
sed -e 's/^X//' > "makefile" << '//E*O*F makefile//'
X# @(#)makefile	1.13	1/28/85
X
X.SUFFIXES: .c .o .s
X
XCC = cc
XCFLAGS = -O
XAS = as
XRANLIB = ranlib
XLIBDIR = /usr/lib
X
XCFILES = dpy.c dpymove.c dpyplace.c dpyget.c dpyread.c dpywindow.c
XOFILES = dpy.o dpymove.o dpyplace.o dpyget.o dpyread.o dpywindow.o
XSOURCES = ${CFILES} dpy.h vaxsubs.s m16subs.s gensubs.c
X
X# Machine dependent assembly routines.  Define MACHINEFILES appropriately
X# to select the target machine.  Gensubs is a portable version of the
X# subroutines applicable to any machine (but slower than the assembly ones).
X
XGENFILES = gensubs.o		# generic subroutines
XM16FILES = m16subs.o		# 32032 subroutines
XVAXFILES = vaxsubs.o		# VAX subroutines
XMACHINEFILES = ${VAXFILES}	# subroutines to be used
X
X
Xlibdpy.a: ${OFILES} ${MACHINEFILES}
X	ar rc libdpy.a ${OFILES} ${MACHINEFILES}
X	${RANLIB} libdpy.a
X
Xsources: ${SOURCES}
X
X${SOURCES}:
X	sccs get $@
X
X${OFILES}: dpy.h
X
Xinstall: libdpy.a
X	cp libdpy.a ${LIBDIR}
X	${RANLIB} ${LIBDIR}/libdpy.a
X	chmod 644 ${LIBDIR}/libdpy.a
X
Xclean:
X	rm -f *.o libdpy.a
X
X.s.o:
X	${AS} -o $*.o $*.s
X
X.c.o:
X	${CC} -c ${CFLAGS} $*.c
//E*O*F makefile//
echo x - test1.c
sed -e 's/^X//' > "test1.c" << '//E*O*F test1.c//'
X/*
X * Example program.  Randomly fill up the screen with numbers until
X * it all turns to asterisks.
X */
X
Xmain()
X{
X	register int row, col, ch;
X	register int rows, cols;
X
X	if (dpyinit(0, 0, 0)) exit(1);
X	dpymove(-1, -1);
X	rows = dpygetrow() + 1;
X	cols = dpygetcol() + 1;
X	dpyhome();
X	while (1) {
X		dpyupdate();
X		row = random() % rows;
X		col = random() % cols;
X		ch = dpyget(row, col);
X		if (ch == ' ') ch = '1';
X		else if (ch == '9') ch = '*';
X		else if (ch != '*') ch++;
X		dpyplace(row, col, ch);
X	}
X}
//E*O*F test1.c//
echo x - test2.c
sed -e 's/^X//' > "test2.c" << '//E*O*F test2.c//'
X/*
X * Example program.  Split the screen into three windows, input using the
X * top window until an escape is typed, and show it in the bottom window.
X * The middle window is just a border.  Continue until a ^E is typed.
X */
X
X#include <sgtty.h>
X#define	BOARDER	10		/* row for boarder window */
X#define	BUFSIZE	1000		/* maximum chars which can be input */
X#define	ESC	'\033'		/* escape character */
X#define	QUIT	'\005'		/* quit character (^E) */
X
Xint	grabchar();		/* routine to read tty chars */
X
Xmain()
X{
X	register int	i;		/* character count */
X	char	buf[BUFSIZE];		/* input buffer */
X
X	if (dpyinit(0, CBREAK, ECHO)) exit(1);
X	dpywindow(BOARDER, BOARDER, 0, -1);
X	while (dpywrite("-----", 5) == 0) ;
X	while (1) {
X		dpywindow(0, BOARDER - 1, 0, -1);
X		i = dpyread("Input: ", grabchar, buf, sizeof(buf));
X		if ((i > 0) && (buf[i-1] == QUIT)) break;
X		dpywindow(BOARDER + 1, -1, 0, -1);
X		dpyprintf("Read %d chars:\n", i);
X		dpywrite(buf, i);
X		dpyclearwindow();
X		dpyupdate();
X	}
X	dpyclose();
X}
X
X
X/*
X * Read next char from tty, quitting on an end of file or escape character.
X * The escape character is removed from the buffer, but the end of file
X * character is kept.
X */
Xgrabchar(oldch)
X{
X	unsigned char	newch;
X
X	if ((oldch == QUIT) || (read(0, &newch, 1) != 1) || (newch == ESC))
X		return(-1);
X	return(newch);
X}
//E*O*F test2.c//
echo done



More information about the Comp.sources.unix mailing list