Back to index

radiance  4R0+20100331
rhdisp.c
Go to the documentation of this file.
00001 #ifndef lint
00002 static const char    RCSid[] = "$Id: rhdisp.c,v 3.52 2008/03/11 02:21:47 greg Exp $";
00003 #endif
00004 /*
00005  * Holodeck display process.
00006  */
00007 
00008 #include <string.h>
00009 #include <ctype.h>
00010 
00011 #include "rterror.h"
00012 #include "rholo.h"
00013 #include "rhdisp.h"
00014 #include "rhdriver.h"
00015 #include "selcall.h"
00016 
00017 #ifndef VIEWHISTLEN
00018 #define VIEWHISTLEN  4      /* number of remembered views */
00019 #endif
00020 
00021 #ifndef FSIZDEF
00022 #define FSIZDEF             0.125  /* default focus frame size */
00023 #endif
00024 
00025 #if defined(freebsd)
00026 #define fbufcnt(f)   ((f)->_r)
00027 #elif defined(__GNUC__)
00028 #define fbufcnt(f)   ((f)->_IO_read_end - (f)->_IO_read_ptr)
00029 #else
00030 #define fbufcnt(f)   ((f)->_cnt)
00031 #endif
00032 
00033 HOLO   *hdlist[HDMAX+1];    /* global holodeck list */
00034 
00035 char   *hdgfn[HDMAX];              /* holodeck section geometry list */
00036 char   *hdpfn[HDMAX];              /* section portal list */
00037 
00038 char   cmdlist[DC_NCMDS][8] = DC_INIT;
00039 
00040 int    imm_mode = 0;        /* bundles are being delivered immediately */
00041 
00042 int    do_outside = 0;             /* render from outside sections */
00043 
00044 double eyesepdist = 1;             /* eye separation distance */
00045 
00046 char   *progname;           /* global argv[0] */
00047 
00048 FILE   *sstdin, *sstdout;   /* server's standard input and output */
00049 
00050 #ifdef DEBUG
00051 extern time_t time();
00052 static time_t tmodesw;
00053 static time_t timm, tadd;
00054 static long   nimmrays, naddrays;
00055 #endif
00056 
00057 #define RDY_SRV             01
00058 #define RDY_DEV             02
00059 #define RDY_SIN             04
00060 
00061 static int disp_wait(void);
00062 static void add_holo(HDGRID *hdg, char *gfn, char *pfn);
00063 static void disp_bundle(PACKHEAD *p);
00064 static void new_view(register VIEW *v);
00065 static void set_focus(char *args);
00066 static int usr_input(void);
00067 static void printview(void);
00068 
00069 
00070 int
00071 main(
00072        int    argc,
00073        char   *argv[]
00074 )
00075 {
00076        int    rdy, inp, res = 0, pause = 0;
00077 
00078        progname = argv[0];
00079        if (argc < 3)
00080               error(USER, "bad command line arguments");
00081                                    /* open our device */
00082        dev_open(argv[1]);
00083                                    /* open server process i/o */
00084        sstdout = fdopen(atoi(argv[2]), "w");
00085        if (argc < 4 || (inp = atoi(argv[3])) < 0)
00086               sstdin = NULL;
00087        else
00088               sstdin = fdopen(inp, "r");
00089                                    /* set command error vector */
00090        erract[COMMAND].pf = eputs;
00091 #ifdef DEBUG
00092        tmodesw = time(NULL);
00093 #endif
00094                                    /* enter main loop */
00095        do {
00096               rdy = disp_wait();
00097               if (rdy & RDY_SRV) {        /* process server result */
00098                      res = serv_result();
00099                      if (pause && res != DS_SHUTDOWN) {
00100                             serv_request(DR_ATTEN, 0, NULL);
00101                             while ((res = serv_result()) != DS_ACKNOW &&
00102                                           res != DS_SHUTDOWN)
00103                                    ;
00104                      }
00105               }
00106               if (rdy & RDY_DEV) {        /* user input from driver */
00107                      inp = dev_input();
00108                      if (inp & DFL(DC_SETVIEW))
00109                             new_view(&odev.v);
00110                      else if (inp & DFL(DC_LASTVIEW))
00111                             new_view(NULL);
00112                      if (inp & DFL(DC_REDRAW))
00113                             imm_mode = beam_sync(1) > 0;
00114                      if (inp & DFL(DC_GETVIEW))
00115                             printview();
00116                      if (inp & DFL(DC_FOCUS))
00117                             set_focus(odev_args);
00118                      if (inp & DFL(DC_KILL)) {
00119                             serv_request(DR_KILL, 0, NULL);
00120                             pause = 0;
00121                      }
00122                      if (inp & DFL(DC_CLOBBER))
00123                             serv_request(DR_CLOBBER, 0, NULL);
00124                      if (inp & DFL(DC_RESTART)) {
00125                             serv_request(DR_RESTART, 0, NULL);
00126                             pause = 0;
00127                      }
00128                      if (inp & DFL(DC_RESUME)) {
00129                             serv_request(DR_NOOP, 0, NULL);
00130                             pause = 0;
00131                      }
00132                      if (inp & DFL(DC_PAUSE))
00133                             pause = 1;
00134                      if (inp & DFL(DC_QUIT))
00135                             serv_request(DR_SHUTDOWN, 0, NULL);
00136               }
00137               if (rdy & RDY_SIN && !imm_mode)    /* user input from sstdin */
00138                      switch (usr_input()) {
00139                      case DC_PAUSE:
00140                             pause = 1;
00141                             break;
00142                      case DC_RESUME:
00143                             serv_request(DR_NOOP, 0, NULL);
00144                             /* fall through */
00145                      case DC_KILL:
00146                      case DC_RESTART:
00147                             pause = 0;
00148                             break;
00149                      }
00150        } while (res != DS_SHUTDOWN);
00151 #ifdef DEBUG
00152        if (timm && nimmrays)
00153               fprintf(stderr,
00154                      "%s: %.1f rays recalled/second (%ld rays total)\n",
00155                             progname, (double)nimmrays/timm, nimmrays);
00156        if (tadd && naddrays)
00157               fprintf(stderr,
00158                      "%s: %.1f rays calculated/second (%ld rays total)\n",
00159                             progname, (double)naddrays/tadd, naddrays);
00160 #endif
00161                                    /* all done */
00162        quit(0);
00163        return 0; /* pro forma return */
00164 }
00165 
00166 
00167 static int
00168 disp_wait(void)                    /* wait for more input */
00169 {
00170        fd_set readset, errset;
00171        int    flgs;
00172        int    n;
00173                             /* see if we can avoid select call */
00174        if (hdlist[0] == NULL)
00175               return(RDY_SRV);     /* initialize first */
00176        flgs = 0;            /* flag what's ready already */
00177        if (imm_mode || fbufcnt(stdin) > 0)
00178               flgs |= RDY_SRV;
00179        if (sstdin != NULL && fbufcnt(sstdin) > 0)
00180               flgs |= RDY_SIN;
00181        if (odev.inpready)
00182               flgs |= RDY_DEV;
00183        if (flgs)            /* got something? */
00184               return(flgs);
00185        if (dev_flush())     /* else flush output & check keyboard+mouse */
00186               return(RDY_DEV);
00187                             /* if nothing, we need to call select */
00188        FD_ZERO(&readset); FD_ZERO(&errset);
00189        FD_SET(0, &readset);
00190        FD_SET(0, &errset);
00191        FD_SET(odev.ifd, &readset);
00192        FD_SET(odev.ifd, &errset);
00193        n = odev.ifd+1;
00194        if (sstdin != NULL) {
00195               FD_SET(fileno(sstdin), &readset);
00196               FD_SET(fileno(sstdin), &errset);
00197               if (fileno(sstdin) >= n)
00198                      n = fileno(sstdin) + 1;
00199        }
00200        n = select(n, &readset, NULL, &errset, NULL);
00201        if (n < 0) {
00202               if (errno == EINTR)
00203                      return(0);
00204               error(SYSTEM, "select call failure in disp_wait");
00205        }
00206        if (FD_ISSET(0, &readset) || FD_ISSET(0, &errset))
00207               flgs |= RDY_SRV;
00208        if (FD_ISSET(odev.ifd, &readset) || FD_ISSET(odev.ifd, &errset))
00209               flgs |= RDY_DEV;
00210        if (sstdin != NULL && (FD_ISSET(fileno(sstdin), &readset) ||
00211                             FD_ISSET(fileno(sstdin), &errset)))
00212               flgs |= RDY_SIN;
00213        return(flgs);
00214 }
00215 
00216 
00217 static void
00218 add_holo(            /* register a new holodeck section */
00219        HDGRID *hdg,
00220        char   *gfn,
00221        char   *pfn
00222 )
00223 {
00224        VIEW   nv;
00225        double d;
00226        register int  hd;
00227 
00228        for (hd = 0; hd < HDMAX && hdlist[hd] != NULL; hd++)
00229               ;
00230        if (hd >= HDMAX)
00231               error(INTERNAL, "too many holodeck sections in add_holo");
00232        hdlist[hd] = (HOLO *)malloc(sizeof(HOLO));
00233        if (hdlist[hd] == NULL)
00234               error(SYSTEM, "out of memory in add_holo");
00235        memcpy((void *)hdlist[hd], (void *)hdg, sizeof(HDGRID));
00236        hdcompgrid(hdlist[hd]);
00237        hdgfn[hd] = savestr(gfn);
00238        hdpfn[hd] = pfn && *pfn ? savestr(pfn) : (char *)NULL;
00239        if (hd)
00240               return;
00241                                    /* set initial viewpoint */
00242        nv = odev.v;
00243        VSUM(nv.vp, hdlist[0]->orig, hdlist[0]->xv[0], 0.5);
00244        VSUM(nv.vp, nv.vp, hdlist[0]->xv[1], 0.5);
00245        VSUM(nv.vp, nv.vp, hdlist[0]->xv[2], 0.5);
00246        fcross(nv.vdir, hdlist[0]->xv[1], hdlist[0]->xv[2]);
00247        VCOPY(nv.vup, hdlist[0]->xv[2]);
00248        if (do_outside) {
00249               normalize(nv.vdir);
00250               d = VLEN(hdlist[0]->xv[1]);
00251               d += VLEN(hdlist[0]->xv[2]);
00252               VSUM(nv.vp, nv.vp, nv.vdir, -d);
00253        }
00254        new_view(&nv);
00255 }
00256 
00257 
00258 static void
00259 disp_bundle(                /* display a ray bundle */
00260        register PACKHEAD    *p
00261 )
00262 {
00263        GCOORD gc[2];
00264        FVECT  ro, rd, wp;
00265        double d;
00266        register int  i;
00267                                    /* get beam coordinates */
00268        if ((p->hd < 0) | (p->hd >= HDMAX) || hdlist[p->hd] == NULL)
00269               error(INTERNAL, "bad holodeck number in disp_bundle");
00270        if (!hdbcoord(gc, hdlist[p->hd], p->bi))
00271               error(INTERNAL, "bad beam index in disp_bundle");
00272                                    /* display each ray */
00273        for (i = p->nr; i--; ) {
00274               hdray(ro, rd, hdlist[p->hd], gc, packra(p)[i].r);
00275               d = hddepth(hdlist[p->hd], packra(p)[i].d);
00276               if (d < .99*FHUGE) {
00277                      VSUM(wp, ro, rd, d); /* might be behind viewpoint */
00278                      dev_value(packra(p)[i].v, rd, wp);
00279               } else
00280                      dev_value(packra(p)[i].v, rd, NULL);
00281        }
00282 #ifdef DEBUG
00283        if (imm_mode) nimmrays += p->nr;
00284        else naddrays += p->nr;
00285 #endif
00286 }
00287 
00288 
00289 static void
00290 new_view(                   /* change view parameters */
00291        register VIEW *v
00292 )
00293 {
00294        static VIEW   viewhist[VIEWHISTLEN];
00295        static unsigned      nhist;
00296        VIEW   *dv;
00297        int    i, res[2];
00298        int16  *slist;
00299        char   *err;
00300                             /* restore previous view? */
00301        if (v == NULL) {
00302               if (nhist > 1)              /* get one before last setting */
00303                      nhist--;
00304               else                 /* else go to end of list */
00305                      while (nhist < VIEWHISTLEN && viewhist[nhist].type)
00306                             nhist++;
00307               v = viewhist + ((nhist-1)%VIEWHISTLEN);
00308        } else
00309 again:
00310        if ((err = setview(v)) != NULL) {
00311               error(COMMAND, err);
00312               return;
00313        }
00314        if (!dev_view(v))    /* notify display driver */
00315               goto again;
00316        if (v->type == VT_PAR) {
00317               error(COMMAND, "cannot handle parallel views");
00318               return;
00319        }
00320        beam_init(odev.firstuse);   /* compute new beam set */
00321        for (i = 0; (dv = dev_auxview(i, res)) != NULL; i++) {
00322               if ((slist = beam_view(dv, res[0], res[1])) == NULL) {
00323                      if (!nhist) {
00324                             error(COMMAND, "invalid starting view");
00325                             return;
00326                      }
00327                      *v = *(viewhist + ((nhist-1)%VIEWHISTLEN));
00328                      goto again;   /* XXX overloading dev_section()? */
00329               }
00330               DCHECK(*slist < 0, WARNING, "no visible sections in new_view");
00331               for ( ; *slist >= 0; slist++)
00332                      dev_section(hdgfn[*slist], hdpfn[*slist]);
00333        }
00334        dev_section(NULL,NULL);     /* end section list */
00335        dev_flush();         /* update display */
00336                             /* update server */
00337        imm_mode = beam_sync(odev.firstuse) > 0;
00338                             /* record new view */
00339        if (v < viewhist || v >= viewhist+VIEWHISTLEN) {
00340               *(viewhist + (nhist%VIEWHISTLEN)) = *v;
00341               nhist++;
00342        }
00343 }
00344 
00345 
00346 static void
00347 set_focus(                  /* set focus frame */
00348        char   *args
00349 )
00350 {
00351        double hcent, vcent, hsiz, vsiz;
00352        VIEW   *dv, vwfocus;
00353        int    i, res[2];
00354 
00355        i = sscanf(args, "%lf %lf %lf %lf", &hcent, &vcent, &hsiz, &vsiz);
00356        if (i < 2 || hcent < 0 || hcent > 1 || vcent < 0 || vcent > 1) {
00357               beam_init(0);                      /* restore view */
00358               for (i = 0; (dv = dev_auxview(i, res)) != NULL; i++)
00359                      beam_view(dv, res[0], res[1]);
00360               beam_sync(0);                      /* update server */
00361               return;
00362        }
00363        if (i < 4 || hsiz <= hcent || hsiz > 1 || vsiz <= vcent || vsiz > 1)
00364               hsiz = vsiz = FSIZDEF;                    /* gave center only */
00365        else {
00366               hsiz -= hcent; hcent += 0.5*hsiz;  /* gave min and max */
00367               vsiz -= vcent; vcent += 0.5*vsiz;
00368        }
00369        beam_init(0);                             /* add basic views */
00370        for (i = 0; (dv = dev_auxview(i, res)) != NULL; i++)
00371               beam_view(dv, res[0]>>4, res[1]>>4);
00372        vwfocus = odev.v;                  /* add focus view */
00373        switch (odev.v.type) {
00374        case VT_PER:
00375               vwfocus.horiz = 2.*180./PI*atan(
00376                             hsiz * tan(PI/180./2.*odev.v.horiz) );
00377               vwfocus.vert = 2.*180./PI*atan(
00378                             vsiz * tan(PI/180./2.*odev.v.vert) );
00379               break;
00380        case VT_PAR:
00381        case VT_ANG:
00382               vwfocus.horiz = hsiz * odev.v.horiz;
00383               vwfocus.vert = vsiz * odev.v.vert;
00384               break;
00385        case VT_PLS:
00386               vwfocus.horiz = hsiz * sin((PI/180./2.)*odev.v.horiz) /
00387                             (1.0 + cos((PI/180./2.)*odev.v.horiz));
00388               vwfocus.horiz *= vwfocus.horiz;
00389               vwfocus.horiz = (2.*180./PI)*acos((1. - vwfocus.horiz) /
00390                                           (1. + vwfocus.horiz));
00391               vwfocus.vert = vsiz * sin((PI/180./2.)*odev.v.vert) /
00392                             (1.0 + cos((PI/180./2.)*odev.v.vert));
00393               vwfocus.vert *= vwfocus.vert;
00394               vwfocus.vert = (2.*180./PI)*acos((1. - vwfocus.vert) /
00395                                           (1. + vwfocus.vert));
00396               break;
00397        case VT_HEM:
00398               vwfocus.horiz = 2.*180./PI*asin(
00399                             hsiz * sin(PI/180./2.*odev.v.horiz) );
00400               vwfocus.vert = 2.*180./PI*asin(
00401                             vsiz * sin(PI/180./2.*odev.v.vert) );
00402               break;
00403        case VT_CYL:
00404               vwfocus.horiz = hsiz * odev.v.horiz;
00405               vwfocus.vert = 2.*180./PI*atan(
00406                             vsiz * tan(PI/180./2.*odev.v.vert) );
00407               break;
00408        default:
00409               error(INTERNAL, "bad view type in set_focus");
00410        }
00411        vwfocus.hoff = (odev.v.hoff + hcent - 0.5)/hsiz;
00412        vwfocus.voff = (odev.v.voff + vcent - 0.5)/vsiz;
00413        setview(&vwfocus);
00414        beam_view(&vwfocus, (int)(3*odev.hres*hsiz)+100,
00415                      (int)(3*odev.vres*vsiz)+100);
00416        beam_sync(0);                             /* update server */
00417 }
00418 
00419 
00420 static int
00421 usr_input(void)                    /* get user input and process it */
00422 {
00423        VIEW   vparams;
00424        char   cmd[256];
00425        register char *args;
00426        register int  i;
00427 
00428        if (fgets(cmd, sizeof(cmd), sstdin) == NULL) {
00429               fclose(sstdin);
00430               sstdin = NULL;
00431               return(-1);
00432        }
00433        if (*cmd == '\n')
00434               return(DC_RESUME);
00435        for (args = cmd; *args && !isspace(*args); args++)
00436               ;
00437        while (isspace(*args))
00438               *args++ = '\0';
00439        if (*args && args[i=strlen(args)-1] == '\n')
00440               args[i] = '\0';
00441        for (i = 0; i < DC_NCMDS; i++)
00442               if (!strcmp(cmd, cmdlist[i]))
00443                      break;
00444        if (i >= DC_NCMDS) {
00445               dev_auxcom(cmd, args);
00446               return(-1);
00447        }
00448        switch (i) {
00449        case DC_SETVIEW:            /* set the view */
00450               vparams = odev.v;
00451               if (!sscanview(&vparams, args))
00452                      error(COMMAND, "missing view options");
00453               else
00454                      new_view(&vparams);
00455               break;
00456        case DC_GETVIEW:            /* print the current view */
00457               printview();
00458               break;
00459        case DC_LASTVIEW:           /* restore previous view */
00460               new_view(NULL);
00461               break;
00462        case DC_FOCUS:                     /* set focus frame */
00463               set_focus(args);
00464               break;
00465        case DC_PAUSE:                     /* pause the current calculation */
00466        case DC_RESUME:                    /* resume the calculation */
00467               /* handled in main() */
00468               break;
00469        case DC_REDRAW:                    /* redraw from server */
00470               imm_mode = beam_sync(1) > 0;
00471               dev_clear();
00472               break;
00473        case DC_KILL:               /* kill rtrace process(es) */
00474               serv_request(DR_KILL, 0, NULL);
00475               break;
00476        case DC_CLOBBER:            /* clobber holodeck */
00477               serv_request(DR_CLOBBER, 0, NULL);
00478               break;
00479        case DC_RESTART:            /* restart rtrace */
00480               serv_request(DR_RESTART, 0, NULL);
00481               break;
00482        case DC_QUIT:               /* quit request */
00483               serv_request(DR_SHUTDOWN, 0, NULL);
00484               break;
00485        default:
00486               error(CONSISTENCY, "bad command id in usr_input");
00487        }
00488        return(i);
00489 }
00490 
00491 
00492 static void
00493 printview(void)                    /* print our current view to server stdout */
00494 {
00495        fputs(VIEWSTR, sstdout);
00496        fprintview(&odev.v, sstdout);
00497        fputc('\n', sstdout);
00498        fflush(sstdout);
00499 }
00500 
00501 
00502 extern int
00503 serv_result(void)                  /* get next server result and process it */
00504 {
00505        static char   *buf = NULL;
00506        static int    bufsiz = 0;
00507        MSGHEAD       msg;
00508                                    /* read message header */
00509        if (fread((char *)&msg, sizeof(MSGHEAD), 1, stdin) != 1)
00510               goto readerr;
00511        if (msg.nbytes > 0) {              /* get the message body */
00512               if (msg.nbytes > bufsiz) {
00513                      if (buf == NULL)
00514                             buf = (char *)malloc(bufsiz=msg.nbytes);
00515                      else
00516                             buf = (char *)realloc((void *)buf,
00517                                           bufsiz=msg.nbytes);
00518                      if (buf == NULL)
00519                             error(SYSTEM, "out of memory in serv_result");
00520               }
00521               if (fread(buf, 1, msg.nbytes, stdin) != msg.nbytes)
00522                      goto readerr;
00523        }
00524        switch (msg.type) {         /* process results */
00525        case DS_BUNDLE:
00526               if (msg.nbytes < sizeof(PACKHEAD) ||
00527                             msg.nbytes != packsiz(((PACKHEAD *)buf)->nr))
00528                      error(INTERNAL, "bad display packet from server");
00529               disp_bundle((PACKHEAD *)buf);
00530               break;
00531        case DS_ADDHOLO:
00532               if (msg.nbytes < sizeof(HDGRID)+2)
00533                      error(INTERNAL, "bad holodeck record from server");
00534               add_holo((HDGRID *)buf, buf+sizeof(HDGRID),
00535                      buf+sizeof(HDGRID)+strlen(buf+sizeof(HDGRID))+1);
00536               break;
00537        case DS_OUTSECT:
00538               do_outside = 1;
00539               goto noargs;
00540        case DS_EYESEP:
00541               if (msg.nbytes <= 1 || (eyesepdist = atof(buf)) <= FTINY)
00542                      error(INTERNAL, "bad eye separation from server");
00543               break;
00544        case DS_STARTIMM:
00545        case DS_ENDIMM:
00546               if (!(imm_mode = msg.type==DS_STARTIMM))
00547                      dev_flush();
00548 #ifdef DEBUG
00549               {
00550                      time_t tnow = time(NULL);
00551                      if (msg.type==DS_STARTIMM) tadd += tnow - tmodesw;
00552                      else timm += tnow - tmodesw;
00553                      tmodesw = tnow;
00554               }
00555 #endif
00556               goto noargs;
00557        case DS_ACKNOW:
00558        case DS_SHUTDOWN:
00559               goto noargs;
00560        default:
00561               error(INTERNAL, "unrecognized result from server process");
00562        }
00563        return(msg.type);           /* return message type */
00564 noargs:
00565        if (msg.nbytes) {
00566               sprintf(errmsg, "unexpected body with server message %d",
00567                             msg.type);
00568               error(INTERNAL, errmsg);
00569        }
00570        return(msg.type);
00571 readerr:
00572        if (feof(stdin))
00573               error(SYSTEM, "server process died");
00574        error(SYSTEM, "error reading from server process");
00575        return -1;  
00576 }
00577 
00578 
00579 extern void
00580 serv_request( /* send a request to the server process */
00581        int    type,
00582        int    nbytes,
00583        char   *p
00584 )
00585 {
00586        MSGHEAD       msg;
00587        int    m;
00588                             /* consistency checks */
00589        DCHECK(nbytes < 0 || nbytes > 0 & p == NULL,
00590                      CONSISTENCY, "bad buffer handed to serv_request");
00591                             /* get server's attention for big request */
00592        if (nbytes >= BIGREQSIZ-sizeof(MSGHEAD)) {
00593               serv_request(DR_ATTEN, 0, NULL);
00594               while ((m = serv_result()) != DS_ACKNOW)
00595                      if (m == DS_SHUTDOWN)       /* the bugger quit on us */
00596                             quit(0);
00597        }
00598        msg.type = type;     /* write and flush the message */
00599        msg.nbytes = nbytes;
00600        fwrite((char *)&msg, sizeof(MSGHEAD), 1, stdout);
00601        if (nbytes > 0)
00602               fwrite(p, 1, nbytes, stdout);
00603        if (fflush(stdout) < 0)
00604               error(SYSTEM, "write error in serv_request");
00605 }
00606 
00607 
00608 void
00609 eputs(               /* put error message to stderr */
00610        register char  *s
00611 )
00612 {
00613        static int  midline = 0;
00614 
00615        if (!*s)
00616               return;
00617        if (!midline++) {    /* prepend line with program name */
00618               fputs(progname, stderr);
00619               fputs(": ", stderr);
00620        }
00621        fputs(s, stderr);
00622        if (s[strlen(s)-1] == '\n') {
00623               fflush(stderr);
00624               midline = 0;
00625        }
00626 }
00627 
00628 
00629 void
00630 quit(                /* clean up and exit */
00631        int    code
00632 )
00633 {
00634        if (code)
00635               exit(code);
00636        if (odev.v.type)
00637               dev_close();
00638        exit(0);
00639 }