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