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