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