xref: /openbsd/usr.bin/systat/engine.c (revision 76d0caae)
1 /* $OpenBSD: engine.c,v 1.29 2021/07/02 15:34:16 millert Exp $	 */
2 /*
3  * Copyright (c) 2001, 2007 Can Erkin Acar <canacar@openbsd.org>
4  *
5  * Permission to use, copy, modify, and distribute this software for any
6  * purpose with or without fee is hereby granted, provided that the above
7  * copyright notice and this permission notice appear in all copies.
8  *
9  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
16  */
17 
18 
19 #include <sys/ioctl.h>
20 #include <sys/types.h>
21 #include <sys/queue.h>
22 
23 #include <ctype.h>
24 #include <curses.h>
25 #include <signal.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <term.h>
29 #include <unistd.h>
30 #include <math.h>
31 #include <err.h>
32 
33 /* XXX These are defined in term.h and conflict with our variable names */
34 #ifdef columns
35 #undef columns
36 #endif
37 
38 #ifdef lines
39 #undef lines
40 #endif
41 
42 #include "engine.h"
43 
44 #define MINIMUM(a, b) (((a) < (b)) ? (a) : (b))
45 
46 /* circular linked list of views */
47 TAILQ_HEAD(view_list, view_ent) view_head =
48 				  TAILQ_HEAD_INITIALIZER(view_head);
49 struct view_ent {
50 	field_view *view;
51 	TAILQ_ENTRY(view_ent) entries;
52 };
53 
54 static struct timespec ts_delay = { 5, 0 };
55 static struct itimerval it_delay = { { 0, 0 }, { 5, 0 } };
56 
57 int dispstart = 0;
58 int humanreadable = 0;
59 int interactive = 1;
60 int averageonly = 0;
61 int maxprint = 0;
62 int paused = 0;
63 int rawmode = 0;
64 int rawwidth = DEFAULT_WIDTH;
65 int sortdir = 1;
66 int columns, lines;
67 u_int32_t num_disp = 0;
68 int max_disp = -1;
69 
70 volatile sig_atomic_t gotsig_close = 0;
71 volatile sig_atomic_t gotsig_resize = 0;
72 volatile sig_atomic_t gotsig_alarm = 0;
73 int need_update = 0;
74 int need_sort = 0;
75 int separate_thousands = 0;
76 
77 SCREEN *screen;
78 
79 field_view *curr_view = NULL;
80 struct view_ent *curr_view_ent = NULL;
81 struct view_manager *curr_mgr = NULL;
82 
83 int curr_line = 0;
84 int home_line = 0;
85 
86 /* line buffer for raw mode */
87 char linebuf[MAX_LINE_BUF];
88 int linepos = 0;
89 
90 /* temp storage for state printing */
91 char tmp_buf[MAX_LINE_BUF];
92 
93 char cmdbuf[MAX_LINE_BUF];
94 int cmd_len = -1;
95 struct command *curr_cmd = NULL;
96 char *curr_message = NULL;
97 enum message_mode message_mode = MESSAGE_NONE;
98 int message_cont = 1;
99 
100 void print_cmdline(void);
101 
102 
103 /* screen output functions */
104 
105 char * tb_ptr = NULL;
106 int tb_len = 0;
107 
108 void
109 tb_start(void)
110 {
111 	tb_ptr = tmp_buf;
112 	tb_len = sizeof(tmp_buf);
113 	tb_ptr[0] = '\0';
114 }
115 
116 void
117 tb_end(void)
118 {
119 	tb_ptr = NULL;
120 	tb_len = 0;
121 }
122 
123 int
124 tbprintf(char *format, ...)
125 	GCC_PRINTFLIKE(1,2)       /* defined in curses.h */
126 {
127 	int len;
128 	va_list arg;
129 
130 	if (tb_ptr == NULL || tb_len <= 0)
131 		return 0;
132 
133 	va_start(arg, format);
134 	len = vsnprintf(tb_ptr, tb_len, format, arg);
135 	va_end(arg);
136 
137 	if (len > tb_len)
138 		tb_end();
139 	else if (len > 0) {
140 		tb_ptr += len;
141 		tb_len -= len;
142 	}
143 
144 	return len;
145 }
146 
147 int
148 tbprintft(char *format, ...)
149 	GCC_PRINTFLIKE(1,2)       /* defined in curses.h */
150 {
151 	int len;
152 	va_list arg;
153 	char buf[MAX_LINE_BUF];
154 
155 	if (tb_ptr == NULL || tb_len <= 0)
156 		return 0;
157 
158 	va_start(arg, format);
159 	len = vsnprintf(buf, tb_len, format, arg);
160 	va_end(arg);
161 
162 	if (len > tb_len)
163 		tb_end();
164 	else if (len > 0) {
165 		int d, s;
166 		int digits, curdigit;
167 
168 		if (!separate_thousands) {
169 			strlcpy(tb_ptr, buf, tb_len);
170 			return len;
171 		}
172 
173 		/* count until we hit a non digit. (e.g. the prefix) */
174 		for (digits = 0; digits < len; digits++)
175 			if (!isdigit((unsigned char)buf[digits]))
176 				break;
177 
178 		curdigit = digits;
179 		d = s = 0;
180 		/* insert thousands separators while copying */
181 		while (curdigit && d < tb_len) {
182 			if (curdigit < digits && curdigit % 3 == 0)
183 				tb_ptr[d++] = ',';
184 			tb_ptr[d++] = buf[s++];
185 			curdigit--;
186 		}
187 		/* copy the remaining non-digits */
188 		while (len > digits && d < tb_len) {
189 			tb_ptr[d++] = buf[s++];
190 			digits++;
191 		}
192 		tb_ptr[d] = '\0';
193 		tb_ptr += d;
194 		tb_len -= d;
195 		len = d;
196 	}
197 	return len;
198 }
199 
200 void
201 move_horiz(int offset)
202 {
203 	if (rawmode) {
204 		if (offset <= 0)
205 			linepos = 0;
206 		else if (offset >= MAX_LINE_BUF)
207 			linepos = MAX_LINE_BUF - 1;
208 		else
209 			linepos = offset;
210 	} else {
211 		move(curr_line, offset);
212 	}
213 }
214 
215 void
216 print_str(int len, const char *str)
217 {
218 	if (len <= 0)
219 		return;
220 
221 	if (rawmode) {
222 		int length = MINIMUM(len, MAX_LINE_BUF - linepos);
223 		if (length <= 0)
224 			return;
225 		bcopy(str, &linebuf[linepos], length);
226 		linepos += length;
227 	} else
228 		addnstr(str, len);
229 }
230 
231 void
232 clear_linebuf(void)
233 {
234 	memset(linebuf, ' ', MAX_LINE_BUF);
235 }
236 
237 void
238 end_line(void)
239 {
240 	if (rawmode) {
241 		linebuf[rawwidth] = '\0';
242 		printf("%s\n", linebuf);
243 		clear_linebuf();
244 	}
245 	curr_line++;
246 }
247 
248 void
249 end_page(void)
250 {
251 	if (rawmode) {
252 		linepos = 0;
253 		clear_linebuf();
254 		fflush(stdout);
255 	} else {
256 		move(home_line, 0);
257 		print_cmdline();
258 		refresh();
259 	}
260 	curr_line = 0;
261 }
262 
263 /* field output functions */
264 
265 void
266 print_fld_str(field_def *fld, const char *str)
267 {
268 	int len, offset;
269 	char *cpos;
270 
271 	if (str == NULL || fld == NULL)
272 		return;
273 
274 	if (fld->start < 0)
275 		return;
276 
277 	len = strlen(str);
278 
279 	if (len >= fld->width) {
280 		move_horiz(fld->start);
281 		print_str(fld->width, str);
282 	} else {
283 		switch (fld->align) {
284 		case FLD_ALIGN_RIGHT:
285 			move_horiz(fld->start + (fld->width - len));
286 			break;
287 		case FLD_ALIGN_CENTER:
288 			move_horiz(fld->start + (fld->width - len) / 2);
289 			break;
290 		case FLD_ALIGN_COLUMN:
291 			if ((cpos = strchr(str, ':')) == NULL) {
292 				offset = (fld->width - len) / 2;
293 			} else {
294 				offset = (fld->width / 2) - (cpos - str);
295 				if (offset < 0)
296 					offset = 0;
297 				else if (offset > (fld->width - len))
298 					offset = fld->width - len;
299 			}
300 			move_horiz(fld->start + offset);
301 			break;
302 		default:
303 			move_horiz(fld->start);
304 			break;
305 		}
306 		print_str(len, str);
307 	}
308 }
309 
310 void
311 print_bar_title(field_def *fld)
312 {
313 	char buf[16];
314 	int len, i, d, tr, tw, val, pos, cur;
315 
316 	int divs[] = {20, 10, 5, 4, 3, 2, 1, 0};
317 
318 	if (fld->width < 1)
319 		return;
320 
321 	len = snprintf(buf, sizeof(buf), " %d\\", fld->arg);
322 	if (len >= sizeof(buf))
323 		return;
324 
325 	for (i = 0; divs[i]; i++)
326 		if (divs[i] * len <= fld->width)
327 			break;
328 
329 	if (divs[i] == 0) {
330 		print_fld_str(fld, "*****");
331 		return;
332 	}
333 
334 	d = divs[i];
335 
336 	val = 0;
337 	pos = 0;
338 	tr = fld->arg % d;
339 	tw = fld->width % d;
340 
341 	tb_start();
342 	cur = 0;
343 	for(i = 0; i < d; i++) {
344 		tw += fld->width;
345 		tr += fld->arg;
346 
347 		while (tr >= d) {
348 			val++;
349 			tr -= d;
350 		}
351 		while (tw >= d) {
352 			pos++;
353 			tw -= d;
354 		}
355 
356 		len = snprintf(buf, sizeof(buf), "%d\\", val);
357 		if (len >= sizeof(buf))
358 			len = strlen(buf);
359 		while (cur < pos - len) {
360 			tbprintf(" ");
361 			cur++;
362 		}
363 		tbprintf("%s", buf);
364 		cur += len;
365 	}
366 
367 	print_fld_tb(fld);
368 }
369 
370 void
371 print_fld_bar(field_def *fld, int value)
372 {
373 	int i, tw, val;
374 
375 	if (fld->width < 1)
376 		return;
377 
378 	val = 0;
379 	tw = fld->arg / 2;
380 
381 	tb_start();
382 
383 	for(i = 0; i < fld->width; i++) {
384 		tw += fld->arg;
385 
386 		while (tw >= fld->width) {
387 			val++;
388 			tw -= fld->width;
389 		}
390 		if (val > value)
391 			break;
392 		tbprintf("#");
393 	}
394 
395 	print_fld_tb(fld);
396 }
397 
398 void
399 print_fld_tb(field_def *fld)
400 {
401 	print_fld_str(fld, tmp_buf);
402 	tb_end();
403 }
404 
405 void
406 print_title(void)
407 {
408 	field_def **fp;
409 
410 	if (curr_view != NULL && curr_view->view != NULL) {
411 		for (fp = curr_view->view; *fp != NULL; fp++) {
412 			switch((*fp)->align) {
413 			case FLD_ALIGN_LEFT:
414 			case FLD_ALIGN_RIGHT:
415 			case FLD_ALIGN_CENTER:
416 			case FLD_ALIGN_COLUMN:
417 				print_fld_str(*fp, (*fp)->title);
418 				break;
419 			case FLD_ALIGN_BAR:
420 				print_bar_title(*fp);
421 				break;
422 			}
423 		}
424 	}
425 	end_line();
426 }
427 
428 /* view related functions */
429 void
430 hide_field(field_def *fld)
431 {
432 	if (fld == NULL)
433 		return;
434 
435 	fld->flags |= FLD_FLAG_HIDDEN;
436 }
437 
438 void
439 show_field(field_def *fld)
440 {
441 	if (fld == NULL)
442 		return;
443 
444 	fld->flags &= ~((unsigned int) FLD_FLAG_HIDDEN);
445 }
446 
447 void
448 reset_fields(void)
449 {
450 	field_def **fp;
451 	field_def *fld;
452 
453 	if (curr_view == NULL)
454 		return;
455 
456 	if (curr_view->view == NULL)
457 		return;
458 
459 	for (fp = curr_view->view; *fp != NULL; fp++) {
460 		fld = *fp;
461 		fld->start = -1;
462 		fld->width = fld->norm_width;
463 	}
464 }
465 
466 void
467 field_setup(void)
468 {
469 	field_def **fp;
470 	field_def *fld;
471 	int st, fwid, change;
472 	int width = columns;
473 
474 	reset_fields();
475 
476 	dispstart = 0;
477 	st = 0;
478 
479 	for (fp = curr_view->view; *fp != NULL; fp++) {
480 		fld = *fp;
481 		if (fld->flags & FLD_FLAG_HIDDEN)
482 			continue;
483 
484 		if (width <= 1)
485 			break;
486 
487 		if (st != 1)
488 			width--;
489 
490 		fld->start = 1;
491 		fwid = fld->width;
492 		st++;
493 		if (fwid >= width) {
494 			fld->width = width;
495 			width = 0;
496 		} else
497 			width -= fwid;
498 	}
499 
500 	while (width > 0) {
501 		change = 0;
502 		for (fp = curr_view->view; *fp != NULL; fp++) {
503 			fld = *fp;
504 			if (fld->flags & FLD_FLAG_HIDDEN)
505 				continue;
506 			if ((fld->width < fld->max_width) &&
507 			    (fld->increment <= width)) {
508 				int w = fld->width + fld->increment;
509 				if (w > fld->max_width)
510 					w = fld->max_width;
511 				width += fld->width - w;
512 				fld->width = w;
513 				change = 1;
514 			}
515 			if (width <= 0) break;
516 		}
517 		if (change == 0) break;
518 	}
519 
520 	st = 0;
521 	for (fp = curr_view->view; *fp != NULL; fp++) {
522 		fld = *fp;
523 		if (fld->flags & FLD_FLAG_HIDDEN)
524 			continue;
525 		if (fld->start < 0) break;
526 		fld->start = st;
527 		st += fld->width + 1;
528 	}
529 }
530 
531 void
532 set_curr_view(struct view_ent *ve)
533 {
534 	field_view *v;
535 
536 	reset_fields();
537 
538 	if (ve == NULL) {
539 		curr_view_ent = NULL;
540 		curr_view = NULL;
541 		curr_mgr = NULL;
542 		return;
543 	}
544 
545 	v = ve->view;
546 
547 	if ((curr_view != NULL) && (curr_mgr != v->mgr)) {
548 		gotsig_alarm = 1;
549 		if (v->mgr != NULL && v->mgr->select_fn != NULL)
550 			v->mgr->select_fn();
551 	}
552 
553 	curr_view_ent = ve;
554 	curr_view = v;
555 	curr_mgr = v->mgr;
556 	field_setup();
557 	need_update = 1;
558 }
559 
560 void
561 add_view(field_view *fv)
562 {
563 	struct view_ent *ent;
564 
565 	if (fv == NULL)
566 		return;
567 
568 	if (fv->view == NULL || fv->name == NULL || fv->mgr == NULL)
569 		return;
570 
571 	ent = malloc(sizeof(struct view_ent));
572 	if (ent == NULL)
573 		return;
574 
575 	ent->view = fv;
576 	TAILQ_INSERT_TAIL(&view_head, ent, entries);
577 
578 	if (curr_view == NULL)
579 		set_curr_view(ent);
580 }
581 
582 int
583 set_view(const char *opt)
584 {
585 	struct view_ent *ve, *vm = NULL;
586 	field_view *v;
587 	int len;
588 
589 	if (opt == NULL || (len = strlen(opt)) == 0)
590 		return 1;
591 
592 	TAILQ_FOREACH(ve, &view_head, entries) {
593 		v = ve->view;
594 		if (strncasecmp(opt, v->name, len) == 0) {
595 			if (vm)
596 				return 1;
597 			vm = ve;
598 		}
599 	}
600 
601 	if (vm) {
602 		set_curr_view(vm);
603 		return 0;
604 	}
605 
606 	return 1;
607 }
608 
609 void
610 foreach_view(void (*callback)(field_view *))
611 {
612 	struct view_ent *ve;
613 
614 	TAILQ_FOREACH(ve, &view_head, entries) {
615 		callback(ve->view);
616 	}
617 }
618 
619 int
620 set_view_hotkey(int ch)
621 {
622 	struct view_ent *ve;
623 	field_view *v;
624 	int key = tolower(ch);
625 
626 	TAILQ_FOREACH(ve, &view_head, entries) {
627 		v = ve->view;
628 		if (key == v->hotkey) {
629 			set_curr_view(ve);
630 			return 1;
631 		}
632 	}
633 
634 	return 0;
635 }
636 
637 void
638 next_view(void)
639 {
640 	struct view_ent *ve;
641 
642 	if (TAILQ_EMPTY(&view_head) || curr_view_ent == NULL)
643 		return;
644 
645 	ve = TAILQ_NEXT(curr_view_ent, entries);
646 	if (ve == NULL)
647 		ve = TAILQ_FIRST(&view_head);
648 
649 	set_curr_view(ve);
650 }
651 
652 void
653 prev_view(void)
654 {
655 	struct view_ent *ve;
656 
657 	if (TAILQ_EMPTY(&view_head) || curr_view_ent == NULL)
658 		return;
659 
660 	ve = TAILQ_PREV(curr_view_ent, view_list, entries);
661 	if (ve == NULL)
662 		ve = TAILQ_LAST(&view_head, view_list);
663 
664 	set_curr_view(ve);
665 }
666 
667 /* generic field printing */
668 
669 void
670 print_fld_age(field_def *fld, unsigned int age)
671 {
672 	int len;
673 	unsigned int h, m, s;
674 
675 	if (fld == NULL)
676 		return;
677 	len = fld->width;
678 
679 	if (len < 1)
680 		return;
681 
682 	s = age % 60;
683 	m = age / 60;
684 	h = m / 60;
685 	m %= 60;
686 
687 	tb_start();
688 	if (tbprintf("%02u:%02u:%02u", h, m, s) <= len)
689 		goto ok;
690 
691 	tb_start();
692 	if (tbprintf("%u", age) <= len)
693 		goto ok;
694 
695 	tb_start();
696 	age /= 60;
697 	if (tbprintf("%um", age) <= len)
698 		goto ok;
699 	if (age == 0)
700 		goto err;
701 
702 	tb_start();
703 	age /= 60;
704 	if (tbprintf("%uh", age) <= len)
705 		goto ok;
706 	if (age == 0)
707 		goto err;
708 
709 	tb_start();
710 	age /= 24;
711 	if (tbprintf("%ud", age) <= len)
712 		goto ok;
713 
714 err:
715 	print_fld_str(fld, "*");
716 	tb_end();
717 	return;
718 
719 ok:
720 	print_fld_tb(fld);
721 }
722 
723 void
724 print_fld_sdiv(field_def *fld, u_int64_t size, int d)
725 {
726 	int len;
727 	char *mult = "KMGTPE";
728 	int i = -1;
729 
730 	if (fld == NULL)
731 		return;
732 
733 	len = fld->width;
734 	if (len < 1)
735 		return;
736 
737 	if (humanreadable) {
738 		while (size >= 10000 && sizeof(mult) >= i + 1) {
739 			i++;
740 			size /= d;
741 		}
742 		tb_start();
743 		if (tbprintft("%llu%.1s", size, i == -1 ? "" : mult + i) <= len)
744 			goto ok;
745 		goto err;
746 	}
747 	do {
748 		tb_start();
749 		if (tbprintft("%llu%.1s", size, i == -1 ? "" : mult + i) <= len)
750 			goto ok;
751 		i++;
752 		size /= d;
753 	} while (size != 0 && sizeof(mult) >= i);
754 err:
755 	tb_start();
756 	print_fld_str(fld, "*");
757 	tb_end();
758 	return;
759 
760 ok:
761 	print_fld_tb(fld);
762 }
763 
764 void
765 print_fld_size(field_def *fld, u_int64_t size)
766 {
767 	print_fld_sdiv(fld, size, 1024);
768 }
769 
770 void
771 print_fld_ssdiv(field_def *fld, int64_t size, int d)
772 {
773 	int len;
774 
775 	if (fld == NULL)
776 		return;
777 
778 	len = fld->width;
779 	if (len < 1)
780 		return;
781 
782 	tb_start();
783 	if (tbprintft("%lld", size) <= len)
784 		goto ok;
785 
786 	tb_start();
787 	size /= d;
788 	if (tbprintft("%lldK", size) <= len)
789 		goto ok;
790 	if (size == 0)
791 		goto err;
792 
793 	tb_start();
794 	size /= d;
795 	if (tbprintft("%lldM", size) <= len)
796 		goto ok;
797 	if (size == 0)
798 		goto err;
799 
800 	tb_start();
801 	size /= d;
802 	if (tbprintft("%lldG", size) <= len)
803 		goto ok;
804 	if (size == 0)
805 		goto err;
806 
807 	tb_start();
808 	size /= d;
809 	if (tbprintft("%lldT", size) <= len)
810 		goto ok;
811 
812 err:
813 	print_fld_str(fld, "*");
814 	tb_end();
815 	return;
816 
817 ok:
818 	print_fld_tb(fld);
819 }
820 
821 void
822 print_fld_ssize(field_def *fld, int64_t size)
823 {
824 	print_fld_ssdiv(fld, size, 1024);
825 }
826 
827 void
828 print_fld_rate(field_def *fld, double rate)
829 {
830 	if (rate < 0) {
831 		print_fld_str(fld, "*");
832 	} else {
833 		print_fld_size(fld, rate);
834 	}
835 }
836 
837 void
838 print_fld_bw(field_def *fld, double bw)
839 {
840 	if (bw < 0) {
841 		print_fld_str(fld, "*");
842 	} else {
843 		print_fld_sdiv(fld, bw, 1000);
844 	}
845 }
846 
847 void
848 print_fld_uint(field_def *fld, unsigned int size)
849 {
850 	int len;
851 
852 	if (fld == NULL)
853 		return;
854 
855 	len = fld->width;
856 	if (len < 1)
857 		return;
858 
859 	tb_start();
860 	if (tbprintft("%u", size) > len)
861 		print_fld_str(fld, "*");
862 	else
863 		print_fld_tb(fld);
864 	tb_end();
865 }
866 
867 void
868 print_fld_float(field_def *fld, double f, int prec)
869 {
870 	int len;
871 
872 	if (fld == NULL)
873 		return;
874 
875 	len = fld->width;
876 	if (len < 1)
877 		return;
878 
879 	tb_start();
880 	if (tbprintf("%*.*f", len, prec, f) > len)
881 		print_fld_str(fld, "*");
882 	else
883 		print_fld_tb(fld);
884 	tb_end();
885 }
886 
887 
888 /* ordering */
889 
890 int
891 foreach_order(void (*callback)(order_type *))
892 {
893 	order_type *o;
894 
895 	if (curr_view == NULL || curr_view->mgr == NULL ||
896 	    curr_view->mgr->order_list == NULL)
897 		return -1;
898 	o = curr_view->mgr->order_list;
899 	do {
900 		callback(o++);
901 	} while (o->name != NULL);
902 	return 0;
903 }
904 
905 void
906 set_order(const char *opt)
907 {
908 	order_type *o;
909 
910 	if (curr_view == NULL || curr_view->mgr == NULL)
911 		return;
912 
913 	curr_view->mgr->order_curr = curr_view->mgr->order_list;
914 
915 	if (opt == NULL)
916 		return;
917 
918 	o = curr_view->mgr->order_list;
919 
920 	if (o == NULL)
921 		return;
922 
923 	for (;o->name != NULL; o++) {
924 		if (strcasecmp(opt, o->match) == 0) {
925 			curr_view->mgr->order_curr = o;
926 			return;
927 		}
928 	}
929 }
930 
931 int
932 set_order_hotkey(int ch)
933 {
934 	order_type *o;
935 	int key = ch;
936 
937 	if (curr_view == NULL || curr_view->mgr == NULL)
938 		return 0;
939 
940 	o = curr_view->mgr->order_list;
941 
942 	if (o == NULL)
943 		return 0;
944 
945 	for (;o->name != NULL; o++) {
946 		if (key == o->hotkey) {
947 			if (curr_view->mgr->order_curr == o) {
948 				sortdir *= -1;
949 			} else {
950 				curr_view->mgr->order_curr = o;
951 			}
952 			return 1;
953 		}
954 	}
955 
956 	return 0;
957 }
958 
959 void
960 next_order(void)
961 {
962 	order_type *o, *oc;
963 
964 	if (curr_view->mgr->order_list == NULL)
965 		return;
966 
967 	oc = curr_view->mgr->order_curr;
968 
969 	for (o = curr_view->mgr->order_list; o->name != NULL; o++) {
970 		if (oc == o) {
971 			o++;
972 			if (o->name == NULL)
973 				break;
974 			curr_view->mgr->order_curr = o;
975 			return;
976 		}
977 	}
978 
979 	curr_view->mgr->order_curr = curr_view->mgr->order_list;
980 }
981 
982 
983 /* main program functions */
984 
985 int
986 read_view(void)
987 {
988 	if (curr_mgr == NULL)
989 		return (0);
990 
991 	if (paused)
992 		return (0);
993 
994 	if (curr_mgr->read_fn != NULL)
995 		return (curr_mgr->read_fn());
996 
997 	return (0);
998 }
999 
1000 
1001 int
1002 disp_update(void)
1003 {
1004 	int li;
1005 
1006 	if (maxprint < 0)
1007 		dispstart = 0;
1008 	else if (dispstart + maxprint > num_disp)
1009 		dispstart = num_disp - maxprint;
1010 
1011 	if (dispstart < 0)
1012 		dispstart = 0;
1013 
1014 	if (curr_view == NULL)
1015 		return 0;
1016 
1017 	if (curr_mgr != NULL) {
1018 		curr_line = 0;
1019 
1020 		if (curr_mgr->header_fn != NULL) {
1021 			li = curr_mgr->header_fn();
1022 			if (li < 0)
1023 				return (1);
1024 			curr_line = ++li;
1025 			home_line = li + maxprint + 1;
1026 		}
1027 
1028 		print_title();
1029 
1030 		if (curr_mgr->print_fn != NULL)
1031 			curr_mgr->print_fn();
1032 	}
1033 
1034 	return (0);
1035 }
1036 
1037 void
1038 sort_view(void)
1039 {
1040 	if (curr_mgr != NULL)
1041 		if (curr_mgr->sort_fn != NULL)
1042 			curr_mgr->sort_fn();
1043 }
1044 
1045 void
1046 sig_close(int sig)
1047 {
1048 	gotsig_close = 1;
1049 }
1050 
1051 void
1052 sig_resize(int sig)
1053 {
1054 	gotsig_resize = 1;
1055 }
1056 
1057 void
1058 sig_alarm(int sig)
1059 {
1060 	gotsig_alarm = 1;
1061 }
1062 
1063 void
1064 setup_term(int dmax)
1065 {
1066 	max_disp = dmax;
1067 	maxprint = dmax;
1068 
1069 	if (rawmode) {
1070 		columns = rawwidth;
1071 		lines = DEFAULT_HEIGHT;
1072 		clear_linebuf();
1073 	} else {
1074 		if (dmax < 0)
1075 			dmax = 0;
1076 
1077 		screen = newterm(NULL, stdout, stdin);
1078 		if (screen == NULL) {
1079 			rawmode = 1;
1080 			interactive = 0;
1081 			setup_term(dmax);
1082 			return;
1083 		}
1084 		columns = COLS;
1085 		lines = LINES;
1086 
1087 		if (maxprint > lines - HEADER_LINES)
1088 			maxprint = lines - HEADER_LINES;
1089 
1090 		nonl();
1091 		keypad(stdscr, TRUE);
1092 		intrflush(stdscr, FALSE);
1093 
1094 		halfdelay(10);
1095 		noecho();
1096 	}
1097 
1098 	if (dmax == 0)
1099 		maxprint = lines - HEADER_LINES;
1100 
1101 	field_setup();
1102 }
1103 
1104 void
1105 do_resize_term(void)
1106 {
1107 	struct winsize ws;
1108 
1109 	if (rawmode)
1110 		return;
1111 
1112 	if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == -1)
1113 		return;
1114 
1115 	resizeterm(ws.ws_row, ws.ws_col);
1116 
1117 	columns = COLS;
1118 	lines = LINES;
1119 
1120 	maxprint = max_disp;
1121 
1122 	if (maxprint == 0 || maxprint > lines - HEADER_LINES)
1123 		maxprint = lines - HEADER_LINES;
1124 
1125 	clear();
1126 
1127 	field_setup();
1128 }
1129 
1130 struct command *
1131 command_set(struct command *cmd, const char *init)
1132 {
1133 	struct command *prev = curr_cmd;
1134 
1135 	if (cmd) {
1136 		if (init) {
1137 			cmd_len = strlcpy(cmdbuf, init, sizeof(cmdbuf));
1138 			if (cmd_len >= sizeof(cmdbuf)) {
1139 				cmdbuf[0] = '\0';
1140 				cmd_len = 0;
1141 			}
1142 		} else {
1143 			cmd_len = 0;
1144 			cmdbuf[0] = 0;
1145 		}
1146 	}
1147 	message_set(NULL);
1148 	curr_cmd = cmd;
1149 	need_update = 1;
1150 	return prev;
1151 }
1152 
1153 void
1154 message_toggle(enum message_mode mode)
1155 {
1156 	message_mode = message_mode != mode ? mode : MESSAGE_NONE;
1157 	need_update = 1;
1158 	message_cont = 1;
1159 }
1160 
1161 const char *
1162 message_set(const char *msg)
1163 {
1164 	free(curr_message);
1165 
1166 	if (msg) {
1167 		curr_message = strdup(msg);
1168 		message_cont = 0;
1169 	} else {
1170 		curr_message = NULL;
1171 		message_cont = 1;
1172 	}
1173 	return NULL;
1174 }
1175 
1176 void
1177 print_cmdline(void)
1178 {
1179 	if (curr_cmd) {
1180 		attron(A_STANDOUT);
1181 		mvprintw(home_line, 0, "%s: ", curr_cmd->prompt);
1182 		attroff(A_STANDOUT);
1183 		printw("%s", cmdbuf);
1184 	} else if (curr_message) {
1185 		mvprintw(home_line, 0, "> %s", curr_message);
1186 	}
1187 	clrtoeol();
1188 }
1189 
1190 
1191 void
1192 cmd_keyboard(int ch)
1193 {
1194 	if (curr_cmd == NULL)
1195 		return;
1196 
1197 	if (ch > 0 && isprint(ch)) {
1198 		if (cmd_len < sizeof(cmdbuf) - 1) {
1199 			cmdbuf[cmd_len++] = ch;
1200 			cmdbuf[cmd_len] = 0;
1201 		} else
1202 			beep();
1203 	}
1204 
1205 	switch (ch) {
1206 	case KEY_ENTER:
1207 	case 0x0a:
1208 	case 0x0d:
1209 	{
1210 		struct command * c = command_set(NULL, NULL);
1211 		c->exec(cmdbuf);
1212 		break;
1213 	}
1214 	case KEY_BACKSPACE:
1215 	case KEY_DC:
1216 	case CTRL_H:
1217 		if (cmd_len > 0) {
1218 			cmdbuf[--cmd_len] = 0;
1219 		} else
1220 			beep();
1221 		break;
1222 	case 0x1b:
1223 	case CTRL_G:
1224 		if (cmd_len > 0) {
1225 			cmdbuf[0] = '\0';
1226 			cmd_len = 0;
1227 		} else
1228 			command_set(NULL, NULL);
1229 		break;
1230 	default:
1231 		break;
1232 	}
1233 }
1234 
1235 void
1236 keyboard(void)
1237 {
1238 	int ch;
1239 
1240 	ch = getch();
1241 
1242 	if (curr_cmd) {
1243 		cmd_keyboard(ch);
1244 		print_cmdline();
1245 		return;
1246 	}
1247 
1248 	if (curr_mgr != NULL)
1249 		if (curr_mgr->key_fn != NULL)
1250 			if (curr_mgr->key_fn(ch))
1251 				return;
1252 
1253 	if (curr_message != NULL) {
1254 		if (ch > 0) {
1255 			message_set(NULL);
1256 			need_update = 1;
1257 		}
1258 	}
1259 
1260 	switch (ch) {
1261 	case ' ':
1262 		gotsig_alarm = 1;
1263 		break;
1264 	case 'o':
1265 		next_order();
1266 		need_sort = 1;
1267 		break;
1268 	case 'p':
1269 		paused = !paused;
1270 		gotsig_alarm = 1;
1271 		break;
1272 	case 'q':
1273 		gotsig_close = 1;
1274 		break;
1275 	case 'r':
1276 		sortdir *= -1;
1277 		need_sort = 1;
1278 		break;
1279 	case 'v':
1280 		/* FALLTHROUGH */
1281 	case KEY_RIGHT:
1282 		/* FALLTHROUGH */
1283 	case CTRL_F:
1284 		next_view();
1285 		break;
1286 	case KEY_LEFT:
1287 		/* FALLTHROUGH */
1288 	case CTRL_B:
1289 		prev_view();
1290 		break;
1291 	case KEY_DOWN:
1292 		/* FALLTHROUGH */
1293 	case CTRL_N:
1294 		dispstart++;
1295 		need_update = 1;
1296 		break;
1297 	case KEY_UP:
1298 		/* FALLTHROUGH */
1299 	case CTRL_P:
1300 		dispstart--;
1301 		need_update = 1;
1302 		break;
1303 	case KEY_NPAGE:
1304 		/* FALLTHROUGH */
1305 	case CTRL_V:
1306 		dispstart += maxprint;
1307 		need_update = 1;
1308 		break;
1309 	case KEY_PPAGE:
1310 		/* FALLTHROUGH */
1311 	case META_V:
1312 		dispstart -= maxprint;
1313 		need_update = 1;
1314 		break;
1315 	case KEY_HOME:
1316 		/* FALLTHROUGH */
1317 	case CTRL_A:
1318 		dispstart = 0;
1319 		need_update = 1;
1320 		break;
1321 	case KEY_END:
1322 		/* FALLTHROUGH */
1323 	case CTRL_E:
1324 		dispstart = num_disp;
1325 		need_update = 1;
1326 		break;
1327 	case CTRL_L:
1328 		clear();
1329 		need_update = 1;
1330 		break;
1331 	default:
1332 		break;
1333 	}
1334 
1335 	if (set_order_hotkey(ch))
1336 		need_sort = 1;
1337 	else
1338 		set_view_hotkey(ch);
1339 }
1340 
1341 void
1342 engine_initialize(void)
1343 {
1344 	signal(SIGTERM, sig_close);
1345 	signal(SIGINT, sig_close);
1346 	signal(SIGQUIT, sig_close);
1347 	signal(SIGWINCH, sig_resize);
1348 	signal(SIGALRM, sig_alarm);
1349 }
1350 
1351 void
1352 engine_loop(int countmax)
1353 {
1354 	int count = 0;
1355 
1356 	for (;;) {
1357 		if (gotsig_alarm) {
1358 			read_view();
1359 			need_sort = 1;
1360 			gotsig_alarm = 0;
1361 			setitimer(ITIMER_REAL, &it_delay, NULL);
1362 		}
1363 
1364 		if (need_sort) {
1365 			sort_view();
1366 			need_sort = 0;
1367 			need_update = 1;
1368 
1369 			/* XXX if sort took too long */
1370 			if (gotsig_alarm) {
1371 				gotsig_alarm = 0;
1372 				setitimer(ITIMER_REAL, &it_delay, NULL);
1373 			}
1374 		}
1375 
1376 		if (need_update) {
1377 			erase();
1378 			if (!averageonly ||
1379 			    (averageonly && count == countmax - 1))
1380 				disp_update();
1381 			if (message_cont) {
1382 				switch (message_mode) {
1383 				case MESSAGE_NONE:
1384 					message_set(NULL);
1385 					break;
1386 				case MESSAGE_HELP:
1387 					show_help();
1388 					break;
1389 				case MESSAGE_VIEW:
1390 					show_view();
1391 					break;
1392 				case MESSAGE_ORDER:
1393 					show_order();
1394 					break;
1395 				}
1396 			}
1397 			end_page();
1398 			need_update = 0;
1399 			if (countmax && ++count >= countmax)
1400 				break;
1401 		}
1402 
1403 		if (gotsig_close)
1404 			break;
1405 		if (gotsig_resize) {
1406 			do_resize_term();
1407 			gotsig_resize = 0;
1408 			need_update = 1;
1409 		}
1410 
1411 		if (interactive && need_update == 0)
1412 			keyboard();
1413 		else if (interactive == 0)
1414 			nanosleep(&ts_delay, NULL);
1415 	}
1416 
1417 	if (rawmode == 0)
1418 		endwin();
1419 }
1420 
1421 int
1422 check_termcap(void)
1423 {
1424 	char *term_name;
1425 	int status;
1426 	static struct termios screen_settings;
1427 
1428 	if (!interactive)
1429 		/* pretend we have a dumb terminal */
1430 		return(1);
1431 
1432 	/* get the terminal name */
1433 	term_name = getenv("TERM");
1434 	if (term_name == NULL)
1435 		return(1);
1436 
1437 	/* now get the termcap entry */
1438 	if ((status = tgetent(NULL, term_name)) != 1) {
1439 		if (status == -1)
1440 			warnx("can't open termcap file");
1441 		else
1442 			warnx("no termcap entry for a `%s' terminal",
1443 			    term_name);
1444 
1445 		/* pretend it's dumb and proceed */
1446 		return(1);
1447 	}
1448 
1449 	/* "hardcopy" immediately indicates a very stupid terminal */
1450 	if (tgetflag("hc"))
1451 		return(1);
1452 
1453         /* get necessary capabilities */
1454         if (tgetstr("cl", NULL) == NULL || tgetstr("cm", NULL) == NULL)
1455 		return(1);
1456 
1457 	/* if stdout is not a terminal, pretend we are a dumb terminal */
1458 	if (tcgetattr(STDOUT_FILENO, &screen_settings) == -1)
1459 		return(1);
1460 
1461 	return(0);
1462 }
1463 
1464 void
1465 refresh_delay(double delay)
1466 {
1467 	double secs, frac;
1468 
1469 	frac = modf(delay, &secs);
1470 	ts_delay.tv_sec = secs;
1471 	ts_delay.tv_nsec = frac * 1000000000.0;
1472 	if (!timespecisset(&ts_delay))
1473 		ts_delay.tv_nsec = 1000000000;
1474 	TIMESPEC_TO_TIMEVAL(&it_delay.it_value, &ts_delay);
1475 }
1476