wm - a window manager (part 3 of 4)

sources-request at genrad.UUCP sources-request at genrad.UUCP
Sun Aug 4 00:07:09 AEST 1985


Mod.sources:  Volume 2, Issue 33
Submitted by: Tom Truscott <decvax!mcnc!rti-sel!trt>


#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	misc.c
#	save.c
#	shell.c
#	vterm.c
#	wlist.c
#	wm.c
# This archive created: Fri Aug  2 13:13:19 1985
export PATH; PATH=/bin:$PATH
echo shar: extracting "'misc.c'" '(11326 characters)'
if test -f 'misc.c'
then
	echo shar: will not over-write existing file "'misc.c'"
else
sed 's/^X//' << \SHAR_EOF > 'misc.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * Miscellaneous routines for the window manager.
 */
#include "wm.h"

/*
 * Get next unused slot in window structure array.
 * Returns slot number, or -1 if no slot available.
 */
int GetSlot()
{
    register int w;

    for (w = MINWINDOW; w < MAXWINDOWS; w++)
	if (!(win[w].flags&INUSE))
	    return(w);
    
    return(-1);
}

/*
 * Prompt user for a window name.
 */
askwindow()
{
    register int w, c;


    w = -1;
    c = tty_getch();

    if (c == CANCEL1  ||  c == CANCEL2)
	showmsg("Canceled.");

    else if (c == 'l')
    {
	if (iswindow(lastw))
	    w = lastw;
	else
	    showmsg("No last window.");
    }

    else
    {
	if ( ! isdigit(c))
	    showmsg("Indicate window by number, or 'l' for last window.");
	else if ( ! iswindow(ctoi(c)))
	    showmsg("Window #%d does not exist.", ctoi(c));
	else
	    w = ctoi(c);
    }

    return(w);
}

/*
 * Reshape window.
 * Returns 0 on normal completion, -1 otherwise.
 * On abnormal completion (e.g. the user cancels)
 * if this is a new window (flag) it will be deleted,
 * otherwise it is restored to its original state..
 * In the impossible(?) event that the window cannot
 * be restored it is deleted, sorry.
 */
getbounds(w, flag)
register int w;
int flag;
{
    register WINDOW *wp, *twp;

    /* Unpleasant hack: we save the real window contents while
     * a stunt double gets moved about.
     */
    wp = win[w].wptr;
    if ((win[w].wptr=newwin(wlines(wp),wcols(wp),wbegy(wp),wbegx(wp)))==NULL) {
	win[w].wptr = wp;
	showmsg("Cannot allocate temporary window!");
	return(-1);
    }

    showmsg("Move cursor to lower left corner (using hjkl), then type x.");
    if (getpos(w, 0) != 0) {
	delwin(win[w].wptr);
	win[w].wptr = wp;
	if (flag||NewWindow(w, wlines(wp), wcols(wp), wbegy(wp), wbegx(wp))) {
	    WListDelete(w);
	    FreeWindow(w);
	}
	RedrawScreen();
	return(-1);
    }

    showmsg("Now move cursor to upper right corner, then type x.");
    if (getpos(w, 1) != 0) {
	delwin(win[w].wptr);
	win[w].wptr = wp;
	if (flag||NewWindow(w, wlines(wp), wcols(wp), wbegy(wp), wbegx(wp))) {
	    WListDelete(w);
	    FreeWindow(w);
	}
	RedrawScreen();
	return(-1);
    }

    twp = win[w].wptr;
    win[w].wptr = wp;
    if (NewWindow(w, wlines(twp), wcols(twp), wbegy(twp), wbegx(twp))) {
	delwin(twp);
	WListDelete(w);
	FreeWindow(w);
	RedrawScreen();
	return(-1);
    }
    delwin(twp);
    RedrawScreen();
    return(0);
}

/*
 * Key definitions used only by routine getpos
 * These keys are used only for entering position of new window
 */
# define RIGHTCHAR	'l'
# define UPCHAR		'k'
# define LEFTCHAR	'h'
# define DOWNCHAR	'j'
# define BIGRIGHTCHAR	'L'	/* jump			*/
# define BIGUPCHAR	'K'	/* one-fifth of the	*/
# define BIGLEFTCHAR	'H'	/* way across		*/
# define BIGDOWNCHAR	'J'	/* the screen		*/
# define EXECCHAR	'x'

/*
 * move window on screen using UPCHAR, etc.
 * If flag is 0, then window is dragged at lower left.
 * If flag is non-zero, then window is re-sized at upper right.
 * Does not permit bottom (y=LINES-1) line, as it is saved for messages
 * Returns 0 on normal completion, -1 if user cancels.
 */
getpos(w, flag)

int w, flag;
{
    register WINDOW *wp;
    register int x0, y0;
    register int c;
    int bigvert, bighoriz;
    int lines, cols;	/* original size of window */
    int aline, acol;	/* 'anchored' corner of window */
    int top, bot, left, right;

    bigvert=LINES/5+1;
    bighoriz=COLS/5+1;

    wp = win[w].wptr;
    lines = wlines(wp);
    cols = wcols(wp);
    y0 = wbegy(wp)+lines-1;
    x0 = wbegx(wp);
    if (flag) {	/* re-size box */
	aline = y0;
	acol = x0;
	y0 = wbegy(wp);
	x0 = wbegx(wp)+cols-1;
    }
    RedrawScreen();
    (void) movecursor(y0,x0);
    (void) fflush(stdout);

    while ((c = tty_getch()) != EXECCHAR)
    {
	switch (c)
	{
	case KEY_HOME:		x0=y0=0;	break;
	case KEY_RIGHT:
	case RIGHTCHAR:		x0 += 1;	break;
	case KEY_UP:
	case UPCHAR:		y0 -= 1;	break;
	case KEY_BACKSPACE:
	case KEY_LEFT:
	case LEFTCHAR:		x0 -= 1;	break;
	case KEY_DOWN:
	case DOWNCHAR:		y0 += 1;	break;
	case BIGRIGHTCHAR:	x0 += bighoriz;	break;
	case BIGUPCHAR:		y0 -= bigvert;	break;
	case BIGLEFTCHAR:	x0 -= bighoriz;	break;
	case BIGDOWNCHAR:	y0 += bigvert;	break;
	default:
	    if (c == CANCEL1  ||  c == CANCEL2)
	    {
		showmsg("Canceled.");
		return(-1);
	    }
	    else
		flash();
	    break;
	}
	x0 = MAX(x0, 0); x0 = MIN(x0, COLS-1);
	y0 = MAX(y0, 0); y0 = MIN(y0, LINES-2);

	if (!flag) {	/* drag box */
	    bot = y0;
	    left = x0;
	    top = y0+1 - lines; top = MAX(top, 0);
	    right = x0+cols-1; right = MIN(right, COLS-1);
	} else {	/* re-size box */
	    bot = MAX(y0, aline);
	    left = MIN(x0, acol);
	    top = MIN(y0, aline);
	    right = MAX(x0, acol);
	}
	if (NewWindow(w, bot+1-top, right+1-left, top, left))
	    return(-1);
	wp = win[w].wptr;
	if (!tty_inputpending()) {
	    RedrawScreen();
	    (void) movecursor(y0,x0);
	    (void) fflush(stdout);
	}
    }

    return(0);
}

/*
 * If c is a control character, make it printable,
 * e.g. '\007' ==> '^G'.
 */
char *
mkprint(c)

register int c;
{
    static char pbuf[3];


    pbuf[0] = (c>='\040' && c<'\177'   ?   c   :   '^');
    pbuf[1] = (c<'\040' ? c+0100 : c<'\177' ? '\0' : '?');
    pbuf[2] = '\0';

    return(pbuf);
}

/*
 * Send a setenv command for wmvirt terminal to shell in window w.
 * Note: this is a sad kludge.  If fails if 'vi' or anything
 * other than the wm-activated shell is active in the window.
 * It is rumored that 4.3 BSD supports an ioctl to change
 * the window size (and corresponding signals that are understood
 * by screen managers).  That will provide a better alternative.
 * Note: the setenv hack will still be needed for sessions
 * on remote machines via "tip".
 * Rlogin should (in 4.2 BSD does not) pass along TERMCAP
 * in addition to TERM.
 *
 * mode 0 -- disconnect termcap (unlink sneakytermcap file)
 * mode 1 -- set termcap, attempting sneaky termcap method first.
 * mode 2 -- set termcap, storing termcap string in environment
 * mode 3 -- set termcap by writing a shell command to the window
 */
XSetTerm(w, mode)

register int w, mode;
{
    register int i, fd;
    register char *s, *lasts;

#ifdef SNEAKYTERMCAP
    if (mode < 3) {
/*
 * Use of /tmp to hold the termcap files is a security hole
 * on most UNIX systems.  Safer, but more trouble,
 * would be to put these files in a directory in the
 * users home directory.
 */
	char termfile[100];
	int oldmask;
	(void) sprintf(termfile, "/tmp/WM.%d.%d",
			(mode==1? getppid(): getpid()), w);
	(void) unlink(termfile);
	if (mode == 0)
	    return;
	if (mode == 1) {
	    (void) setenv("TERM", "wmvirt");
	    (void) setenv("TERMCAP", termfile);
	}
	s = termcap(w);
	oldmask = umask(0);
	fd = creat(termfile, 0644);
	(void) umask(oldmask);
	if (fd >= 0 && write(fd, s, strlen(s)) == strlen(s)
	 && write(fd, "\n", 1) == 1
	 && close(fd) == 0)
	    return;
	if (fd >= 0)
	    (void) close(fd);
	if (mode == 1) {
	    (void) setenv("TERMCAP", s);
	    return;
	}
	/* gotta do it the ugly way ... */
    }
#endif

    if (mode == 0)
	return;

    /* As suggested by Dave Eckhardt (psuvax1!dae), we check for
     * shellnames *ending* with csh as a clue that a csh is runnning.
     * (This check is also made by the SUSPEND command.)
     */
    if ((i = strlen(shellname)) >= 3
     && strcmp(shellname+i-3,"csh") == 0)
	s = "\nsetenv TERM wmvirt; setenv TERMCAP '";
    else
	s = "\nexport TERM TERMCAP; TERM=wmvirt; TERMCAP='";

    fd = win[w].pty;
    (void) write(fd, s, strlen(s));


    s = termcap(w);
    /* This crazy loop attempts to shield special chars from the tty driver,
     * and to fold the lines to avoid bumping into TTYHOG.
     * A TTYHOG of 255 is much too small, but lots of systems have that. */
    lasts = s;
    for (i = 0; s[i]; i++) {
	if (s[i] == killchar() || s[i] == erasechar()) {
	    if (i)
		(void) write(fd, s, i);
	    (void) write(fd, "\\", 1);
	    s += i;
	    i = 0;
	}
        else if (s[i] == ':' && i+(s-lasts) > 180 && i > 0 && s[i-1] != '\\') {
	    (void) write(fd, s, i+1);
	    (void) write(fd, "\\\r:", 3);
	    s += i+1;
	    lasts = s;
	    i = 0;
	}
    }
    (void) write(fd, s, strlen(s));

    (void) write(fd, "'\n", 2);
}

/*
 * Find the largest unobscured rectangle on the screen,
 * returning its description as (lines, cols, begline, begcol)
 * via reference parameters.
 * The window being fitted is 'w'.
 * Returns -1 if no unobscured rectangle is found.
 *
 * Note: this algorithm is based on one from Jon Bentley's
 * "Programming Pearls" column in the CACM.  Many readers
 * independently discovered the algorithm, including some
 * who wrote to Bentley and got mentioned in his column (sigh).
 * An interesting question is, is there a faster algorithm?
 * (Faster in the worst case, that is.)
 */
fitwindow(w, lp, cp, blp, bcp)
int w, *lp, *cp, *blp, *bcp;
{
    short *wbase;			/* vaguely like a WINDOW pointer */
    register short *wptop, *wpbot;	/* Ye Olde manual code optimization */
    register int x, ytop, ybot;
    int bestarea, bestsofar, besttohere, bestx;

    /* Allocate an appropriately sized array */
    if (LINES > 32000
     || (wbase = alloc(LINES*COLS, short)) == NULL)
	return(-1);

    /* Compute cumulative coverage table in LINES*COLS steps */
    /* This is probably the slower loop, due to the subroutine call */
    for (x = 0; x < COLS; x++)
	for (ytop=0,wptop=wbase+x; ytop < LINES-1; ytop++,wptop+=COLS)
	    wptop[0] = covers(w, ytop, x) + ((ytop > 0)? wptop[-COLS]: 0);

    /* Find largest rectangle in LINES*LINES/2*COLS steps */
    bestarea = 0;
    for (ytop = 0; ytop < LINES-1; ytop++) {
	for (ybot  = ytop; ybot < LINES-1; ybot++) {
	    /* Find largest rectangle in this strip */
	    bestsofar = besttohere = 0;
	    wptop = wbase + (ytop-1)*COLS;
	    for (x=0,wpbot=wbase+ybot*COLS; x < COLS; x++,wpbot++,wptop++) {
		if (wpbot[0] - ((ytop > 0)? wptop[0]: 0))
		    besttohere = 0;
		else if (++besttohere > bestsofar) {
		    bestsofar = besttohere;
		    bestx = x+1 - bestsofar;
		}
	    }
	    if (bestsofar*(ybot+1-ytop) > bestarea) {
		bestarea = bestsofar*(ybot+1-ytop);
		*lp = ybot+1-ytop;
		*cp = bestsofar;
		*blp = ytop;
		*bcp = bestx;
	    }
	}
    }
    free((char *)wbase);

    if (bestarea <= 0)
	return(-1);
    return(0);
}

/*
 * Returns "" if n == 1, otherwise "s".
 * Useful for printing messages such as "1 line" or "2 lines".
 */
char *
plural(n)
int n;
{
	return (n == 1? "": "s");
}

/*
 * This routine is equivalent to 'malloc',
 * but returns a 'double *' which makes lint happier.
 * If only malloc were declared this way in the lint library
 * this kludge would be unnecessary.
 */
double *
Malloc(n)
unsigned int n;
{
	extern char *malloc();	/* The tyranny of the lint library */
	return((double *)malloc(n));	/* Ignore lint warning */
}
SHAR_EOF
if test 11326 -ne "`wc -c < 'misc.c'`"
then
	echo shar: error transmitting "'misc.c'" '(should have been 11326 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'save.c'" '(4419 characters)'
if test -f 'save.c'
then
	echo shar: will not over-write existing file "'save.c'"
else
sed 's/^X//' << \SHAR_EOF > 'save.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * save.c  M. Lennon  2/07/85
 * Save and restore windows between WM sessions.
 */

#include "wm.h"


/* Save windows from this session in HOME/.wmrc.
 *
 * Produces an ascii file containing info
 * necessary for restoring windows in next WM session.
 */
XSave(file)

char *file;	/* name of save file */
{
    register FILE *fp;			/* save file pointer */
    register WINDOW *w;
    register int i;


    if (*file == '\0')
	return(FALSE);
    if ((fp=fopen(file,"w")) == NULL)
	return(FALSE);

    fprintf(fp, "%s\n", mkprint(prefix));

    for (i=botw; i>=0; i=win[i].next)
    {
	w = win[i].wptr;
	fprintf(fp, "%d %d %d %d %d\n",
	  i,
	  (win[i].flags&YFLEX)? 0: wlines(w),
	  (win[i].flags&XFLEX)? 0: wcols(w),
	  wbegy(w), wbegx(w));
    }

    (void)fclose(fp);
    return(TRUE);
}

/*
 * Restore windows from previous session
 * as stored in ~/.wmrc.
 * Returns number of windows restored (0 on error).
 */
Restore(file)

char *file;	/* name of restore file */
{
    register FILE *fp;			/* restore file pointer */
    int w;				/* window index */
    int lines, cols, begline, begcol;	/* window parameters */
    int count;		/* number of windows restored */
    int lshrink, cshrink; /* amount that windows must be shrunk to fit */
    int rc;		/* temporary to hold return code from fscanf */
    char ccbuf[10], tbuf[100];	/* temporary buffers */
    register char *p;	/* temp pointer into tbuf */


     /* Open save/restore file.
      */
    if (*file == '\0')
	return(0);
    if ((fp=fopen(file,"r")) == NULL) {
	showmsg("\007Cannot read '%s'.", file);
	sleep(2);
	return(0);
    }
    
     /* Read first line of ~/.wmrc to get the WM prefix character.
      */
    if (fscanf(fp,"%2s%1[\n]", ccbuf, tbuf) != 2) {
	(void)fclose(fp);
	showmsg("\007Bad .wmrc file '%s'.", file);
	sleep(2);
	return(0);
    }
    if (ccbuf[0]=='^' && ccbuf[1])
	prefix = (ccbuf[1]=='?' ? '\177' : ccbuf[1]-0100);
    else prefix = ccbuf[0];

    
     /* Restore the windows.
      * Stop trying if input error encountered.
      */
    count = 0;
    for (;;)
    {
	 /* Read window parameters.
	  * Check for end of file, format error,
	  * bad window name, and bad window dimensions.
	  */
	rc = fscanf(fp,"%d%d%d%d%d%1[\n]",
		&w,&lines,&cols,&begline,&begcol,tbuf);
	if (rc == EOF)
	    break;
	if (rc != 6 || lines < 0 || cols < 0 || begline < 0 || begcol < 0
	 || w < MINWINDOW || w >= MAXWINDOWS || (win[w].flags&INUSE)) {
	    showmsg("\007Bad window entry, file '%s'.", file);
	    sleep(2);
	    break;
	}

	/*
	 * check for "flex" windows
	 */
	if (lines == 0 && begline == 0) {
	    lines = LINES-1;
	    win[w].flags |= YFLEX;
	}
	if (cols == 0 && begcol == 0) {
	    cols = COLS;
	    win[w].flags |= XFLEX;
	}

	/* Check for windows which are beyond this screen */
	/* Bug: if .wmrc is updated these windows are omitted! */
	if (begline >= LINES-1 || begcol >= COLS) {
	    showmsg("\007Window #%d is off this screen.", w);
	    sleep(2);
	    continue;
	}

	/* Check for windows which must be shrunk to fit */
	/* Bug(?): if .wmrc is updated the new sizes are saved */
	lshrink = cshrink = 0;
	if (begline+lines > LINES-1) {
	    lshrink = begline+lines-(LINES-1);
	    lines -= lshrink;
	}
	if (begcol+cols > COLS) {
	    cshrink = begcol+cols-COLS;
	    cols -= cshrink;
	}
	if (lshrink+cshrink) {
	    p = tbuf;
	    (void)sprintf(p, "\007Window #%d shrunk by", w); p += strlen(p);
	    if (lshrink) {
		(void)sprintf(p, " %d line%s%s", lshrink, plural(lshrink),
			cshrink? " and": "");
		p += strlen(p);
	    }
	    if (cshrink) {
		(void)sprintf(p, " %d column%s", cshrink, plural(cshrink));
		p += strlen(p);
	    }
	    (void)sprintf(p, ".");
	    showmsg("%s", tbuf);
	    sleep(2);
	}

	 /* Construct new window.
	  */
	if (NewWindow(w,lines,cols,begline,begcol))
	    continue;	/* cannot happen? */
	WListAdd(w);
	count++;
    }

    (void)fclose(fp);
    return(count);
}
SHAR_EOF
if test 4419 -ne "`wc -c < 'save.c'`"
then
	echo shar: error transmitting "'save.c'" '(should have been 4419 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'shell.c'" '(7153 characters)'
if test -f 'shell.c'
then
	echo shar: will not over-write existing file "'shell.c'"
else
sed 's/^X//' << \SHAR_EOF > 'shell.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * This file contains routines dealing
 * with the window shells.
 */

#include "wm.h"
#include <signal.h>
#include <errno.h>

static struct sgttyb sgttybuf;
static struct tchars tcharsbuf;
static struct ltchars ltcharsbuf;
static int ttymode;
static int ttydisc;
static int ttyfd;		/* file descriptor for /dev/tty */


/*
 * Initialize parameters needed for creating new window shells.
 */
XShellInit()
{
    (void) ioctl(0, (int)TIOCGETD, (char*)&ttydisc);
    (void) ioctl(0, (int)TIOCGETC, (char*)&tcharsbuf);
    (void) ioctl(0, (int)TIOCLGET, (char*)&ttymode);
    (void) ioctl(0, (int)TIOCGLTC, (char*)&ltcharsbuf);
    (void) ioctl(0, (int)TIOCGETP, (char*)&sgttybuf);

     /*
      * The psuedo-tty driver should probably not produce
      * internal magic delay characters (cf. sys/tty.c (ttyoutput)).
      * It seems easiest to turn off all delays here.
      * (Even that is not all that easy, due to an XTABS glitch.)
      */
    {
	register int i = ALLDELAY;
	if ((sgttybuf.sg_flags&TBDELAY) == XTABS)
		i &= ~TBDELAY;
	sgttybuf.sg_flags &= ~i;
    }

     /* We will use 'ttyfd' later when setting
      * controlling terminals for new shells.
      */
    ttyfd = open("/dev/tty", 0);

    strcpy(shellpgm, getenv("SHELL") ? getenv("SHELL") : "sh");
    strcpy(shellname, rindex(shellpgm,'/') ? rindex(shellpgm,'/')+1 : shellpgm);
}

/*
 * spawn shell process for window number w
 * Finds first available pty and its matching pts and opens them
 * Returns TRUE if it found you some pty's else FALSE
 */
NewShell(w)

register int w;
{
    static char ptlist[] = "0123456789abcdef";
    char ptyname[100], ptsname[100];	/* names of pty master/slave devices */
    int fpty, fpts;			/* descriptors for ""   ""    ""     */
    register int c, i;			/* index */
    int ptydisc;
    extern int errno;


     /* Look for available pty master/slave pair.
      */
    for (c = 'p';; c++) {
	for (i = 0; ptlist[i]; i++) {
	    (void) sprintf(ptyname, "/dev/pty%c%c", c, ptlist[i]);
	    if ((fpty = open(ptyname, 2)) < 0) {
		if (errno == ENOENT)
		    return(-1);
		continue;
	    }
	    (void) sprintf(ptsname, "/dev/tty%c%c", c, ptlist[i]);
	    if ((fpts = open(ptsname, 2)) < 0) {
		(void) close(fpty);
		continue;
	    }
	    /* This doesn't close the security hole,
	     * but it helps avoid certain problems.
	     */
	    if (ioctl(fpts, (int)TIOCGETD, (char *)&ptydisc) || ptydisc) {
		(void) close(fpts);
		(void) close(fpty);
		continue;
	    }
	    /* Okay, this one will do */
	    goto gottatty;
	}
    }
gottatty:;
    (void) ioctl(fpty, (int)FIOCLEX, (char *)0);



     /* Fork a new shell.
      */
    switch (win[w].pid=fork())
    {
    default:		/* parent */
	(void) close(fpts);
	win[w].pty=fpty;
	break;

    case 0:		/* child */
	 /* Set up stdin, stdout, stderr streams. */
	dup2(fpts,0); dup2(fpts,1); dup2(fpts,2);
	if (fpts > 2)
	    (void) close(fpts);
	 /* Set up slave as new controlling terminal. */
	SetCntrlTerm(ptsname);
	 /* Set up process groups. */
	SetProcGrp();
	 /* Set pty terminal attributes. */
	InitPseudoTty();
	 /* Set env variables TERM & TERMCAP. */
#ifdef SNEAKYTERMCAP
	SetTerm(w, 1);
#else
	(void) setenv("TERM", "wmvirt");
	(void) setenv("TERMCAP", termcap(w));
#endif
	 /* Exec the shell. */
	execlp(shellpgm, shellname, (char *)0);
	exit(1);			/* exec failed */
	break;

    case -1:		/* fork failed */
	(void) close(fpty);
	(void) close(fpts);
	break;
    }

    return(win[w].pid < 0);
}

/*
 * Set up terminal attributes for new pseudo-tty.
 * The attributes are those of user's regular terminal.
 * This way, the pseudo-tty will behave just like user's terminal.
 */
InitPseudoTty()
{
     /* Set tty discipline, edit characters,
      * mode, etc.
      */
    (void) ioctl(0, (int)TIOCSETP, (char*)&sgttybuf);
    (void) ioctl(0, (int)TIOCSETD, (char*)&ttydisc);
    (void) ioctl(0, (int)TIOCSETC, (char*)&tcharsbuf);
    (void) ioctl(0, (int)TIOCLSET, (char*)&ttymode);
    (void) ioctl(0, (int)TIOCSLTC, (char*)&ltcharsbuf);
}

/*
 * Make 'cterm' the new controlling terminal for
 * this process. Use TIOCNOTTY to turn off
 * current control terminal. Then when we open
 * 'cterm', it automatically becomes the new
 * controlling terminal.
 * Can you say 'kludge'? I knew you could.
 */
XSetCntrlTerm(cterm)

char *cterm;
{
     /* We really ought to check the return values
      * of these calls. Oh, well.
      */
    (void) ioctl(ttyfd, (int)TIOCNOTTY, (char*)0);
    (void) close(ttyfd);
    ttyfd = open(cterm, 0);
    (void) close(ttyfd);
}

/*
 * Set up a new process group for a process.
 * Process group id will be the pid of the current process.
 * Also set up terminal process group for the benefit of
 * csh job control facilities.
 */
XSetProcGrp()
{
    int pgrp;

    pgrp = getpid();
    (void) setpgrp(0, pgrp);
    (void) ioctl(0, (int)TIOCSPGRP, (char*)&pgrp);
}

/*
 * Kill shell (process group) in window 'w'.
 */
KillShell(w)

register int w;
{
    if (win[w].pid <= 0)
	return;

     /* Close pty file.
      */
    (void) close(win[w].pty);

     /* Send SIGHUP to all process associated
      * with window w.
      */
    (void) kill(win[w].pid, SIGHUP);

#ifdef SNEAKYTERMCAP
    SetTerm(w, 0);
#endif
    win[w].pid = 0;
}

setenv(name, val)

char *name, *val;
{
    register int n, i;
    register char **ep, *oldval;
    char *namecmp();
    extern char **environ;


    ep = environ;

     /* See if the environment variable is already set.
      */
    for (n=0; ep[n]!=NULL; n++)
	if ((oldval=namecmp(name,ep[n])) != NULL)
	    break;
    
     /* If the environment variable is already set and
      * the new value is no longer than the old one,
      * we can just overwrite the old one.
      */
    if (ep[n] != NULL && strlen(oldval) >= strlen(val))
	strcpy(oldval, val);

     /* Else we have to reallocate (name=value).
      */
    else
    {
	 /* If environment variable not already set,
	  * we have to reallocate entire 'environ' array
	  * with one additional slot in order to add the new variable.
	  * Make sure to terminate array with a NULL entry.
	  */
	if (ep[n] == NULL)
	{
	    if ((ep=alloc(n+2, char*)) == NULL)
		return(-1);

	    for (i=0; i<n; i++)
		ep[i] = environ[i];

	    ep[n+1] = NULL;

	    environ = ep;
	}

	 /* Allocate space for new variable, add it to 'environ'.
	  */
	if ((ep[n]=alloc(strlen(name)+strlen(val)+2, char)) == NULL)
	    return(-1);
	(void) sprintf(ep[n], "%s=%s", name, val);
    }

    return(0);
}

static char *namecmp(s1, s2)

register char *s1, *s2;
{
    for ( ; *s1==*s2; s1++,s2++)
	;

    if (*s1 == '\0' && *s2 == '=')
	return(s2+1);

    return(NULL);
}
SHAR_EOF
if test 7153 -ne "`wc -c < 'shell.c'`"
then
	echo shar: error transmitting "'shell.c'" '(should have been 7153 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'vterm.c'" '(11456 characters)'
if test -f 'vterm.c'
then
	echo shar: will not over-write existing file "'vterm.c'"
else
sed 's/^X//' << \SHAR_EOF > 'vterm.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * This file contains routines for low-level virtual
 * terminal emulation.
 */

#include "wm.h"

#define FULLWIDTH(wp)	(wcols(wp)==COLS)
#define FULLSCREEN(wp)	(FULLWIDTH(wp) && wlines(wp)==LINES-1)

/* Termcap entry for virtual terminals.
 */
static char TERMCAP[]="wm|wmvirt|wm virtual terminal:am:bs:ce=\\EK:ho=\\EH:cd=\\EB:cl=\\ES:nd=\\EC:up=\\EA:cm=\\EY%+ %+ :";
#define TBUFLEN	(sizeof(TERMCAP)+300)	/* max length of termcap string */

extern char *tgoto(), *tparm();

/*
 * Add 'n' characters of 'p' to window 'w' at its current (y, x) coordinate.
 */
WMaddbuf(w, p, n)
int w;
register char *p;
register int n;
{
    register WINDOW *wp;
    register int y, x;
    register int c;

    /*
     * Are we in the midst of an escape sequence?
     */
    while (win[w].pend[0] && --n >= 0)
	WMescape(w, toascii(*p++));

    wp = win[w].wptr;
    getyx(wp, y, x);


    while (--n >= 0) switch (c = toascii(*p++))
    {
    case '\t':
	x = (x+8) & ~07;
	while (x >= wcols(wp)) {
	    WMaddbuf(w, "\n", 1);
	    x -= wcols(wp);
	}
	wmove(wp, y = wcury(wp), x);
	break;
    case '\n':
	if (++y >= wlines(wp)) {
	    --y;
	    wmove(wp, 0, x); WMdeleteln(w);
	}
	wmove(wp, y, x);
	break;
    case '\r':
	wmove(wp, y, x = 0);
	break;
    case '\b':
	if (x>0) wmove(wp, y, --x);
	break;
    case '\007':
	beep();
	break;
    case '\0':
	break;
    case ESC:
	win[w].pend[0] = ESC;
	win[w].pend[1] = '\0';
	while (win[w].pend[0] && --n >= 0)
	    WMescape(w, toascii(*p++));
	getyx(wp, y, x);
	break;
    /* Dummy cases to fool pcc into generating a fast switch table */
    case 01: case 02: case 03: case 04: case 05: case 06:
    default:
	if (isprint(c))
	{
	    waddch(wp, c);
	    if (++x >= wcols(wp)) {
		if (++y >= wlines(wp)) {
		    --y;
		    wmove(wp, 0, 0); WMdeleteln(w);
		}
		wmove(wp, y, x = 0);
	    }
	}
	else
	{
	    char *s = mkprint(c);
	    WMaddbuf(w, s, strlen(s));
	    getyx(wp, y, x);
	}
	break;
    }
}

/*
 * Construct virtual terminal escape sequence
 * one character at a time.
 * When escape sequence is complete, perform
 * the indicated action in window 'w'.
 */
WMescape(w, c)
int w;
int c;
{
    register WINDOW *wp;
    register int y, x;
    register char *pend;
    int oldx, oldy;


    pend = win[w].pend;
    wp = win[w].wptr;
    getyx(wp, y, x);

     /* ESC-Y is a multi-character escape sequence
      * so we need to make sure we have all the
      * characters before we start processing it.
      */
    if (c == 'Y' && pend[1] == '\0') { pend[1]=c; pend[2]='\0'; return; }

    else if (pend[1] == 'Y')
    {
	if (pend[2]=='\0') { pend[2]=c; return; }
	else               { pend[3]=c; c='Y'; }
    }

     /* Process escape sequence.
      */
    pend[0] = '\0';	/* escape no longer pending */
    switch (c)
    {
    case 'Y':				/* cursor motion */
	y = oldy = pend[2]-' '; x = oldx = pend[3]-' ';
	if (x < 0) x = 0;
	if (y < 0) y = 0;
	if (x >= wcols(wp)) x = wcols(wp)-1;
	if (y >= wlines(wp)) y = wlines(wp)-1;
	if (y != oldy || x != oldx)
	    showmsg("Bad cursor motion to (%d,%d).", oldy, oldx);
	wmove(wp, y, x);
	break;
    case 'K':				/* clear to end of line */
	wclrtoeol(wp);
	break;
    case 'B':				/* clear to bottom of window */
	wclrtobot(wp);
	break;
    case 'H':				/* home cursor */
	wmove(wp, 0, 0);
	break;
    case 'R':				/* visual bell */
	flash();
	break;
    case 'D':				/* delete line */
	WMdeleteln(w);
	break;
    case 'L':				/* insert line */
	WMinsertln(w);
	break;
    case 'P':				/* insert character */
#ifdef CURSEASSIST
#ifndef TERMINFO
	if (FULLWIDTH(wp) && insert_character && !insert_null_glitch) {
	    (void) movecursor(wbegy(wp)+y, wbegx(wp)+x);
	    putp(insert_character);	winsch(curscr, ' ');
	}
#endif
#endif
	winsch(wp, ' ');
	break;
    case 'Q':				/* delete character */
#ifdef CURSEASSIST
#ifndef TERMINFO
	if (FULLWIDTH(wp) && delete_character) {
	    (void) movecursor(wbegy(wp)+y, wbegx(wp)+x);
	    putp(delete_character);	wdelch(curscr);
	}
#endif
#endif
	wdelch(wp);
	break;
    case 'S':				/* erase window */
	werase(wp);
#ifdef	CURSEASSIST
	if (FULLSCREEN(wp) && !msgbirth)
	    clearok(curscr, TRUE);
#endif
	break;
    case 'C':				/* non-destructive blank */
	if (++x >= wcols(wp)) {
	    WMaddbuf(w, "\n", 1);
	    x = 0;
	}
	wmove(wp, wcury(wp), x);
	break;
    case 'A':				/* cursor up */
	if (--y>=0) wmove(wp, y, x);
	break;
    case 'O':				/* enter standout mode */
	wstandout(wp);
	break;
    case 'E':				/* leave standout mode */
	wstandend(wp);
	break;
    default:
	{
	    char *s;
	    s = mkprint(ESC);
	    WMaddbuf(w, s, strlen(s));
	    s = mkprint(c);
	    WMaddbuf(w, s, strlen(s));
	}
	break;
    }
}

/*
 * Insert a line just above the current line of window wp.
 * The cursor location in wp is not changed.
 */
WMinsertln(w)
int w;
{
    register WINDOW *wp;
    register int curline, curcol;

    wp = win[w].wptr;
    wrefresh(wp);	/* smooths scrolling.  Crucial for untouchwin. */
    winsertln(wp);

#ifdef CURSEASSIST
     /* If this terminal has scrolling regions, use them */
    if (has_scroll_region && FULLWIDTH(wp)) {
	/* First, get curscr management out of the way. */
	curline = cursrow(); curcol = curscol();
	Cmove(wbegy(wp)+wlines(wp)-1, 0);
	Cdeleteln();
	Cmove(wbegy(wp)+wcury(wp), 0);
	Cinsertln();
	Cmove(curline, curcol);
	/* now update the screen itself */
	(void) movecursor(wbegy(wp)+wcury(wp), wbegx(wp)+wcurx(wp));
	putp(save_cursor);	/* Save since CS garbles cursor */
	putp(tgoto(change_scroll_region,
	    wbegy(wp)+wlines(wp)-1, wbegy(wp)+wcury(wp)));
	putp(restore_cursor);	/* CS garbles cursor */
	putp(scroll_reverse);
	putp(tgoto(change_scroll_region, LINES-1, 0));
	putp(restore_cursor);	/* Once again put it back */
	Untouchwin(wp);
    }

    /* Else if this terminal has scrolling rectangles, use them now. */
#ifdef SET_WINDOW
    else if (has_scroll_window) {
	overwrite(wp, curscr);	/* slow but easy */
	putp(tparm(set_window,
	    wbegy(wp)+wcury(wp), wbegy(wp)+wlines(wp)-1,
	    wbegx(wp), wbegx(wp)+wcols(wp)-1));
	putp(scroll_reverse);
	putp(tparm(set_window, 0, LINES-1, 0, COLS-1));
	/* get back to where curses thinks we are */
	putp(tgoto(cursor_address, curscol(), cursrow()));
	Untouchwin(wp);
    }
#endif

    /* Else if this terminal has ins/del line, now is the time */
    else if (has_insdel_line && FULLWIDTH(wp)) {
	/* Open a line above current line in window wp,
	 * then delete wp's bottom line.
	 * Perform identical operations on curscr
	 * as we do on the terminal itself.
	 */
	(void) movecursor(wbegy(wp)+wcury(wp), 0);
	putp(insert_line);		 Cinsertln();
	(void) movecursor(wbegy(wp)+wlines(wp), 0);
	putp(delete_line);		 Cdeleteln();
	RestoreMsg();
	Untouchwin(wp);
    }
#endif
}

/*
 * This routine deletes the current line in window wp.
 * The cursor location in wp is not changed.
 */
WMdeleteln(w)
int w;
{
    register WINDOW *wp;
    register int curline, curcol;

    wp = win[w].wptr;

     /*
      * See what we can do about windows that scroll slowly
      */
     if (!(win[w].flags&FAST)) {
	static int lines_since_refresh = 0;
	if ((lines_since_refresh += 7) >= wlines(wp)) {
	    wrefresh(wp);
	    lines_since_refresh = 0;
	}
	wdeleteln(wp);
#ifdef BUGGYTERMINFO
	touchwin(wp);
#endif
	return;
    }

    wrefresh(wp);	/* smooths scrolling.  Crucial for untouchwin. */
    wdeleteln(wp);
#ifdef BUGGYTERMINFO
    /* wdeleteln neglects first/bottom info for the last line */
    touchwin(wp);
#endif

#ifdef CURSEASSIST
     /* If we're deleting top line of a full screen window,
      * this is the same as scrolling.
      * (We do not this if we have scrolling region support
      *  and there is a wm message, but what a bother.)
      */
    if (FULLSCREEN(wp) && wcury(wp)==0 && !(has_scroll_region && msgbirth))
    {
	ZapMsgLine();	/* so it doesn't scroll up into our window */
	curline = cursrow(); curcol = curscol();
	Cmove(0, 0); Cdeleteln();
	Cmove(curline, curcol);

	/* Cause screen to scroll.
	 * Since wm almost always 'wants' the cursor on LINES-2,
	 * there is a cheap heuristic thrown in.
	 */
	(void) movecursor(LINES, curcol);
#ifndef TERMINFO
	if (cursor_up) {
	    putp(cursor_up);
	    Cmove(LINES-2, curcol);
	}
#endif
	RestoreMsg();
	Untouchwin(wp);
    }

     /* Else if this terminal has scrolling regions, use them. */
    else if (has_scroll_region && FULLWIDTH(wp)) {
	curline = cursrow(); curcol = curscol();
	Cmove(wbegy(wp)+wcury(wp), 0);
	Cdeleteln();	/* it is about to be deleted */
	Cmove(wbegy(wp)+wlines(wp)-1, 0);
	Cinsertln();	/* it is about to be cleared */
	Cmove(curline, curcol);
	(void) movecursor(wbegy(wp)+wlines(wp)-1, wbegx(wp)+wcurx(wp));
	putp(save_cursor);	/* Save since CS garbles cursor */
	putp(tgoto(change_scroll_region,
	    wbegy(wp)+wlines(wp)-1, wbegy(wp)+wcury(wp)));
	putp(restore_cursor);	/* put cursor back */
	putp(scroll_forward);
	putp(tgoto(change_scroll_region, LINES-1, 0));
	putp(restore_cursor);		/* put cursor back */
	Untouchwin(wp);
    }

     /* Else if this terminal has scrolling rectangles, use them. */
#ifdef SET_WINDOW
     else if (has_scroll_window) {
	overwrite(wp, curscr);	/* slow but easy */
	putp(tparm(set_window,
	    wbegy(wp)+wcury(wp), wbegy(wp)+wlines(wp)-1,
	    wbegx(wp), wbegx(wp)+wcols(wp)-1));
	putp(tgoto(cursor_address, 0, wlines(wp)-1));
	putp(scroll_forward);
	putp(tparm(set_window, 0, LINES-1, 0, COLS-1));
	putp(tgoto(cursor_address, curscol(), cursrow()));
	Untouchwin(wp);
    }
#endif

    /* Else if this terminal has insdel line, use that. */
    else if (has_insdel_line && FULLWIDTH(wp)) {
	/* Open a line below the last line in window wp,
	 * then delete wp's current line.
	 */
	(void) movecursor(wbegy(wp)+wlines(wp), 0);
	putp(insert_line);	Cinsertln();
	(void) movecursor(wbegy(wp)+wcury(wp), 0);
	putp(delete_line);	Cdeleteln();
	RestoreMsg();
	Untouchwin(wp);
    }
#endif
}

/*
 * Construct termcap for wmvirt terminal in window w.
 */
char *
termcap(w)
int w;
{
    register WINDOW *wp;
    static char tbuf[TBUFLEN];	/* termcap buffer */

    wp = win[w].wptr;
    (void)sprintf(tbuf, "%sco#%d:li#%d:", TERMCAP, wcols(wp), wlines(wp));

     /* If terminal scrolls 'quickly', add insert/delete line to termcap. */
    if ((win[w].flags&FAST)
     && (has_insdel_line || has_scroll_region || has_scroll_window))
	strcat(tbuf, "al=\\EL:dl=\\ED:");

     /* If terminal has insert/delete character options, add them  here */
    if (insert_character || (enter_insert_mode && exit_insert_mode))
	strcat(tbuf, "ic=\\EP:");
    if (delete_character)
	strcat(tbuf, "dc=\\EQ:");

     /* If terminal has standout capabilities, add that too. */
    if (enter_standout_mode && exit_standout_mode)
	strcat(tbuf, "so=\\EO:se=\\EE:");

    /* Include vb if terminal has a visual bell */
    if (flash_screen)
	strcat(tbuf, "vb=\\ER:");

    /* Include keypad capabilities if there is room left */
    if (strlen(tbuf)+strlen(keycap) < TBUFLEN)
	strcat(tbuf, keycap);

    return(tbuf);
}
SHAR_EOF
if test 11456 -ne "`wc -c < 'vterm.c'`"
then
	echo shar: error transmitting "'vterm.c'" '(should have been 11456 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'wlist.c'" '(3611 characters)'
if test -f 'wlist.c'
then
	echo shar: will not over-write existing file "'wlist.c'"
else
sed 's/^X//' << \SHAR_EOF > 'wlist.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * Code for rearranging window display order
 */

#include "wm.h"


/*
 * Make window 'w' the new top window.
 * Insert 'w' into window list (keeps track
 * of redraw order).
 * 'topw' (pointer to the top window) is set to 'w'.
 * Recompute obscured window status.
 */
WListAdd(w)

register int w;
{
    register int wt;	/* temporary window pointer */

     /* Add w to empty list.
      */
    if (botw < 0)
	botw=w;

     /* Add w to top of nonempty list.
      */
    else
    {
	for (wt=botw; win[wt].next>=0; wt=win[wt].next)
	    ;
	win[wt].next=w;
    }


    win[w].next = -1;
    topw = w;

    /* Recompute obscured window status */
    WObscure();
}

/*
 * Delete window 'w'.
 * Remove it from window list.
 * Recompute obscured windows.
 */
WListDelete(w)

register int w;
{
    register int wt;	/* temporary window pointer */

    if ( ! iswindow(w))
	return;

     /* Don't read from this window any more.
      */
    DisablePty(w);

     /* Delete w from list.
      */
    if (botw==w)
	botw=win[botw].next;
    else
    {
	for (wt=botw; wt>=0; wt=win[wt].next)
	    if (win[wt].next==w) break;
	if (wt>=0)
	    win[wt].next=win[win[wt].next].next;
    }

     /* Find new topw.
      */
    wt=botw;
    while (wt>=0 && win[wt].next>=0)
	wt=win[wt].next;
    topw=wt;

    /* Recompute obscured window info */
    WObscure();
}

/*
 * Recompute obscured ('blocked') and 'covers' information
 * Enable/Disable ptys appropriately.
 */
WObscure()
{
    register int w, wt, i;

    for (w = botw; w >= 0; w = win[w].next) {
	EnablePty(w);
	win[w].flags &= ~BLOCKED;
	for (i = 0; i < MAXWINDOWS; i++)
	    win[w].covers[i] = FALSE;
	for (wt = botw; wt != w; wt = win[wt].next) {
	    if (!overlap(w, wt))
		continue;
	    win[w].covers[wt] = TRUE;
	    win[wt].flags |= BLOCKED;
	    /* We could disable the obscured window's pty at this point,
	     * but we'll wait and do it in 'readptys()'.
	     */
	    /* (we should probably disable it now if it was previously) */
	}
    }
}

/* Note: the arithmetic 'bottom' is actual the visual 'top'!
 * Also, RIGHT and TOP are the index of the column (row) just past
 * the index of the window's actual right (top).
 */
#define BOTTOM(wp)	(wbegy(wp))
#define	TOP(wp)		(BOTTOM(wp)+wlines(wp))
#define LEFT(wp)	(wbegx(wp))
#define	RIGHT(wp)	(LEFT(wp)+wcols(wp))

/*
 * Determine if two windows overlap.
 * Windows must have at least a row (column) separating them
 * to permit the border lines to be drawn.
 */
overlap(w1, w2)

int w1, w2;
{
    register WINDOW *wp1, *wp2;

    wp1 = win[w1].wptr;
    wp2 = win[w2].wptr;
    return(LEFT(wp1)   <= RIGHT(wp2)
	&& LEFT(wp2)   <= RIGHT(wp1)
	&& BOTTOM(wp1) <= TOP(wp2)
	&& BOTTOM(wp2) <= TOP(wp1));
}

/*
 * Returns 1 if a window (or its border) above w cover point (y,x),
 * otherwise returns 0.
 */
covers(w, y, x)
int w;
register int y, x;
{
    register int wt;
    register WINDOW *wp;

    for (wt = w; (wt = win[wt].next) >= 0;) {
	wp = win[wt].wptr;
	if (LEFT(wp)	<= x+1
	 && x		<= RIGHT(wp)
	 && BOTTOM(wp)	<= y+1
	 && y		<= TOP(wp))
	    return(1);
    }
    return(0);
}
SHAR_EOF
if test 3611 -ne "`wc -c < 'wlist.c'`"
then
	echo shar: error transmitting "'wlist.c'" '(should have been 3611 characters)'
fi
fi # end of overwriting check
echo shar: extracting "'wm.c'" '(12415 characters)'
if test -f 'wm.c'
then
	echo shar: will not over-write existing file "'wm.c'"
else
sed 's/^X//' << \SHAR_EOF > 'wm.c'
/*
 *************
 * DISTRIBUTION NOTICE  July 30 1985
 * A Revised Edition of WM, by Matt Lennon and Tom Truscott,
 *		Research Triangle Institute, (919) 541-7005.
 * Based on the original by Robert Jacob (decvax!nrl-css!jacob),
 *		Naval Research Laboratory, (202) 767-3365.
 * No claims or warranties of any sort are made for this distribution.
 * General permission is granted to copy, but not for profit,
 * any of this distribution, provided that this notice
 * is always included in the copies.
 *************
 */
/*
 * wm.c  R. Jacob  7/28/1980
 * Simple multiple-window monitor for Unix
 * allows windows to overlap
 *
 * This is the code for the main program
 *
 * This version runs as only one process (plus the shells)
 * This is intended for Berkeley 4.2 VAX Unix only.
 */

#include "wm.h"
#include <signal.h>
#include <sys/wait.h>
#include <sys/time.h>
#include <sys/resource.h>

#define LINEBUF		64	/* size of pty input buffer */


/*
 * Real declarations for stuff defined as extern in wm.h
 */
struct win_struct win[MAXWINDOWS];	/* array of windows */
int botw, topw, lastw;			/* bottom, top, last windows */
int prefix = '\033';			/* prefix character */
char savefile[100];			/* name of save/restore file */
char shellname[20];			/* name of shell */
char shellpgm[100];			/* pathname of shell */
int configflag = FALSE;			/* true if .wmrc config. has changed */
#ifndef TERMINFO
char *change_scroll_region, *save_cursor, *restore_cursor;
char *set_window;
#endif
int has_scroll_window = FALSE;	/* true if terminal has 'usable' set_window */
int has_scroll_region = FALSE;	/* true if terminal has 'usable' SR */
int has_insdel_line = FALSE;	/* true if terminal has both ins+del line */

static int savereadmask;		/* pty readmask */
static int childdied = FALSE;		/* set when a window shell stops */
static int restorflag=TRUE;		/* TRUE if we're restoring windows */
static int MaxQueued	= 150;
static long pausetime = 200000L;
static int clamp_down;	/* number of chars to remain snappy after ctrl-S */
static int Divisor;	/* read/write reduction factor */

main(argc, argv)

int argc;
char *argv[];
{
    register int c;		/* input character */
    int readmask;		/* temp pty readmask */
    register int w;		/* window index */
    register int quit = FALSE;	/* Did user give the 'quit' command? */
    register struct timeval *tp;
    struct timeval timeout;
    register char *s;


    setbuf(stdout, alloc(BUFSIZ, char));
    DoCmdArgs(argc, argv);

    Startup();

    /* Adjust MaxQueued and pausetime values for fast terminals */
    {
	int ttyspeed;
	if ((ttyspeed = baudrate()) > 4800) {
	    pausetime /= 2;
	    if (ttyspeed > B9600)
		MaxQueued *= 2;
	}
    }

     /* Try to restore window arrangement from a previous
      * WM session. 'Restore()' returns number of windows restored.
      * If no windows restored, start from scratch,
      * providing user with a full screen window.
      */
    ClearScreen();
    if (restorflag==FALSE || Restore(savefile) <= 0)
    {
	if (savefile[0] == '\0')
	    strcpy(savefile, ".wmrc");
	w = GetSlot();
	if (NewWindow(w, LINES-1, COLS, 0, 0))
	{
	    showmsg("Sorry, can't create any windows.");
	    FreeWindow(w);
	    Shutdown(1);
	}
	WListAdd(w);
    }
    RedrawScreen();

    showmsg("Welcome to WM. Type %sh for help.", mkprint(prefix));
    RestoreCursor();


     /* Main processing loop.
      */
    do
    {
	if (childdied)
	    ReapShell();

	 /* If the shell in the top window has died (pid == -1),
	  * or was never started to begin with (pid == 0),
	  * start a new shell.
	  */
	if (win[topw].pid <= 0)
	{
	    if (win[topw].pid < 0)
		showmsg("\007Shell in current window died. Restarting...");

	    if (NewShell(topw))
	    {
		showmsg("\007Sorry, can't start up new shell.");
		win[topw].pid = -1;
	    }
	    else
		EnablePty(topw);
	    RestoreCursor();
	}

	 /* Poll user's terminal and ptys.
	  */
	readmask = savereadmask;
	tp = 0;
	Divisor = (clamp_down? 4: 1);

#ifdef	TIOCOUTQ
	{
	    long n;
	    if (ioctl(1, (int)TIOCOUTQ, (char*)&n)==0 && n > MaxQueued/Divisor)
	    {
		readmask &= 01;
		tp = &timeout;
		tp->tv_sec = 0;
		tp->tv_usec = pausetime/Divisor;
	    }
	}
#endif

	if (select(8*sizeof(readmask), &readmask, 0, 0, tp) <= 0)
	    continue;

	/* Terminate messages after a few seconds */
	if (msgbirth && abs(time((time_t *)0) - msgbirth) > 3)
	    showmsg("");

	 /* If no input from the user, read ptys.
	  */
	if ((readmask&01) == 0) {
	    readptys(readmask);
	    continue;
	}

	 /* If user input is not the WM command prefix character,
	  * just send input to the appropriate pty.
	  */
	do {
	    if ((c = tty_getch()) != prefix) {
		(void)write(win[topw].pty, tty_text, tty_textlen);
		if (c == CTRL(S))
		    clamp_down = LINES*COLS/2;
	    }

	     /* Process WM command.
	      */
	    else
	    {
		showmsg("#%d Command?", topw);
		c = tty_getch();
		showmsg("");
		if (c != prefix)
		    quit = docmd(c);
		else
		    (void)write(win[topw].pty, tty_text, tty_textlen);
		RestoreCursor();
	    }
	} while (tty_backcnt > 0);
    } while ( ! quit);


     /* If user has changed the window configuration since
      * the session began, see if they want to save the
      * current configuration.
      */
    if (restorflag && configflag)
    {
	showmsg("Save current (modified) window configuration? [no] ");
	c = tty_getch();
	if (c != 'y' && c != 'Y')
	    showmsg("");
	else if ( (s = WPrompt("save file", savefile)) == NULL)
	    ;
	else if (Save(s) != 0)
	    showmsg("Saved current window configuration in '%s'.", s);
	else
	    showmsg("Sorry, can't save current window configuration.");
    }


     /* Shut down.
      */
    Shutdown(0);
}

static char USAGE[] = "[ -n ]  [ -f savefile ]";

/*
 * Interpret command line arguments to wm.
 */
DoCmdArgs(argc, argv)

register int argc;	/* arg count */
register char *argv[];	/* arg list */
{
    for (argv++,argc--; argc>0; argv++,argc--)
    {
	if (**argv != '-')
	{
	    fprintf(stderr, "usage: wm %s\n", USAGE);
	    exit(1);
	}
	switch ((*argv)[1])
	{
	case 'f':	/* next arg is name of save/restore file */
	    strcpy(savefile, *++argv);
	    argc--;
	    break;
	case 'n':	/* don't restore/save window configuration */
	    restorflag = FALSE;
	    break;
	default:
	    fprintf(stderr, "wm: unknown option '%s'\n", *argv);
	    fprintf(stderr, "usage: wm %s\n", USAGE);
	    exit(1);
	}
    }
}

/*
 * Initialize WM.
 */
XStartup()
{
    register int w;		/* window pointer */
    int onintr(), sigchild();	/* interrupt handler */

    savereadmask = 01;
    ShellInit();	/* this call must precede the suspend()! */

     /* Catch signals.
      * Note: there is a tiny window from here to the return of raw().
      * Signals could be ignored until then, but it is a bother.
      */
    if (signal(SIGHUP, SIG_IGN) != SIG_IGN)
	(void)signal(SIGHUP,  onintr);
    if (signal(SIGQUIT, SIG_IGN) != SIG_IGN)
	(void)signal(SIGQUIT, onintr);
    if (signal(SIGINT, SIG_IGN) != SIG_IGN)
	(void)signal(SIGINT,  onintr);
    if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
	(void)signal(SIGPIPE, onintr);
    (void)signal(SIGCHLD, sigchild);

     /* Initialize curses stuff.
      */
    if ((w = (int)initscr()) == ERR || !cursor_address) {
	/* This ERR nonsense is for the benefit of terminfo curses.
	 * Has initscr cleaned up correctly (e.g. reset the tty)?
	 * I sure wish initscr always suceeded.
	 */
	if (w != ERR)
	    endwin();
	fprintf(stderr, "Sorry.  Need cursor addressing to play WM\n");
	/* If 'wm' is run via exec from a .profile, then exiting here
	 * would log the luser out.  Unfortunately, we cannot reliably
	 * determine if wm's parent is a shell, so we cannot
	 * simply exit now.
	 */
	fprintf(stderr, "Spawning a normal shell\n");
	execlp(shellpgm, shellname, (char *)0);
	exit(1);
    }
    noecho(); raw();
    leaveok(stdscr, TRUE);

#ifdef TERMINFO
#ifdef    BUGGYTERMINFO
    /* buggyterminfo neglects the move_standout_mode problem */
    if (!move_standout_mode)
	set_attributes = enter_standout_mode = exit_standout_mode = NULL;
    /* in buggyterminfo, idlok leads to core dumps */
#else
    idlok(curscr, TRUE);	/* the first arg is pointless, yes? */
#endif
#else
    /*
     * hack to check for scrolling-region capability (vt100)
     * since curses does not itself check.
     */
    change_scroll_region = getcap("cs");
    save_cursor = getcap("sc");
    restore_cursor = getcap("rc");
    set_window = getcap("sw");
#ifdef GAGMEKEYPAD
    init_keypad();
#endif
#endif

    /* ensure there is a 'scroll_forward' string */
    if (!scroll_forward)
	scroll_forward = "\n";
    if (change_scroll_region && save_cursor
     && restore_cursor && scroll_reverse)
	has_scroll_region = TRUE;
    if (insert_line && delete_line)
	has_insdel_line = TRUE;
    if (set_window && scroll_reverse)
	has_scroll_window = TRUE;

     /* Init window structure array.
      */
    topw = botw = lastw = -1;
    for (w=0; w<MAXWINDOWS; w++)
	win[w].flags = 0;

     /* Set up save/restore file name.
      * If there is a file '.wmrc' in the current directory,
      * use it.  Otherwise use .wmrc in the user's home directory.
      */
    if (*savefile == '\0')
    {
	if (access(".wmrc",0) == 0)
	    strcpy(savefile, ".wmrc");
	else if (getenv("HOME") != NULL) {
	    (void)sprintf(savefile, "%s/.wmrc", getenv("HOME"));
	    if (access(savefile,0) != 0)
		*savefile = '\0';
	}
    }
}

/*
 * Shut down WM and exit.
 */
XShutdown(code)

int code;	/* exit code */
{
    register int w;	/* window pointer */


     /* Kill processes associated with each window.
      */
    for (w=0; w<MAXWINDOWS; w++)
	if (win[w].flags&INUSE) { KillShell(w); FreeWindow(w); }

    (void) movecursor(LINES-1, 0);
    endwin();
    putchar('\n');

    exit(code);
}

/*
 * Check each pty for input.
 * If present, read input and send it to that window.
 * The readmask from the last 'select()' call tells us
 * which pty's have input pending.
 */
readptys(rmask)

register int rmask;	/* IN: read mask from last 'select()' call */
{
    char buf[LINEBUF];	/* input buffer */
    register int w;	/* window */
#ifdef oldway
    register int i;	/* index */
#endif
    register int n;	/* number of bytes pending on read */


    for (w=botw; w>=0; w=win[w].next)
    {
	if ((rmask & (01<<win[w].pty)) == 0)
	    continue;

	 /* If window is blocked, notify user that window
	  * has pending output.
	  */
	if (win[w].flags&BLOCKED)
	{
	    DisablePty(w);
	    showmsg("\007Output pending in window #%d.", w);
	    continue;
	}

	 /* Read and process output for window w.
	  */
	n = read(win[w].pty, buf, LINEBUF/Divisor);
	if (n <= 0)
	    continue;
	WMaddbuf(w, buf, n);
	wrefresh(win[w].wptr);
	if (clamp_down)
	    if ((clamp_down -= n) < 0)
		clamp_down = 0;
    }

    RestoreCursor();
}

/*
 * Signal handler.
 */
onintr(n)
{
    (void)signal(n, SIG_IGN);
    Shutdown(1);
}

/*
 * Signal handler for SIGCHLD
 * (received whenever a window shell dies).
 */
sigchild()
{
    (void) signal(SIGCHLD, sigchild);	 /* not needed in 4.2bsd */
    childdied = TRUE;
}

/*
 * Clean up after dead window shell.
 */
ReapShell()
{
    register int w;	/* window index */
    register int pid;	/* process id of child */
    register int pgrp;	/* process group of child */
    union wait status;


     /* Reset flag.
      */
    childdied = FALSE;

     /* Figure out which children died,
      * clean up after them.
      */
    while ((pid = wait3(&status, WNOHANG|WUNTRACED, (struct rusage *)0)) > 0)
    {
	/* It is truly amazing how complex simple things can become */
	if (WIFSTOPPED(status)) {
	    if (status.w_stopsig == SIGTTOU || status.w_stopsig == SIGTTIN)
		continue;	/* Let's not worry about these */
	    showmsg("Cannot suspend a window shell.");
	    RestoreCursor();
	    if ((pgrp = getpgrp(pid)) <= 0 || killpg(pgrp, SIGCONT))
		(void) kill(pid, SIGCONT);	/* so there! */
	    continue;
	}
	for (w=botw; w>=0; w=win[w].next)
	    if (win[w].pid == pid)
	    {
		DisablePty(w);
		KillShell(w);
		win[w].pid = -1;
		break; /* out of for loop */
	    }
    }
}

/*
 * Enable pty of window w for reading.
 */
EnablePty(w)

register int w;	/* window whose pty we're enabling */
{
    if (win[w].pid > 0)
	savereadmask |=  (01 << win[w].pty);
}

/*
 * Disable pty of window w for reading.
 */
DisablePty(w)

register int w;	/* window whose pty we're disabling */
{
    if (win[w].pid > 0)
	savereadmask &= ~(01 << win[w].pty);
}
SHAR_EOF
if test 12415 -ne "`wc -c < 'wm.c'`"
then
	echo shar: error transmitting "'wm.c'" '(should have been 12415 characters)'
fi
fi # end of overwriting check
#	End of shell archive
exit 0



More information about the Mod.sources mailing list