home *** CD-ROM | disk | FTP | other *** search
/ rtsi.com / 2014.01.www.rtsi.com.tar / www.rtsi.com / OS9 / TOP / USR / SRC / yahtzee.t.Z / yahtzee.t / shwin.c < prev    next >
Text File  |  1988-07-28  |  10KB  |  388 lines

  1. /* shwin.c
  2.  *    contains a routine to implement a shell window under
  3.  *    System 5 using fairly crude multiplexing.  For performance
  4.  *    reasons there is very heavy use of code in-lining.
  5.  */
  6.  
  7. #if (defined(SYS5) || defined(SYS5_3)) && ! defined(cyber)
  8.  
  9. /* How it works:
  10.  *    A child is created and it execs a csh, all terminal IO from the
  11.  *    child uses 3 pipes (stdin, stdout, stderr). The parent process
  12.  *    keeps a check on data coming from these pipes (the childs stdout
  13.  *    and stderr) and data coming from the terminal.  Data read from
  14.  *    the terminal is sent down the childs stdin pipe.
  15.  *    Curses is used to control the window environment (see the code).
  16.  *    The above description implies the parent is 'busy waiting' on
  17.  *    data from the pipes, to avoid this undesirable feature if
  18.  *    the parent receives no terminal input or output from the pipes
  19.  *    it goes to sleep.  If this keeps happening the parent goes
  20.  *    to sleep for longer and longer times (until it hits a maximum
  21.  *    sleep time). As the comments say, the multiplexing is fairly
  22.  *    crude.
  23.  */
  24.  
  25. #include <stdio.h>
  26. #include <fcntl.h>
  27. #include <curses.h>
  28. #include <signal.h>
  29. #include <time.h>
  30.  
  31. #define ERROR_EXIT {perror("yahtzee"); exit(-1);}
  32. #define WERROR_EXIT {perror("yahtzee"); return(-1);}
  33.  
  34. /* the maximum sleep time */
  35. #define MAX_SLEEP 10
  36.  
  37. /* the rate at which the sleep clock is incremented */
  38. #define SLEEP_INC 0.01
  39.  
  40. /* read buffer size */
  41. #define BUFFER_SIZE 1024
  42.  
  43. /* column in which the time appears in the top row */
  44. #define TIME_COL 52
  45.  
  46. /* macro to read from a file descriptor and write the contents to the
  47.  * given window, if nothing is read the sleep counter is incremented */
  48. #define READ_WWRITE(infd, outwin) \
  49.     status = read(infd, buffer, BUFFER_SIZE); \
  50.     if (status > 0) \
  51.         { \
  52.         buffer[status] = '\0'; \
  53.         waddstr(outwin, buffer); \
  54.         wrefresh(outwin); \
  55.         bdcount = 0.0; \
  56.         }
  57.  
  58. /* draw a line across screen at row a */
  59. #define line_across(a) {for (i = 0; i < COLS; ++i) \
  60.     mvwaddch(screen, a, i, '-'); }
  61.  
  62. /* update the ticking clock in the top row of the screen */
  63. #define UPDATE_TIME { \
  64.     time(&cur_time); \
  65.     strncpy(time_buf, ctime(&cur_time), 19); \
  66.     time_buf[19] = '\0'; \
  67.     mvwaddstr(screen, 0, TIME_COL, time_buf); \
  68.     wrefresh(screen); \
  69.     }
  70.  
  71. extern int execl(), fork(), pipe(), close(), fcntl();
  72. extern int BadStandout;
  73. static int child_dead;
  74. int shell_window_active = 0;
  75.  
  76. shwin(backwin)
  77.  
  78. WINDOW *backwin;
  79.  
  80.     {
  81.     int Pread_Cwrite[2], Pwrite_Cread[2], pid, status, stat_flg, i,
  82.         Stderr_Pr_Cw[2], buffer_index = 0, tmp_y, tmp_x, tilde = FALSE,
  83.         clock_toggle = TRUE, old_y, old_x;
  84.     char buffer[BUFFER_SIZE], ch, in_buf[BUFFER_SIZE], time_buf[30];
  85.     extern void signal_catch();
  86.     register unsigned int sleep_now;
  87.     register float bdcount = 0.0;
  88.     WINDOW *wstdout, *wstderr, *screen;
  89.     void (*oldcld)(), (*oldterm)(), (*oldint)(), (*oldquit)(), (*oldpipe)();
  90.     long cur_time;
  91.  
  92.  
  93.     getyx(backwin, tmp_y, tmp_x);
  94.     wmove(backwin, 0, 0);
  95.     wrefresh(backwin);
  96.  
  97. /* create the child's output pipe */
  98.     if (pipe(Pread_Cwrite) < 0)
  99.         ERROR_EXIT
  100.  
  101. /* create the child's input pipe */
  102.     if (pipe(Pwrite_Cread) < 0)
  103.         ERROR_EXIT
  104.  
  105. /* create the one-way stderr pipe of the child, WARNING: this pipe is
  106.  * never written to by the parent, programs such as less will not work */
  107.     if (pipe(Stderr_Pr_Cw) < 0)
  108.         ERROR_EXIT
  109.  
  110. /* create child process */
  111.     if ((pid = fork()) == 0)
  112.         {
  113.  
  114. /* direct child stdout to the relevant pipe */
  115.         close(fileno(stdout)); /* stdout */
  116.         fcntl(Pread_Cwrite[1], F_DUPFD, fileno(stdout));
  117.         close(Pread_Cwrite[1]);
  118.  
  119. /* direct child stdin to the relevant pipe */
  120.         close(fileno(stdin)); /* stdin */
  121.         fcntl(Pwrite_Cread[0], F_DUPFD, fileno(stdin));
  122.         close(Pwrite_Cread[0]);
  123.  
  124. /* direct child stderr to the relevant pipe */
  125.         close(fileno(stderr));
  126.         fcntl(Stderr_Pr_Cw[1], F_DUPFD, fileno(stderr));
  127.         close(Stderr_Pr_Cw[1]);
  128.  
  129. /* exec csh */
  130.         execl("/bin/csh", "csh", "-i", 0);
  131.         ERROR_EXIT
  132.         }
  133.     if (pid == -1)
  134.         ERROR_EXIT
  135.     child_dead = FALSE;
  136.     ++shell_window_active;
  137.  
  138. /* catch signals associated with child death, ignore interrupts */
  139.     oldcld = signal(SIGCLD, signal_catch);
  140.     oldterm = signal(SIGTERM, signal_catch);
  141.     oldint = signal(SIGINT, SIG_IGN);
  142.     oldquit = signal(SIGQUIT, SIG_IGN);
  143.     oldpipe = signal(SIGPIPE, signal_catch);
  144.  
  145. /* close pipe descriptors that will not be used */
  146.     close(Pread_Cwrite[1]);
  147.     close(Pwrite_Cread[0]);
  148.     close(Stderr_Pr_Cw[1]);
  149.  
  150. /* get status flags for the childs stdout pipe */
  151.     if ((stat_flg = fcntl(Pread_Cwrite[0], F_GETFL)) == -1)
  152.         ERROR_EXIT
  153.  
  154. /* set the flags to that pipe to no-delay on read */
  155.     if (fcntl(Pread_Cwrite[0], F_SETFL, stat_flg | O_NDELAY) == -1)
  156.         ERROR_EXIT
  157.  
  158. /* likewise for stderr */
  159.     if ((stat_flg = fcntl(Stderr_Pr_Cw[0], F_GETFL)) == -1)
  160.         ERROR_EXIT
  161.     if (fcntl(Stderr_Pr_Cw[0], F_SETFL, stat_flg | O_NDELAY) == -1)
  162.         ERROR_EXIT
  163.  
  164. /* enable mapping of newlines */
  165.     nl();
  166.  
  167. /* create the 'background screen' */
  168.     screen = newwin(6, COLS, 0, 0);
  169.     werase(screen);
  170.  
  171. /* create the stderr window */
  172.     wstderr = newwin(3, COLS, 2, 0);
  173.  
  174. /* create the stdout and stdin window */
  175.     wstdout = newwin(18, COLS, 6, 0);
  176.  
  177. /* format the background screen */
  178.     if (! BadStandout)
  179.         wstandout(screen);
  180.     line_across(1);
  181.     line_across(5);
  182.     wmove(screen, 0, 0);
  183.     waddstr(screen, "-- y a h t z e e -- ~? for help");
  184.     if (! BadStandout)
  185.         wstandend(screen);
  186.  
  187. /* tick the clock */
  188.     UPDATE_TIME
  189.     werase(wstdout);
  190.  
  191. /* allow insert and delete line escape sequences to be used when
  192.  * scrolling the stdin/stdout window (note: stderr uses a redraw
  193.  * mechanism to scroll) */
  194.     idlok(wstdout, TRUE);
  195.  
  196. /* set terminal stdin to no-delay */
  197.     nodelay(wstdout, TRUE);
  198.     werase(wstderr);
  199.     wrefresh(wstdout);
  200.  
  201. /* tell curses to scroll stdout/stdin and stderr windows in cases
  202.  * where the cursor is at the bottom of a window and a CR is put
  203.  * in the window */
  204.     scrollok(wstdout, TRUE);
  205.     scrollok(wstderr, TRUE);
  206.  
  207. /* iterate until the child dies (note: child_dead is not altered by
  208.  * any code inside its loop, your optimiser might try to be clever
  209.  * and get rid of it, use volatile(!)) */
  210.     while (! child_dead)
  211.         {
  212.  
  213. /* do a no-delay read of the stderr pipe, put any output to the
  214.  * stderr window */
  215.         READ_WWRITE(Stderr_Pr_Cw[0], wstderr)
  216.  
  217. /* do a no-delay read of the stdout pipe, put any output to the
  218.  * stdout window */
  219.         READ_WWRITE(Pread_Cwrite[0], wstdout)
  220.  
  221. /* do no-delay reads of the terminal until no more input is forthcoming */
  222.         while ((ch = wgetch(wstdout)) > 0)
  223.             {
  224.  
  225. /* the tilde (~) is the escape char to do other things */
  226.             if (tilde)
  227.                 {
  228.                 tilde = FALSE;
  229.                 switch (ch)
  230.                     {
  231.  
  232. /* two tildes means send a tilde to the shell */
  233.                     case '~' : in_buf[buffer_index] = ch;
  234.                            ++buffer_index;
  235.                            waddch(wstdout, ch);
  236.                            wrefresh(wstdout);
  237.                            break;
  238.  
  239. /* get help */
  240.                     case '?' :
  241.  
  242. /* get rid of no-delay input */
  243.                            fcntl(fileno(stdin),
  244.                             F_SETFL, 0);
  245.                            help_out(8, screen);
  246.  
  247. /* redraw the relvant screens */
  248.                            touchwin(wstderr);
  249.                            wrefresh(wstderr);
  250.                            touchwin(wstdout);
  251.                            wrefresh(wstdout);
  252.  
  253. /* set input to nodelay and continue */
  254.                            nodelay(wstdout, TRUE);
  255.                            break;
  256.  
  257. /* kill the child */
  258.                     case '.' : kill(pid, SIGKILL);
  259.                            break;
  260.  
  261. /* send a sigterm to the child */
  262.                     case 'T' : kill(pid, SIGTERM);
  263.                            break;
  264.  
  265. /* toggle the clock on or off (preferrably off) */
  266.                     case 'c' : clock_toggle = (clock_toggle
  267.                             ? FALSE : TRUE);
  268.                            break;
  269.  
  270. /* default: bad escape sequence */
  271.                     default : flash();
  272.                     }
  273.                 }
  274.             else
  275. /* must be char for shell */
  276.                 {
  277.                 switch (ch)
  278.                     {
  279.  
  280. /* BS and DEL will non-destructively remove chars from the input
  281.  * line for the shell */
  282.                     case (char) 8 :
  283.                     case (char) 127 :
  284.                         buffer_index = (buffer_index > 0
  285.                             ? (buffer_index - 1) :
  286.                             0);
  287.                         getyx(wstdout, old_y, old_x);
  288.                         if (old_x > 0)
  289.                             wmove(wstdout, old_y,
  290.                                 old_x - 1);
  291.                         break;
  292.  
  293. /* get ready for an escape sequence */
  294.                     case '~' : tilde = TRUE;
  295.                         break;
  296.  
  297. /* all other chars are for the shell */
  298.                     default : in_buf[buffer_index] = ch;
  299.                         waddch(wstdout, ch);
  300.                         wrefresh(wstdout);
  301.                           ++buffer_index;
  302.                         break;
  303.                     }
  304.  
  305. /* only send line to shell when it is complete and terminated with
  306.  * a NL, this implies that programs that set the terminal to raw
  307.  * mode that are run inside the shell will not work correcly */
  308.                 if (ch == '\n')
  309.                     {
  310.                     write(Pwrite_Cread[1], in_buf,
  311.                         buffer_index);
  312.                     buffer_index = 0;
  313.                     }
  314.  
  315. /* terminal input means reset the sleep counter */
  316.                 bdcount = 0.0;
  317.                 }
  318.             }
  319.         sleep_now = bdcount;
  320.         bdcount += SLEEP_INC;
  321.  
  322. /* if sufficient time has elapsed since the last read, then slowly
  323.  * put the parent process to sleep */
  324.         if (sleep_now > 2)
  325.             {
  326.             if (sleep_now > MAX_SLEEP)
  327.                 sleep_now = MAX_SLEEP;
  328.             sleep(sleep_now);
  329.             if (clock_toggle)
  330.                 {
  331.  
  332. /* display the clock if so required */
  333.                 UPDATE_TIME
  334.                 wrefresh(wstdout);
  335.                 }
  336.             }
  337.         }
  338.  
  339. /* set terminal read to normal mode (not no-delay), I'm not sure
  340.  * how to achieve this, the nodelay() call doesn't work as I would
  341.  * expect on System 5.3 and I don't have any docs for curses to see
  342.  * how it should be used */
  343. /*    nodelay(wstdout, FALSE);     this doesn't work */
  344.     fcntl(fileno(stdin), F_SETFL, 0); /* this does */
  345.  
  346. /* close the pipe file descriptors */
  347.     close(Pread_Cwrite[0]);
  348.     close(Pwrite_Cread[1]);
  349.     close(Stderr_Pr_Cw[0]);
  350.  
  351. /* cleanup the shell windows */
  352.     delwin(screen);
  353.     delwin(wstderr);
  354.     delwin(wstdout);
  355.     touchwin(backwin);
  356.  
  357. /* restore the previous window */
  358.     wmove(backwin, tmp_y, tmp_x);
  359.     wrefresh(backwin);
  360.  
  361. /* child_dead must be left false so that any recursive calls to
  362.  * this function will not make earlier calls fail */
  363.     child_dead = FALSE;
  364.     --shell_window_active;
  365.  
  366. /* reset the signal handlers */
  367.     signal(SIGCLD, oldcld);
  368.     signal(SIGTERM, oldterm);
  369.     signal(SIGPIPE, oldpipe);
  370.     signal(SIGINT, oldint);
  371.     signal(SIGQUIT, oldquit);
  372.     return(0);
  373.     }
  374.  
  375. /* this is called when the shell dies, it sets the child_dead flag */
  376. void signal_catch()
  377.  
  378.     {
  379.     signal(SIGCLD, SIG_IGN);
  380.     signal(SIGPIPE, SIG_IGN);
  381.     signal(SIGTERM, SIG_IGN);
  382.     child_dead = TRUE;
  383.     }
  384.  
  385. #else
  386. shwin() {}
  387. #endif
  388.