Add/update copyright notice in all source code files.
[mmh] / uip / wmh.c
1
2 /*
3  * wmh.c -- window front-end to nmh
4  *
5  * $Id$
6  *
7  * This code is Copyright (c) 2002, by the authors of nmh.  See the
8  * COPYRIGHT file in the root directory of the nmh distribution for
9  * complete copyright information.
10  */
11
12 /*
13  * TODO:
14  * Pass signals to client during execution
15  *
16  * Figure out a way for the user to say how big the Scan/Display
17  * windows should be, and where all the windows should be.
18  */
19
20 #include <h/mh.h>
21 #include <h/signals.h>
22 #include <h/vmhsbr.h>
23 #include <errno.h>
24 #include <setjmp.h>
25 #include <signal.h>
26
27 #include <sys/uio.h>
28 #include <vt.h>
29 #include <bitmap.h>
30 #include <tools.h>
31
32 #define ALARM ((unsigned int) 10)
33 #define PAUSE ((unsigned int) 2)
34
35 #define SZ(a) (sizeof a / sizeof a[0])
36
37 static struct swit switches[] = {
38 #define PRMPTSW            0
39     { "prompt string", 6 },
40 #define PROGSW             1
41     { "vmhproc program", 7 },
42 #define NPROGSW            2
43     { "novmhproc", 9 },
44 #define VERSIONSW          3
45     { "version", 0 },
46 #define HELPSW             4
47     { "help", 0 },
48     { NULL, NULL }
49 };
50                                         /* PEERS */
51 static int PEERpid = NOTOK;
52
53 static jmp_buf PEERctx;
54
55
56                                         /* WINDOWS */
57 static int dfd = NOTOK;
58 static int twd = NOTOK;
59 static char *myprompt = "(%s) ";
60
61 struct line {
62     int l_no;
63     char *l_buf;
64     struct line *l_prev;
65     struct line *l_next;
66 };
67
68 #define W_NULL  0x00
69 #define W_CMND  0x01
70 #define W_FAKE  0x02
71 #define W_EBAR  0x04
72
73 typedef struct {
74     int w_fd;
75     int w_flags;
76     int w_wd;
77     struct wstate w_ws;
78     char *w_eb;
79     int w_ebloc;
80     int w_ebsize;
81     int w_cbase;
82     int w_height;
83     int w_cheight;
84     int w_width;
85     int w_cwidth;
86     struct line *w_head;
87     struct line *w_top;
88     struct line *w_bottom;
89     struct line *w_tail;
90     char w_buffer[BUFSIZ];
91     int w_bufpos;
92 } WINDOW;
93
94
95 static WINDOW *Scan;
96 static WINDOW *Status;
97 static WINDOW *Display;
98 static WINDOW *Command;
99
100 #define NWIN 4
101 static int numwins;
102 WINDOW *windows[NWIN + 1];
103
104 WINDOW *WINnew ();
105
106
107 #ifdef HAVE_TERMIOS_H
108 static struct termios tio;
109 # define ERASE tio.c_cc[VERASE]
110 # define KILL  tio.c_cc[VKILL]
111 # define INTR  tio.c_cc[VINTR]
112 #else
113 # ifdef HAVE_TERMIO_H
114 static struct termio tio;
115 #  define ERASE tio.c_cc[VERASE]
116 #  define KILL  tio.c_cc[VKILL]
117 #  define INTR  tio.c_cc[VINTR]
118 # else
119 static struct sgttyb tio;
120 static struct tchars tc;
121 #  define ERASE tio.sg_erase
122 #  define KILL  tio.sg_kill
123 #  define INTR  tc.t_intrc
124 #  define EOFC  tc.t_eofc
125 # endif
126 #endif
127
128 #define WERASC ltc.t_werasc
129 static struct ltchars ltc;
130
131 extern int errno;
132
133 int ALRMser (), PIPEser (), SIGser ();
134 int ADJser (), REFser ();
135
136 /*
137  * static prototypes
138  */
139 static void adorn(char *, char *, ...);
140
141
142 int
143 main (int argc, char **argv)
144 {
145     int vecp = 1, nprog = 0;
146     char *cp, buffer[BUFSIZ], **argp;
147     char **arguments, *vec[MAXARGS];
148
149 #ifdef LOCALE
150     setlocale(LC_ALL, "");
151 #endif
152     invo_name = r1bindex (argv[0], '/');
153
154     /* read user profile/context */
155     context_read();
156
157     arguments = getarguments (invo_name, argc,argv, 1);
158     argp = arguments;
159
160     while ((cp = *argp++))
161         if (*cp == '-')
162             switch (smatch (++cp, switches)) {
163                 case AMBIGSW: 
164                     ambigsw (cp, switches);
165                     done (1);
166                 case UNKWNSW: 
167                     vec[vecp++] = --cp;
168                     continue;
169
170                 case HELPSW: 
171                     snprintf (buffer, sizeof(buffer), "%s [switches for vmhproc]",
172                         invo_name);
173                     print_help (buffer, switches, 1);
174                     done (1);
175                 case VERSIONSW:
176                     print_version(invo_name);
177                     done (1);
178
179                 case PRMPTSW:
180                     if (!(myprompt = *argp++) || *myprompt == '-')
181                         adios (NULL, "missing argument to %s", argp[-2]);
182                     continue;
183
184                 case PROGSW: 
185                     if (!(vmhproc = *argp++) || *vmhproc == '-')
186                         adios (NULL, "missing argument to %s", argp[-2]);
187                     continue;
188                 case NPROGSW:
189                     nprog++;
190                     continue;
191             }
192         else
193             vec[vecp++] = cp;
194
195     SIGinit ();
196     if (WINinit (nprog) == NOTOK) {
197         vec[vecp] = NULL;
198
199         vec[0] = r1bindex (vmhproc, '/');
200         execvp (vmhproc, vec);
201         adios (vmhproc, "unable to exec");
202     }
203     PEERinit (vecp, vec);
204
205     vmh ();
206
207     return done (0);
208 }
209
210
211 static void
212 vmh (void)
213 {
214     char buffer[BUFSIZ], prompt[BUFSIZ];
215
216     for (;;) {
217         pLOOP (RC_QRY, NULL);
218
219         snprintf (prompt, sizeof(prompt), myprompt, invo_name);
220
221         switch (WINgetstr (Command, prompt, buffer)) {
222             case NOTOK: 
223                 break;
224
225             case OK: 
226                 done (0);       /* NOTREACHED */
227
228             default: 
229                 if (*buffer)
230                     pLOOP (RC_CMD, buffer);
231                 break;
232         }
233     }
234 }
235
236 /* PEERS */
237
238 static int
239 PEERinit (int vecp, char *vec[])
240 {
241     int pfd0[2], pfd1[2];
242     char buf1[BUFSIZ], buf2[BUFSIZ];
243     register WINDOW **w;
244
245     SIGNAL (SIGPIPE, PIPEser);
246
247     if (pipe (pfd0) == NOTOK || pipe (pfd1) == NOTOK)
248         adios ("pipe", "unable to");
249     switch (PEERpid = vfork ()) {
250         case NOTOK: 
251             adios ("vfork", "unable to");/* NOTREACHED */
252
253         case OK: 
254             for (w = windows; *w; w++)
255                 if ((*w)->w_fd != NOTOK)
256                     close ((*w)->w_fd);
257             close (pfd0[0]);
258             close (pfd1[1]);
259
260             vec[vecp++] = "-vmhread";
261             snprintf (buf1, sizeof(buf1), "%d", pfd1[0]);
262             vec[vecp++] = buf1;
263             vec[vecp++] = "-vmhwrite";
264             snprintf (buf2, sizeof(buf2), "%d", pfd0[1]);
265             vec[vecp++] = buf2;
266             vec[vecp] = NULL;
267
268             SIGNAL (SIGINT, SIG_DFL);
269             SIGNAL (SIGQUIT, SIG_DFL);
270             SIGNAL (SIGTERM, SIG_DFL);
271
272             vec[0] = r1bindex (vmhproc, '/');
273             execvp (vmhproc, vec);
274             perror (vmhproc);
275             _exit (-1);         /* NOTREACHED */
276
277         default: 
278             close (pfd0[1]);
279             close (pfd1[0]);
280
281             rcinit (pfd0[0], pfd1[1]);
282             return pINI ();
283     }
284 }
285
286
287 static int
288 pINI (void)
289 {
290     int len, buflen;
291     char *bp, buffer[BUFSIZ];
292     struct record rcs, *rc;
293     WINDOW **w;
294
295     rc = &rcs;
296     initrc (rc);
297
298     /* Get buffer ready to go */
299     bp = buffer;
300     buflen = sizeof(buffer);
301
302     snprintf (bp, buflen, "%d %d", RC_VRSN, numwins);
303     len = strlen (bp);
304     bp += len;
305     buflen -= len;
306
307     for (w = windows; *w; w++) {
308         snprintf (bp, buflen, " %d", (*w)->w_height);
309         len = strlen (bp);
310         bp += len;
311         buflen -= len;
312     }
313
314     switch (str2rc (RC_INI, buffer, rc)) {
315         case RC_ACK: 
316             return OK;
317
318         case RC_ERR: 
319             if (rc->rc_len)
320                 adios (NULL, "%s", rc->rc_data);
321             else
322                 adios (NULL, "pINI peer error");
323
324         case RC_XXX: 
325             adios (NULL, "%s", rc->rc_data);
326
327         default:
328             adios (NULL, "pINI protocol screw-up");
329     }
330 /* NOTREACHED */
331 }
332
333
334 static int
335 pLOOP (char code, char *str)
336 {
337     int i;
338     struct record rcs, *rc;
339     WINDOW *w;
340
341     rc = &rcs;
342     initrc (rc);
343
344     str2peer (code, str);
345     for (;;)
346         switch (peer2rc (rc)) {
347             case RC_TTY:
348                 if (pTTY () == NOTOK)
349                     return NOTOK;
350                 break;
351
352             case RC_WIN:
353                 if (sscanf (rc->rc_data, "%d", &i) != 1
354                         || i <= 0
355                         || i > numwins) {
356                     fmt2peer (RC_ERR, "no such window \"%s\"", rc->rc_data);
357                     return NOTOK;
358                 }
359                 if ((w = windows[i - 1])->w_flags & W_CMND) {
360                     fmt2peer (RC_ERR, "not a display window \"%s\"", rc->rc_data);
361                     return NOTOK;
362                 }
363                 if (pWIN (w) == NOTOK)
364                     return NOTOK;
365                 break;
366
367             case RC_EOF:
368                 return OK;
369
370             case RC_ERR:
371                 if (rc->rc_len)
372                     adorn (NULL, "%s", rc->rc_data);
373                 else
374                     adorn (NULL, "pLOOP(%s) peer error",
375                             code == RC_QRY ? "QRY" : "CMD");
376                 return NOTOK;
377
378             case RC_FIN:
379                 if (rc->rc_len)
380                     adorn (NULL, "%s", rc->rc_data);
381                 rcdone ();
382                 i = pidwait (PEERpid, OK);
383                 PEERpid = NOTOK;
384                 done (i);
385
386             case RC_XXX: 
387                 adios (NULL, "%s", rc->rc_data);
388
389             default:
390                 adios (NULL, "pLOOP(%s) protocol screw-up",
391                         code == RC_QRY ? "QRY" : "CMD");
392         }
393 }
394
395
396 static int
397 pTTY (void)
398 {
399     SIGNAL_HANDLER hstat, istat, qstat, tstat;
400     struct record rcs, *rc;
401
402     rc = &rcs;
403     initrc (rc);
404
405     if (ChangeWindowDepth (dfd, twd, 0) == NOTOK)
406         adios ("failed", "ChangeWindowDepth");
407
408     /* should block here instead of ignore */
409     hstat = SIGNAL (SIGHUP, SIG_IGN);
410     istat = SIGNAL (SIGINT, SIG_IGN);
411     qstat = SIGNAL (SIGQUIT, SIG_IGN);
412     tstat = SIGNAL (SIGTERM, SIG_IGN);
413
414     rc2rc (RC_ACK, 0, NULL, rc);
415
416     SIGNAL (SIGHUP, hstat);
417     SIGNAL (SIGINT, istat);
418     SIGNAL (SIGQUIT, qstat);
419     SIGNAL (SIGTERM, tstat);
420
421     switch (rc->rc_type) {
422         case RC_EOF: 
423             rc2peer (RC_ACK, 0, NULL);
424             return OK;
425
426         case RC_ERR: 
427             if (rc->rc_len)
428                 adorn (NULL, "%s", rc->rc_data);
429             else
430                 adorn (NULL, "pTTY peer error");
431             return NOTOK;
432
433         case RC_XXX: 
434             adios (NULL, "%s", rc->rc_data);
435
436         default:
437             adios (NULL, "pTTY protocol screw-up");
438     }
439 /* NOTREACHED */
440 }
441
442
443 static int
444 pWIN (WINDOW *w)
445 {
446     int i;
447
448     if ((i = pWINaux (w)) == OK)
449         WINless (w);
450
451     return i;
452 }
453
454
455 static int
456 pWINaux (WINDOW *w)
457 {
458     register int n;
459     register char *bp;
460     register struct line *lp, *mp;
461     struct record rcs, *rc;
462
463     rc = &rcs;
464     initrc (rc);
465
466     for (lp = w->w_head; lp; lp = mp) {
467         mp = lp->l_next;
468         free (lp->l_buf);
469         free ((char *) lp);
470     }
471     w->w_head = w->w_top = w->w_bottom = w->w_tail = NULL;
472     w->w_bufpos = 0;
473
474     for (;;)
475         switch (rc2rc (RC_ACK, 0, NULL, rc)) {
476             case RC_DATA: 
477                 for (bp = rc->rc_data, n = rc->rc_len; n-- > 0; )
478                     WINputc (w, *bp++);
479                 break;
480
481             case RC_EOF: 
482                 rc2peer (RC_ACK, 0, NULL);
483                 if (w->w_bufpos)
484                     WINputc (w, '\n');
485                 return OK;
486
487             case RC_ERR: 
488                 if (rc->rc_len)
489                     adorn (NULL, "%s", rc->rc_data);
490                 else
491                     adorn (NULL, "pWIN peer error");
492                 return NOTOK;
493
494             case RC_XXX: 
495                 adios (NULL, "%s", rc->rc_data);
496
497             default:
498                 adios (NULL, "pWIN protocol screw-up");
499         }
500 /* NOTREACHED */
501 }
502
503
504 static int
505 pFIN (void)
506 {
507     int status;
508
509     if (PEERpid <= OK)
510         return OK;
511
512     rc2peer (RC_FIN, 0, NULL);
513     rcdone ();
514
515     switch (setjmp (PEERctx)) {
516         case OK: 
517             SIGNAL (SIGALRM, ALRMser);
518             alarm (ALARM);
519
520             status = pidwait (PEERpid, OK);
521
522             alarm (0);
523             break;
524
525         default: 
526             kill (PEERpid, SIGKILL);
527             status = NOTOK;
528             break;
529     }
530     PEERpid = NOTOK;
531
532     return status;
533 }
534
535 /* WINDOWS */
536
537 /* should dynamically determine all this stuff from gconfig... */
538
539 #define MyX     20              /* anchored hpos */
540 #define MyY     40              /*   .. vpos */
541 #define MyW     800             /*   .. width */
542 #define MyH     500             /*   .. height */
543 #define MyS     30              /*   .. height for Status, about one line */
544
545
546 #define MySlop  45              /* slop */
547
548 #define EWIDTH  25              /* Width of vertical EBAR */
549 #define ESLOP   5               /*   .. slop */
550
551
552 static intWINinit (nprog) {
553     short wx, wy, wh, sy;
554     struct gconfig gc;
555
556     if (GetGraphicsConfig (fileno (stderr), &gc) == NOTOK)
557         if (nprog)
558             return NOTOK;
559         else
560             adios (NULL, "not a window");
561
562     if ((dfd = open ("/dev/ttyw0", O_RDWR)) == NOTOK)
563         adios ("/dev/ttyw0", "unable to open");
564
565     if ((twd = GetTopWindow (dfd)) == NOTOK)
566         adios ("failed", "GetTopWindow");
567
568     BlockRefreshAdjust (1);
569
570     numwins = 0;
571
572     wx = gc.w - (MyX + MyW + EWIDTH + ESLOP);
573     Scan = WINnew (wx, wy = MyY, MyW, wh = MyH * 2 / 3, "Scan", W_EBAR);
574
575     wy += wh + MySlop;
576     Status = WINnew (wx, sy = wy, MyW, wh = MyS, "Status", W_FAKE);
577
578     wy += wh + MySlop;
579     Display = WINnew (wx, wy, MyW, MyH, "Display", W_EBAR);
580
581     Command = WINnew (wx, sy, MyW, MyS, invo_name, W_CMND);
582
583     windows[numwins] = NULL;
584
585     return OK;
586 }
587
588
589 WINDOW *
590 WINnew (short wx, short wy, short ww, short wh, char *name, int flags)
591 {
592     register WINDOW *w;
593
594     if ((w = (WINDOW *) calloc (1, sizeof *w)) == NULL)
595         adios (NULL, "unable to allocate window");
596
597     if ((w->w_flags = flags) & W_FAKE) {
598         w->w_fd = NOTOK;
599         w->w_height = 1;
600
601         goto out;
602     }
603
604     if (w->w_flags & W_EBAR)
605         ww += EWIDTH + ESLOP;
606     else
607         wx += EWIDTH + ESLOP;
608
609     if ((w->w_fd = OpenWindow (wx, wy, ww, wh, name)) == NOTOK)
610         adios ("failed", "OpenWindow");
611     if ((w->w_wd = GetTopWindow (dfd)) == NOTOK)
612         adios ("failed", "GetTopWindow");
613     if (GetWindowState (w->w_fd, &w->w_ws) == NOTOK)
614         adios ("failed", "GetWindowState");
615     if (SetLineDisc (w->w_fd, TWSDISC) == NOTOK)
616         adios ("failed", "SetLineDisc");
617
618     SetBuf (w->w_fd, 1024);
619     SetAdjust (w->w_fd, numwins, ADJser);
620     SetRefresh (w->w_fd, numwins, REFser);
621
622     SetAddressing (w->w_fd, VT_ABSOLUTE);
623
624     if (w->w_flags & W_EBAR) {
625         w->w_eb = CreateElevatorBar (w->w_fd, 0, 0, EWIDTH,
626                         w->w_ws.height, VT_Gray50, 1, EB_VERTICAL,
627                         EB_ARROWS, w->w_ebloc = 0, w->w_ebsize = EB_MAX,
628                         VT_White);
629         if (w->w_eb == NULL)
630             adios (NULL, "CreateElevatorBar failed");
631         RefreshElevatorBar (w->w_eb);
632     }
633
634     if ((w->w_cbase = CharacterBaseline (w->w_ws.font)) <= 0)
635         w->w_cbase = 14;
636
637     if ((w->w_cheight = CharacterHeight (w->w_ws.font)) <= 0)
638         w->w_cheight = 20;
639     w->w_height = w->w_ws.height / w->w_cheight;
640     if (w->w_height < 1)
641         w->w_height = 1;
642
643                                                 /* 1 em */
644     if ((w->w_cwidth = CharacterWidth (w->w_ws.font, 'm')) <= 0)
645         w->w_cwidth = 10;
646     w->w_width = (w->w_ws.width - (w->w_eb ? (EWIDTH + ESLOP) : 0))
647                     / w->w_cwidth;
648     if (w->w_width < 1)
649         w->w_width = 1;
650
651 out: ;
652     windows[numwins++] = w;
653
654     return w;
655 }
656
657
658 static int
659 WINgetstr (WINDOW *w, char *prompt, char *buffer)
660 {
661     register int c;
662     register char *bp, *ip;
663     char image[BUFSIZ];
664     struct vtseq vts;
665     register struct vtseq  *vt = &vts;
666
667     if (w->w_eb != NULL)
668         adios (NULL, "internal error--elevator bar found");
669
670     if (w->w_head == NULL
671             && (w->w_head = (struct line *) calloc (1, sizeof *w->w_head))
672                 == NULL)
673         adios (NULL, "unable to allocate line storage");
674     w->w_head->l_buf = image;
675     w->w_top = w->w_bottom = w->w_tail = w->w_head;
676
677     if (ChangeWindowDepth (dfd, w->w_wd, 0) == NOTOK)
678         adios ("failed", "ChangeWindowDepth");
679
680     strncpy (image, prompt, sizeof(image));
681     bp = ip = image + strlen (image);
682
683     Redisplay (w, 0);
684
685     for (;;)
686         switch (getvtseq (w->w_fd, vt)) {
687             case VT_HARDKEY: 
688                 DisplayStatus (w->w_fd, "no hardkeys, please");
689                 break;
690
691             case VT_ASCII: 
692                 switch (c = toascii (vt->u.ascii)) {
693                     case '\f':  /* refresh? */
694                         break;
695
696                     case '\r': 
697                     case '\n': 
698                         strcpy (buffer, ip);
699                         return DONE;
700
701                     default: 
702                         if (c == INTR) {
703                             adorn (NULL, "Interrupt");
704                             return NOTOK;
705                         }
706
707                         if (c == EOFC) {
708                             if (bp <= ip)
709                                 return OK;
710                             break;
711                         }
712
713                         if (c == ERASE) {
714                             if (bp <= ip)
715                                 continue;
716                             bp--;
717                             break;
718                         }
719
720                         if (c == KILL) {
721                             if (bp <= ip)
722                                 continue;
723                             bp = ip;
724                             break;
725                         }
726
727                         if (c == WERASC) {
728                             if (bp <= ip)
729                                 continue;
730                             do {
731                                 bp--;
732                             } while (isspace (*bp) && bp > ip);
733                             if (bp > ip) {
734                                 do {
735                                     bp--;
736                                 } while (!isspace (*bp) && bp > buffer);
737                                 if (isspace (*bp))
738                                     bp++;
739                             }
740                             break;
741                         }
742
743                         if (c < ' ' || c >= '\177')
744                             continue;
745                         *bp++ = c;
746                         break;
747                 }
748                 *bp = NULL;
749                 Redisplay (w, 0);
750                 break;
751
752             case VT_MOUSE: 
753                 switch (vt->u.mouse.buttons
754                         & (VT_MOUSE_LEFT | VT_MOUSE_MIDDLE | VT_MOUSE_RIGHT)) {
755                     case VT_MOUSE_LEFT: 
756                         DisplayStatus (w->w_fd, "use middle or right button");
757                         break;
758
759 #define WPOP    "WMH\0Advance\0Burst\0Exit\0EOF\0"
760                     case VT_MOUSE_MIDDLE: 
761                         SetPosition (w->w_fd, vt->u.mouse.x,
762                                 vt->u.mouse.y);
763                         switch (DisplayPopUp (w->w_fd, WPOP)) {
764                             case 1: /* Advance */
765                         do_advance: ;
766                                 strcpy (buffer, "advance");
767                                 return DONE;
768
769                             case 2: /* Burst */
770                                 strcpy (buffer, "burst");
771                                 return DONE;
772
773                             case 3: /* Exit */
774                                 strcpy (buffer, "exit");
775                                 return DONE;
776
777                             case 4: /* EOF */
778                                 return OK;
779
780                             default: /* failed or none taken */
781                                 break;
782                         }
783                         break;
784 #undef  WPOP
785
786                     case VT_MOUSE_RIGHT: 
787                         goto do_advance;
788                 }
789                 break;
790
791             case VT_EOF: 
792                 adios (NULL, "end-of-file on window");/* NOTREACHED */
793
794             default: 
795                 DisplayStatus (w->w_fd, "unknown VT sequence");
796                 break;
797         }
798 }
799
800
801 static int
802 WINputc (WINDOW *w, char c)
803 {
804     register int i;
805     register char *cp;
806     register struct line *lp;
807
808     switch (c) {
809         default: 
810             if (!isascii (c)) {
811                 if (WINputc (w, 'M') == NOTOK || WINputc (w, '-') == NOTOK)
812                     return NOTOK;
813                 c = toascii (c);
814             }
815             else
816                 if (c < ' ' || c == '\177') {
817                     if (WINputc (w, '^') == NOTOK)
818                         return NOTOK;
819                     c ^= 0100;
820                 }
821             break;
822
823         case '\t': 
824             for (i = 8 - (w->w_bufpos & 0x07); i > 0; i--)
825                 if (WINputc (w, ' ') == NOTOK)
826                     return NOTOK;
827             return OK;
828
829         case '\b':
830             if (w->w_bufpos > 0)
831                 w->w_bufpos--;
832             return OK;
833
834         case '\n': 
835             break;
836     }
837
838     if (c != '\n') {
839         w->w_buffer[w->w_bufpos++] = c;
840         return OK;
841     }
842
843     w->w_buffer[w->w_bufpos] = NULL;
844     w->w_bufpos = 0;
845
846     if ((lp = (struct line *) calloc (1, sizeof *lp)) == NULL)
847         adios (NULL, "unable to allocate line storage");
848
849     lp->l_no = (w->w_tail ? w->w_tail->l_no : 0) + 1;
850     lp->l_buf = getcpy (w->w_buffer);
851     for (cp = lp->l_buf + strlen (lp->l_buf) - 1; cp >= lp->l_buf; cp--)
852         if (isspace (*cp))
853             *cp = NULL;
854         else
855             break;
856
857     if (w->w_head == NULL)
858         w->w_head = lp;
859     if (w->w_top == NULL)
860         w->w_top = lp;
861     if (w->w_bottom == NULL)
862         w->w_bottom = lp;
863     if (w->w_tail)
864         w->w_tail->l_next = lp;
865     lp->l_prev = w->w_tail;
866     w->w_tail = lp;
867
868     return DONE;
869 }
870
871 #define PSLOP   2
872
873
874 static char mylineno[5];
875
876 static bool cancel[] =  { 1 };
877 static struct choice mychoices[] = { LABEL, "cancel", VT_White };
878
879 static struct question myquestions[] = {
880     STRING, "Line", SZ (mylineno), (struct choice *) 0,
881
882     TOGGLE, "", SZ (mychoices),  mychoices
883 };
884
885 static struct menu mymenu = { "Goto", SZ (myquestions), myquestions };
886
887 static int *myanswers[] = { (int *) mylineno, (int *) cancel };
888
889
890 static void
891 WINless (WINDOW *w)
892 {
893     int clear, pos, forw, refresh;
894     struct vtseq vts;
895     register struct vtseq *vt = &vts;
896
897     if (w->w_fd == NOTOK) {
898         if (w->w_head)
899             DisplayStatus (dfd, w->w_top->l_buf);
900         else
901             RemoveStatus (dfd);
902
903         return;
904     }
905
906     if (ChangeWindowDepth (dfd, w->w_wd, 0) == NOTOK)
907         adios ("failed", "ChangeWindowDepth");
908
909     Redisplay (w, 0);
910
911     if (w->w_bottom == w->w_tail)
912         return;
913
914     if (w->w_eb == NULL)
915         adios (NULL, "internal error--no elevator bar");
916
917     for (clear = refresh = 0, forw = 1;;) {
918         if (clear) {
919             RemoveStatus (w->w_fd);
920             clear = 0;
921         }
922         if (refresh) {
923             Redisplay (w, 0);
924             refresh = 0;
925         }
926
927         switch (getvtseq (w->w_fd, vt)) {
928             case VT_HARDKEY: 
929             case VT_ASCII: 
930                 DisplayStatus (w->w_fd, "use the mouse");
931                 clear++;
932                 break;
933
934             case VT_MOUSE: 
935                 switch (vt->u.mouse.buttons
936                         & (VT_MOUSE_LEFT | VT_MOUSE_MIDDLE | VT_MOUSE_RIGHT)) {
937                     case VT_MOUSE_LEFT: 
938                         if ((pos = vt->u.mouse.x) < EWIDTH) {
939                             pos = w->w_ebloc = DoElevatorBar (w->w_eb, pos,
940                                     vt->u.mouse.y);
941                             refresh = WINgoto (w, ((pos * (w->w_tail->l_no
942                                                 - w->w_head->l_no))
943                                         / EB_MAX) + w->w_head->l_no);
944                         }
945                         break;
946
947 #define WPOP "Paging\0Next\0Prev\0Left\0Right\0First\0Last\0Goto ...\0Exit\0"
948                     case VT_MOUSE_MIDDLE: 
949                         SetPosition (w->w_fd, vt->u.mouse.x,
950                                 vt->u.mouse.y);
951                         switch (DisplayPopUp (w->w_fd, WPOP)) {
952                             case 1: /* Next */
953                         do_next_page: ;
954                                 if (w->w_bottom == w->w_tail)
955                                     forw = 0;
956                                 refresh = WINgoto (w, w->w_bottom->l_no + 1 - PSLOP);
957                                 break;
958
959                             case 2: /* Prev */
960                         do_prev_page: ;
961                                 if (w->w_top == w->w_head)
962                                     forw = 1;
963                                 refresh = WINgoto (w, w->w_top->l_no
964                                         - w->w_height + PSLOP);
965                                 break;
966
967                             case 3: /* Left */
968                             case 4: /* Right */
969                                 DisplayStatus (w->w_fd, "not yet");
970                                 clear++;
971                                 break;
972
973                             case 5: /* First */
974                                 forw = 1;
975                                 refresh = WINgoto (w, w->w_head->l_no);
976                                 break;
977
978                             case 6: /* Last */
979                                 forw = 0;
980                                 refresh = WINgoto (w, w->w_tail->l_no
981                                         - w->w_height + 1);
982                                 break;
983
984                             case 7: /* Goto ... */
985                                 snprintf (mylineno, sizeof(mylineno),
986                                         "%d", w->w_top->l_no);
987                                 cancel[0] = 0;
988                                 if (PresentMenu (&mymenu, myanswers)
989                                         || cancel[0])
990                                     break;
991                                 if (sscanf (mylineno, "%d", &pos) != 1) {
992                                     DisplayStatus (w->w_fd, "bad format");
993                                     clear++;
994                                     break;
995                                 }
996                                 if (pos < w->w_head->l_no
997                                         || pos > w->w_tail->l_no) {
998                                     DisplayStatus (w->w_fd, "no such line");
999                                     clear++;
1000                                     break;
1001                                 }
1002                                 refresh = WINgoto (w, pos);
1003                                 break;
1004
1005                             case 8: /* Exit */
1006                                 return;
1007
1008                             default: /* failed or none taken */
1009                                 break;
1010                         }
1011                         break;
1012 #undef  WPOP
1013
1014                     case VT_MOUSE_RIGHT: 
1015                         if (forw) {
1016                             if (w->w_bottom == w->w_tail)
1017                                 return;
1018                             else
1019                                 goto do_next_page;
1020                         }
1021                         else
1022                             goto do_prev_page;
1023                 }
1024                 break;
1025
1026             case VT_EOF: 
1027                 adios (NULL, "end-of-file on window");/* NOTREACHED */
1028
1029             default: 
1030                 DisplayStatus (w->w_fd, "unknown VT sequence");
1031                 clear++;
1032                 break;
1033         }
1034     }
1035 }
1036
1037
1038 static int
1039 WINgoto (WINDOW *w, int n)
1040 {
1041     register int i, j;
1042     register struct line *lp;
1043
1044     if (n > (i = w->w_tail->l_no - w->w_height + 1))
1045         n = i;
1046     if (n < w->w_head->l_no)
1047         n = w->w_head->l_no;
1048
1049     if ((i = n - (lp = w->w_head)->l_no)
1050             > (j = abs (n - w->w_top->l_no)))
1051         i = j, lp = w->w_top;
1052
1053     if (i > (j = abs (w->w_tail->l_no - n)))
1054         i = j, lp = w->w_tail;
1055
1056     if (n >= lp->l_no) {
1057         for (; lp; lp = lp->l_next)
1058             if (lp->l_no == n)
1059                 break;
1060     }
1061     else {
1062         for (; lp; lp = lp->l_prev)
1063             if (lp->l_no == n)
1064                 break;
1065         if (!lp)
1066             lp = w->w_head;
1067     }
1068
1069     if (w->w_top == lp)
1070         return 0;
1071
1072     w->w_top = lp;
1073
1074     return 1;
1075 }
1076
1077
1078 static int
1079 ADJser (int id, short ww, short wh)
1080 {
1081     register WINDOW *w;
1082
1083     if (id < 0 || id >= numwins)
1084         adios (NULL, "ADJser on bogus window (%d)", id);
1085     w = windows[id];
1086     if (w->w_fd == NOTOK)
1087         adios (NULL, "ADJser on closed window (%d)", id);
1088
1089     w->w_ws.width = w->w_ws.tw = ww;
1090     w->w_ws.height = w->w_ws.th = wh;
1091
1092     if (w->w_eb) {
1093         DeleteElevatorBar (w->w_eb);
1094         w->w_eb = CreateElevatorBar (w->w_fd, 0, 0, EWIDTH,
1095                         w->w_ws.height, VT_Gray50, 1, EB_VERTICAL,
1096                         EB_ARROWS, w->w_ebloc = 0, w->w_ebsize = EB_MAX,
1097                         VT_White);
1098         if (w->w_eb == NULL)
1099             adios (NULL, "CreateElevatorBar failed");
1100     }
1101
1102     Redisplay (w, 1);
1103 }
1104
1105
1106 static int
1107 REFser (int id, short wx, short wy, short ww, short wh)
1108 {
1109     short cx, cy, cw, ch;
1110     register WINDOW *w;
1111
1112     if (id < 0 || id >= numwins)
1113         adios (NULL, "REFser on bogus window (%d)", id);
1114     w = windows[id];
1115     if (w->w_fd == NOTOK)
1116         adios (NULL, "REFser on closed window (%d)", id);
1117
1118
1119     if (GetWindowState (w->w_fd, &w->w_ws) == NOTOK)
1120         adios ("failed", "GetWindowState");
1121
1122     GetPermanentClipping (w->w_fd, &cx, &cy, &cw, &ch);
1123     SetPermanentClipping (w->w_fd, wx, wy, ww, wh);
1124     Redisplay (w, 1);
1125     SetPermanentClipping (w->w_fd, cx, cy, cw, ch);
1126 }
1127
1128
1129 static void
1130 Redisplay (WINDOW *w, int doeb)
1131 {
1132     register int y;
1133     short sx;
1134     register struct line *lp;
1135
1136     if (w->w_fd == NOTOK)
1137         return;
1138
1139     sx = w->w_eb ? (EWIDTH + ESLOP) : 0;
1140     w->w_height = w->w_ws.height / w->w_cheight;
1141     if (w->w_height < 1)
1142         w->w_height = 1;
1143
1144     w->w_width = (w->w_ws.width - (w->w_eb ? (EWIDTH + ESLOP) : 0))
1145         / w->w_cwidth;
1146     if (w->w_width < 1)
1147         w->w_width = 1;
1148
1149     SetPosition (w->w_fd, sx, 0);
1150     SetColor (w->w_fd, VT_White);
1151     PaintRectangleInterior (w->w_fd, w->w_ws.width, w->w_ws.height);
1152
1153     if (w->w_head) {
1154         SetColor (w->w_fd, VT_Black);
1155         for (lp = w->w_top, y = 0;
1156                 lp && y < w->w_height;
1157                 w->w_bottom = lp, lp = lp->l_next, y++) {
1158             SetPosition (w->w_fd, sx, y * w->w_cheight + w->w_cbase);
1159             PaintString (w->w_fd, VT_STREND, lp->l_buf);
1160         }
1161     }
1162
1163     if (w->w_eb) {
1164         if ((y = EB_LOC (w)) != w->w_ebloc)
1165             MoveElevator (w->w_eb, w->w_ebloc = y);
1166         if ((y = EB_SIZE (w)) != w->w_ebsize)
1167             SizeElevator (w->w_eb, w->w_ebsize = y);
1168         if (doeb)
1169             RefreshElevatorBar (w->w_eb);
1170     }
1171
1172     Flush (w->w_fd);
1173 }
1174
1175
1176 static int
1177 EB_SIZE (WINDOW *w)
1178 {
1179     register int i;
1180
1181     if (w->w_head == NULL)
1182         return 0;
1183
1184     if ((i = w->w_tail->l_no - w->w_head->l_no) <= 0)
1185         return EB_MAX;
1186
1187     return (((w->w_bottom->l_no - w->w_top->l_no) * EB_MAX) / i);
1188 }
1189
1190
1191 static int
1192 EB_LOC (WINDOW *w)
1193 {
1194     register int i;
1195
1196     if (w->w_head == NULL)
1197         return 0;
1198
1199     if ((i = w->w_tail->l_no - w->w_head->l_no) <= 0)
1200         return EB_MAX;
1201
1202     return (((w->w_top->l_no - w->w_head->l_no) * EB_MAX) / i);
1203 }
1204
1205 /* SIGNALS */
1206
1207 static void
1208 SIGinit (void)
1209 {
1210     foreground ();
1211     if (ioctl (fileno (stdin), TIOCGETP, (char *) &tio) == NOTOK)
1212         adios ("failed", "ioctl TIOCGETP");
1213     if (ioctl (fileno (stdin), TIOCGETC, (char *) &tc) == NOTOK)
1214         adios ("failed", "ioctl TIOCGETC");
1215     if (ioctl (fileno (stdin), TIOCGLTC, (char *) &ltc) == NOTOK)
1216         adios ("failed", "ioctl TIOCGLTC");
1217     sideground ();
1218
1219     SIGNAL (SIGHUP, SIGser);
1220     SIGNAL (SIGINT, SIGser);
1221     SIGNAL (SIGQUIT, SIGser);
1222 }
1223
1224
1225 static void
1226 foreground (void)
1227 {
1228 #ifdef TIOCGPGRP
1229     int pgrp, tpgrp;
1230     SIGNAL_HANDLER tstat;
1231
1232     if ((pgrp = getpgrp (0)) == NOTOK)
1233         adios ("process group", "unable to determine");
1234     for (;;) {
1235         if (ioctl (fileno (stdin), TIOCGPGRP, (char *) &tpgrp) == NOTOK)
1236             adios ("tty's process group", "unable to determine");
1237         if (pgrp == tpgrp)
1238             break;
1239
1240         tstat = SIGNAL (SIGTTIN, SIG_DFL);
1241         kill (0, SIGTTIN);
1242         SIGNAL (SIGTTIN, tstat);
1243     }
1244     
1245     SIGNAL (SIGTTIN, SIG_IGN);
1246     SIGNAL (SIGTTOU, SIG_IGN);
1247     SIGNAL (SIGTSTP, SIG_IGN);
1248 #endif  TIOCGPGRP
1249 }
1250
1251
1252 static void
1253 sideground (void)
1254 {
1255 #ifdef  TIOCGPGRP
1256     SIGNAL (SIGTTIN, SIG_DFL);
1257     SIGNAL (SIGTTOU, SIG_DFL);
1258     SIGNAL (SIGTSTP, SIG_DFL);
1259 #endif  TIOCGPGRP
1260 }
1261
1262
1263 static int
1264 ALRMser (int sig)
1265 {
1266      longjmp (PEERctx, DONE);
1267 }
1268
1269
1270 static int
1271 PIPEser (int sig)
1272 {
1273 #ifndef RELIABLE_SIGNALS
1274     SIGNAL (sig, SIG_IGN);
1275 #endif
1276
1277     adios (NULL, "lost peer");
1278 }
1279
1280
1281 static int
1282 SIGser (int sig)
1283 {
1284 #ifndef RELIABLE_SIGNALS
1285     SIGNAL (sig, SIG_IGN);
1286 #endif
1287
1288     done (1);
1289 }
1290
1291
1292 int
1293 done (int status)
1294 {
1295     if (dfd != NOTOK)
1296         RemoveStatus (dfd);
1297
1298     pFIN ();
1299
1300     exit (status);
1301     return 1;  /* dead code to satisfy the compiler */
1302 }
1303
1304
1305 static void
1306 adorn (char *what, char *fmt, ...)
1307 {
1308     va_list ap
1309     char *cp;
1310
1311     cp = invo_name;
1312     invo_name = NULL;
1313
1314     va_start(ap, fmt);
1315     advertise (what, NULL, fmt, ap);
1316     va_end(ap);
1317
1318     invo_name = cp;
1319 }
1320
1321
1322 void
1323 advertise (char *what, char *tail, va_list ap)
1324 {
1325     int eindex = errno;
1326     char buffer[BUFSIZ], err[BUFSIZ];
1327     struct iovec iob[20];
1328     register struct iovec *iov = iob;
1329
1330     fflush (stdout);
1331     fflush (stderr);
1332
1333     if (invo_name) {
1334         iov->iov_len = strlen (iov->iov_base = invo_name);
1335         iov++;
1336         iov->iov_len = strlen (iov->iov_base = ": ");
1337         iov++;
1338     }
1339     
1340     vsnprintf (buffer, sizeof(buffer), fmt, ap);
1341     iov->iov_len = strlen (iov->iov_base = buffer);
1342     iov++;
1343     if (what) {
1344         if (*what) {
1345             iov->iov_len = strlen (iov->iov_base = " ");
1346             iov++;
1347             iov->iov_len = strlen (iov->iov_base = what);
1348             iov++;
1349             iov->iov_len = strlen (iov->iov_base = ": ");
1350             iov++;
1351         }
1352         if (!(iov->iov_base = strerror (eindex))) {
1353             snprintf (err, sizeof(err), "unknown error %d", eindex);
1354             iov->iov_base = err;
1355         }
1356         iov->iov_len = strlen (iov->iov_base);
1357         iov++;
1358     }
1359     if (tail && *tail) {
1360         iov->iov_len = strlen (iov->iov_base = ", ");
1361         iov++;
1362         iov->iov_len = strlen (iov->iov_base = tail);
1363         iov++;
1364     }
1365     iov->iov_len = strlen (iov->iov_base = "\n");
1366     iov++;
1367
1368     if (dfd != NOTOK)
1369         DisplayVector (iob, iov - iob);
1370     else
1371         writev (fileno (stderr), iob, iov - iob);
1372 }
1373
1374
1375 static void
1376 DisplayVector (struct iovec *iov, int n)
1377 {
1378     register int i;
1379     register char *cp;
1380     char buffer[BUFSIZ];
1381
1382     for (i = 0, cp = NULL; i < n; i++, iov++) {
1383         snprintf (buffer, sizeof(buffer), "%*.*s", iov->iov_len,
1384                 iov->iov_len, iov->iov_base);
1385         cp = add (buffer, cp);
1386     }
1387
1388     DisplayStatus (dfd, cp);
1389     free (cp);
1390     sleep (PAUSE);
1391     RemoveStatus (dfd);
1392 }