dhcpcd: Remove chroot setup and restore things as they were before
[dragonfly.git] / usr.bin / top / display.c
1 /*
2  * Copyright (c) 1984 through 2008, William LeFebvre
3  * All rights reserved.
4  * 
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions are met:
7  * 
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  * 
11  *     * Redistributions in binary form must reproduce the above
12  * copyright notice, this list of conditions and the following disclaimer
13  * in the documentation and/or other materials provided with the
14  * distribution.
15  * 
16  *     * Neither the name of William LeFebvre nor the names of other
17  * contributors may be used to endorse or promote products derived from
18  * this software without specific prior written permission.
19  * 
20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32
33 /*
34  *  Top users/processes display for Unix
35  *  Version 3
36  */
37
38 /*
39  *  This file contains the routines that display information on the screen.
40  *  Each section of the screen has two routines:  one for initially writing
41  *  all constant and dynamic text, and one for only updating the text that
42  *  changes.  The prefix "i_" is used on all the "initial" routines and the
43  *  prefix "u_" is used for all the "updating" routines.
44  *
45  *  ASSUMPTIONS:
46  *        None of the "i_" routines use any of the termcap capabilities.
47  *        In this way, those routines can be safely used on terminals that
48  *        have minimal (or nonexistant) terminal capabilities.
49  *
50  *        The routines should be called in this order:  *_loadave, *_uptime,
51  *        i_timeofday, *_procstates, *_cpustates, *_memory, *_swap,
52  *        *_message, *_header, *_process, *_endscreen.
53  */
54
55 #include "os.h"
56 #include <ctype.h>
57 #include <stdarg.h>
58 #include <sys/types.h>
59 #include <sys/uio.h>
60 #include <unistd.h>
61
62 #include "top.h"
63 #include "machine.h"
64 #include "screen.h"             /* interface to screen package */
65 #include "layout.h"             /* defines for screen position layout */
66 #include "display.h"
67 #include "boolean.h"
68 #include "utils.h"
69
70 #ifdef ENABLE_COLOR
71 #include "color.h"
72 #endif
73
74 #define CURSOR_COST 8
75
76 #define MESSAGE_DISPLAY_TIME 5
77
78 /* imported from screen.c */
79 extern int overstrike;
80
81 static int lmpid = -1;
82 static int display_width = MAX_COLS;
83
84 /* cursor positions of key points on the screen are maintained here */
85 /* layout.h has static definitions, but we may change our minds on some
86    of the positions as we make decisions about what needs to be displayed */
87
88 static int x_lastpid = X_LASTPID;
89 static int y_lastpid = Y_LASTPID;
90 static int x_loadave = X_LOADAVE;
91 static int y_loadave = Y_LOADAVE;
92 static int x_minibar = X_MINIBAR;
93 static int y_minibar = Y_MINIBAR;
94 static int x_uptime = X_UPTIME;
95 static int y_uptime = Y_UPTIME;
96 static int x_procstate = X_PROCSTATE;
97 static int y_procstate = Y_PROCSTATE;
98 static int x_cpustates = X_CPUSTATES;
99 static int y_cpustates = Y_CPUSTATES;
100 static int x_kernel = X_KERNEL;
101 static int y_kernel = Y_KERNEL;
102 static int x_mem = X_MEM;
103 static int y_mem = Y_MEM;
104 static int x_swap = X_SWAP;
105 static int y_swap = Y_SWAP;
106 static int y_message = Y_MESSAGE;
107 static int x_header = X_HEADER;
108 static int y_header = Y_HEADER;
109 static int x_idlecursor = X_IDLECURSOR;
110 static int y_idlecursor = Y_IDLECURSOR;
111 static int y_procs = Y_PROCS;
112
113 /* buffer and colormask that describes the content of the screen */
114 /* these are singly dimensioned arrays -- the row boundaries are
115    determined on the fly.
116 */
117 static char *screenbuf = NULL;
118 static char *colorbuf = NULL;
119 static char scratchbuf[MAX_COLS];
120 static int bufsize = 0;
121
122 /* lineindex tells us where the beginning of a line is in the buffer */
123 #define lineindex(l) ((l)*MAX_COLS)
124
125 /* screen's cursor */
126 static int curr_x, curr_y;
127 static int curr_color;
128
129 /* virtual cursor */
130 static int virt_x, virt_y;
131
132 static char **procstate_names;
133 static char **cpustate_names;
134 static char **memory_names;
135 static char **swap_names;
136 static char **kernel_names;
137
138 static int num_procstates;
139 static int num_cpustates;
140 static int num_memory;
141 static int num_swap;
142 static int num_kernel;
143
144 static int *lprocstates;
145 static int *lcpustates;
146
147 static int *cpustate_columns;
148 static int cpustate_total_length;
149
150 static int header_status = Yes;
151
152 /* pending messages are stored in a circular buffer, where message_first
153    is the next one to display, and message_last is the last one
154    in the buffer.  Counters wrap around at MAX_MESSAGES.  The buffer is
155    empty when message_first == message_last and full when 
156    message_last + 1 == message_first.  The pointer message_current holds
157    the message currently being displayed, or "" if there is none.
158 */
159 #define MAX_MESSAGES 16
160 static char *message_buf[MAX_MESSAGES];
161 static int message_first = 0;
162 static int message_last = 0;
163 static struct timeval message_time = {0, 0};
164 static char *message_current = NULL;
165 static int message_length = 0;
166 static int message_hold = 1;
167 static int message_barrier = No;
168
169 #ifdef ENABLE_COLOR
170 static int load_cidx[3];
171 static int header_cidx;
172 static int *cpustate_cidx;
173 static int *memory_cidx;
174 static int *swap_cidx;
175 static int *kernel_cidx;
176 #else
177 #define memory_cidx NULL
178 #define swap_cidx NULL
179 #define kernel_cidx NULL
180 #endif
181
182
183 /* internal support routines */
184
185 /*
186  * static int string_count(char **pp)
187  *
188  * Pointer "pp" points to an array of string pointers, which is
189  * terminated by a NULL.  Return the number of string pointers in
190  * this array.
191  */
192
193 static int
194 string_count(char **pp)
195
196 {
197     register int cnt = 0;
198
199     if (pp != NULL)
200     {
201         while (*pp++ != NULL)
202         {
203             cnt++;
204         }
205     }
206     return(cnt);
207 }
208
209 void
210 display_clear()
211
212 {
213     dprintf("display_clear\n");
214     screen_clear();
215     memzero(screenbuf, bufsize);
216     memzero(colorbuf, bufsize);
217     curr_x = curr_y = 0;
218 }
219
220 /*
221  * void display_move(int x, int y)
222  *
223  * Efficiently move the cursor to x, y.  This assumes the cursor is
224  * currently located at curr_x, curr_y, and will only use cursor
225  * addressing when it is less expensive than overstriking what's
226  * already on the screen.
227  */
228
229 void
230 display_move(int x, int y)
231
232 {
233     char buff[128];
234     char *p;
235     char *bufp;
236     char *colorp;
237     int cnt = 0;
238     int color = curr_color;
239
240     dprintf("display_move(%d, %d): curr_x %d, curr_y %d\n", x, y, curr_x, curr_y);
241
242     /* are we in a position to do this without cursor addressing? */
243     if (curr_y < y || (curr_y == y && curr_x <= x))
244     {
245         /* start buffering up what it would take to move there by rewriting
246            what's on the screen */
247         cnt = CURSOR_COST;
248         p = buff;
249
250         /* one newline for every line */
251         while (cnt > 0 && curr_y < y)
252         {
253 #ifdef ENABLE_COLOR
254             if (color != 0)
255             {
256                 p = strcpyend(p, color_setstr(0));
257                 color = 0;
258                 cnt -= 5;
259             }
260 #endif
261             *p++ = '\n';
262             curr_y++;
263             curr_x = 0;
264             cnt--;
265         }
266
267         /* write whats in the screenbuf */
268         bufp = &screenbuf[lineindex(curr_y) + curr_x];
269         colorp = &colorbuf[lineindex(curr_y) + curr_x];
270         while (cnt > 0 && curr_x < x)
271         {
272 #ifdef ENABLE_COLOR
273             if (color != *colorp)
274             {
275                 color = *colorp;
276                 p = strcpyend(p, color_setstr(color));
277                 cnt -= 5;
278             }
279 #endif
280             if ((*p = *bufp) == '\0')
281             {
282                 /* somwhere on screen we haven't been before */
283                 *p = *bufp = ' ';
284             }
285             p++;
286             bufp++;
287             colorp++;
288             curr_x++;
289             cnt--;
290         }
291     }
292
293     /* move the cursor */
294     if (cnt > 0)
295     {
296         /* screen rewrite is cheaper */
297         *p = '\0';
298         fputs(buff, stdout);
299         curr_color = color;
300     }
301     else
302     {
303         screen_move(x, y);
304     }
305
306     /* update our position */
307     curr_x = x;
308     curr_y = y;
309 }
310
311 /*
312  * display_write(int x, int y, int newcolor, int eol, char *new)
313  *
314  * Optimized write to the display.  This writes characters to the
315  * screen in a way that optimizes the number of characters actually
316  * sent, by comparing what is being written to what is already on
317  * the screen (according to screenbuf and colorbuf).  The string to
318  * write is "new", the first character of "new" should appear at
319  * screen position x, y.  If x is -1 then "new" begins wherever the
320  * cursor is currently positioned.  The string is written with color
321  * "newcolor".  If "eol" is true then the remainder of the line is
322  * cleared.  It is expected that "new" will have no newlines and no
323  * escape sequences.
324  */
325
326 void
327 display_write(int x, int y, int newcolor, int eol, char *new)
328
329 {
330     char *bufp;
331     char *colorp;
332     int ch;
333     int diff;
334
335     dprintf("display_write(%d, %d, %d, %d, \"%s\")\n",
336             x, y, newcolor, eol, new);
337
338     /* dumb terminal handling here */
339     if (!smart_terminal)
340     {
341         if (x != -1)
342         {
343             /* make sure we are on the right line */
344             while (curr_y < y)
345             {
346                 putchar('\n');
347                 curr_y++;
348                 curr_x = 0;
349             }
350
351             /* make sure we are on the right column */
352             while (curr_x < x)
353             {
354                 putchar(' ');
355                 curr_x++;
356             }
357         }
358
359         /* write */
360         fputs(new, stdout);
361         curr_x += strlen(new);
362
363         return;
364     }
365
366     /* adjust for "here" */
367     if (x == -1)
368     {
369         x = virt_x;
370         y = virt_y;
371     }
372     else
373     {
374         virt_x = x;
375         virt_y = y;
376     }
377
378     /* a pointer to where we start */
379     bufp = &screenbuf[lineindex(y) + x];
380     colorp = &colorbuf[lineindex(y) + x];
381
382     /* main loop */
383     while ((ch = *new++) != '\0')
384     {
385         /* if either character or color are different, an update is needed */
386         /* but only when the screen is wide enough */
387         if (y < (smart_terminal ? screen_length : Largest) && x < display_width &&
388             (ch != *bufp || newcolor != *colorp))
389         {
390             /* check cursor */
391             if (y != curr_y || x != curr_x)
392             {
393                 /* have to move the cursor */
394                 display_move(x, y);
395             }
396
397             /* write character */
398 #ifdef ENABLE_COLOR
399             if (curr_color != newcolor)
400             {
401                 fputs(color_setstr(newcolor), stdout);
402                 curr_color = newcolor;
403             }
404 #endif
405             putchar(ch);
406             *bufp = ch;
407             *colorp = curr_color;
408             curr_x++;
409         }
410
411         /* move */
412         x++;
413         virt_x++;
414         bufp++;
415         colorp++;
416     }
417
418     /* eol handling */
419     if (eol && *bufp != '\0')
420     {
421         dprintf("display_write: clear-eol (bufp = \"%s\")\n", bufp);
422         /* make sure we are color 0 */
423 #ifdef ENABLE_COLOR
424         if (curr_color != 0)
425         {
426             fputs(color_setstr(0), stdout);
427             curr_color = 0;
428         }
429 #endif
430
431         /* make sure we are at the end */
432         if (x != curr_x || y != curr_y)
433         {
434             screen_move(x, y);
435             curr_x = x;
436             curr_y = y;
437         }
438
439         /* clear to end */
440         screen_cleareol(strlen(bufp));
441
442         /* clear out whats left of this line's buffer */
443         diff = display_width - x;
444         if (diff > 0)
445         {
446             memzero(bufp, diff);
447             memzero(colorp, diff);
448         }
449     }
450 }
451
452 void
453 display_fmt(int x, int y, int newcolor, int eol, char *fmt, ...)
454
455 {
456     va_list argp;
457
458     va_start(argp, fmt);
459
460     vsnprintf(scratchbuf, MAX_COLS, fmt, argp);
461     display_write(x, y, newcolor, eol, scratchbuf);
462 }
463
464 void
465 display_cte()
466
467 {
468     int len;
469     int y;
470     char *p;
471     int need_clear = 0;
472
473     /* is there anything out there that needs to be cleared? */
474     p = &screenbuf[lineindex(virt_y) + virt_x];
475     if (*p != '\0')
476     {
477         need_clear = 1;
478     }
479     else
480     {
481         /* this line is clear, what about the rest? */
482         y = virt_y;
483         while (++y < screen_length)
484         {
485             if (screenbuf[lineindex(y)] != '\0')
486             {
487                 need_clear = 1;
488                 break;
489             }
490         }
491     }
492
493     if (need_clear)
494     {
495         dprintf("display_cte: clearing\n");
496
497         /* we will need this later */
498         len = lineindex(virt_y) + virt_x;
499
500         /* move to x and y, then clear to end */
501         display_move(virt_x, virt_y);
502         if (!screen_cte())
503         {
504             /* screen has no clear to end, so do it by hand */
505             p = &screenbuf[len];
506             len = strlen(p);
507             if (len > 0)
508             {
509                 screen_cleareol(len);
510             }
511             while (++virt_y < screen_length)
512             {
513                 display_move(0, virt_y);
514                 p = &screenbuf[lineindex(virt_y)];
515                 len = strlen(p);
516                 if (len > 0)
517                 {
518                     screen_cleareol(len);
519                 }
520             }
521         }
522
523         /* clear the screenbuf */
524         memzero(&screenbuf[len], bufsize - len);
525         memzero(&colorbuf[len], bufsize - len);
526     }
527 }
528
529 static void
530 summary_format(int x, int y, int *numbers, char **names, int *cidx)
531
532 {
533     register int num;
534     register char *thisname;
535     register char *lastname = NULL;
536     register int color;
537
538     /* format each number followed by its string */
539     while ((thisname = *names++) != NULL)
540     {
541         /* get the number to format */
542         num = *numbers++;
543         color = 0;
544
545         /* display only non-zero numbers */
546         if (num != 0)
547         {
548             /* write the previous name */
549             if (lastname != NULL)
550             {
551                 display_write(-1, -1, 0, 0, lastname);
552             }
553
554 #ifdef ENABLE_COLOR
555             if (cidx != NULL)
556             {
557                 /* choose a color */
558                 color = color_test(*cidx++, num);
559             }
560 #endif
561
562             /* write this number if positive */
563             if (num > 0)
564             {
565                 display_write(x, y, color, 0, itoa(num));
566             }
567
568             /* defer writing this name */
569             lastname = thisname;
570
571             /* next iteration will not start at x, y */
572             x = y = -1;
573         }
574     }
575
576     /* if the last string has a separator on the end, it has to be
577        written with care */
578     if (lastname != NULL)
579     {
580         if ((num = strlen(lastname)) > 1 &&
581             lastname[num-2] == ',' && lastname[num-1] == ' ')
582         {
583             display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname);
584         }
585         else
586         {
587             display_write(-1, -1, 0, 1, lastname);
588         }
589     }
590 }
591
592 static void
593 summary_format_memory(int x, int y, long *numbers, char **names, int *cidx)
594
595 {
596     register long num;
597     register int color;
598     register char *thisname;
599     register char *lastname = NULL;
600
601     /* format each number followed by its string */
602     while ((thisname = *names++) != NULL)
603     {
604         /* get the number to format */
605         num = *numbers++;
606         color = 0;
607
608         /* display only non-zero numbers */
609         if (num != 0)
610         {
611             /* write the previous name */
612             if (lastname != NULL)
613             {
614                 display_write(-1, -1, 0, 0, lastname);
615             }
616
617             /* defer writing this name */
618             lastname = thisname;
619
620 #ifdef ENABLE_COLOR
621             /* choose a color */
622             color = color_test(*cidx++, num);
623 #endif
624
625             /* is this number in kilobytes? */
626             if (thisname[0] == 'K')
627             {
628                 display_write(x, y, color, 0, format_k(num));
629                 lastname++;
630             }
631             else
632             {
633                 display_write(x, y, color, 0, itoa((int)num));
634             }
635
636             /* next iteration will not start at x, y */
637             x = y = -1;
638         }
639     }
640
641     /* if the last string has a separator on the end, it has to be
642        written with care */
643     if (lastname != NULL)
644     {
645         if ((num = strlen(lastname)) > 1 &&
646             lastname[num-2] == ',' && lastname[num-1] == ' ')
647         {
648             display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname);
649         }
650         else
651         {
652             display_write(-1, -1, 0, 1, lastname);
653         }
654     }
655 }
656
657 /*
658  * int display_resize()
659  *
660  * Reallocate buffer space needed by the display package to accomodate
661  * a new screen size.  Must be called whenever the screen's size has
662  * changed.  Returns the number of lines available for displaying 
663  * processes or -1 if there was a problem allocating space.
664  */
665
666 int
667 display_resize()
668
669 {
670     register int top_lines;
671     register int newsize;
672
673     /* calculate the current dimensions */
674     /* if operating in "dumb" mode, we only need one line */
675     top_lines = smart_terminal ? screen_length : 1;
676
677     /* we don't want more than MAX_COLS columns, since the machine-dependent
678        modules make static allocations based on MAX_COLS and we don't want
679        to run off the end of their buffers */
680     display_width = screen_width;
681     if (display_width >= MAX_COLS)
682     {
683         display_width = MAX_COLS - 1;
684     }
685
686     /* see how much space we need */
687     newsize = top_lines * (MAX_COLS + 1);
688
689     /* reallocate only if we need more than we already have */
690     if (newsize > bufsize)
691     {
692         /* deallocate any previous buffer that may have been there */
693         if (screenbuf != NULL)
694         {
695             free(screenbuf);
696         }
697         if (colorbuf != NULL)
698         {
699             free(colorbuf);
700         }
701
702         /* allocate space for the screen and color buffers */
703         bufsize = newsize;
704         screenbuf = (char *)calloc(bufsize, sizeof(char));
705         colorbuf = (char *)calloc(bufsize, sizeof(char));
706         if (screenbuf == NULL || colorbuf == NULL)
707         {
708             /* oops! */
709             return(-1);
710         }
711     }
712     else
713     {
714         /* just clear them out */
715         memzero(screenbuf, bufsize);
716         memzero(colorbuf, bufsize);
717     }
718
719     /* adjust total lines on screen to lines available for procs */
720     if (top_lines > y_procs)
721         top_lines -= y_procs;
722     else
723         top_lines = 0;
724
725     /* return number of lines available */
726     /* for dumb terminals, pretend like we can show any amount */
727     return(smart_terminal ? top_lines : Largest);
728 }
729
730 int
731 display_lines()
732
733 {
734     return(smart_terminal ? screen_length : Largest);
735 }
736
737 int
738 display_columns()
739
740 {
741     return(display_width);
742 }
743
744 /*
745  * int display_init(struct statics *statics)
746  *
747  * Initialize the display system based on information in the statics
748  * structure.  Returns the number of lines available for displaying
749  * processes or -1 if there was an error.
750  */
751
752 int
753 display_init(struct statics *statics)
754
755 {
756     register int top_lines;
757     register char **pp;
758     register char *p;
759     register int *ip;
760     register int i;
761
762     /* certain things may influence the screen layout,
763        so look at those first */
764
765         /* More than one core will shif the parts of the display down */
766     if (enable_ncpus != 0 && n_cpus > 1)
767     {
768        /* adjust screen placements */
769        y_mem = y_mem + n_cpus -1;
770        y_swap = y_swap + n_cpus -1;
771        y_message = y_message + n_cpus -1;
772        y_header = y_header + n_cpus -1;
773        y_idlecursor = y_idlecursor + n_cpus -1;
774        y_procs = y_procs + n_cpus -1;
775     }
776
777     /* a kernel line shifts parts of the display down */
778     kernel_names = statics->kernel_names;
779     if ((num_kernel = string_count(kernel_names)) > 0)
780     {
781         /* adjust screen placements */
782         y_mem++;
783         y_swap++;
784         y_message++;
785         y_header++;
786         y_idlecursor++;
787         y_procs++;
788     }
789
790     /* a swap line shifts parts of the display down one */
791     swap_names = statics->swap_names;
792     if ((num_swap = string_count(swap_names)) > 0)
793     {
794         /* adjust screen placements */
795         y_message++;
796         y_header++;
797         y_idlecursor++;
798         y_procs++;
799     }
800     
801     /* call resize to do the dirty work */
802     top_lines = display_resize();
803
804     /*
805      * save pointers and allocate space for names.  Even if top_lines <= -1
806      * the code will dereference many of these pointers and arrays.
807      */
808     procstate_names = statics->procstate_names;
809     num_procstates = string_count(procstate_names);
810
811     lprocstates = (int *)calloc(num_procstates, sizeof(int));
812
813     cpustate_names = statics->cpustate_names;
814     num_cpustates = string_count(cpustate_names);
815     lcpustates = (int *)calloc(num_cpustates, sizeof(int));
816     cpustate_columns = (int *)calloc(num_cpustates, sizeof(int));
817     memory_names = statics->memory_names;
818     num_memory = string_count(memory_names);
819
820     /* calculate starting columns where needed */
821     cpustate_total_length = 0;
822     pp = cpustate_names;
823     ip = cpustate_columns;
824     while (*pp != NULL)
825     {
826         *ip++ = cpustate_total_length;
827         if ((i = strlen(*pp++)) > 0)
828         {
829             cpustate_total_length += i + 8;
830         }
831     }
832
833 #ifdef ENABLE_COLOR
834     /* set up color tags for loadavg */
835     load_cidx[0] = color_tag("1min");
836     load_cidx[1] = color_tag("5min");
837     load_cidx[2] = color_tag("15min");
838
839     /* find header color */
840     header_cidx = color_tag("header");
841
842     /* color tags for cpu states */
843     cpustate_cidx = (int *)malloc(num_cpustates * sizeof(int));
844     i = 0;
845     p = strcpyend(scratchbuf, "cpu.");
846     while (i < num_cpustates)
847     {
848         strcpy(p, cpustate_names[i]);
849         cpustate_cidx[i++] = color_tag(scratchbuf);
850     }
851
852     /* color tags for kernel */
853     if (num_kernel > 0)
854     {
855         kernel_cidx = (int *)malloc(num_kernel * sizeof(int));
856         i = 0;
857         p = strcpyend(scratchbuf, "kernel.");
858         while (i < num_kernel)
859         {
860             strcpy(p, homogenize(kernel_names[i]+1));
861             kernel_cidx[i++] = color_tag(scratchbuf);
862         }
863     }
864
865     /* color tags for memory */
866     memory_cidx = (int *)malloc(num_memory * sizeof(int));
867     i = 0;
868     p = strcpyend(scratchbuf, "memory.");
869     while (i < num_memory)
870     {
871         strcpy(p, homogenize(memory_names[i]+1));
872         memory_cidx[i++] = color_tag(scratchbuf);
873     }
874
875     /* color tags for swap */
876     if (num_swap > 0)
877     {
878         swap_cidx = (int *)malloc(num_swap * sizeof(int));
879         i = 0;
880         p = strcpyend(scratchbuf, "swap.");
881         while (i < num_swap)
882         {
883             strcpy(p, homogenize(swap_names[i]+1));
884             swap_cidx[i++] = color_tag(scratchbuf);
885         }
886     }
887 #endif
888
889     /* return number of lines available (or error) */
890     return(top_lines);
891 }
892
893 static void
894 pr_loadavg(double avg, int i)
895
896 {
897     int color = 0;
898
899 #ifdef ENABLE_COLOR
900     color = color_test(load_cidx[i], (int)(avg * 100));
901 #endif
902     display_fmt(x_loadave + X_LOADAVEWIDTH * i, y_loadave, color, 0,
903                 avg < 10.0 ? " %5.2f" : " %5.1f", avg);
904     display_write(-1, -1, 0, 0, (i < 2 ? "," : ";"));
905 }
906
907 void
908 i_loadave(int mpid, double *avenrun)
909
910 {
911     register int i;
912
913     /* mpid == -1 implies this system doesn't have an _mpid */
914     if (mpid != -1)
915     {
916         display_fmt(0, 0, 0, 0,
917                     "last pid: %5d;  load avg:", mpid);
918         x_loadave = X_LOADAVE;
919     }
920     else
921     {
922         display_write(0, 0, 0, 0, "load averages:");
923         x_loadave = X_LOADAVE - X_LASTPIDWIDTH;
924     }
925     for (i = 0; i < 3; i++)
926     {
927         pr_loadavg(avenrun[i], i);
928     }
929
930     lmpid = mpid;
931 }
932
933 void
934 u_loadave(int mpid, double *avenrun)
935
936 {
937     register int i;
938
939     if (mpid != -1)
940     {
941         /* change screen only when value has really changed */
942         if (mpid != lmpid)
943         {
944             display_fmt(x_lastpid, y_lastpid, 0, 0,
945                         "%5d", mpid);
946             lmpid = mpid;
947         }
948     }
949
950     /* display new load averages */
951     for (i = 0; i < 3; i++)
952     {
953         pr_loadavg(avenrun[i], i);
954     }
955 }
956
957 static char minibar_buffer[64];
958 #define MINIBAR_WIDTH 20
959
960 void
961 i_minibar(int (*formatter)(char *, int))
962 {
963     (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH));
964
965     display_write(x_minibar, y_minibar, 0, 0, minibar_buffer);
966 }
967
968 void
969 u_minibar(int (*formatter)(char *, int))
970 {
971     (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH));
972
973     display_write(x_minibar, y_minibar, 0, 0, minibar_buffer);
974 }
975
976 static int uptime_days;
977 static int uptime_hours;
978 static int uptime_mins;
979 static int uptime_secs;
980
981 void
982 i_uptime(time_t *bt, time_t *tod)
983
984 {
985     time_t uptime;
986
987     if (*bt != -1)
988     {
989         uptime = *tod - *bt;
990         uptime += 30;
991         uptime_days = uptime / 86400;
992         uptime %= 86400;
993         uptime_hours = uptime / 3600;
994         uptime %= 3600;
995         uptime_mins = uptime / 60;
996         uptime_secs = uptime % 60;
997
998         /*
999          *  Display the uptime.
1000          */
1001
1002         display_fmt(x_uptime, y_uptime, 0, 0,
1003                     "  up %d+%02d:%02d:%02d",
1004                     uptime_days, uptime_hours, uptime_mins, uptime_secs);
1005     }
1006 }
1007
1008 void
1009 u_uptime(time_t *bt, time_t *tod)
1010
1011 {
1012     i_uptime(bt, tod);
1013 }
1014
1015
1016 void
1017 i_timeofday(time_t *tod)
1018
1019 {
1020     /*
1021      *  Display the current time.
1022      *  "ctime" always returns a string that looks like this:
1023      *  
1024      *  Sun Sep 16 01:03:52 1973
1025      *  012345678901234567890123
1026      *            1         2
1027      *
1028      *  We want indices 11 thru 18 (length 8).
1029      */
1030
1031     int x;
1032
1033     /* where on the screen do we start? */
1034     x = (smart_terminal ? screen_width : 79) - 8;
1035
1036     /* but don't bump in to uptime */
1037     if (x < x_uptime + 19)
1038     {
1039         x = x_uptime + 19;
1040     }
1041
1042     /* display it */
1043     display_fmt(x, 0, 0, 1, "%-8.8s", &(ctime(tod)[11]));
1044 }
1045
1046 static int ltotal = 0;
1047 static int lthreads = 0;
1048
1049 /*
1050  *  *_procstates(total, brkdn, names) - print the process summary line
1051  */
1052
1053
1054 void
1055 i_procstates(int total, int *brkdn, int threads)
1056
1057 {
1058     /* write current number of processes and remember the value */
1059     display_fmt(0, y_procstate, 0, 0,
1060                 "%d %s: ", total, threads ? "threads" : "processes");
1061     ltotal = total;
1062
1063     /* remember where the summary starts */
1064     x_procstate = virt_x;
1065
1066     if (total > 0)
1067     {
1068         /* format and print the process state summary */
1069         summary_format(-1, -1, brkdn, procstate_names, NULL);
1070
1071         /* save the numbers for next time */
1072         memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
1073         lthreads = threads;
1074     }
1075 }
1076
1077 void
1078 u_procstates(int total, int *brkdn, int threads)
1079
1080 {
1081     /* if threads state has changed, do a full update */
1082     if (lthreads != threads)
1083     {
1084         i_procstates(total, brkdn, threads);
1085         return;
1086     }
1087
1088     /* update number of processes only if it has changed */
1089     if (ltotal != total)
1090     {
1091         display_fmt(0, y_procstate, 0, 0,
1092                     "%d", total);
1093
1094         /* if number of digits differs, rewrite the label */
1095         if (digits(total) != digits(ltotal))
1096         {
1097             display_fmt(-1, -1, 0, 0, " %s: ", threads ? "threads" : "processes");
1098             x_procstate = virt_x;
1099         }
1100
1101         /* save new total */
1102         ltotal = total;
1103     }
1104
1105     /* see if any of the state numbers has changed */
1106     if (total > 0 && memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0)
1107     {
1108         /* format and update the line */
1109         summary_format(x_procstate, y_procstate, brkdn, procstate_names, NULL);
1110         memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
1111     }
1112 }
1113
1114 /*
1115  *  *_cpustates(states, names) - print the cpu state percentages
1116  */
1117
1118 /* cpustates_tag() calculates the correct tag to use to label the line */
1119
1120 char *
1121 cpustates_tag()
1122
1123 {
1124     register char *use;
1125
1126     static char *short_tag = "CPU: ";
1127     static char *long_tag = "CPU states: ";
1128
1129     /* if length + strlen(long_tag) >= screen_width, then we have to
1130        use the shorter tag (we subtract 2 to account for ": ") */
1131     if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width)
1132     {
1133         use = short_tag;
1134     }
1135     else
1136     {
1137         use = long_tag;
1138     }
1139
1140     /* set x_cpustates accordingly then return result */
1141     x_cpustates = strlen(use);
1142     return(use);
1143 }
1144
1145 void
1146 i_cpustates(int *states)
1147
1148 {
1149     int value;
1150     char **names;
1151     char *thisname;
1152     int *colp;
1153     int color = 0;
1154     int cpu;
1155 #ifdef ENABLE_COLOR
1156     int *cidx = cpustate_cidx;
1157 #endif
1158
1159     /* initialize */
1160     names = cpustate_names;
1161     colp = cpustate_columns;
1162
1163     /* print tag */
1164     if (enable_ncpus !=0 && n_cpus > 1) {
1165                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1166                         int y_pos = y_cpustates;
1167                         y_pos = y_pos + cpu;
1168                         colp = cpustate_columns;
1169                         names = cpustate_names;
1170                         display_write(0, y_cpustates+cpu, 0, 0, cpustates_tag());
1171
1172                         /* now walk thru the names and print the line */
1173                         while ((thisname = *names++) != NULL) {
1174                                 if (*thisname != '\0') {
1175                                         /* retrieve the value and remember it */
1176                                         value = *states;
1177
1178 #ifdef ENABLE_COLOR
1179                                         /* determine color number to use */
1180                                         color = color_test(*cidx++, value/10);
1181 #endif
1182                                         /* if percentage is >= 1000, print it as 100% */
1183                                         display_fmt(x_cpustates + *colp, y_pos,
1184                                                 color, 0,
1185                                                 (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"),
1186                                                 ((float)value)/10.,
1187                                                 thisname,
1188                                                 *names != NULL ? ", " : "");
1189
1190                                 }
1191                                 /* increment */
1192                                 colp++;
1193                                 states++;
1194                         }
1195                         /* copy over values into "last" array */
1196                         memcpy(lcpustates, states, num_cpustates * sizeof(int));
1197                 }
1198     } else {
1199     display_write(0, y_cpustates, 0, 0, cpustates_tag());
1200
1201     /* now walk thru the names and print the line */
1202     while ((thisname = *names++) != NULL)
1203     {
1204         if (*thisname != '\0')
1205         {
1206             /* retrieve the value and remember it */
1207             value = *states;
1208
1209 #ifdef ENABLE_COLOR
1210             /* determine color number to use */
1211             color = color_test(*cidx++, value/10);
1212 #endif
1213
1214             /* if percentage is >= 1000, print it as 100% */
1215             display_fmt(x_cpustates + *colp, y_cpustates,
1216                         color, 0,
1217                         (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"),
1218                         ((float)value)/10.,
1219                         thisname,
1220                         *names != NULL ? ", " : "");
1221
1222         }
1223         /* increment */
1224         colp++;
1225         states++;
1226     }
1227
1228     /* copy over values into "last" array */
1229     memcpy(lcpustates, states, num_cpustates * sizeof(int));
1230  }
1231
1232 }
1233
1234 void
1235 u_cpustates(int *states)
1236
1237 {
1238     int value;
1239     char **names = cpustate_names;
1240     char *thisname;
1241     int *lp;
1242     int *colp;
1243     int color = 0;
1244     int cpu;
1245 #ifdef ENABLE_COLOR
1246     int *cidx = cpustate_cidx;
1247 #endif
1248
1249
1250     if (enable_ncpus != 0 && n_cpus > 1 ) {
1251                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1252                         lp = lcpustates;
1253                         int y_pos = y_cpustates;
1254                         y_pos = y_pos + cpu;
1255                         colp = cpustate_columns;
1256                         char **names = cpustate_names;
1257                         /* we could be much more optimal about this */
1258                         while ((thisname = *names++) != NULL) {
1259                                 if (*thisname != '\0') {
1260                                                 /* yes, change it */
1261                                                 /* retrieve value and remember it */
1262                                                 value = *states;
1263
1264 #ifdef ENABLE_COLOR
1265                                                 /* determine color number to use */
1266                                                 color = color_test(*cidx, value/10);
1267 #endif
1268                                                 /* if percentage is >= 1000, print it as 100% */
1269                                                 display_fmt(x_cpustates + *colp, y_pos, color, 0,
1270                                                                         (value >= 1000 ? "%4.0f" : "%4.1f"),
1271                                                                         ((double)value)/10.);
1272
1273 #ifdef ENABLE_COLOR
1274                                         cidx++;
1275 #endif
1276                                 }
1277                                 /* increment and move on */
1278                                 lp++;
1279                                 states++;
1280                                 colp++;
1281                         }
1282                 }
1283     } else {
1284     lp = lcpustates;
1285     colp = cpustate_columns;
1286
1287     /* we could be much more optimal about this */
1288     while ((thisname = *names++) != NULL)
1289     {
1290         if (*thisname != '\0')
1291         {
1292             /* did the value change since last time? */
1293             if (*lp != *states)
1294             {
1295                 /* yes, change it */
1296                 /* retrieve value and remember it */
1297                 value = *states;
1298
1299 #ifdef ENABLE_COLOR
1300                 /* determine color number to use */
1301                 color = color_test(*cidx, value/10);
1302 #endif
1303
1304                 /* if percentage is >= 1000, print it as 100% */
1305                 display_fmt(x_cpustates + *colp, y_cpustates, color, 0,
1306                             (value >= 1000 ? "%4.0f" : "%4.1f"),
1307                             ((double)value)/10.);
1308
1309                 /* remember it for next time */
1310                 *lp = value;
1311             }
1312 #ifdef ENABLE_COLOR
1313             cidx++;
1314 #endif
1315         }
1316
1317         /* increment and move on */
1318         lp++;
1319         states++;
1320         colp++;
1321     }
1322   }
1323 }
1324
1325 void
1326 z_cpustates()
1327
1328 {
1329     register int i = 0;
1330     register char **names = cpustate_names;
1331     register char *thisname;
1332     register int *lp;
1333     int cpu;
1334
1335     /* print tag */
1336         if (enable_ncpus != 0 && n_cpus > 1) {
1337                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1338                         display_write(0, y_cpustates + cpu, 0, 0, cpustates_tag());
1339                         char **names = cpustate_names;
1340                         i = 0;
1341                         while ((thisname = *names++) != NULL) {
1342                                 if (*thisname != '\0') {
1343                                         display_fmt(-1, -1, 0, 0, "%s    %% %s", i++ == 0 ? "" : ", ",
1344                                                 thisname);
1345                                 }
1346                         }
1347                         /* fill the "last" array with all -1s, to insure correct updating */
1348                         lp = lcpustates;
1349                         i = num_cpustates;
1350                         while (--i >= 0) {
1351                                 *lp++ = -1;
1352                         }
1353                 }
1354         } else {
1355     display_write(0, y_cpustates, 0, 0, cpustates_tag());
1356
1357     while ((thisname = *names++) != NULL)
1358     {
1359         if (*thisname != '\0')
1360         {
1361             display_fmt(-1, -1, 0, 0, "%s    %% %s", i++ == 0 ? "" : ", ",
1362                         thisname);
1363         }
1364     }
1365
1366     /* fill the "last" array with all -1s, to insure correct updating */
1367     lp = lcpustates;
1368     i = num_cpustates;
1369     while (--i >= 0)
1370     {
1371         *lp++ = -1;
1372     }
1373   }
1374 }
1375
1376 /*
1377  *  *_kernel(stats) - print "Kernel: " followed by the kernel summary string
1378  *
1379  *  Assumptions:  cursor is on "lastline", the previous line
1380  */
1381
1382 void
1383 i_kernel(int *stats)
1384
1385 {
1386     if (num_kernel > 0)
1387     {
1388         display_write(0, y_kernel, 0, 0, "Kernel: ");
1389
1390         /* format and print the kernel summary */
1391         summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx);
1392     }
1393 }
1394
1395 void
1396 u_kernel(int *stats)
1397
1398 {
1399     if (num_kernel > 0)
1400     {
1401         /* format the new line */
1402         summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx);
1403     }
1404 }
1405
1406 /*
1407  *  *_memory(stats) - print "Memory: " followed by the memory summary string
1408  *
1409  *  Assumptions:  cursor is on "lastline", the previous line
1410  */
1411
1412 void
1413 i_memory(long *stats)
1414
1415 {
1416     display_write(0, y_mem, 0, 0, "Memory: ");
1417
1418     /* format and print the memory summary */
1419     summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx);
1420 }
1421
1422 void
1423 u_memory(long *stats)
1424
1425 {
1426     /* format the new line */
1427     summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx);
1428 }
1429
1430 /*
1431  *  *_swap(stats) - print "Swap: " followed by the swap summary string
1432  *
1433  *  Assumptions:  cursor is on "lastline", the previous line
1434  *
1435  *  These functions only print something when num_swap > 0
1436  */
1437
1438 void
1439 i_swap(long *stats)
1440
1441 {
1442     if (num_swap > 0)
1443     {
1444         /* print the tag */
1445         display_write(0, y_swap, 0, 0, "Swap: ");
1446
1447         /* format and print the swap summary */
1448         summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx);
1449     }
1450 }
1451
1452 void
1453 u_swap(long *stats)
1454
1455 {
1456     if (num_swap > 0)
1457     {
1458         /* format the new line */
1459         summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx);
1460     }
1461 }
1462
1463 /*
1464  *  *_message() - print the next pending message line, or erase the one
1465  *                that is there.
1466  *
1467  *  Note that u_message is (currently) the same as i_message.
1468  *
1469  *  Assumptions:  lastline is consistent
1470  */
1471
1472 /*
1473  *  i_message is funny because it gets its message asynchronously (with
1474  *      respect to screen updates).  Messages are taken out of the
1475  *      circular message_buf and displayed one at a time.
1476  */
1477
1478 void
1479 i_message(struct timeval *now)
1480
1481 {
1482     struct timeval my_now;
1483     int i = 0;
1484
1485     dprintf("i_message(%08x)\n", now);
1486
1487     /* if now is NULL we have to get it ourselves */
1488     if (now == NULL)
1489     {
1490         time_get(&my_now);
1491         now = &my_now;
1492     }
1493
1494     /* now that we have been called, messages no longer need to be held */
1495     message_hold = 0;
1496
1497     dprintf("i_message: now %d, message_time %d\n",
1498             now->tv_sec, message_time.tv_sec);
1499
1500     if (smart_terminal)
1501     {
1502         /* is it time to change the message? */
1503         if (timercmp(now, &message_time, > ))
1504         {
1505             /* yes, free the current message */
1506             dprintf("i_message: timer expired\n");
1507             if (message_current != NULL)
1508             {
1509                 free(message_current);
1510                 message_current = NULL;
1511             }
1512
1513             /* is there a new message to be displayed? */
1514             if (message_first != message_last)
1515             {
1516                 /* move index to next message */
1517                 if (++message_first == MAX_MESSAGES) message_first = 0;
1518
1519                 /* make the next message the current one */
1520                 message_current = message_buf[message_first];
1521
1522                 /* show it */
1523                 dprintf("i_message: showing \"%s\"\n", message_current);
1524                 display_move(0, y_message);
1525                 screen_standout(message_current);
1526                 i = strlen(message_current);
1527
1528                 /* set the expiration timer */
1529                 message_time = *now;
1530                 message_time.tv_sec += MESSAGE_DISPLAY_TIME;
1531
1532                 /* clear the rest of the line */
1533                 screen_cleareol(message_length - i);
1534                 putchar('\r');
1535                 message_length = i;
1536             }
1537             else
1538             {
1539                 /* just clear what was there before, if anything */
1540                 if (message_length > 0)
1541                 {
1542                     display_move(0, y_message);
1543                     screen_cleareol(message_length);
1544                     putchar('\r');
1545                     message_length = 0;
1546                 }
1547             }
1548         }
1549     }
1550 }
1551
1552 void
1553 u_message(struct timeval *now)
1554
1555 {
1556     i_message(now);
1557 }
1558
1559 static int header_length;
1560
1561 /*
1562  *  *_header(text) - print the header for the process area
1563  *
1564  *  Assumptions:  cursor is on the previous line and lastline is consistent
1565  */
1566
1567 void
1568 i_header(char *text)
1569
1570 {
1571     int header_color = 0;
1572
1573 #ifdef ENABLE_COLOR
1574     header_color = color_test(header_cidx, 0);
1575 #endif
1576     header_length = strlen(text);
1577     if (header_status)
1578     {
1579         display_write(x_header, y_header, header_color, 1, text);
1580     }
1581 }
1582
1583 /*ARGSUSED*/
1584 void
1585 u_header(char *text)
1586
1587 {
1588     int header_color = 0;
1589
1590 #ifdef ENABLE_COLOR
1591     header_color = color_test(header_cidx, 0);
1592 #endif
1593     display_write(x_header, y_header, header_color, 1,
1594                   header_status ? text : "");
1595 }
1596
1597 /*
1598  *  *_process(line, thisline) - print one process line
1599  *
1600  *  Assumptions:  lastline is consistent
1601  */
1602
1603 void
1604 i_process(int line, char *thisline)
1605
1606 {
1607     /* truncate the line to conform to our current screen width */
1608     thisline[display_width] = '\0';
1609
1610     /* write the line out */
1611     display_write(0, y_procs + line, 0, 1, thisline);
1612 }
1613
1614 void
1615 u_process(int line, char *new_line)
1616
1617 {
1618     i_process(line, new_line);
1619 }
1620
1621 void
1622 i_endscreen()
1623
1624 {
1625     if (smart_terminal)
1626     {
1627         /* move the cursor to a pleasant place */
1628         display_move(x_idlecursor, y_idlecursor);
1629     }
1630     else
1631     {
1632         /* separate this display from the next with some vertical room */
1633         fputs("\n\n", stdout);
1634     }
1635     fflush(stdout);
1636 }
1637
1638 void
1639 u_endscreen()
1640
1641 {
1642     if (smart_terminal)
1643     {
1644         /* clear-to-end the display */
1645         display_cte();
1646
1647         /* move the cursor to a pleasant place */
1648         display_move(x_idlecursor, y_idlecursor);
1649         fflush(stdout);
1650     }
1651     else
1652     {
1653         /* separate this display from the next with some vertical room */
1654         fputs("\n\n", stdout);
1655     }
1656 }
1657
1658 void
1659 display_header(int t)
1660
1661 {
1662     header_status = t != 0;
1663 }
1664
1665 void
1666 message_mark()
1667
1668 {
1669     message_barrier = Yes;
1670 }
1671
1672 void
1673 message_expire()
1674
1675 {
1676     message_time.tv_sec = 0;
1677     message_time.tv_usec = 0;
1678 }
1679
1680 void
1681 message_flush()
1682
1683 {
1684     message_first = message_last;
1685     message_time.tv_sec = 0;
1686     message_time.tv_usec = 0;
1687 }
1688
1689 /*
1690  * void new_message_v(char *msgfmt, va_list ap)
1691  *
1692  * Display a message in the message area.  This function takes a va_list for
1693  * the arguments.  Safe to call before display_init.  This function only
1694  * queues a message for display, and allowed for multiple messages to be
1695  * queued.  The i_message function drains the queue and actually writes the
1696  * messages on the display.
1697  */
1698
1699
1700 void
1701 new_message_v(char *msgfmt, va_list ap)
1702
1703 {
1704     int i;
1705     int empty;
1706     char msg[MAX_COLS];
1707
1708     /* if message_barrier is active, remove all pending messages */
1709     if (message_barrier)
1710     {
1711         message_flush();
1712         message_barrier = No;
1713     }
1714
1715     /* first, format the message */
1716     (void) vsnprintf(msg, sizeof(msg), msgfmt, ap);
1717
1718     /* where in the buffer will it go? */
1719     i = message_last + 1;
1720     if (i >= MAX_MESSAGES) i = 0;
1721
1722     /* make sure the buffer is not full */
1723     if (i != message_first)
1724     {
1725         /* insert it in to message_buf */
1726         message_buf[i] = strdup(msg);
1727         dprintf("new_message_v: new message inserted in slot %d\n", i);
1728
1729         /* remember if the buffer is empty and set the index */
1730         empty = message_last == message_first;
1731         message_last = i;
1732
1733         /* is message_buf otherwise empty and have we started displaying? */
1734         if (empty && !message_hold)
1735         {
1736             /* we can display the message now */
1737             i_message(NULL);
1738         }
1739     }
1740 }
1741
1742 /*
1743  * void new_message(int type, char *msgfmt, ...)
1744  *
1745  * Display a message in the message area.  It is safe to call this function
1746  * before display_init.  Messages logged before the display is drawn will be
1747  * held and displayed later.
1748  */
1749
1750 void
1751 new_message(char *msgfmt, ...)
1752
1753 {
1754     va_list ap;
1755
1756     va_start(ap, msgfmt);
1757     new_message_v(msgfmt, ap);
1758     va_end(ap);
1759 }
1760
1761 /*
1762  * void message_error(char *msgfmt, ...)
1763  *
1764  * Put an error message in the message area.  It is safe to call this function
1765  * before display_init.  Messages logged before the display is drawn will be
1766  * held and displayed later.
1767  */
1768
1769 void
1770 message_error(char *msgfmt, ...)
1771
1772 {
1773     va_list ap;
1774
1775     va_start(ap, msgfmt);
1776     new_message_v(msgfmt, ap);
1777     fflush(stdout);
1778     va_end(ap);
1779 }
1780
1781 /*
1782  * void message_clear()
1783  *
1784  * Clear message area and flush all pending messages.
1785  */
1786
1787 void
1788 message_clear()
1789
1790 {
1791     /* remove any existing message */
1792     if (message_current != NULL)
1793     {
1794         display_move(0, y_message);
1795         screen_cleareol(message_length);
1796         free(message_current);
1797         message_current = NULL;
1798     }
1799
1800     /* flush all pending messages */
1801     message_flush();
1802 }
1803
1804 /*
1805  * void message_prompt_v(int so, char *msgfmt, va_list ap)
1806  *
1807  * Place a prompt in the message area.  A prompt is different from a 
1808  * message as follows: it is displayed immediately, overwriting any
1809  * message that may already be there, it may be highlighted in standout
1810  * mode (if "so" is true), the cursor is left to rest at the end of the
1811  * prompt.  This call causes all pending messages to be flushed.
1812  */
1813
1814 void
1815 message_prompt_v(int so, char *msgfmt, va_list ap)
1816
1817 {
1818     char msg[MAX_COLS];
1819     int i;
1820
1821     /* clear out the message buffer */
1822     message_flush();
1823
1824     /* format the message */
1825     i = vsnprintf(msg, sizeof(msg), msgfmt, ap);
1826
1827     /* this goes over any existing message */
1828     display_move(0, y_message);
1829
1830     /* clear the entire line */
1831     screen_cleareol(message_length);
1832
1833     /* show the prompt */
1834     if (so)
1835     {
1836         screen_standout(msg);
1837     }
1838     else
1839     {
1840         fputs(msg, stdout);
1841     }
1842
1843     /* make it all visible */
1844     fflush(stdout);
1845
1846     /* even though we dont keep a copy of the prompt, track its length */
1847     message_length = i < MAX_COLS ? i : MAX_COLS;
1848 }
1849
1850 /*
1851  * void message_prompt(char *msgfmt, ...)
1852  *
1853  * Place a prompt in the message area (see message_prompt_v).
1854  */
1855
1856 void
1857 message_prompt(char *msgfmt, ...)
1858
1859 {
1860     va_list ap;
1861
1862     va_start(ap, msgfmt);
1863     message_prompt_v(Yes, msgfmt, ap);
1864     va_end(ap);
1865 }
1866
1867 void
1868 message_prompt_plain(char *msgfmt, ...)
1869
1870 {
1871     va_list ap;
1872
1873     va_start(ap, msgfmt);
1874     message_prompt_v(No, msgfmt, ap);
1875     va_end(ap);
1876 }
1877
1878 /*
1879  * int readline(char *buffer, int size, int numeric)
1880  *
1881  * Read a line of input from the terminal.  The line is placed in
1882  * "buffer" not to exceed "size".  If "numeric" is true then the input
1883  * can only consist of digits.  This routine handles all character
1884  * editing while keeping the terminal in cbreak mode.  If "numeric"
1885  * is true then the number entered is returned.  Otherwise the number
1886  * of character read in to "buffer" is returned.
1887  */
1888
1889 int
1890 readline(char *buffer, int size, int numeric)
1891
1892 {
1893     register char *ptr = buffer;
1894     register char ch;
1895     register char cnt = 0;
1896
1897     /* allow room for null terminator */
1898     size -= 1;
1899
1900     /* read loop */
1901     while ((fflush(stdout), read(0, ptr, 1) > 0))
1902     {
1903         /* newline or return means we are done */
1904         if ((ch = *ptr) == '\n' || ch == '\r')
1905         {
1906             break;
1907         }
1908
1909         /* handle special editing characters */
1910         if (ch == ch_kill)
1911         {
1912             /* return null string */
1913             *buffer = '\0';
1914             putchar('\r');
1915             return(-1);
1916         }
1917         else if (ch == ch_werase)
1918         {
1919             /* erase previous word */
1920             if (cnt <= 0)
1921             {
1922                 /* none to erase! */
1923                 putchar('\7');
1924             }
1925             else
1926             {
1927                 /*
1928                  * First: remove all spaces till the first-non-space 
1929                  * Second: remove all non-spaces till the first-space
1930                  */
1931                 while(cnt > 0 && ptr[-1] == ' ')
1932                 {
1933                     fputs("\b \b", stdout);
1934                     ptr--;
1935                     cnt--;
1936                 }
1937                 while(cnt > 0 && ptr[-1] != ' ')
1938                 {
1939                     fputs("\b \b", stdout);
1940                     ptr--;
1941                     cnt--;
1942                 }
1943             }
1944         }
1945         else if (ch == ch_erase)
1946         {
1947             /* erase previous character */
1948             if (cnt <= 0)
1949             {
1950                 /* none to erase! */
1951                 putchar('\7');
1952             }
1953             else
1954             {
1955                 fputs("\b \b", stdout);
1956                 ptr--;
1957                 cnt--;
1958             }
1959         }
1960         /* check for character validity and buffer overflow */
1961         else if (cnt == size || (numeric && !isdigit((int)ch)) ||
1962                 !isprint((int)ch))
1963         {
1964             /* not legal */
1965             putchar('\7');
1966         }
1967         else
1968         {
1969             /* echo it and store it in the buffer */
1970             putchar(ch);
1971             ptr++;
1972             cnt++;
1973         }
1974     }
1975
1976     /* all done -- null terminate the string */
1977     *ptr = '\0';
1978
1979     /* add response length to message_length */
1980     message_length += cnt;
1981
1982     /* return either inputted number or string length */
1983     putchar('\r');
1984     return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
1985 }
1986
1987 void
1988 display_pagerstart()
1989
1990 {
1991     display_clear();
1992 }
1993
1994 void
1995 display_pagerend()
1996
1997 {
1998     char ch;
1999
2000     screen_standout("Hit any key to continue: ");
2001     fflush(stdout);
2002     (void) read(0, &ch, 1);
2003 }
2004
2005 void
2006 display_pager(char *fmt, ...)
2007
2008 {
2009     va_list ap;
2010
2011     int ch;
2012     char readch;
2013     char buffer[MAX_COLS];
2014     char *data;
2015
2016     /* format into buffer */
2017     va_start(ap, fmt);
2018     (void) vsnprintf(buffer, MAX_COLS, fmt, ap);
2019     va_end(ap);
2020     data = buffer;
2021
2022     while ((ch = *data++) != '\0')
2023     {
2024         putchar(ch);
2025         if (ch == '\n')
2026         {
2027             if (++curr_y >= screen_length - 1)
2028             {
2029                 screen_standout("...More...");
2030                 fflush(stdout);
2031                 (void) read(0, &readch, 1);
2032                 putchar('\r');
2033                 switch(readch)
2034                 {
2035                 case '\r':
2036                 case '\n':
2037                     curr_y--;
2038                     break;
2039
2040                 case 'q':
2041                     return;
2042
2043                 default:
2044                     curr_y = 0;
2045                 }
2046             }
2047         }
2048     }
2049 }