top - Import DragonFly specific patches for 3.8beta1.
[dragonfly.git] / contrib / 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 (x < display_width && (ch != *bufp || newcolor != *colorp))
388         {
389             /* check cursor */
390             if (y != curr_y || x != curr_x)
391             {
392                 /* have to move the cursor */
393                 display_move(x, y);
394             }
395
396             /* write character */
397 #ifdef ENABLE_COLOR
398             if (curr_color != newcolor)
399             {
400                 fputs(color_setstr(newcolor), stdout);
401                 curr_color = newcolor;
402             }
403 #endif
404             putchar(ch);
405             *bufp = ch;
406             *colorp = curr_color;
407             curr_x++;
408         }
409
410         /* move */
411         x++;
412         virt_x++;
413         bufp++;
414         colorp++;
415     }
416
417     /* eol handling */
418     if (eol && *bufp != '\0')
419     {
420         dprintf("display_write: clear-eol (bufp = \"%s\")\n", bufp);
421         /* make sure we are color 0 */
422 #ifdef ENABLE_COLOR
423         if (curr_color != 0)
424         {
425             fputs(color_setstr(0), stdout);
426             curr_color = 0;
427         }
428 #endif
429
430         /* make sure we are at the end */
431         if (x != curr_x || y != curr_y)
432         {
433             screen_move(x, y);
434             curr_x = x;
435             curr_y = y;
436         }
437
438         /* clear to end */
439         screen_cleareol(strlen(bufp));
440
441         /* clear out whats left of this line's buffer */
442         diff = display_width - x;
443         if (diff > 0)
444         {
445             memzero(bufp, diff);
446             memzero(colorp, diff);
447         }
448     }
449 }
450
451 void
452 display_fmt(int x, int y, int newcolor, int eol, char *fmt, ...)
453
454 {
455     va_list argp;
456
457     va_start(argp, fmt);
458
459     vsnprintf(scratchbuf, MAX_COLS, fmt, argp);
460     display_write(x, y, newcolor, eol, scratchbuf);
461 }
462
463 void
464 display_cte()
465
466 {
467     int len;
468     int y;
469     char *p;
470     int need_clear = 0;
471
472     /* is there anything out there that needs to be cleared? */
473     p = &screenbuf[lineindex(virt_y) + virt_x];
474     if (*p != '\0')
475     {
476         need_clear = 1;
477     }
478     else
479     {
480         /* this line is clear, what about the rest? */
481         y = virt_y;
482         while (++y < screen_length)
483         {
484             if (screenbuf[lineindex(y)] != '\0')
485             {
486                 need_clear = 1;
487                 break;
488             }
489         }
490     }
491
492     if (need_clear)
493     {
494         dprintf("display_cte: clearing\n");
495
496         /* we will need this later */
497         len = lineindex(virt_y) + virt_x;
498
499         /* move to x and y, then clear to end */
500         display_move(virt_x, virt_y);
501         if (!screen_cte())
502         {
503             /* screen has no clear to end, so do it by hand */
504             p = &screenbuf[len];
505             len = strlen(p);
506             if (len > 0)
507             {
508                 screen_cleareol(len);
509             }
510             while (++virt_y < screen_length)
511             {
512                 display_move(0, virt_y);
513                 p = &screenbuf[lineindex(virt_y)];
514                 len = strlen(p);
515                 if (len > 0)
516                 {
517                     screen_cleareol(len);
518                 }
519             }
520         }
521
522         /* clear the screenbuf */
523         memzero(&screenbuf[len], bufsize - len);
524         memzero(&colorbuf[len], bufsize - len);
525     }
526 }
527
528 static void
529 summary_format(int x, int y, int *numbers, char **names, int *cidx)
530
531 {
532     register int num;
533     register char *thisname;
534     register char *lastname = NULL;
535     register int color;
536
537     /* format each number followed by its string */
538     while ((thisname = *names++) != NULL)
539     {
540         /* get the number to format */
541         num = *numbers++;
542         color = 0;
543
544         /* display only non-zero numbers */
545         if (num != 0)
546         {
547             /* write the previous name */
548             if (lastname != NULL)
549             {
550                 display_write(-1, -1, 0, 0, lastname);
551             }
552
553 #ifdef ENABLE_COLOR
554             if (cidx != NULL)
555             {
556                 /* choose a color */
557                 color = color_test(*cidx++, num);
558             }
559 #endif
560
561             /* write this number if positive */
562             if (num > 0)
563             {
564                 display_write(x, y, color, 0, itoa(num));
565             }
566
567             /* defer writing this name */
568             lastname = thisname;
569
570             /* next iteration will not start at x, y */
571             x = y = -1;
572         }
573     }
574
575     /* if the last string has a separator on the end, it has to be
576        written with care */
577     if (lastname != NULL)
578     {
579         if ((num = strlen(lastname)) > 1 &&
580             lastname[num-2] == ',' && lastname[num-1] == ' ')
581         {
582             display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname);
583         }
584         else
585         {
586             display_write(-1, -1, 0, 1, lastname);
587         }
588     }
589 }
590
591 static void
592 summary_format_memory(int x, int y, long *numbers, char **names, int *cidx)
593
594 {
595     register long num;
596     register int color;
597     register char *thisname;
598     register char *lastname = NULL;
599
600     /* format each number followed by its string */
601     while ((thisname = *names++) != NULL)
602     {
603         /* get the number to format */
604         num = *numbers++;
605         color = 0;
606
607         /* display only non-zero numbers */
608         if (num != 0)
609         {
610             /* write the previous name */
611             if (lastname != NULL)
612             {
613                 display_write(-1, -1, 0, 0, lastname);
614             }
615
616             /* defer writing this name */
617             lastname = thisname;
618
619 #ifdef ENABLE_COLOR
620             /* choose a color */
621             color = color_test(*cidx++, num);
622 #endif
623
624             /* is this number in kilobytes? */
625             if (thisname[0] == 'K')
626             {
627                 display_write(x, y, color, 0, format_k(num));
628                 lastname++;
629             }
630             else
631             {
632                 display_write(x, y, color, 0, itoa((int)num));
633             }
634
635             /* next iteration will not start at x, y */
636             x = y = -1;
637         }
638     }
639
640     /* if the last string has a separator on the end, it has to be
641        written with care */
642     if (lastname != NULL)
643     {
644         if ((num = strlen(lastname)) > 1 &&
645             lastname[num-2] == ',' && lastname[num-1] == ' ')
646         {
647             display_fmt(-1, -1, 0, 1, "%.*s", num-2, lastname);
648         }
649         else
650         {
651             display_write(-1, -1, 0, 1, lastname);
652         }
653     }
654 }
655
656 /*
657  * int display_resize()
658  *
659  * Reallocate buffer space needed by the display package to accomodate
660  * a new screen size.  Must be called whenever the screen's size has
661  * changed.  Returns the number of lines available for displaying 
662  * processes or -1 if there was a problem allocating space.
663  */
664
665 int
666 display_resize()
667
668 {
669     register int top_lines;
670     register int newsize;
671
672     /* calculate the current dimensions */
673     /* if operating in "dumb" mode, we only need one line */
674     top_lines = smart_terminal ? screen_length : 1;
675
676     /* we don't want more than MAX_COLS columns, since the machine-dependent
677        modules make static allocations based on MAX_COLS and we don't want
678        to run off the end of their buffers */
679     display_width = screen_width;
680     if (display_width >= MAX_COLS)
681     {
682         display_width = MAX_COLS - 1;
683     }
684
685     /* see how much space we need */
686     newsize = top_lines * (MAX_COLS + 1);
687
688     /* reallocate only if we need more than we already have */
689     if (newsize > bufsize)
690     {
691         /* deallocate any previous buffer that may have been there */
692         if (screenbuf != NULL)
693         {
694             free(screenbuf);
695         }
696         if (colorbuf != NULL)
697         {
698             free(colorbuf);
699         }
700
701         /* allocate space for the screen and color buffers */
702         bufsize = newsize;
703         screenbuf = (char *)calloc(bufsize, sizeof(char));
704         colorbuf = (char *)calloc(bufsize, sizeof(char));
705         if (screenbuf == NULL || colorbuf == NULL)
706         {
707             /* oops! */
708             return(-1);
709         }
710     }
711     else
712     {
713         /* just clear them out */
714         memzero(screenbuf, bufsize);
715         memzero(colorbuf, bufsize);
716     }
717
718     /* adjust total lines on screen to lines available for procs */
719     top_lines -= y_procs;
720
721     /* return number of lines available */
722     /* for dumb terminals, pretend like we can show any amount */
723     return(smart_terminal ? top_lines : Largest);
724 }
725
726 int
727 display_lines()
728
729 {
730     return(smart_terminal ? screen_length : Largest);
731 }
732
733 int
734 display_columns()
735
736 {
737     return(display_width);
738 }
739
740 /*
741  * int display_init(struct statics *statics)
742  *
743  * Initialize the display system based on information in the statics
744  * structure.  Returns the number of lines available for displaying
745  * processes or -1 if there was an error.
746  */
747
748 int
749 display_init(struct statics *statics)
750
751 {
752     register int top_lines;
753     register char **pp;
754     register char *p;
755     register int *ip;
756     register int i;
757
758     /* certain things may influence the screen layout,
759        so look at those first */
760
761         /* More than one core will shif the parts of the display down */
762     if (enable_ncpus != 0 && n_cpus > 1)
763     {
764        /* adjust screen placements */
765        y_mem = y_mem + n_cpus -1;
766        y_swap = y_swap + n_cpus -1;
767        y_message = y_message + n_cpus -1;
768        y_header = y_header + n_cpus -1; 
769        y_idlecursor = y_idlecursor + n_cpus -1;
770        y_procs = y_procs + n_cpus -1;
771     }
772         
773     /* a kernel line shifts parts of the display down */
774     kernel_names = statics->kernel_names;
775     if ((num_kernel = string_count(kernel_names)) > 0)
776     {
777         /* adjust screen placements */
778         y_mem++;
779         y_swap++;
780         y_message++;
781         y_header++;
782         y_idlecursor++;
783         y_procs++;
784     }
785
786     /* a swap line shifts parts of the display down one */
787     swap_names = statics->swap_names;
788     if ((num_swap = string_count(swap_names)) > 0)
789     {
790         /* adjust screen placements */
791         y_message++;
792         y_header++;
793         y_idlecursor++;
794         y_procs++;
795     }
796     
797     /* call resize to do the dirty work */
798     top_lines = display_resize();
799
800     /* only do the rest if we need to */
801     if (top_lines > -1)
802     {
803         /* save pointers and allocate space for names */
804         procstate_names = statics->procstate_names;
805         num_procstates = string_count(procstate_names);
806         lprocstates = (int *)calloc(num_procstates, sizeof(int));
807
808         cpustate_names = statics->cpustate_names;
809         num_cpustates = string_count(cpustate_names);
810         lcpustates = (int *)calloc(num_cpustates, sizeof(int));
811         cpustate_columns = (int *)calloc(num_cpustates, sizeof(int));
812         memory_names = statics->memory_names;
813         num_memory = string_count(memory_names);
814
815         /* calculate starting columns where needed */
816         cpustate_total_length = 0;
817         pp = cpustate_names;
818         ip = cpustate_columns;
819         while (*pp != NULL)
820         {
821             *ip++ = cpustate_total_length;
822             if ((i = strlen(*pp++)) > 0)
823             {
824                 cpustate_total_length += i + 8;
825             }
826         }
827     }
828
829 #ifdef ENABLE_COLOR
830     /* set up color tags for loadavg */
831     load_cidx[0] = color_tag("1min");
832     load_cidx[1] = color_tag("5min");
833     load_cidx[2] = color_tag("15min");
834
835     /* find header color */
836     header_cidx = color_tag("header");
837
838     /* color tags for cpu states */
839     cpustate_cidx = (int *)malloc(num_cpustates * sizeof(int));
840     i = 0;
841     p = strcpyend(scratchbuf, "cpu.");
842     while (i < num_cpustates)
843     {
844         strcpy(p, cpustate_names[i]);
845         cpustate_cidx[i++] = color_tag(scratchbuf);
846     }
847
848     /* color tags for kernel */
849     if (num_kernel > 0)
850     {
851         kernel_cidx = (int *)malloc(num_kernel * sizeof(int));
852         i = 0;
853         p = strcpyend(scratchbuf, "kernel.");
854         while (i < num_kernel)
855         {
856             strcpy(p, homogenize(kernel_names[i]+1));
857             kernel_cidx[i++] = color_tag(scratchbuf);
858         }
859     }
860
861     /* color tags for memory */
862     memory_cidx = (int *)malloc(num_memory * sizeof(int));
863     i = 0;
864     p = strcpyend(scratchbuf, "memory.");
865     while (i < num_memory)
866     {
867         strcpy(p, homogenize(memory_names[i]+1));
868         memory_cidx[i++] = color_tag(scratchbuf);
869     }
870
871     /* color tags for swap */
872     if (num_swap > 0)
873     {
874         swap_cidx = (int *)malloc(num_swap * sizeof(int));
875         i = 0;
876         p = strcpyend(scratchbuf, "swap.");
877         while (i < num_swap)
878         {
879             strcpy(p, homogenize(swap_names[i]+1));
880             swap_cidx[i++] = color_tag(scratchbuf);
881         }
882     }
883 #endif
884
885     /* return number of lines available (or error) */
886     return(top_lines);
887 }
888
889 static void
890 pr_loadavg(double avg, int i)
891
892 {
893     int color = 0;
894
895 #ifdef ENABLE_COLOR
896     color = color_test(load_cidx[i], (int)(avg * 100));
897 #endif
898     display_fmt(x_loadave + X_LOADAVEWIDTH * i, y_loadave, color, 0,
899                 avg < 10.0 ? " %5.2f" : " %5.1f", avg);
900     display_write(-1, -1, 0, 0, (i < 2 ? "," : ";"));
901 }
902
903 void
904 i_loadave(int mpid, double *avenrun)
905
906 {
907     register int i;
908
909     /* mpid == -1 implies this system doesn't have an _mpid */
910     if (mpid != -1)
911     {
912         display_fmt(0, 0, 0, 0,
913                     "last pid: %5d;  load avg:", mpid);
914         x_loadave = X_LOADAVE;
915     }
916     else
917     {
918         display_write(0, 0, 0, 0, "load averages:");
919         x_loadave = X_LOADAVE - X_LASTPIDWIDTH;
920     }
921     for (i = 0; i < 3; i++)
922     {
923         pr_loadavg(avenrun[i], i);
924     }
925
926     lmpid = mpid;
927 }
928
929 void
930 u_loadave(int mpid, double *avenrun)
931
932 {
933     register int i;
934
935     if (mpid != -1)
936     {
937         /* change screen only when value has really changed */
938         if (mpid != lmpid)
939         {
940             display_fmt(x_lastpid, y_lastpid, 0, 0,
941                         "%5d", mpid);
942             lmpid = mpid;
943         }
944     }
945
946     /* display new load averages */
947     for (i = 0; i < 3; i++)
948     {
949         pr_loadavg(avenrun[i], i);
950     }
951 }
952
953 static char minibar_buffer[64];
954 #define MINIBAR_WIDTH 20
955
956 void
957 i_minibar(int (*formatter)(char *, int))
958 {
959     (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH));
960
961     display_write(x_minibar, y_minibar, 0, 0, minibar_buffer);
962 }
963
964 void
965 u_minibar(int (*formatter)(char *, int))
966 {
967     (void)((*formatter)(minibar_buffer, MINIBAR_WIDTH));
968
969     display_write(x_minibar, y_minibar, 0, 0, minibar_buffer);
970 }
971
972 static int uptime_days;
973 static int uptime_hours;
974 static int uptime_mins;
975 static int uptime_secs;
976
977 void
978 i_uptime(time_t *bt, time_t *tod)
979
980 {
981     time_t uptime;
982
983     if (*bt != -1)
984     {
985         uptime = *tod - *bt;
986         uptime += 30;
987         uptime_days = uptime / 86400;
988         uptime %= 86400;
989         uptime_hours = uptime / 3600;
990         uptime %= 3600;
991         uptime_mins = uptime / 60;
992         uptime_secs = uptime % 60;
993
994         /*
995          *  Display the uptime.
996          */
997
998         display_fmt(x_uptime, y_uptime, 0, 0,
999                     "  up %d+%02d:%02d:%02d",
1000                     uptime_days, uptime_hours, uptime_mins, uptime_secs);
1001     }
1002 }
1003
1004 void
1005 u_uptime(time_t *bt, time_t *tod)
1006
1007 {
1008     i_uptime(bt, tod);
1009 }
1010
1011
1012 void
1013 i_timeofday(time_t *tod)
1014
1015 {
1016     /*
1017      *  Display the current time.
1018      *  "ctime" always returns a string that looks like this:
1019      *  
1020      *  Sun Sep 16 01:03:52 1973
1021      *  012345678901234567890123
1022      *            1         2
1023      *
1024      *  We want indices 11 thru 18 (length 8).
1025      */
1026
1027     int x;
1028
1029     /* where on the screen do we start? */
1030     x = (smart_terminal ? screen_width : 79) - 8;
1031
1032     /* but don't bump in to uptime */
1033     if (x < x_uptime + 19)
1034     {
1035         x = x_uptime + 19;
1036     }
1037
1038     /* display it */
1039     display_fmt(x, 0, 0, 1, "%-8.8s", &(ctime(tod)[11]));
1040 }
1041
1042 static int ltotal = 0;
1043 static int lthreads = 0;
1044
1045 /*
1046  *  *_procstates(total, brkdn, names) - print the process summary line
1047  */
1048
1049
1050 void
1051 i_procstates(int total, int *brkdn, int threads)
1052
1053 {
1054     /* write current number of processes and remember the value */
1055     display_fmt(0, y_procstate, 0, 0,
1056                 "%d %s: ", total, threads ? "threads" : "processes");
1057     ltotal = total;
1058
1059     /* remember where the summary starts */
1060     x_procstate = virt_x;
1061
1062     if (total > 0)
1063     {
1064         /* format and print the process state summary */
1065         summary_format(-1, -1, brkdn, procstate_names, NULL);
1066
1067         /* save the numbers for next time */
1068         memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
1069         lthreads = threads;
1070     }
1071 }
1072
1073 void
1074 u_procstates(int total, int *brkdn, int threads)
1075
1076 {
1077     /* if threads state has changed, do a full update */
1078     if (lthreads != threads)
1079     {
1080         i_procstates(total, brkdn, threads);
1081         return;
1082     }
1083
1084     /* update number of processes only if it has changed */
1085     if (ltotal != total)
1086     {
1087         display_fmt(0, y_procstate, 0, 0,
1088                     "%d", total);
1089
1090         /* if number of digits differs, rewrite the label */
1091         if (digits(total) != digits(ltotal))
1092         {
1093             display_fmt(-1, -1, 0, 0, " %s: ", threads ? "threads" : "processes");
1094             x_procstate = virt_x;
1095         }
1096
1097         /* save new total */
1098         ltotal = total;
1099     }
1100
1101     /* see if any of the state numbers has changed */
1102     if (total > 0 && memcmp(lprocstates, brkdn, num_procstates * sizeof(int)) != 0)
1103     {
1104         /* format and update the line */
1105         summary_format(x_procstate, y_procstate, brkdn, procstate_names, NULL);
1106         memcpy(lprocstates, brkdn, num_procstates * sizeof(int));
1107     }
1108 }
1109
1110 /*
1111  *  *_cpustates(states, names) - print the cpu state percentages
1112  */
1113
1114 /* cpustates_tag() calculates the correct tag to use to label the line */
1115
1116 char *
1117 cpustates_tag()
1118
1119 {
1120     register char *use;
1121
1122     static char *short_tag = "CPU: ";
1123     static char *long_tag = "CPU states: ";
1124
1125     /* if length + strlen(long_tag) >= screen_width, then we have to
1126        use the shorter tag (we subtract 2 to account for ": ") */
1127     if (cpustate_total_length + (int)strlen(long_tag) - 2 >= screen_width)
1128     {
1129         use = short_tag;
1130     }
1131     else
1132     {
1133         use = long_tag;
1134     }
1135
1136     /* set x_cpustates accordingly then return result */
1137     x_cpustates = strlen(use);
1138     return(use);
1139 }
1140
1141 void
1142 i_cpustates(int *states)
1143
1144 {
1145     int value;
1146     char **names;
1147     char *thisname;
1148     int *colp;
1149     int color = 0;
1150     int cpu;
1151 #ifdef ENABLE_COLOR
1152     int *cidx = cpustate_cidx;
1153 #endif
1154
1155     /* initialize */
1156     names = cpustate_names;
1157     colp = cpustate_columns;
1158
1159     /* print tag */
1160     if (enable_ncpus !=0 && n_cpus > 1) {
1161                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1162                         int y_pos = y_cpustates;
1163                         y_pos = y_pos + cpu;
1164                         colp = cpustate_columns;        
1165                         names = cpustate_names;
1166                         display_write(0, y_cpustates+cpu, 0, 0, cpustates_tag());
1167
1168                         /* now walk thru the names and print the line */
1169                         while ((thisname = *names++) != NULL) {
1170                                 if (*thisname != '\0') {
1171                                         /* retrieve the value and remember it */
1172                                         value = *states;
1173
1174 #ifdef ENABLE_COLOR
1175                                         /* determine color number to use */
1176                                         color = color_test(*cidx++, value/10);
1177 #endif
1178                                         /* if percentage is >= 1000, print it as 100% */
1179                                         display_fmt(x_cpustates + *colp, y_pos,
1180                                                 color, 0,
1181                                                 (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"),
1182                                                 ((float)value)/10.,
1183                                                 thisname,
1184                                                 *names != NULL ? ", " : "");
1185
1186                                 }
1187                                 /* increment */
1188                                 colp++;
1189                                 states++;
1190                         }
1191                         /* copy over values into "last" array */
1192                         memcpy(lcpustates, states, num_cpustates * sizeof(int));
1193                 }
1194     } else {
1195     display_write(0, y_cpustates, 0, 0, cpustates_tag());
1196
1197     /* now walk thru the names and print the line */
1198     while ((thisname = *names++) != NULL)
1199     {
1200         if (*thisname != '\0')
1201         {
1202             /* retrieve the value and remember it */
1203             value = *states;
1204
1205 #ifdef ENABLE_COLOR
1206             /* determine color number to use */
1207             color = color_test(*cidx++, value/10);
1208 #endif
1209
1210             /* if percentage is >= 1000, print it as 100% */
1211             display_fmt(x_cpustates + *colp, y_cpustates,
1212                         color, 0,
1213                         (value >= 1000 ? "%4.0f%% %s%s" : "%4.1f%% %s%s"),
1214                         ((float)value)/10.,
1215                         thisname,
1216                         *names != NULL ? ", " : "");
1217
1218         }
1219         /* increment */
1220         colp++;
1221         states++;
1222     }
1223
1224     /* copy over values into "last" array */
1225     memcpy(lcpustates, states, num_cpustates * sizeof(int));
1226  }
1227
1228 }
1229
1230 void
1231 u_cpustates(int *states)
1232
1233 {
1234     int value;
1235     char **names = cpustate_names;
1236     char *thisname;
1237     int *lp;
1238     int *colp;
1239     int color = 0;
1240     int cpu;
1241 #ifdef ENABLE_COLOR
1242     int *cidx = cpustate_cidx;
1243 #endif
1244
1245
1246     if (enable_ncpus != 0 && n_cpus > 1 ) {
1247                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1248                         lp = lcpustates;
1249                         int y_pos = y_cpustates;
1250                         y_pos = y_pos + cpu;
1251                         colp = cpustate_columns;
1252                         char **names = cpustate_names;
1253                         /* we could be much more optimal about this */
1254                         while ((thisname = *names++) != NULL) {
1255                                 if (*thisname != '\0') {
1256                                                 /* yes, change it */
1257                                                 /* retrieve value and remember it */
1258                                                 value = *states;
1259                                                 
1260 #ifdef ENABLE_COLOR
1261                                                 /* determine color number to use */
1262                                                 color = color_test(*cidx, value/10);
1263 #endif
1264                                                 /* if percentage is >= 1000, print it as 100% */
1265                                                 display_fmt(x_cpustates + *colp, y_pos, color, 0,
1266                                                                         (value >= 1000 ? "%4.0f" : "%4.1f"),
1267                                                                         ((double)value)/10.);
1268                                                 
1269 #ifdef ENABLE_COLOR
1270                                         cidx++;
1271 #endif
1272                                 }
1273                                 /* increment and move on */
1274                                 lp++;
1275                                 states++;
1276                                 colp++;
1277                         }
1278                 }
1279     } else {
1280     lp = lcpustates;
1281     colp = cpustate_columns;
1282
1283     /* we could be much more optimal about this */
1284     while ((thisname = *names++) != NULL)
1285     {
1286         if (*thisname != '\0')
1287         {
1288             /* did the value change since last time? */
1289             if (*lp != *states)
1290             {
1291                 /* yes, change it */
1292                 /* retrieve value and remember it */
1293                 value = *states;
1294
1295 #ifdef ENABLE_COLOR
1296                 /* determine color number to use */
1297                 color = color_test(*cidx, value/10);
1298 #endif
1299
1300                 /* if percentage is >= 1000, print it as 100% */
1301                 display_fmt(x_cpustates + *colp, y_cpustates, color, 0,
1302                             (value >= 1000 ? "%4.0f" : "%4.1f"),
1303                             ((double)value)/10.);
1304
1305                 /* remember it for next time */
1306                 *lp = value;
1307             }
1308 #ifdef ENABLE_COLOR
1309             cidx++;
1310 #endif
1311         }
1312
1313         /* increment and move on */
1314         lp++;
1315         states++;
1316         colp++;
1317     }
1318   }
1319 }
1320
1321 void
1322 z_cpustates()
1323
1324 {
1325     register int i = 0;
1326     register char **names = cpustate_names;
1327     register char *thisname;
1328     register int *lp;
1329     int cpu;
1330
1331     /* print tag */
1332         if (enable_ncpus != 0 && n_cpus > 1) {
1333                 for (cpu = 0; cpu < n_cpus; ++cpu) {
1334                         display_write(0, y_cpustates + cpu, 0, 0, cpustates_tag());
1335                         char **names = cpustate_names;
1336                         i = 0;
1337                         while ((thisname = *names++) != NULL) {
1338                                 if (*thisname != '\0') {
1339                                         display_fmt(-1, -1, 0, 0, "%s    %% %s", i++ == 0 ? "" : ", ",
1340                                                 thisname);
1341                                 }
1342                         }
1343                         /* fill the "last" array with all -1s, to insure correct updating */
1344                         lp = lcpustates;
1345                         i = num_cpustates;
1346                         while (--i >= 0) {
1347                                 *lp++ = -1;
1348                         }
1349                 }
1350         } else {
1351     display_write(0, y_cpustates, 0, 0, cpustates_tag());
1352
1353     while ((thisname = *names++) != NULL)
1354     {
1355         if (*thisname != '\0')
1356         {
1357             display_fmt(-1, -1, 0, 0, "%s    %% %s", i++ == 0 ? "" : ", ",
1358                         thisname);
1359         }
1360     }
1361
1362     /* fill the "last" array with all -1s, to insure correct updating */
1363     lp = lcpustates;
1364     i = num_cpustates;
1365     while (--i >= 0)
1366     {
1367         *lp++ = -1;
1368     }
1369   }
1370 }
1371
1372 /*
1373  *  *_kernel(stats) - print "Kernel: " followed by the kernel summary string
1374  *
1375  *  Assumptions:  cursor is on "lastline", the previous line
1376  */
1377
1378 void
1379 i_kernel(int *stats)
1380
1381 {
1382     if (num_kernel > 0)
1383     {
1384         display_write(0, y_kernel, 0, 0, "Kernel: ");
1385
1386         /* format and print the kernel summary */
1387         summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx);
1388     }
1389 }
1390
1391 void
1392 u_kernel(int *stats)
1393
1394 {
1395     if (num_kernel > 0)
1396     {
1397         /* format the new line */
1398         summary_format(x_kernel, y_kernel, stats, kernel_names, kernel_cidx);
1399     }
1400 }
1401
1402 /*
1403  *  *_memory(stats) - print "Memory: " followed by the memory summary string
1404  *
1405  *  Assumptions:  cursor is on "lastline", the previous line
1406  */
1407
1408 void
1409 i_memory(long *stats)
1410
1411 {
1412     display_write(0, y_mem, 0, 0, "Memory: ");
1413
1414     /* format and print the memory summary */
1415     summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx);
1416 }
1417
1418 void
1419 u_memory(long *stats)
1420
1421 {
1422     /* format the new line */
1423     summary_format_memory(x_mem, y_mem, stats, memory_names, memory_cidx);
1424 }
1425
1426 /*
1427  *  *_swap(stats) - print "Swap: " followed by the swap summary string
1428  *
1429  *  Assumptions:  cursor is on "lastline", the previous line
1430  *
1431  *  These functions only print something when num_swap > 0
1432  */
1433
1434 void
1435 i_swap(long *stats)
1436
1437 {
1438     if (num_swap > 0)
1439     {
1440         /* print the tag */
1441         display_write(0, y_swap, 0, 0, "Swap: ");
1442
1443         /* format and print the swap summary */
1444         summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx);
1445     }
1446 }
1447
1448 void
1449 u_swap(long *stats)
1450
1451 {
1452     if (num_swap > 0)
1453     {
1454         /* format the new line */
1455         summary_format_memory(x_swap, y_swap, stats, swap_names, swap_cidx);
1456     }
1457 }
1458
1459 /*
1460  *  *_message() - print the next pending message line, or erase the one
1461  *                that is there.
1462  *
1463  *  Note that u_message is (currently) the same as i_message.
1464  *
1465  *  Assumptions:  lastline is consistent
1466  */
1467
1468 /*
1469  *  i_message is funny because it gets its message asynchronously (with
1470  *      respect to screen updates).  Messages are taken out of the
1471  *      circular message_buf and displayed one at a time.
1472  */
1473
1474 void
1475 i_message(struct timeval *now)
1476
1477 {
1478     struct timeval my_now;
1479     int i = 0;
1480
1481     dprintf("i_message(%08x)\n", now);
1482
1483     /* if now is NULL we have to get it ourselves */
1484     if (now == NULL)
1485     {
1486         time_get(&my_now);
1487         now = &my_now;
1488     }
1489
1490     /* now that we have been called, messages no longer need to be held */
1491     message_hold = 0;
1492
1493     dprintf("i_message: now %d, message_time %d\n",
1494             now->tv_sec, message_time.tv_sec);
1495
1496     if (smart_terminal)
1497     {
1498         /* is it time to change the message? */
1499         if (timercmp(now, &message_time, > ))
1500         {
1501             /* yes, free the current message */
1502             dprintf("i_message: timer expired\n");
1503             if (message_current != NULL)
1504             {
1505                 free(message_current);
1506                 message_current = NULL;
1507             }
1508
1509             /* is there a new message to be displayed? */
1510             if (message_first != message_last)
1511             {
1512                 /* move index to next message */
1513                 if (++message_first == MAX_MESSAGES) message_first = 0;
1514
1515                 /* make the next message the current one */
1516                 message_current = message_buf[message_first];
1517
1518                 /* show it */
1519                 dprintf("i_message: showing \"%s\"\n", message_current);
1520                 display_move(0, y_message);
1521                 screen_standout(message_current);
1522                 i = strlen(message_current);
1523
1524                 /* set the expiration timer */
1525                 message_time = *now;
1526                 message_time.tv_sec += MESSAGE_DISPLAY_TIME;
1527
1528                 /* clear the rest of the line */
1529                 screen_cleareol(message_length - i);
1530                 putchar('\r');
1531                 message_length = i;
1532             }
1533             else
1534             {
1535                 /* just clear what was there before, if anything */
1536                 if (message_length > 0)
1537                 {
1538                     display_move(0, y_message);
1539                     screen_cleareol(message_length);
1540                     putchar('\r');
1541                     message_length = 0;
1542                 }
1543             }
1544         }
1545     }
1546 }
1547
1548 void
1549 u_message(struct timeval *now)
1550
1551 {
1552     i_message(now);
1553 }
1554
1555 static int header_length;
1556
1557 /*
1558  *  *_header(text) - print the header for the process area
1559  *
1560  *  Assumptions:  cursor is on the previous line and lastline is consistent
1561  */
1562
1563 void
1564 i_header(char *text)
1565
1566 {
1567     int header_color = 0;
1568
1569 #ifdef ENABLE_COLOR
1570     header_color = color_test(header_cidx, 0);
1571 #endif
1572     header_length = strlen(text);
1573     if (header_status)
1574     {
1575         display_write(x_header, y_header, header_color, 1, text);
1576     }
1577 }
1578
1579 /*ARGSUSED*/
1580 void
1581 u_header(char *text)
1582
1583 {
1584     int header_color = 0;
1585
1586 #ifdef ENABLE_COLOR
1587     header_color = color_test(header_cidx, 0);
1588 #endif
1589     display_write(x_header, y_header, header_color, 1,
1590                   header_status ? text : "");
1591 }
1592
1593 /*
1594  *  *_process(line, thisline) - print one process line
1595  *
1596  *  Assumptions:  lastline is consistent
1597  */
1598
1599 void
1600 i_process(int line, char *thisline)
1601
1602 {
1603     /* truncate the line to conform to our current screen width */
1604     thisline[display_width] = '\0';
1605
1606     /* write the line out */
1607     display_write(0, y_procs + line, 0, 1, thisline);
1608 }
1609
1610 void
1611 u_process(int line, char *new_line)
1612
1613 {
1614     i_process(line, new_line);
1615 }
1616
1617 void
1618 i_endscreen()
1619
1620 {
1621     if (smart_terminal)
1622     {
1623         /* move the cursor to a pleasant place */
1624         display_move(x_idlecursor, y_idlecursor);
1625     }
1626     else
1627     {
1628         /* separate this display from the next with some vertical room */
1629         fputs("\n\n", stdout);
1630     }
1631     fflush(stdout);
1632 }
1633
1634 void
1635 u_endscreen()
1636
1637 {
1638     if (smart_terminal)
1639     {
1640         /* clear-to-end the display */
1641         display_cte();
1642
1643         /* move the cursor to a pleasant place */
1644         display_move(x_idlecursor, y_idlecursor);
1645         fflush(stdout);
1646     }
1647     else
1648     {
1649         /* separate this display from the next with some vertical room */
1650         fputs("\n\n", stdout);
1651     }
1652 }
1653
1654 void
1655 display_header(int t)
1656
1657 {
1658     header_status = t != 0;
1659 }
1660
1661 void
1662 message_mark()
1663
1664 {
1665     message_barrier = Yes;
1666 }
1667
1668 void
1669 message_expire()
1670
1671 {
1672     message_time.tv_sec = 0;
1673     message_time.tv_usec = 0;
1674 }
1675
1676 void
1677 message_flush()
1678
1679 {
1680     message_first = message_last;
1681     message_time.tv_sec = 0;
1682     message_time.tv_usec = 0;
1683 }
1684
1685 /*
1686  * void new_message_v(char *msgfmt, va_list ap)
1687  *
1688  * Display a message in the message area.  This function takes a va_list for
1689  * the arguments.  Safe to call before display_init.  This function only
1690  * queues a message for display, and allowed for multiple messages to be
1691  * queued.  The i_message function drains the queue and actually writes the
1692  * messages on the display.
1693  */
1694
1695
1696 void
1697 new_message_v(char *msgfmt, va_list ap)
1698
1699 {
1700     int i;
1701     int empty;
1702     char msg[MAX_COLS];
1703
1704     /* if message_barrier is active, remove all pending messages */
1705     if (message_barrier)
1706     {
1707         message_flush();
1708         message_barrier = No;
1709     }
1710
1711     /* first, format the message */
1712     (void) vsnprintf(msg, sizeof(msg), msgfmt, ap);
1713
1714     /* where in the buffer will it go? */
1715     i = message_last + 1;
1716     if (i >= MAX_MESSAGES) i = 0;
1717
1718     /* make sure the buffer is not full */
1719     if (i != message_first)
1720     {
1721         /* insert it in to message_buf */
1722         message_buf[i] = strdup(msg);
1723         dprintf("new_message_v: new message inserted in slot %d\n", i);
1724
1725         /* remember if the buffer is empty and set the index */
1726         empty = message_last == message_first;
1727         message_last = i;
1728
1729         /* is message_buf otherwise empty and have we started displaying? */
1730         if (empty && !message_hold)
1731         {
1732             /* we can display the message now */
1733             i_message(NULL);
1734         }
1735     }
1736 }
1737
1738 /*
1739  * void new_message(int type, char *msgfmt, ...)
1740  *
1741  * Display a message in the message area.  It is safe to call this function
1742  * before display_init.  Messages logged before the display is drawn will be
1743  * held and displayed later.
1744  */
1745
1746 void
1747 new_message(char *msgfmt, ...)
1748
1749 {
1750     va_list ap;
1751
1752     va_start(ap, msgfmt);
1753     new_message_v(msgfmt, ap);
1754     va_end(ap);
1755 }
1756
1757 /*
1758  * void message_error(char *msgfmt, ...)
1759  *
1760  * Put an error message in the message area.  It is safe to call this function
1761  * before display_init.  Messages logged before the display is drawn will be
1762  * held and displayed later.
1763  */
1764
1765 void
1766 message_error(char *msgfmt, ...)
1767
1768 {
1769     va_list ap;
1770
1771     va_start(ap, msgfmt);
1772     new_message_v(msgfmt, ap);
1773     fflush(stdout);
1774     va_end(ap);
1775 }
1776
1777 /*
1778  * void message_clear()
1779  *
1780  * Clear message area and flush all pending messages.
1781  */
1782
1783 void
1784 message_clear()
1785
1786 {
1787     /* remove any existing message */
1788     if (message_current != NULL)
1789     {
1790         display_move(0, y_message);
1791         screen_cleareol(message_length);
1792         free(message_current);
1793         message_current = 0;
1794     }
1795
1796     /* flush all pending messages */
1797     message_flush();
1798 }
1799
1800 /*
1801  * void message_prompt_v(int so, char *msgfmt, va_list ap)
1802  *
1803  * Place a prompt in the message area.  A prompt is different from a 
1804  * message as follows: it is displayed immediately, overwriting any
1805  * message that may already be there, it may be highlighted in standout
1806  * mode (if "so" is true), the cursor is left to rest at the end of the
1807  * prompt.  This call causes all pending messages to be flushed.
1808  */
1809
1810 void
1811 message_prompt_v(int so, char *msgfmt, va_list ap)
1812
1813 {
1814     char msg[MAX_COLS];
1815     int i;
1816
1817     /* clear out the message buffer */
1818     message_flush();
1819
1820     /* format the message */
1821     i = vsnprintf(msg, sizeof(msg), msgfmt, ap);
1822
1823     /* this goes over any existing message */
1824     display_move(0, y_message);
1825
1826     /* clear the entire line */
1827     screen_cleareol(message_length);
1828
1829     /* show the prompt */
1830     if (so)
1831     {
1832         screen_standout(msg);
1833     }
1834     else
1835     {
1836         fputs(msg, stdout);
1837     }
1838
1839     /* make it all visible */
1840     fflush(stdout);
1841
1842     /* even though we dont keep a copy of the prompt, track its length */
1843     message_length = i < MAX_COLS ? i : MAX_COLS;
1844 }
1845
1846 /*
1847  * void message_prompt(char *msgfmt, ...)
1848  *
1849  * Place a prompt in the message area (see message_prompt_v).
1850  */
1851
1852 void
1853 message_prompt(char *msgfmt, ...)
1854
1855 {
1856     va_list ap;
1857
1858     va_start(ap, msgfmt);
1859     message_prompt_v(Yes, msgfmt, ap);
1860     va_end(ap);
1861 }
1862
1863 void
1864 message_prompt_plain(char *msgfmt, ...)
1865
1866 {
1867     va_list ap;
1868
1869     va_start(ap, msgfmt);
1870     message_prompt_v(No, msgfmt, ap);
1871     va_end(ap);
1872 }
1873
1874 /*
1875  * int readline(char *buffer, int size, int numeric)
1876  *
1877  * Read a line of input from the terminal.  The line is placed in
1878  * "buffer" not to exceed "size".  If "numeric" is true then the input
1879  * can only consist of digits.  This routine handles all character
1880  * editing while keeping the terminal in cbreak mode.  If "numeric"
1881  * is true then the number entered is returned.  Otherwise the number
1882  * of character read in to "buffer" is returned.
1883  */
1884
1885 int
1886 readline(char *buffer, int size, int numeric)
1887
1888 {
1889     register char *ptr = buffer;
1890     register char ch;
1891     register char cnt = 0;
1892
1893     /* allow room for null terminator */
1894     size -= 1;
1895
1896     /* read loop */
1897     while ((fflush(stdout), read(0, ptr, 1) > 0))
1898     {
1899         /* newline or return means we are done */
1900         if ((ch = *ptr) == '\n' || ch == '\r')
1901         {
1902             break;
1903         }
1904
1905         /* handle special editing characters */
1906         if (ch == ch_kill)
1907         {
1908             /* return null string */
1909             *buffer = '\0';
1910             putchar('\r');
1911             return(-1);
1912         }
1913         else if (ch == ch_werase)
1914         {
1915             /* erase previous word */
1916             if (cnt <= 0)
1917             {
1918                 /* none to erase! */
1919                 putchar('\7');
1920             }
1921             else
1922             {
1923                 /*
1924                  * First: remove all spaces till the first-non-space 
1925                  * Second: remove all non-spaces till the first-space
1926                  */
1927                 while(cnt > 0 && ptr[-1] == ' ')
1928                 {
1929                     fputs("\b \b", stdout);
1930                     ptr--;
1931                     cnt--;
1932                 }
1933                 while(cnt > 0 && ptr[-1] != ' ')
1934                 {
1935                     fputs("\b \b", stdout);
1936                     ptr--;
1937                     cnt--;
1938                 }
1939             }
1940         }
1941         else if (ch == ch_erase)
1942         {
1943             /* erase previous character */
1944             if (cnt <= 0)
1945             {
1946                 /* none to erase! */
1947                 putchar('\7');
1948             }
1949             else
1950             {
1951                 fputs("\b \b", stdout);
1952                 ptr--;
1953                 cnt--;
1954             }
1955         }
1956         /* check for character validity and buffer overflow */
1957         else if (cnt == size || (numeric && !isdigit((int)ch)) ||
1958                 !isprint((int)ch))
1959         {
1960             /* not legal */
1961             putchar('\7');
1962         }
1963         else
1964         {
1965             /* echo it and store it in the buffer */
1966             putchar(ch);
1967             ptr++;
1968             cnt++;
1969         }
1970     }
1971
1972     /* all done -- null terminate the string */
1973     *ptr = '\0';
1974
1975     /* add response length to message_length */
1976     message_length += cnt;
1977
1978     /* return either inputted number or string length */
1979     putchar('\r');
1980     return(cnt == 0 ? -1 : numeric ? atoi(buffer) : cnt);
1981 }
1982
1983 void
1984 display_pagerstart()
1985
1986 {
1987     display_clear();
1988 }
1989
1990 void
1991 display_pagerend()
1992
1993 {
1994     char ch;
1995
1996     screen_standout("Hit any key to continue: ");
1997     fflush(stdout);
1998     (void) read(0, &ch, 1);
1999 }
2000
2001 void
2002 display_pager(char *fmt, ...)
2003
2004 {
2005     va_list ap;
2006
2007     int ch;
2008     char readch;
2009     char buffer[MAX_COLS];
2010     char *data;
2011
2012     /* format into buffer */
2013     va_start(ap, fmt);
2014     (void) vsnprintf(buffer, MAX_COLS, fmt, ap);
2015     va_end(ap);
2016     data = buffer;
2017
2018     while ((ch = *data++) != '\0')
2019     {
2020         putchar(ch);
2021         if (ch == '\n')
2022         {
2023             if (++curr_y >= screen_length - 1)
2024             {
2025                 screen_standout("...More...");
2026                 fflush(stdout);
2027                 (void) read(0, &readch, 1);
2028                 putchar('\r');
2029                 switch(readch)
2030                 {
2031                 case '\r':
2032                 case '\n':
2033                     curr_y--;
2034                     break;
2035
2036                 case 'q':
2037                     return;
2038
2039                 default:
2040                     curr_y = 0;
2041                 }
2042             }
2043         }
2044     }
2045 }