home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Usenet 1994 October
/
usenetsourcesnewsgroupsinfomagicoctober1994disk2.iso
/
games
/
volume5
/
yahtzee
/
part01
/
shwin.c
< prev
next >
Wrap
C/C++ Source or Header
|
1988-07-26
|
10KB
|
388 lines
/* shwin.c
* contains a routine to implement a shell window under
* System 5 using fairly crude multiplexing. For performance
* reasons there is very heavy use of code in-lining.
*/
#if (defined(SYS5) || defined(SYS5_3)) && ! defined(cyber)
/* How it works:
* A child is created and it execs a csh, all terminal IO from the
* child uses 3 pipes (stdin, stdout, stderr). The parent process
* keeps a check on data coming from these pipes (the childs stdout
* and stderr) and data coming from the terminal. Data read from
* the terminal is sent down the childs stdin pipe.
* Curses is used to control the window environment (see the code).
* The above description implies the parent is 'busy waiting' on
* data from the pipes, to avoid this undesirable feature if
* the parent receives no terminal input or output from the pipes
* it goes to sleep. If this keeps happening the parent goes
* to sleep for longer and longer times (until it hits a maximum
* sleep time). As the comments say, the multiplexing is fairly
* crude.
*/
#include <stdio.h>
#include <fcntl.h>
#include <curses.h>
#include <signal.h>
#include <time.h>
#define ERROR_EXIT {perror("yahtzee"); exit(-1);}
#define WERROR_EXIT {perror("yahtzee"); return(-1);}
/* the maximum sleep time */
#define MAX_SLEEP 10
/* the rate at which the sleep clock is incremented */
#define SLEEP_INC 0.01
/* read buffer size */
#define BUFFER_SIZE 1024
/* column in which the time appears in the top row */
#define TIME_COL 52
/* macro to read from a file descriptor and write the contents to the
* given window, if nothing is read the sleep counter is incremented */
#define READ_WWRITE(infd, outwin) \
status = read(infd, buffer, BUFFER_SIZE); \
if (status > 0) \
{ \
buffer[status] = '\0'; \
waddstr(outwin, buffer); \
wrefresh(outwin); \
bdcount = 0.0; \
}
/* draw a line across screen at row a */
#define line_across(a) {for (i = 0; i < COLS; ++i) \
mvwaddch(screen, a, i, '-'); }
/* update the ticking clock in the top row of the screen */
#define UPDATE_TIME { \
time(&cur_time); \
strncpy(time_buf, ctime(&cur_time), 19); \
time_buf[19] = '\0'; \
mvwaddstr(screen, 0, TIME_COL, time_buf); \
wrefresh(screen); \
}
extern int execl(), fork(), pipe(), close(), fcntl();
extern int BadStandout;
static int child_dead;
int shell_window_active = 0;
shwin(backwin)
WINDOW *backwin;
{
int Pread_Cwrite[2], Pwrite_Cread[2], pid, status, stat_flg, i,
Stderr_Pr_Cw[2], buffer_index = 0, tmp_y, tmp_x, tilde = FALSE,
clock_toggle = TRUE, old_y, old_x;
char buffer[BUFFER_SIZE], ch, in_buf[BUFFER_SIZE], time_buf[30];
extern void signal_catch();
register unsigned int sleep_now;
register float bdcount = 0.0;
WINDOW *wstdout, *wstderr, *screen;
void (*oldcld)(), (*oldterm)(), (*oldint)(), (*oldquit)(), (*oldpipe)();
long cur_time;
getyx(backwin, tmp_y, tmp_x);
wmove(backwin, 0, 0);
wrefresh(backwin);
/* create the child's output pipe */
if (pipe(Pread_Cwrite) < 0)
ERROR_EXIT
/* create the child's input pipe */
if (pipe(Pwrite_Cread) < 0)
ERROR_EXIT
/* create the one-way stderr pipe of the child, WARNING: this pipe is
* never written to by the parent, programs such as less will not work */
if (pipe(Stderr_Pr_Cw) < 0)
ERROR_EXIT
/* create child process */
if ((pid = fork()) == 0)
{
/* direct child stdout to the relevant pipe */
close(fileno(stdout)); /* stdout */
fcntl(Pread_Cwrite[1], F_DUPFD, fileno(stdout));
close(Pread_Cwrite[1]);
/* direct child stdin to the relevant pipe */
close(fileno(stdin)); /* stdin */
fcntl(Pwrite_Cread[0], F_DUPFD, fileno(stdin));
close(Pwrite_Cread[0]);
/* direct child stderr to the relevant pipe */
close(fileno(stderr));
fcntl(Stderr_Pr_Cw[1], F_DUPFD, fileno(stderr));
close(Stderr_Pr_Cw[1]);
/* exec csh */
execl("/bin/csh", "csh", "-i", 0);
ERROR_EXIT
}
if (pid == -1)
ERROR_EXIT
child_dead = FALSE;
++shell_window_active;
/* catch signals associated with child death, ignore interrupts */
oldcld = signal(SIGCLD, signal_catch);
oldterm = signal(SIGTERM, signal_catch);
oldint = signal(SIGINT, SIG_IGN);
oldquit = signal(SIGQUIT, SIG_IGN);
oldpipe = signal(SIGPIPE, signal_catch);
/* close pipe descriptors that will not be used */
close(Pread_Cwrite[1]);
close(Pwrite_Cread[0]);
close(Stderr_Pr_Cw[1]);
/* get status flags for the childs stdout pipe */
if ((stat_flg = fcntl(Pread_Cwrite[0], F_GETFL)) == -1)
ERROR_EXIT
/* set the flags to that pipe to no-delay on read */
if (fcntl(Pread_Cwrite[0], F_SETFL, stat_flg | O_NDELAY) == -1)
ERROR_EXIT
/* likewise for stderr */
if ((stat_flg = fcntl(Stderr_Pr_Cw[0], F_GETFL)) == -1)
ERROR_EXIT
if (fcntl(Stderr_Pr_Cw[0], F_SETFL, stat_flg | O_NDELAY) == -1)
ERROR_EXIT
/* enable mapping of newlines */
nl();
/* create the 'background screen' */
screen = newwin(6, COLS, 0, 0);
werase(screen);
/* create the stderr window */
wstderr = newwin(3, COLS, 2, 0);
/* create the stdout and stdin window */
wstdout = newwin(18, COLS, 6, 0);
/* format the background screen */
if (! BadStandout)
wstandout(screen);
line_across(1);
line_across(5);
wmove(screen, 0, 0);
waddstr(screen, "-- y a h t z e e -- ~? for help");
if (! BadStandout)
wstandend(screen);
/* tick the clock */
UPDATE_TIME
werase(wstdout);
/* allow insert and delete line escape sequences to be used when
* scrolling the stdin/stdout window (note: stderr uses a redraw
* mechanism to scroll) */
idlok(wstdout, TRUE);
/* set terminal stdin to no-delay */
nodelay(wstdout, TRUE);
werase(wstderr);
wrefresh(wstdout);
/* tell curses to scroll stdout/stdin and stderr windows in cases
* where the cursor is at the bottom of a window and a CR is put
* in the window */
scrollok(wstdout, TRUE);
scrollok(wstderr, TRUE);
/* iterate until the child dies (note: child_dead is not altered by
* any code inside its loop, your optimiser might try to be clever
* and get rid of it, use volatile(!)) */
while (! child_dead)
{
/* do a no-delay read of the stderr pipe, put any output to the
* stderr window */
READ_WWRITE(Stderr_Pr_Cw[0], wstderr)
/* do a no-delay read of the stdout pipe, put any output to the
* stdout window */
READ_WWRITE(Pread_Cwrite[0], wstdout)
/* do no-delay reads of the terminal until no more input is forthcoming */
while ((ch = wgetch(wstdout)) > 0)
{
/* the tilde (~) is the escape char to do other things */
if (tilde)
{
tilde = FALSE;
switch (ch)
{
/* two tildes means send a tilde to the shell */
case '~' : in_buf[buffer_index] = ch;
++buffer_index;
waddch(wstdout, ch);
wrefresh(wstdout);
break;
/* get help */
case '?' :
/* get rid of no-delay input */
fcntl(fileno(stdin),
F_SETFL, 0);
help_out(8, screen);
/* redraw the relvant screens */
touchwin(wstderr);
wrefresh(wstderr);
touchwin(wstdout);
wrefresh(wstdout);
/* set input to nodelay and continue */
nodelay(wstdout, TRUE);
break;
/* kill the child */
case '.' : kill(pid, SIGKILL);
break;
/* send a sigterm to the child */
case 'T' : kill(pid, SIGTERM);
break;
/* toggle the clock on or off (preferrably off) */
case 'c' : clock_toggle = (clock_toggle
? FALSE : TRUE);
break;
/* default: bad escape sequence */
default : flash();
}
}
else
/* must be char for shell */
{
switch (ch)
{
/* BS and DEL will non-destructively remove chars from the input
* line for the shell */
case (char) 8 :
case (char) 127 :
buffer_index = (buffer_index > 0
? (buffer_index - 1) :
0);
getyx(wstdout, old_y, old_x);
if (old_x > 0)
wmove(wstdout, old_y,
old_x - 1);
break;
/* get ready for an escape sequence */
case '~' : tilde = TRUE;
break;
/* all other chars are for the shell */
default : in_buf[buffer_index] = ch;
waddch(wstdout, ch);
wrefresh(wstdout);
++buffer_index;
break;
}
/* only send line to shell when it is complete and terminated with
* a NL, this implies that programs that set the terminal to raw
* mode that are run inside the shell will not work correcly */
if (ch == '\n')
{
write(Pwrite_Cread[1], in_buf,
buffer_index);
buffer_index = 0;
}
/* terminal input means reset the sleep counter */
bdcount = 0.0;
}
}
sleep_now = bdcount;
bdcount += SLEEP_INC;
/* if sufficient time has elapsed since the last read, then slowly
* put the parent process to sleep */
if (sleep_now > 2)
{
if (sleep_now > MAX_SLEEP)
sleep_now = MAX_SLEEP;
sleep(sleep_now);
if (clock_toggle)
{
/* display the clock if so required */
UPDATE_TIME
wrefresh(wstdout);
}
}
}
/* set terminal read to normal mode (not no-delay), I'm not sure
* how to achieve this, the nodelay() call doesn't work as I would
* expect on System 5.3 and I don't have any docs for curses to see
* how it should be used */
/* nodelay(wstdout, FALSE); this doesn't work */
fcntl(fileno(stdin), F_SETFL, 0); /* this does */
/* close the pipe file descriptors */
close(Pread_Cwrite[0]);
close(Pwrite_Cread[1]);
close(Stderr_Pr_Cw[0]);
/* cleanup the shell windows */
delwin(screen);
delwin(wstderr);
delwin(wstdout);
touchwin(backwin);
/* restore the previous window */
wmove(backwin, tmp_y, tmp_x);
wrefresh(backwin);
/* child_dead must be left false so that any recursive calls to
* this function will not make earlier calls fail */
child_dead = FALSE;
--shell_window_active;
/* reset the signal handlers */
signal(SIGCLD, oldcld);
signal(SIGTERM, oldterm);
signal(SIGPIPE, oldpipe);
signal(SIGINT, oldint);
signal(SIGQUIT, oldquit);
return(0);
}
/* this is called when the shell dies, it sets the child_dead flag */
void signal_catch()
{
signal(SIGCLD, SIG_IGN);
signal(SIGPIPE, SIG_IGN);
signal(SIGTERM, SIG_IGN);
child_dead = TRUE;
}
#else
shwin() {}
#endif